【进程篇】操作系统重点内容总结
目录
进程与线程的区别
操作系统角度:
进程是操作系统进行资源分配和调度的基本单位,是一段运行中的程序,一个操作系统可以并发执行多个进程。一个进程可以有多个线程,进程有自己的内存资源。
线程是CPU进行调度的基本单位,是进程的子任务,依赖于进程存在,多个线程共享进程的内存资源。
从Java 的角度:
一个进程中可以有多个线程,多个线程共享进程的堆和方法区(JDK1.8之后的元空间),但是每个线程有自己的程序计数器、虚拟机栈和本地方法栈。
总结:线程是进程划分的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各个进程是独立的,而各个线程则不一定,因为同一进程中的线程很有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
进程有哪几种状态?
进程有5种状态:
- 创建状态(new):进程正在被创建,尚未到就绪状态
- 就绪状态(ready):进程已处于准备运行状态,即进程获得了处处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行
- 运行状态(running):进程正在处理器上运行的状态
- 阻塞状态(waiting):又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待IO操作完成,即使处理器空闲,该进程也不能运行。
- 结束状态(terminated):进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
进程间的通信方式
大概有7中常见的进程间的通信方式
- 管道/匿名管道(Pipes):用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
- 有名管道(Named Pipes):匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(fifo)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
- 信号(Signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 消息队列(Message Queuing):消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信方式都是先进先出的原则。与管道(无名管道:只存在内存中的文件;有名管道:存在实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正地删除。消息队列可以实现消息地随机查询,消息不一定要以先进先出地次序读取,也可以按消息地类型读取比FIFO更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号量(Semaphores):信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
- 共享内存(Shared memory):使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量。
- 套接字(Sockets):此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持TCP/IP的网络通信的基本操作单元,可以看作是不同主机之间的进程进行双向通信的端点。
线程的同步方式
线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般哪有下面三种线程同步方式:
- 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证共享资源不会被多个线程同时访问。比如 Java 中的synchronized关键字和各种Lock都是这种机制。
- 信号量(Semaphore):他允许同一时刻多个进程访问统一资源,但是需要控制同一时刻访问此资源的最大线程数量。
- 事件(Event):Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
进程的调度算法
为了确定首先执行哪个进程以及最后执行哪个进程以实现最大CPU利用率,计算机科学家已经定义了一些算法:
- 先到先服务(FCFS)调度算法:从就绪队列中选择一个最先进入该队列的进程为之分配资源,使他立即执行并一直执行到完成或发生某事件被阻塞放弃占用CPU。
- 短作业优先调度(SJF)调度算法:从就绪队列中选出一个估计运行时间最短的进程为之分配资源使他立即执行并一直执行到完成或发生某个事件而被阻塞放弃占用CPU时。
- 时间片轮转调度算法:时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
- 优先级调度:为每个进程分配优先级,首先执行具有最高优先级的进程,以此类推。具有相同优先级的进程以FCFS先进先出的方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
- 多级反馈队列调度算法:前面介绍的集中几种调度算法都有一定的局限性。如短进程优先调度算法,进照顾了短进程而忽略了长进程。优先级调度算法照顾了优先级高的进程而忽略了优先级低的进程,多级反馈队列调度算法既能使高优先级的进程得到相应又能让短作业(进程)迅速完成。因此它是目前被公认的一种较好的进程调度算法。
什么是死锁
多个进程/线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放,由于进程/线程被无限期地阻塞,因此程序不可能正常终止。
死锁产生的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用,当这个资源被占用,其他请求该资源的线程就会被阻塞,直到该资源被释放。
- 请求与保持条件:进程已经拥有了至少一个资源,但又提出了新的资源请求,而该资源已经被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,只能由获得该资源的进程自己来释放(只能是主动释放)。
- 循环等待条件:多个进程形成首尾相接循环等待资源的关系。
解决死锁的方法
解决死锁的方法可以从多个角度去分析,一半情况下,有预防,避免,检测和解除四种。
- 预防是采用某种策略,限制并发进程对资源的请求,从而使得死锁的必要条件在系统执行的任何时间都不满足。
- 避免则是系统在分配资源时,根据资源的使用情况提前做出预测,从而避免死锁的发生。
- 检测是指系统设有专门的机构,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
- 解除是与检测相配套的一种措施,用于将进程从死锁状态下解脱出来。
死锁的预防
死锁四大必要条件上面都已经列出来了,只要破坏一种任何一个就能预防死锁的发生。
破坏第一个条件 互斥条件 :使得资源是可以同时访问的,但是我们知道有很多资源往往是不能同时访问的,因此这种做法在大多数场合是行不通的。
破坏第三个条件 不可剥夺条件:也就是说可以采用剥夺式调度算法,但剥夺式调度算法目前一般仅适用于 主存资源 和 处理器资源 的分配,并不适用于所有资源,会导致资源利用率下降。
所以一般比较实用的 预防死锁的方法,是通过考虑破坏第二个条件和第四个条件。
1、静态分配策略
静态分配策略可以破坏死锁产生的第二个条件(占有并等待)。所谓静态分配策略,就是指一个进程必须在执行前就申请到它所需要的全部资源,并且知道它所要的资源都得到满足之后才开始执行。进程要么占有所有的资源然后开始执行,要么不占有资源,不会出现占有一些资源等待一些资源的情况。
静态分配策略逻辑简单,实现也很容易,但这种策略 严重地降低了资源利用率,因为在每个进程所占有的资源中,有些资源是在比较靠后的执行时间里采用的,甚至有些资源是在额外的情况下才使用的,这样就可能造成一个进程占有了一些 几乎不用的资源而使其他需要该资源的进程产生等待 的情况。
2、层次分配策略
层次分配策略破坏了产生死锁的第四个条件(循环等待)。在层次分配策略下,所有的资源被分成了多个层次,一个进程得到某一次的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源,按这种策略,是不可能出现循环等待链的,因为那样的话,就出现了已经申请了较高层的资源,反而去申请了较低层的资源,不符合层次分配策略,证明略。
死锁的避免
上面提到的 破坏 死锁产生的四个必要条件之一就可以成功 预防系统发生死锁 ,但是会导致 低效的进程运行 和 资源使用率 。而死锁的避免相反,它的角度是允许系统中同时存在四个必要条件 ,只要掌握并发进程中与每个进程有关的资源动态申请情况,做出 明智和合理的选择 ,仍然可以避免死锁,因为四大条件仅仅是产生死锁的必要条件。
我们将系统的状态分为 安全状态 和 不安全状态 ,每当在未申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。
如果操作系统能够保证所有的进程在有限的时间内得到需要的全部资源,则称系统处于安全状态,否则说系统是不安全的。很显然,系统处于安全状态则不会发生死锁,系统若处于不安全状态则可能发生死锁。
那么如何保证系统保持在安全状态呢?通过算法,其中最具有代表性的 避免死锁算法 就是 Dijkstra 的银行家算法,银行家算法用一句话表达就是:当一个进程申请使用资源的时候,银行家算法 通过先 试探 分配给该进程资源,然后通过 安全性算法 判断分配后系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待,若能够进入到安全的状态,则就 真的分配资源给该进程。
银行家算法详情可见:《一句话+一张图说清楚——银行家算法》open in new window 。
死锁的避免(银行家算法)改善了 资源使用率低的问题 ,但是它要不断地检测每个进程对各类资源的占用和申请情况,以及做 安全性检查 ,需要花费较多的时间。
死锁的检测
对资源的分配加以限制可以 预防和避免 死锁的发生,但是都不利于各进程对系统资源的充分共享。解决死锁问题的另一条途径是 死锁检测和解除 (这里突然联想到了乐观锁和悲观锁,感觉死锁的检测和解除就像是 乐观锁 ,分配资源时不去提前管会不会发生死锁了,等到真的死锁出现了再来解决嘛,而 死锁的预防和避免 更像是悲观锁,总是觉得死锁会出现,所以在分配资源的时候就很谨慎)。
这种方法对资源的分配不加以任何限制,也不采取死锁避免措施,但系统 定时地运行一个 “死锁检测” 的程序,判断系统内是否出现死锁,如果检测到系统发生了死锁,再采取措施去解除它。
进程-资源分配图
操作系统中的每一刻时刻的系统状态都可以用进程-资源分配图来表示,进程-资源分配图是描述进程和资源申请及分配关系的一种有向图,可用于检测系统是否处于死锁状态。
用一个方框表示每一个资源类,方框中的黑点表示该资源类中的各个资源,每个进程用一个圆圈表示,用 有向边 来表示进程申请资源和资源被分配的情况。
图中 2-21 是进程-资源分配图的一个例子,其中共有三个资源类,每个进程的资源占有和申请情况已清楚地表示在图中。在这个例子中,由于存在 占有和等待资源的环路 ,导致一组进程永远处于等待资源的状态,发生了 死锁。
进程-资源分配图中存在环路并不一定是发生了死锁。因为循环等待资源仅仅是死锁发生的必要条件,而不是充分条件。图 2-22 便是一个有环路而无死锁的例子。虽然进程 P1 和进程 P3 分别占用了一个资源 R1 和一个资源 R2,并且因为等待另一个资源 R2 和另一个资源 R1 形成了环路,但进程 P2 和进程 P4 分别占有了一个资源 R1 和一个资源 R2,它们申请的资源得到了满足,在有限的时间里会归还资源,于是进程 P1 或 P3 都能获得另一个所需的资源,环路自动解除,系统也就不存在死锁状态了。
死锁的检测步骤
知道了死锁检测的原理,我们可以利用下列步骤编写一个 死锁检测 程序,检测系统是否产生了死锁。
- 如果进程-资源分配图中无环路,则此时系统没有发生死锁
- 如果进程-资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
- 如果进程-资源分配图中有环路,且涉及到的资源类有多个资源,此时系统未必会发生死锁。如果能在进程-资源分配图中找出一个 既不阻塞又非独立的进程 ,该进程能够在有限的时间内归还占有的资源,也就是把边给消除掉了,重复此过程,直到能在有限的时间内 消除所有的边 ,则不会发生死锁,否则会发生死锁。(消除边的过程类似于 拓扑排序)
死锁的解除
当死锁检测程序检测到存在死锁发生时,应设法让其解除,让系统从死锁状态中恢复过来,常用的解除死锁的方法有以下四种:
- 立即结束所有进程的执行,重新启动操作系统 :这种方法简单,但以前所在的工作全部作废,损失很大。
- 撤销涉及死锁的所有进程,解除死锁后继续运行 :这种方法能彻底打破死锁的循环等待条件,但将付出很大代价,例如有些进程可能已经计算了很长时间,由于被撤销而使产生的部分结果也被消除了,再重新执行时还要再次进行计算。
- 逐个撤销涉及死锁的进程,回收其资源直至死锁解除。
- 抢占资源 :从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除。