操作系统读书笔记

PS:放在本地总是丢,传上来跟大家分享。看书做点笔记,我会不定时更新。写的不对或者有问题欢迎评论指正。

操作系统

并发:同一时间段多个事务执行。
「并行:同一时刻多个事务执行」
共享:资源被多个内存中并发执行的进线
程共同共同使用。
虚拟:通过时分复用和空分复用将一个物
理实体虚拟为多个。
异步:系统中的进程以走走停停的方式执
行,且以不可预知的速度推进。

中断异常和系统调用

​ 中断是由外设发出请求的,是异步执行,即发出请求后 不确定什么时候会执行。中断操作对用户是透明的,用户察觉不到中断的发生。
​ 异常是由应用软件发生不可预知的错
误时发出的,是同步执行,即异常发生后会立即执行。异常的处理有两种第一种是发生了某些错误,这样操作系统会直接杀死该程序。第二种是由于操作系统未达成某些条件而发生异常,这样操作系统会在处理完成后重新执行这条异常指令。
​ 系统调用是应用程序主动发出的请求,调用操作系统的函数完成作业。调用是同步过程,操作系统返回结果给应用程序是异步过程。也就是说操作系统处理完之后会以异步的形式通知应用程序我已经处理完了,至于应用程序什么时候响应该通知是未知的。
​ 中断和异常都有表,称为中断表和异常表,表中代号和处理地址对应存储。在发生中断和异常时,会去查表然后调到处理地址。
​ 中断在跳转之前先会添加中断标记,然后会保存当前状态,最后处理完中断会恢复现场,清除中断标记,继续执行。
​ 系统调用,Win32API用于Windows,posix用于Linux。JAVA语言调用API不算系统调用,在虚拟机底层才系统调用。程序在用户态控制权很低,当转到内核态时控制权最高,可以执行任何指令。不同内核有自己的栈,当系统调用时,会有状态的切换和内核栈的建立和切换,所以系统调用的代价远高于函数调用,但是和其安全性相比,是值得的。

操作系统的开销:
①系统调用时会从用户线程切换到内核线程,调用完毕又要切换回用户线程,随之会有堆栈的保存和切换的开销。
②有时候还要传递处理,系统调用不能传递引用,只能进行拷贝。这样代价很高
③应用软件是被操作系统所不信任
的,因此每次调用都会验证参数以防止其恶意破坏。
④需要建立中断/异常/系统调用号与对应服务例程的映射关系的初始化开销。
⑤刷新TLB和cache的开销。

内存管理:

​ 计算机的内存像金字塔,越靠近CPU的地方的内存速度越快,容量越小。
​ 计算机地址空间分为逻辑地址和物理地址。通过编码产生编码地址,然后通过编译器变成逻辑地址,在内存调用时会去查操作系统的表,逻辑地址和物理地址对应表。得到物理地址取出指令然后执行。
​ 外部碎片:程序之间的碎片存储空间。内部碎片:在程序单元中的未使用碎片空间。
​ 连续内存分配:
​ 首次分配,找到可以使用的就直接用。最优分配,找到最合适大小的在使用。最差分配,找到最大的去使用。
​ 压缩式碎片整理:通过拷贝程序改变其地址去整理碎片地址。将中间留有碎片空间的程序通过拷贝放到一起。拷贝只能在程序未执行时发生,还有拷贝的开销也很大。
​ 交换式内存整理:运行程序需要更多的内存,可以抢占等待的程序并且回收他们的内存。但换入换出开销很大。
​ 非连续内存分配:
​ 分段:将地址空间分成一个一个的内存块,分配给每个程序。每一个块的大小是不固定的。
​ 分页:将地址空间分成一个一个页帧,以页帧为单位将每页存储到物理内存的一块中,块和页的大小相等。这样可以将页表放置在任一块中,实现了离散分配。
​ 建立页表提供映射以方便查找。为了降低查询页表的开销,建立TLB快表,将近期常用的地址空间与物理空间的映射关系存放在快表中。当查询地址空间时先查询快表,如果快表中没有,则去访问页表,将查询到的结果存入快表。
​ 由于虚拟内存的大小都很大,根据虚拟内存的大小建立的页表也是巨大的。为了减小页表的大小,可以使用多级页表。
​ 反向页表也物理内存块编号为键,以虚拟内存编号为值,因为往往虚拟内存要大于物理内存。利用反向页表也可以节省空间开销。

虚拟内存
覆盖技术:按自身逻辑把程序分成几个功能上相对独立的模块,不会同时执行的模块可以共享同一块内存区域,按时间先后运行(分时)。缺点: (1) 设计开销,程序员要划分模块和确定覆盖关系,编程复杂度增加了 (2) 覆盖模块从外存装入内存,实际是以时间来换空间。
在这里插入图片描述

交换技术:将暂时不运行的进程移到外存,留出更大的内存空间给正在运行的程序。但由于硬盘速度很慢,所以要当内存确实不够用时进行。

虚存机制: 在分页、分段内存管理的硬件支持下,在装入程序时,只把当前需要执行的部分页或段装入内存,就可以开始执行;当执行到指令或数据不在内存上时(缺页、缺段异常),由处理器通知操作系统,若有空余空间则将相应的页面或段调入内存,继续执行; 另一方面,os将内存中暂时不用的页、段调出保存在外存上以腾出空间。

页面置换算法

1.最佳置换算法(OPT)(理想置换算法):从主存中移出永远不再需要的页面;如无这样的页面存在,则选择最长时间不需要访问的页面。于所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。

2.先进先出置换算法(FIFO):是最简单的页面置换算法。这种算法的基本思想是:当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。其理由是:最早调入主存的页面不再被使用的可能性最大。

belady现象:采用FIFO算法时,如果对—个进程未分配它所要求的全部页面,有时就会出现分配的页面数增多但缺页率反而提高的异常现象。

3.最近最久未使用(LRU)算法:这种算法的基本思想是:利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。

4. 时钟(CLOCK)置换算法

LRU算法的性能接近于OPT,但是实现起来比较困难,且开销大;FIFO算法实现简单,但性能差。所以操作系统的设计者尝试了很多算法,试图用比较小的开销接近LRU的性能,这类算法都是CLOCK算法的变体。

简单的CLOCK算法是给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置为1;当该页随后再被访问到时,它的使用位也被置为1。对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;如果所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。由于该算法循环地检查各页面的情况,故称为CLOCK算法,又称为最近未用(Not Recently Used, NRU)算法。

5.二次机会法**

与CLOCK算法相比,多了一位dirty 位,表示写数据。如果写,则为1,否则为0.同时使用脏位和用位。和CLOCK算法一样,用指针与之关联,两个位都为0才可以被淘汰,否则将不为0的置零。

6.最不常用算法(least frequently used)LFU
选择置换访问次数最少的那个页面
对每个页面设置访问计数器,每当一个页面被访问时,++。淘汰数值最小的那个。
硬盘计数器空间开销,排序查找时间开销;

全局页面置换算法

工作集(working set):一个进程当前使用的逻辑页面集合

常驻集:在当前时刻,进程实际驻留在内存当中的页面集合。

工作集缺页置换算法W(t, Δ),将相当于一个滑动窗口。t为当前执行时刻。Δ为窗口大小。随着程序的执行,窗口一直滑动,发生缺页中断时,不在窗口的页面则换出去。

缺页率置换算法:将工作集页面置换算法的窗口大小变为可变的。由缺页率来决定(缺页率=缺页次数/内存访问次数
=1/缺页的平均时间间隔 )若缺页率高则增加工作集(Δ)来分配更多物理页面,若过低则减少工作集来减少其物理页面。使缺页率保持在一个合理的范围内。各个程序之间保持一个平衡。

抖动问题(thrashing): 如果分配给一个进程的物理页面太少,常驻集远小于工作集,则缺页率会很大,频繁在内外存之间替换页面,使进程的运行慢,这种状态成为”抖动”。

进程

1.进程的定义:一个具有一定独立功能的程序在一个数据集合上一次动态执行的过程。只有当一个程序被OS加载到内存中,CPU对其执行时,这个过程是动态的,称为进程。

2.进程的组成:包含了正在运行的程序的所有状态信息。

​ 进程与程序的联系:程序是进程的基础,代码控制操作,可以多次执行程序,每次构成不同的进程。

​ 进程与程序的区别:程序静态,有序代码的集合;进程动态,执行中的程序。

3.进程的特点:动态性:进程是动态的,是处于运行状态的程序。

​ 并发性:同一时间有多个进程在执行。

​ 独立性:正确性不受影响,操作系统会给每个进程分配不同的页表。

​ 制约性,因访问共享数据/资源或进程间同步产生制约,要同步互斥;

在这里插入图片描述
4.进程控制结构:

​ 描述进程的数据结构:进程控制块,PROCESS control block PCB 。OS给每个进程都维护了一个PCB,保存与之有关的所有状态信息。

​ PCB进程控制块:进程存在的唯一标识,操作系统管理控制进程运行所用的集合信息,描述进程的基本情况和运行变化的过程。用PCB的生成,回收,组织管理来实现进程的创建,管理和终止。

​ PCB的组织方式:
​ 链表(便于插删,用于通用的OS):同一状态的进程PCB为一链表,多个状态对应更多个不同的链表,就绪链表,阻塞链表
​ 索引表(数组,不利于插删,使用于固定数目的进程,相对创建更快捷):同一状态的归入一个index表(每一个index指向PCB),就绪/阻塞索引表。

5.进程状态:

​ 进程的生命周期:创建,运行,等待,唤醒,结束。

​ 创建:①系统初始化时,创建INIT进程,INIT进程再负责创建其他进程。

​ ②用户请求创建一个新进程。

​ ③正在运行的进程执行了创建进程的系统调用。

​ 运行:OS内核选择一个就绪的进程,让其占用CPU并执行。

​ 等待(阻塞):①请求并等待系统服务,无法马上开始。

​ ②启动某种操作(和其它进程协调工作),无法马上完成。

​ ③需要的数据没有达到(控制台输入)。

​ 唤醒:需要的资源被满足,等待的事件到达,都意味着可将该进程的PCB插入到就绪队列。因为自身没有占用CPU,所以只能等待OS或者其他进程唤醒。

​ 结束:自愿结束(正常退出,错误退出),强制性退出(致命错误,如访问了不该访问的内存。被操作系统或其他进程杀死)。

6.进程状态变化:

​ 运行态(running):正在占用CPU并执行。

​ 就绪态(ready):一切准备工作都已经做好,等待CPU执行。

​ 阻塞态(blocked):等待资源。

​ 创建态(new):已创建还没就绪。

​ 结束态(exit):正在从OS中消失,PCB还在。
在这里插入图片描述

7.进程挂起:

​ 挂起:将进程放到硬盘上,不占用内存空间。

​ 解挂:将硬盘上的进程拿到内存来运行。

8.进程的上下文切换

​ 就是从一个进程切换到另一个进程。切换时要保存一系列的信息,以便后边还能切换回来(寄存器,CPU状态等)。

9.进程的僵尸状态:(Zombie)

​ 快要死了,但是还没死。不能自己回收自己,只能等待父进程来回收。
在这里插入图片描述

线程

1.线程定义:进程中的一条执行流程。

​ 进程=线程+资源管理

​ 资源管理:包括地址空间(代码段,数据段),打开的文件等的资源平台(环境)等 。

​ 一个进程所拥有的线程共用进程的资源。

2.多线程:在进程空间内有多个控制流且执行流程不一样,有各自独立的寄存器和堆栈,但共享代码段,数据段,资源。

3.线程与进程的比较

​ ①进程是资源分配单元(内存,打开的文件,访问的网络等),线程是CPU调度单位。

​ ②进程拥有一个完整的资源平台,而线程只独享必不可少的资源(寄存器,栈)

​ ③线程同样具有就绪,阻塞和执行三种基本状态和转换关系。

​ ④线程能减少并发执行的时空开销:
线程的创建时间、终止时间、同一进程内线程的切换时间都更短,因为进程要创建一些对内存和打开的文件的管理信息,而线程可以直接用所属的进程的信息,因为同一进程内的线程有同一个地址空间,同一个页表,所有信息可以重用,无失效处理。而进程要切页表,开销大,访问的地址空间不一样,cache,TLB等硬件信息的访问开销大。另外线程的数据传递不用通过内核,直接通过内存地址可以访问到,效率很高。

4.线程的三种实现方式:

​ ①用户线程:在用户空间实现,OS看不到,由应用程序的用户线程库来管理。用户线程和内核线程间,可以是一对一,一对多,多对多的关系。

​ 优点:开销小,调度灵活,便于管理。

​ 缺点:一个线程挂了,整个进程都会停。

​ ②内核线程,OS内核来完成线程的创建终止和管理。OS的调度单位不再是进程而是线程。进程主要完成资源的管理。缺点:开销太大,每次切换都要完成一次用户态到内核态的变化。

​ ③轻量级进程:内核支持的用户线程。一个进程有一个或多个轻量级进程,每个轻量级进程由一个单独的内核线程来支持。

CPU调度**:

1.批处理系统调度算法:

重要指标(吞吐量, 周转时间, CPU利用率, 公平平衡)

​ ①**非抢占式的先来先服务(FCFS):**按照进程就绪的先后顺序使用CPU。

​ 特点:公平,实现简单,但是长进程后边的短进程需要等很长时间,这样总体的周转时间会很大。不利于用户体验。

​ ②非抢占式的最短作业优先(SJF):具有最短完成时间的进程优先执行。

​ ③最短剩余时间优先(SRTN):SJF抢占式版,即当一个新就绪的进程比当前运行进程具有更短的完成时间,系统抢占当前进程,将当前正在运行的进程挂到就绪态,选择新就绪的进程执行。

​ 特点:改善短作业的周转时间,如果源源不断有短任务到来,可能使长的 任务长时间得不到运行,产生饥饿现象。

​ ④非抢占式的最高响应比优先(HRRN):计算出每个进程的响应比R,之后总是选择R最高的进程执行。

​ 响应比R = (等待时间+ 执行时间) / 执行时间(预估)

2.交互系统中采用的调度算法:

重要指标(响应时间, 公平平衡)

​ ①时间片轮转调度算法:每个进程被分配一个时间片,允许该进程在该时间段运行。如果在时间片结束时该进程还在运行,则挂到就绪态并将CPU分配给另一个进程。如果该进程在时间时间片结束前阻塞或结束,则CPU立即进行切换。

  • 当时间片太长时,其降级为FCFS算法,引起对短的交互请求响应时间长。

  • 当时间片太短时,会导致频繁切换进程,浪费CPU时间。

  • 通常选择为20ms~50ms。

    虚拟轮转法:

  • 主要基于时间片轮转法进行改进,解决在CPU调度中对于I/O密集型进程的不友好。

  • 其设置了一个辅助队列,对于I/O密集型进程执行完一个时间片后,则放进辅助队列,CPU调度时总是先检查辅助队列中是否村在完成I/O操作的进程。

  • 如果不为空总是优先调度辅助队列里的进程,直到为空,才调度就绪队列的进程。

​ ③**最高优先级调度算法:**选择优先级最高的进程优先执行。

  • 优先级可以静态不变,也可以动态调整。
  • 优先数决定优先级
  • 就绪队列可按照优先级组织
  • 实现简单,但不公平,可能导致优先级低的进程产生饥饿现象
  • 可能产生优先级反转问题(基于优先级的抢占式算法),即一个低优先级进程持有一个高优先级进程所需要的资源,使得高优先级进程等待低优先级进程运行。

​ ④多级反馈队列调度算法:

  • 设置多个就绪队列,并为各队列赋予不同的优先级。
  • 第一个队列的优先级最高,依次递减。对于各个队列进程执行时间片的大小也不同,优先级越高的队列,分配到的时间片越少。
  • 当第一级队列为空时,再第二级队列进行调度,依次类推,各级队列按照时间片轮转方式进行调度。
  • 当一个进程创建后,首先把它放入第一队列的队尾。按照FCFS原则排队等待调度。当轮到该进程执行时,如果它在该时间片完成,便可准备撤离系统,如果第一个时间片结束时尚未完成,则将该进程转入第二队列的队尾,再同样的按照FCFS等待调度执行。

​ ⑤**公平共享调度算法(FFS):**多用户共享一台计算机,每个用户的进程是不同的,在用户级别公平调度。

​ 例如:linux的CFS:定义了一种新的模型,它给cfs_rq(cfs的run queue)中的每一个进程安排一个虚拟时钟,vruntime。如果一个进程得以执行,随着时间的增长(也就是一个个tick的到来),其vruntime将不断增大。没有得到执行的进程vruntime不变。
​ 而调度器总是选择vruntime跑得最慢的那个进程来执行。这就是所谓的“完全公平”。为了区别不同[优先级]的进程,优先级高的进程vruntime增长得慢,以至于它可能得到更多的运行机会。

linux内核分析——CFS(完全公平调度算法)

3.实时系统调度算法:

​ 正确性依赖于其时间和功能两方面的一种操作系统。

性能指标(时间约束的及时性, 速度和平均性能相对不重要)

​ 1.强实时系统:需要在保证的时间内完成重要的任务,必须完成

​ 2.软实时系统:要求重要的进程的优先级更高,尽量完成,并非必须

①速率单调调度(RM):

  • 最佳静态优先级调度
  • 通过周期安排优先级
  • 周期越短优先级越高
  • 执行周期最短的任务

②最早期限调度(EDF):

  • 最佳的动态优先级调度
  • Deadline越早优先级越高
  • 执行Deadline最早的任务

同步的一些概念:

原子操作(atomic operation):只一次不存在任何中断或失败的执行,要么成功要么没执行。实际操作往往不是原子操作,甚至单个机器指令都不一定是原子的。

临界区(critical section):进程中访问共享资源的代码区域,当另一个进程处于相应代码区时便不会执行。

互斥(mutual exclusion) : 任意时刻只能有一个进程进入临界区访问。当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区,并访问任何相同的共享资源。

死锁(Dead lock): 多个进程互相等待完成特定任务而导致均无法完成自身任务。

饥饿(starvation): 一个可执行的进程长期等待执行,被调度器持续忽略。

临界区的特点:
  • ​ 互斥:同一时间临界区中最多存在一个进程。

  • ​ progress:如果一个线程想要进入临界区,那么它最终会成功。

  • ​ 有限等待:如果一个线程i处于入口区,那么在i的请求被接受之前,其他进程进入临界区的时间是有限制的。

  • ​ 无忙等待(可选):如果一个进程在等待进入临界区,那么在它可以进入之前可以被挂起。

    **三种满足性能的方法:**禁用硬件中断,基于软件,更高级的抽象。
    1. 禁用硬件中断:没有中断,就没有进程上下文切换,没有并发。减少不确定性,进入临界区禁用中断,退出后再开启。

      问题: ①一旦禁用中断,线程将无法停止。整个系统都会为你停下来,可能导致其他线程处于饥饿。

      ​ ②要是临界区太长,无法限制响应中断所需的时间,可能有硬件影响。一般都用于短的临界区。

      ​ ③多CPU下存在问题。一个CPU只能屏蔽自身,其余CPU任然可以产生中断。

      2.基于软件:共享数据项。

      ​ Peterson算法:

      int turn = 0;
      bool flag[2] = {false, false};
      do {
      	/** 进入临界区*/
      	flag[i] = true;
      	turn = j;
      	while(flag[j] && turn == j);
      	...
      	/** 退出临界区 */
      	flag[i] = false;
      } while(1);
      

      ​ Bakery算法:N个进程的临界区。

      • ​ 进入临界区之前,进程接受一个数字
      • ​ 得到的数字最小的进入临界区
      • ​ 如果进程Pi和Pj收到相同的数字,那么如果i < j,Pi先进入临界区,否则Pj先进入临界区
      • 编号方案总是按照枚举的增加顺序生成数字

      ​ **问题:**复杂:需要共享数据。

      ​ 耗资源:需要忙等,浪费CPU。

      ​ 没有硬件保证的情况下,没有真正的软件解决方案。

    3.基于硬件原子操作的高层抽象实现

    硬件提供了一些原语,像中断禁用,原子操作指令等。

    操作系统提供更高级的编程抽象来简化并行编程,例如:锁,信号量。

​ 锁是一个抽象的数据结构。是一个二进制状态(锁定/解锁)。

​ Lock::Acquire() - 锁被释放前一直等待,然后得到锁。

​ Lock::Release() - 释放锁,唤醒任何等待的进程。

被封装的原子操作:

​ ①Test-and-Set 测试和置位

  • 从内存中读取值

  • 测试该值是否为1(然后返回真或假)

  • 内存值设为1

    实现机理:

    boolean TestAndSet (boolean *target) {
    	boolean rv = *target;
    	*target = TRUE;
    	return rv;
    }
    

    举例子:

    class lock { 
    	int value = 0; 
    	waitqueue q;
    }
    
    lock::acquire() {
    	while (test-and-set(value)) {
    		/** 添加该线程到等待队列 */
    		schedule();
    	}
    	/** 执行临界区 */
    }
    
    lock::release(){
    	value=0;
    	/** 将该线程从等待队列删除 */
    	wakeup(t);
    }
    

    ​ ②exchange 交换

​ 交换内存中的两个值。

​ 实现机理:

void Exchange (boolean *a, boolean *b) {
 	boolean temp = *a;
 	*a = *b;
 	*b = temp;
}

​ 举例子:

/** 共享变量 */
int lock = 0;

/** 线程T */
int key;
do {
	key = 1;
	while (key == 1) {
		exchange(lock, key);
	}
	/** 执行临界区 */
	...
	
	/** 退出临界区 */
	lock = 0;
}

实现简单,易扩展到多临界区,开销小,适用于单处理器或共享主存的多处理器中任意数量的进程。广泛使用。
在这里插入图片描述

信号量,管程

1.信号量(Semaphore)

​ 一个整形int (sem),可以进行两个原子操作。

  • P()操作:将信号量的值减一,如果小于0阻塞,否则继续。

  • V()操作:将信号量的值加一,如果小于等于0唤醒挂起的线程。

    特征:信号量是整数,有符号,一般初始值大于0.一旦小于0就需要挂起不能继续。必须等待其他线程执行V操作才能唤醒。通常使用FIFO算法,较为公平。信号量是被保护的变量,初始化完成后只能通过P() V()这两个原子操作改变值,P操作会阻塞,V不会

2.信号量实现

class Semaphore {
	int sem;
	Waitqueue q;
}

Semaphore::P() {
	sem--;
	if(sem<0){
		Add this thread t to q;   
		block(t);
	}
}

Semaphore::V() {
	Sem++;
	If(sem<=0) {
		Remove a thread t from q;   
		wakeup(t);
	}
}

3.管程(monitor)

​ 包含了一系列的共享变量,以及针对这些变量的操作的函数的组合/模块

  • ​ 一个锁:指定临界区,确保互斥性;

  • 0或者多个条件变量:等待/通知信号量用于管理并发访问共享数据

由于信号量的操作分散,难以控制,读写和维护都很困难。因此就提出了一种集中式同步进程——管程。管程将共享变量和对他们的操作都集中在一个模块中,操作系统或并发程序就是由这样的模块构成。

​ 特征:

  1. 模块化。管程是一个基本程序单位,可以单独编译。
  2. 抽象数据类型。管程中不仅有数据,还有对数据的操作。
  3. 信息掩蔽。管程外可以调用管程内定义的一些函数,但函数的具体实现对外不可见。

同一时刻只能有一个线程访问管程,因此需要锁机制保证其互斥性。获取锁后进入执行。管程中有许多共享变量和资源,执行过程中某个条件得不到满足,就将该线程挂起到该共享变量维持的队列中等待,并且将锁释放掉。别的线程得到锁继续访问,当条件满足之后被唤醒继续执行。

死锁

死锁:一组阻塞的进程(两个或多个),持有一种资源,等待获取另一个进程所占有的资源,而导致谁都无法执行。

**死锁的特征:**四个的必要条件

  • 互斥
  • 持有并等待
  • 无抢占,资源只能被进程自愿释放
  • 循环等待,形成闭环

死锁处理办法:

  • Deadlock Prevention (死锁预防)

    打破死锁出现的条件

    1. 互斥:占用非共享资源 会增加不确定性 不推荐
    2. 占用并等待:保证当一个进程请求资源时,不持有任何其他的资源 all or nothing 需要进程请求并分配其所有资源,资源利用率低,可能饥饿
    3. 无抢占:允许抢占占有某些资源的进程
    4. 循环等待:对所有资源类型进行排序,并要求每个进程按照资源的顺序进行申请,会出现资源利用不够
  • Deadlock Avoidance (死锁避免)

    当进程运行过程中,根据申请资源的情况判断会不会死锁,如果会就不给资源

    银行家算法

    进程数量n,资源类型数量m

    Max 总需求量 n*m矩阵 Max[i, j] = k表示进程Pi最多需要资源类型Rj的数量为k

    Available 剩余空闲量 长度为m的向量,如果available[j]=k 则有k个类型Rj的资源实例可用
    Allocation 已分配量 n*m矩阵 allocation[I, j]=k 进程Pi已经分配了资源类型Rj的k个实例

    Need 未来需要量 n*m矩阵 need[i, j]=k 进程Pi可能需要资源类型Rj的k个实例

    Max[i, j]=need[I,j]+allocation[i,j]

    在这里插入图片描述

    寻找Need比Available 小的线程,执行完之后释放其Allocation 。

  • Deadlock Detection (死锁检测)

  • Recovery from Deadlock (死锁恢复)

    通过杀死所有死锁进程,在一个时间内终止一个进程直到死锁消除等方式完成,都干扰了进程的正常运行,都有强制性。
    多数情况下,就是重启

进程间通信IPC(Inter Process Communication)

直接通信:

  1. 进程必须正确的命名对方
    • send( P, message) - 发送信息到进程P
    • receive(Q, message)-从进程Q接收消息
  2. 通信链路的属性
    • 自动建立链路
    • 一条链路恰好对应一对通信进程
    • 每对进程之间只有一个链接存在
    • 链接可以是单向的,但通常为双向的

间接通信:为了实现间接通信,要发送到共享区,发送方和接收方都不关注具体的另一方是谁

  1. 定向从消息队列接收消息:
    • 每个消息队列都有一个唯一的ID
    • 只有他们共享了一个消息队列,进程才能通信
  2. 通信链路的属性:
    • 只有进程共享一个共同的消息队列,才建立链路
    • 链接可以与许多进程相关联
    • 每对进程可以共享多个通信链路
    • 连接可以是单向或者双向
  3. 操作:
    • 建立一个新的消息队列
    • 通过消息队列发送和接收消息
    • 销毁消息队列
  4. 原语的定义如下:
    • send(A, message) – 发送消息到队列A
    • receive(A, message) - 从队列A接受消息

IPC的常用手段:信号,管道,消息队列,共享内存

  1. 信号

    • 硬件中断interrupt,信号是软件中断,通知有事件要处理 ,应用程序会catch,默认停下来处理信号,特殊处理函数

    • examples: SIGFPE, SIGKILL, SIGUSRI, SIGSTOP, SIGCONT

  2. 接收到信号时会发生什么

    • catch : 指定信号处理函数被调用
    • ignore: 依靠操作系统的默认操作 , 如abort, memory dump, suspend or resume process
    • mask: 闭塞信号因此不会传送(可能是暂时的,当处理同样类型的信号)
  3. 不足:不能传输要交换的任何数据

  4. 效率很高,异步。一般处理完后会回到被打断的进程

管道:用于数据交换,与信号不同。理解为内存中的一个buffer

消息队列: 管道必须有父进程,数据是字节流,没有数据结构。消息队列可以多个不相干的进程来传递数据,而且message作为一个字节序列存储,message queues是消息数组。是一个有意义的结构化

共享内存: 直接的方式。不用系统调用send&receive,数据量最大,最快。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值