操作系统-基础知识面试常见篇
操作系统基础背景知识
操作系统是什么?
操作系统是管理计算机软件和硬件的程序,操作系统屏蔽了硬件的复杂性,由操作系统来调用系统内存、磁盘等硬件资源保证应用程序正常运行,操作系统内核是操作系统的核心部分,内核是连接应用程序和硬件的桥梁
操作系统内核和中央处理器(CPU)的区别如下
- 内核是操作系统层面的,CPU是硬件层面的
- CPU是提供运算、处理指令的能力,内核主要负责的是系统管理等,屏蔽了硬件操作
应用程序、内核、CPU三者关系如下
内核态和用户态的区别
用户态的进程可以直接获取较低权限的应用程序数据,但是需要磁盘、内存、CPU做支持的操作,就需要向操作系统发出系统调用请求,由操作系统将数据从内核态取出并拷贝到用户态进程后,用户态进程才能正常获取数据
内核态进程可以访问计算机的任何资源、包括系统的内存空间、设备、驱动程序等,权限极高。不过内核态由于过高的权限,因此有严格的权限校验和较高的开销)(上下文切换等),应减少内核态的进入来保证系统的性能和稳定性
用户态切换到内核态的三种方式如下
- 系统调用 用户态进程主动切换内核态的方式,主要是为了使用内核态才能做的事情
- 中断 比如用户态发出请求磁盘资源后,内核态发出请求磁盘的请求后就重新切回用户态继续做用户态的工作,直到磁盘资源完成,CPU被中断回去处理磁盘资源请求完成后的后续工作,这也是用户转内核的方式之一
- 异常 CPU在用户态程序中运行,发生不可知异常,当前进程会切到内核中处理该异常的程序中去
系统调用一般是跟系统相关的操作,需要切为内核态由操作系统来处理,比如请求磁盘资源、内存管理、进程控制、文件管理、设备管理等内容
进程和线程
概念
进程是程序的运行实例,线程是轻量级进程,多个线程支撑一个进程,进程之间是独立的,但是线程之间可能会互相影响,进程开销大,线程开销小,但不利于资源隔离
有进程为什么要线程?
- 线程内共享资源的通信行为不需要再走内核态,进程已经取来放在xx空间了,进程内的线程都可以直接读取,不需要走内核态
- 线程切换成本小
- 多线程并发处理能减低阻塞的风险,如果进程执行IO阻塞了,程序直接卡死,线程阻塞只是影响单个功能,程序其他模块依然正常运行
线程之间的同步方式
- 互斥锁 实际上是操作系统的 monitor指令实现,跟JAVA的synchronized关键字一致
- 读写锁 多个线程共享读,但只有一个线程可以写(并发情况下),互斥锁的优化,读多写少很合适
- 信号量 可以控制同一时刻访问资源的最大线程数,通过计数实现
- 屏障 障是一种同步原语,用于等待多个线程到达某个点再一起继续执行。当一个线程到达屏障时,它会停止执行并等待其他线程到达屏障,直到所有线程都到达屏障后,它们才会一起继续执行。比如 Java 中的
CyclicBarrier
是这种机制。 - 事件 Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
PCB
进程控制块,操作系统中用来管理和追踪进程的数据结构,进程执行的时候,PCB会一直追踪进程状态,主要记录以下信息
- 进程名、进程标识符
- 进程状态、进程优先级、进程阻塞原因
- 进程对资源的占用情况
- 其他等等
进程主要有以下五种状态
进程之间的通信方式如下
- 管道/匿名管道 内存中的文件形式存在 具有亲缘关系的父子进程或兄弟进程之间的通信
- 有名进程 以磁盘文件的形式存在,可以实现本地任意两个进程的通信
- 信号
- 消息队列 克服了信号承载信息少,管道只能接受无格式字节流和缓冲区大小的问题
- 信号量
- 共享内存 多个进程访问一个内存块,依靠同步操作实现
- 套接字 主要用在客户端和服务器之间网络通信,是不同主机之间进程双向通信的端点
进程常见调度算法
- 先到先服务调度算法(FCFS,First Come, First Served) : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 短作业优先的调度算法(SJF,Shortest Job First) : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 时间片轮转调度算法(RR,Round-Robin) : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
- 多级反馈队列调度算法(MFQ,Multi-level Feedback Queue):前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
- 优先级调度算法(Priority):为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
什么是僵尸进程和孤儿进程?如何排查
僵尸进程: 子进程终止,但是父进程还在执行,父进程没调用wait()/ waitpid()等系统调用来获取子进程的状态信息,释放子进程占用资源,导致子进程的PCB依然在系统中。出现的原因可能是子进程直接通过exit()进行结束进程生命,进程会释放全部资源但PCB依然在系统中。这种情况下子进程称为僵尸进程
孤儿进程:一个进程的父进程已终止或不存在,但是进程依然在运行,这种情况下的进程就是孤儿进程。如果出现这种情况,操作系统会把孤儿进程交给init()进程来管理并进行回收
Linux中通过top命令查找,如果其中zombie为0代表僵尸进程为0,定位命令如下
ps -A -ostat,ppid,pid,cmd |grep -e '^[Zz]'
死锁
基础概念和出现条件
死锁的定义: 两个或多个进程因争夺资源而陷入相互等待的永久阻塞状态
举个例子 你给我钱我就给你药,你给我药我就给你钱,如果没有一个人先妥协那就会无限期卡死在这个循环之中
通过上面这个例子我们应该可以摸索出死锁的出现条件
- 非抢占 -》 没有线程可以强行抢夺其他线程锁定中的资源,必须等待其他线程锁定的资源被释放,如果能直接抢还死锁个鸡毛。
- 循环等待-》 a等b,b等c,c等d的情况
- 互斥 -》 资源处在非共享状态,如果线程a有a,线程b有b,线程a需要b的资源,线程b需要a的资源。而b的资源是支持2个线程同时访问的,那完全就不死锁了。a访问到b的资源后释放导致a资源释放,释放后b线程也正常运行
- 占用并等待 -》 一个进程占用一个资源并等待另一个资源,另一个资源被其他进程占用
如果系统发生死锁一定要满足上面的条件,但凡有一个不满足都无法出现死锁的情况
解决死锁的方案
预防、避免、检查、解除
预防
目的是破坏四个条件之一,
- 对于互斥这个条件是难以行不通的,因为很多资源的不支持同时访问,会出现问题,
- 对于非抢占可以使用剥夺式调度算法,这个算法目前适用于主存资源和处理器资源分配,不适用所有资源。
- 可以通过静态分配策略来破坏占用并等待这个条件,通过提前获取线程需要的资源是否能获取再判断执行与否。可以避免线程启动后占用某些其他线程需要的资源再卡住。导致死锁的情况。
- 层次分配策略解决循环等待的条件, 在层次分配策略下,所有的资源被分成了多个层次,一个进程得到某一次的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源,按这种策略,是不可能出现循环等待链的,因为那样的话,就出现了已经申请了较高层的资源,反而去申请了较低层的资源,不符合层次分配策略,
避免
死锁的避免是允许存在四个必要条件,但是通过获取资源动态申请情况来避免死锁。将系统状态分为安全和非安全状态。在为申请者分配资源前检查系统状态,判断是否会出现死锁后再进行分配。保证系统保持安全状态避免死锁的经典算法 银行家算法, 银行家算法用一句话表达就是:当一个进程申请使用资源的时候,银行家算法 通过先 试探 分配给该进程资源,然后通过 安全性算法 判断分配后系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待,若能够进入到安全的状态,则就 真的分配资源给该进程。
检查
对资源分配加以限制来避免和预防死锁的出现是上面的做法,但不利于资源共享和性能。检查有点像乐观锁,检查通过定期扫描系统是否出现死锁然后再去解除
解除
- 立即结束所有进程的执行,重新启动操作系统:这种方法简单,但以前所在的工作全部作废,损失很大。
- 撤销涉及死锁的所有进程,解除死锁后继续运行:这种方法能彻底打破死锁的循环等待条件,但将付出很大代价,例如有些进程可能已经计算了很长时间,由于被撤销而使产生的部分结果也被消除了,再重新执行时还要再次进行计算。
- 逐个撤销涉及死锁的进程,回收其资源直至死锁解除。
- 抢占资源:从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除