一.进程与线程
1.进程的概念与特征
-
问题一:为什么要引入进程的概念?
多道程序运行的背景下,允许多个程序并发执行,从而程序失去封闭性、不可间断性的特征,为此引入进程,以便更好地描述和控制程序的并发执行,实现操作系统的基本特性。
-
问题二:什么是进程及进程是由什么组成的?
①进程(映像)的组成:程序段、数据段、进程控制块(PCB)引入进程控制块的原因在于为了使参与并发执行的程序(含有数据)能够独立运行,故配置一个专门的数据结构PCB
进程映像即为进程实体,进程实体是静态的,而进程是动态的。
②进程的定义:(不同角度下进程定义不同)
-
进程是程序的一次执行过程
-
进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位
-
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位
③进程的特征:
性质 说明 动态 基本的特征,程序的一次执行,具有生命周期 并发 重要特征,多个进程实体可以同存于内存中,能在一段时间内同时运行 独立 只有建立了PCB的程序才能作为独立单位运行、获得资源、接受调度 异步 进程执行间,各自按照独立、不可预知的速度向前推进,结果不可再现,需要进程同步机制 结构 每个进程都配备一个PCB进行描述,每个进程实体均由程序段、数据段和进程控制段组成 -
-
问题三:进程是怎样解决问题的?
进程把能够识别程序运行状态的一些变量存放在PCB中,通过这些变量系统能够更好地了解进程的状态,并在适当的时候进行进程的切换,避免一些资源的浪费。甚至划分了一个更小的调度单位——线程,来提高系统的并发度。
用“人生历程”类比“进程”,人是经历人生的主体,进程实体(进程映像)是经历进程过程的主体;人由很多组成,人的身份信息是唯一标识;进程实体有三部分组成功,PCB是进程的唯一标识;人在一生会有很多不同的阶段,或失意或昂扬;进程在运行中也会经历阻塞、就绪、运行等不用状态。
2.进程的状态与转换
-
进程的状态
状态 说明 运行 进程正在处理机上运行 就绪 进程准备运行,已经获得了除处理机的所有资源 阻塞 进程正在等待某一事件而暂停运行,等待某资源(不包括处理机)为可用或等待IO完成 创建 正在创建过程中:创建空PCB>>向PCB中写入控制和管理进程的信息>>由系统为进程分配运行时所需要的资源>>一切完成,可转入就绪态 结束 进程正常结束或因一些原因中断退出;结束:置进程为结束态>>进一步处理资源释放和回收 -
进程状态之间的切换(行到列转换)
状态 运行 就绪 阻塞 创建 结束 运行 / 时间片用完或更高优先级进程进入 进程请求资源或等待IO / 退出 就绪 处理机调度成功 / / / / 阻塞 / 等待事件到来,IO操作结束或中断 / / / 创建 / 所有资源分配完成 / / / 结束 / / / / / [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhfE57Cr-1592922967751)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20200619164623056.png)]
3.进程控制
- 进程创建
- 相关说明:一个进程允许创建另一个进程,分别称为父/子进程,子进程继承父进程的资源;子进程撤销时将所有从父进程处获得的资源归还;撤销父进程时要连带撤销其所有子进程。
- 引起进程创建的情景:用户登录/作业调度/系统提供服务/用户的应用请求
- 创建过程:
a.为新进程分配一个唯一的进程标识符,并申请空白的PCB,若申请失败,则创建失败,结束;
b.为新程序分配资源(进程的程序、数据和用户栈所必要的内存空间),并在PCB中进行标记体现。若内存空间不足申请资源失败,进程将处于阻塞状态,等待内存资源;
c.初始化PCB(标志信息,处理机状态信息和处理机控制信息,进程优先级)
d.将PCB插入到就绪队列中,等待调度执行
-
进程终止
-
引起进程终止的情景:
正常结束:进程任务完成
异常结束:存储区越界、非法指令、特权指令错、IO故障
外界干预:操作员或操作系统干预、父进程请求、父进程终止
-
终止过程
a.根据被终止进程的进程标识号,检索PCB,从PCB中读出进程当前状态 b.若进程处于执行状态,则终止进程的运行,将处理机资源分配给其他进程 c.若该进程有其他子进程,将子进程也全部终止 d.将该进程所拥有的资源归还给OS或者该进程的父进程 e.将该进程的PCB从所在队列中删除
-
-
进程的阻塞和唤醒
-
阻塞的情景:
正在执行的进程因请求系统元失败,等待某个操作的完成,新数据尚未到达,无新工作可做,由系统自动执行,因此只有运行态的进程(获得了处理机资源)才可能转换为阻塞态
-
阻塞的原语过程:
a.通过该进程的标识号,找到该进程对应的PCB b.保护正在执行的进程的现场,将该进程的状态置为阻塞,停止运行 c.将其PCB插入到相应事件的等待队列中去
-
唤醒的情景:
期待的事件到来,IO操作已完成,期待的数据到达,则由相关的进程调用Wakeup原语
-
唤醒的原语过程:
a.在该到来的事件的等待队列中找到相应进程的PCB b.将其从等待队列中移出,并置状态为就绪状态 c.把PCB插入到就绪队列中,等待调度
-
-
进程切换
- 处理机模式切换:模式切换指的是处理机在用户态和核心态之间的切换,处理机逻辑上可能在同一进程上运行,进程的环境信息可能也并未改变。
- 调度与切换:调度是一种决策行为,表示判断应该怎样行为;切换是一种执行行为,是实际对进程进程切换和改变;先有资源的调度,再有进程的切换。
- 进程切换:处理机从一个进程转到另一个进程上运行,进程的运行环境发生了变化,过程如下——
- 保存处理机的上下文,包括程序计数器和其他寄存器
- 更新当前PCB信息
- 把进程的PCB移入到相应的队列(就绪、阻塞)
- 选择另一个进程执行,更新它的PCB
- 更新内存管理的数据结构
- 恢复处理机上下文
4.进程的组织(结构)
-
进程控制块
-
系统通过PCB了解进程的运行状态信息——PCB内包含有进程描述信息、进程控制和管理信息、资源分配信息和处理机相关信息(详见后文)
-
系统通过PCB对进程进行控制和管理:“前文在讲到使用原语对进程进行一些操作的时候,通常都需要处理PCB相关信息”
-
PCB包含的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SUW1k7DB-1592922967781)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20200620211212796.png)]
-
-
程序段:进程调度程序调度到CPU进行运行的那一部分程序代码,程序可以被多个进程进行共享。
-
数据段:进程对应的程序加工处理的原始数据/程序执行时产生的中间或最终结果
5.进程的通信
-
共享存储
-
共享存储的分类:
- 低级方式:基于数据结构的共享
- 高级方式:基于存储区的共享;os值负责为进程提供使用的存储空间和同步互斥工具(PV信号量操作),用户自己安排读写指令来进行数据交换。
-
关于共享存储的理解:
-
字面意思:就是多个进程通过划分一段大家都可以进行数据操作的空间,在该空间中进行操作进行数据交换
-
注意:!!用户进程空间一般是独立的,进程运行期间不可以访问其他进程的空间,因此共享空间的实现基于特殊的系统调用来实现。
-
具象模型:综合上述两条,所谓共享存储,就是在A和B之间放一个麻袋,A和B可以通过麻袋进行物品交换,但不能直接拿对方手上的东西。
-
-
-
消息传递
- 基本概念:消息传递机制中进程间的数据交换是以格式化的消息为单位的,借助操作系统提供的发送/接受消息原语进行数据交换
- 通信方式的类型
- 直接通信方式:发送进程直接把消息发送给接收进程,并挂在接收进程的消息缓冲队列上
- 间接通信方式:发送进程把消息发送给某个中间实体中,接受进程从中间实体中取得消息;中间实体一般称为信箱
-
管道通信
- 管道通信的基本概念:管道是一个连接读写进程以实现通信的“共享文件(pipe文件)”。写进程以字符流的形式将大量数据写入管道;读进程从管道中读数据。
- 管道所具有的特性和能力:互斥、同步以及确定对方进程的存在——管道本质也是一个文件,但是其有一些适合进行通信的特性:
- ①限制大小(4KB for Linux),使其不会不加检验地增长。
- ②因为读写进程工作的快慢差异以及管道本身大小具有限制,当读空管道时操作会阻塞,写满管道时亦同。
6.线程与多线程模型
-
线程的基本概念
-
背景介绍:
引入进程——更好地使多道程序并发执行,提供资源利用率。
【存在的问题:进程创建以及进程间切换时的开销巨大】
引入线程——减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
-
定义:线程是“轻量级的进程”,是一个基本的CPU执行单元,也是程序执行流的最小单元。
-
构成:线程ID、程序计数器、寄存器集合和堆栈。
-
特点:
①线程是进程中的一个实体,是被系统独立调度和分派的基本单位
②线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它与和它同属于一个进程中的所有其他线程共享进程拥有的全部资源
③线程也具有就绪、阻塞和运行三种状态。
-
-
线程 vs. 进程
比较方面 传统os 拥有线程的os CPU调度基本单位 进程 线程 资源分配的基本单位 进程 进程 并发性 进程间 进程间
线程间进/线程切换时的系统开销 CPU环境的保存和新CPU环境的设置 保存和设置少量寄存器内容 地址空间资源 进程间空间相互独立 同一进程内的线程共享进程的资源 进/线程通信 需要进程同步和互斥手段的辅助,系统的支持 线程间可以直接读写进程数据段(如:全局变量) -
线程的属性
- 轻型实体,不拥有系统资源,具有唯一的标识符和线程控制块(记录了寄存器和栈等状态)
- 不同线程可以执行相同程序
- 同一进程内线程共享进程内的资源
- 当多CPU运行时,同一进程内的各个线程占用不用的CPU,可以缩短进程处理事件
- 线程具有生命周期,会经历阻塞态、就绪态和运行态
-
线程的实现方式
-
用户级线程:有关线程管理的所有工作(生成、切换、撤销)都应由应用程序来完成,应用程序可以通过使用线程库设计成多线程程序。
-
内核级线程:线程管理的所有工作均由内核完成,应用程序值有一个到内核级线程的编程接口。
-
组合方式:线程创建、调度和同步在用户控件完成,但是多个用户级线程可以被映射到一些内核级线程上。
-
-
多线程模型
模型(用户-内核) 优点 缺点 多对一模型 进程管理效率高 多个线程不能在多处理机上并发执行 一对一 并发能力强 进程创建开销大 多对多(n-m,m≤n) 对上述两种方法的折中 集两者之所长
二.处理机调度
1.调度的概念
- 调度:在多个进程争用处理机的过程中,处理机调度即为对处理机进行分配,从就绪队列中,按照一定的算法(公平、高效)选择一个进程并将处理机分配给该进程运行,以实现进程并发地执行。
- 三级调度的层次
- 作业调度:(高级调度)从外存上处于后备状态的作业中选择作业进入内存,需要分配内存、IO设备等必须的资源,建立相应的进程,使他们获得竞争处理机的权利。次数少。
- 中级调度:(内存调度)将内存中暂时无法运行的程序调至外存挂起;将外存上已具备运行条件的就绪进程唤醒后,调入内存放在就绪队列上等待。
- 进程调度:(低级调度)按照某种方法和策略从就绪队列中选取进程,将处理机分配给它。频率高。
2.进程调度方式
进程调度方式:当某一个进程正在处理机上执行,如果有某个更为重要或紧迫的进程需要处理,即优先级更高的进程进入就绪队列,处理机的分配策略。
-
非剥夺式(非抢占式):当一个进程正在处理机上执行时,即使有某个更为紧迫或重要的进程进入就绪队列,仍然让当前进程继续执行,直到该进程完成或发生某种事件进入阻塞状态。
- 实现简单,系统开销小
- 不能用于分时系统和实时系统
-
剥夺式(抢占式):一个进程正在处理机上执行,若有更为紧迫的进程需要使用处理机,则立即暂停当前进程
- 系统吞吐率和相应效率得到提高
- 剥夺也需要遵循一定的原则:优先权、短进程优先和时间片原则
3.典型的调度算法
-
调度算法的评价指标
评价指标 含义 CPU利用率 / 系统吞吐量 单位时间内CPU完成作业的数量 周转时间 作业提交到作业完成所经历的时间 等待时间 进程处于等待处理机的状态的时间之和 响应时间 用户提交请求到首次产生响应所用的时间 -
典型的调度算法
-
考虑因素较为单一的一些调度算法
调度算法 应用类型 机制 特点 其他 先来先服务(FCFS) 作业调度
进程调度选择最先进入队列的项 算法简单,效率较低
长作业有利
CPU繁忙型作业有利不可剥夺算法 短作业/进程优先(SJF/SPF) 作业调度
进程调度选择估计运行时间最短的作业或进程 长作业不利,可能会出现饥饿现象
不能保证紧迫性优先
运行时间长短具有主观性不可剥夺算法
SJF的平均等待事件、平均周转时间最少优先级调度 作业调度
进程调度从后备队列中选择优先级最高的作业或进程 优先级反映的就是作业的紧迫程度 具有不同子类 优先级调度算法的子类分析:
子类一
1.非剥夺式优先级调度算法
2.剥夺式优先级调度
子类二
1.静态优先级:在进程创建的时候确定优先级,且整个进程的运行期间保持不变
2.动态优先级:进程运行过程中,根据进程情况动态调整(占用CPU的时间长短、就绪进程等待CPU时间的长短)
-
对多个因素综合考虑的调度算法
- 高响应比优先调度算法(作业调度)
- 综合了FCFS和SJF,同时考虑作业的长短和作业的等待事件
- 响应比Rp = (等待时间+要求服务时间)/要求服务时间
- 机制:每次进行作业调度的时候,计算后备作业队列中每个作业的响应比,选出最高响应比的项投入运行
- 特点:等待事件相同时,短作业优先;作业时长相同时,先来先服务;克服了长作业的饥饿现象
- 时间片轮转(进程调度)
- 综合考虑了先来先服务原则以及进程的响应时间和等待时间
- 机制:按照FCFS依次给进程分配时间片,但是每一个进程一次仅能运行一个时间片,被剥夺的进程返回就绪队列的队尾重新排队
- 特点:剥夺式调度;时间片的长短对系统性能影响大,时间片大退化成FCFS,时间片小系统开销大。
时间片影响因素:系统的响应时间、就绪队列中的进程数目、系统的处理能力
- 多级反馈队列调度
- 同时考虑到了系统的吞吐量,IO设备利用率,响应时间等
- 机制:按照作业时间长短等设置多个就绪队列,每个队列赋予不同的优先级,第1级队列优先级最高,往下次之;各个队列的时间片长短不一,优先级越高时间片越短;新加入的进程,先放在第1级上,在每一个队列上进行FCFS的调度,时间片用完则该进程降到下一级队列的队尾;最后在最后一级队列上进行时间片轮转直到所有进程运行完
- 抢占式:当较低级队列上有新进程进入,被抢占的进程放到它所属队列的末尾
- 特点:对于不同环境有不同的效果——终端型作业用户:短作业优先;短批处理作业用户:周转时间短;长批处理作业用户:不会出现饥饿现象,因为进程在前几个队列中得到部分执行
- 高响应比优先调度算法(作业调度)
-
三.进程同步
1. 进程同步的基本概念
-
临界资源:一次仅允许一个进程使用的资源,比如说物理设备:打印机;变量、数据等;
临界区:访问临界资源的那段代码 -
临界资源的访问过程:
- 进入区:检查可否进入临界区,可则设定正在访问临界区的标志
- 临界区:访问临界资源的那部分代码
- 退出区:将正在访问临界区的标志清除
- 剩余区:代码中的其余部分
-
同步
直接制约关系,指为了完成某种任务而建立的多个进程为了协调互相之间的工作次序而等待、传递信息。
进程间的直接制约关系源于它们之间的相互合作。
-
互斥
间接制约关系,指争夺同一临界资源的多个进程之间必须等待占用临界资源的当前进程退出临界区之后,另一个进程才允许去访问临界资源。
争夺资源不成功的进程会被系统阻塞,直到得到所需要的设备或资源的时候,阻塞态恢复成就绪态,再等待处理机的调度。(!!注意:是先恢复成就绪状态)
2.实现临界区互斥的基本方法
(1).软件实现方法
-
单标志法
设置一个公用整型变量turn来指示被允许进入临界区的进程编号;该方法可以确保每次只允许一个进程进入临界区,但同时也要求两个进程必须轮番进入临界区。
P0 P1 while(turn !=0); while(turn!=1); critical section; critical section; turn=1; turn=0; remainder section;remainder section;
-
双标志法先检查
对于每一个进程单独设置一个是否占用该资源的标识符,在每一个进程访问临界资源之前,先查看一下临界资源是否正在被访问,若正在被访问则等待,否则将自己的标志置为正在访问。
Pi Pj while(flag[j]); while(flag[i]); flag[i]=true; flag[j]=true; critical section; critical section; flag[i]=false; flag[j]=false; remainder section;remainder section;
相较于算法1,不再需要交替进入;但是若检查标志和占用资源之间存在一定空闲时间,那么有可能造成两个进程同时占用临界资源的现象。
-
双标志法后检查
为了避免算法2的缺陷,算法3先将自己的标志设置成占用资源的状态之后再检查对方的状态,如果对方标志也是占用的状态,则要等待Pi Pj flag[i]=true; flag[j]=true; while(flag[j]); while(flag[i]); critical section; critical section; flag[i]=false; flag[j]=false; remainder section;remainder section;
本质上算法三和算法只是两句语句的顺序发生了调换,算法三避免了进程同时进入临界区的情况,但若两个进程同时请求,有可能造成“饥饿”现象,谁也无法进入。
-
Peterson’s Algorithm
该算法是对算法一和算法三的结合,用flag标志来设置进程是否想要占用临界资源;用turn来标志和临界区被谁占用,可以有效解决“饥饿”的现象。Pi Pj flag[i]=true; flag[j]=true; turn=j; turn=i; while(flag[j]&&turn=j); while(flag[i]&&turn=i); critical section; critical section; flag[i]=false; flag[j]=false; remainder section; remainder section;
(2).硬件实现方法(元方法)
计算机中提供了特殊的硬件指令,允许对一个字中的内容进行检测和修正,或者对两个字的内容进行交换等。
-
中断屏蔽方法
-
背景知识:CPU只在发生中断的时候引起进程的切换,因此在进程使用临界资源的时候关中断(禁止一切中断的发生),就可以保证当前进程将临界区代码顺利执行完。
-
机制:
/* 关中断; 执行临界区代码; 开中断; */
-
评价:限制了处理机交替执行程序的能力,效率会降低;且把内核关中断的权利交给用户具有不安全性。
-
-
硬件指令方法
①TestAndSet指令:
- 功能描述:读出指定标志后把该标志设为真
bool TestAndSet(bool *lock){ bool old; old = *lock; *lock = true; return old; }
- 互斥算法的实现:为每一个临界区设置一个共享的布尔变量,表示该资源是否被占用(true为占用),利用TestAndSet检查和修改标志lock
while TestAndSet(&lock); //每次都将变量置为占用,再返回该布尔变量原来的状态,若占用则会一直持续上述操作 //这个操作机制优点类似算法三 while(TestAndSet(&lock)); 进程的临界区代码段; lock = false; 进程的其他代码;
②Swap指令
- 功能描述:swap指令就是交换两个字(字节)的内容
Swap(bool *a,bool *b){ bool tmp; tmp = *a; *a = *b; *b = tmp; }
- 利用Swap TestAndSet指令实现互斥算法:为临界区设置共享变量lock,再为每个进程设置一个局部布尔变量key,用于和lock交换信息
key = true; while(key!=false) swap(&lock,&key);//此处将lock的状态移到key上面,如果lock之前为false的话,那么循环检查就可以结束了,否则要一致循环检查,直到占用临界资源的进程结束 进程的临界区代码 lock = false; 进程的其他代码;
③说明:
- 上述对两个硬件指令的功能描述均采用代码的形式描述,但实际上这些指令并非通过软件定义实现的,是由硬件逻辑直接实现的,且是不会被中断的原子指令
- 优点:可以适合于任意数目的进程
- 缺点:进程等待进入临界区会耗费处理机的时间,随机选择进程进入临界区会导致部分进程出现“饥饿”现象
(3).信号量
①.信号量的概念与基础
-
信号量:是一种功能较强的机制,可用于解决互斥与同步问题,只能被两个标准的原语wait(S)和signal(S)来访问,即P操作和V操作
-
整型信号量:用于表示资源数目
wait操作时忙等待,处理机效率较低wait(S){ while(S<=0); s = s-1; } signal(S){ S = S+1; }
-
记录型变量:称为记录型,是因为采用了记录型的数据结构;除了需要一个整型变量value,还需要增加一个进程链表L,链接所有等待该资源的进程
typedef struct{ int value; struct process *L; }semaphore; void wait(semaphore S){ S.value--; if(S.value<0){ add this process to S.L; block(S,L);//进程主动调用阻塞原语,让权等待 } } void signal(semaphore S){ S.value++; if(S.value<=0){//说明之前有阻塞的进程 remove a process P from S.L; wakeup(P);//将队列中第一个等待的进程唤醒 } }
②.信号量的应用
-
实现同步,假如P2进程需要使用P1进程的结果
semaphore S = 0;//公共信号量 P1(){ ... x; //核心代码段 V(S); ... } P2(){ ... P(S); y; //核心代码段,且在这样的机制下一定在x之后执行 ... }
-
实现互斥
semaphore S = 1;//可用资源数 P1(){ ... P(S); 临界区代码; V(S); ... } P2(){ ... P(S); 临界区代码; V(S); ... }
-
实现前驱关系
- 前驱关系可以用来描述程序之间或者语句之间的关系
- 需要设置若干个初值为0的信号量,把一个前驱图转化成若干个前驱关系——若某一个节点执行:需要先P一下所有它之前的节点是否运行完,待该节点运行完后,再将所有与它相连的后续节点对应的信号量V一下。
(4).管程
-
管程的定义:由一组数据以及定义在这组数据之上的对这组数据的操作组成的软件模块,这组操作可以初始化并改变管程中的数据和同步进程。
-
管程的组成:
- 在管程内部的局部共享结构的数据说明
- 对数据结构进行操作的一组过程
- 对管城内的局部共享数据设置初始值的语句
-
管程的基本特性:
-
在管程内的局部数据只能被管程内部的局部过程所访问
-
一个进程只有通过调用管程内的过程才能进入管程访问共享数据
-
每次仅允许一个进程在管程内执行某个内部过程
成员变量 通过成员变量可以对系统中设备进行区分和描述 成员函数 对成员变量的操作,进程进入管程后可以通过成员函数对实际的系统设备进行操作
-
(5).经典同步问题
以下的问题模型就是在求解一些更加复杂的实际问题时可以参考的经典范式
①.生产者-消费者问题
-
问题描述:一组生产者和一组消费者共享一个初始为空、大小为n的缓冲区;缓冲区非空,则消费者可以取消息;缓冲区不满,生产者可以放消息;每次只允许一个生产者放入消息或者一个消费者从中取消息
-
问题分析:
①关系分析:生产者和消费者是同步关系——一个生产了另一个才可能消费;二者也是互斥关系,对缓冲区互斥访问。
②整理思路:两个进程的同步、互斥问题,注意PV操作的位置
③信号量设置:信号类型 信号名称 信号作用 初值 含义 互斥 mutex 互斥访问缓冲区 1 表示缓冲区个数为1 同步 full 生产消费者同步工作 0 满的缓冲区的长度数 同步 empty 生产消费者同步工作 n 空的缓冲区的长度数 3.程序描述
semaphore mutex = 1; semaphore empty = n; semaphore full = 0; produce(){ while(1){ produce an item in nextp; P(empty);//互斥同步关系同步存在时,同步信号量的P操作一定要先于互斥信号量 P(mutex); add nextp to buffer; V(mutex); V(full);//V操作互斥同步的先后关系并没有硬性关系,只不过先V(full)的话可以先一步题型消费者进程出现了一个可取的消息 } } consumer(){ while(1){ P(full); P(mutex);//顺序上同 remove an item from buffer; V(mutex); V(empty); consume an item; } }
②.读者写者问题
-
问题描述:读者和写者两组并发进程共享一个文件,多个读进程可以同时访问共享数据,但是写进程和其他进程同时访问共享数据时会导致数据不一致的错误,要求:多个读者可以同时对文件读;只允许一个写进程写消息;写者完成操作之前不允许其他读者和写者工作
-
问题分析:
①关系分析:读者和写者是互斥的、写者和写者也是互斥的,读者之间不存在关系。
②整理思路:因为写者和其他进程只存在互斥关系,是一个简单的互斥模型;读者不仅要实现互斥,还要能够容忍多读者环境,这里引入计数器,计数器的操作要是互斥的。
③信号量设置:信号类型 信号名称 信号作用 初值 含义 互斥 mutex 互斥访问计数器 1 表示计数器个数为1 互斥 rw 生产消费者同步工作 1 读者和写者一次只能允许一个进程工作 信号量 count 记录当前正在工作的读者数目 0 读者数目 互斥 w 防止写者饥饿问题 1 可以容下的写进程数目 3.程序描述
int count = 0; semaphore mutex = 1; semaphore rw = 1; semaphore w = 1//a.新增信号量 writer(){ while(1){ P(w);//b P(rw); writing; V(rw); V(w);//c } } reader(){ while(1){ P(w);//d P(mutex); if(count == 0) P(rw); count++; V(mutex); V(w);//e.这里结合de来看就可以发现新增一个信号量,即使当前还有读进程在工作,但下一个占用w信号量的可能是写者进程,这一点就让写者不再饥饿 reading; P(mutex); count--; if(count==0) V(rw); V(mutex); } }
③.哲学家进餐问题
-
问题描述:一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,每个哲学家面前是一碗米饭;哲学家用毕生精力思考和进餐,在思考时没有任何影响,但是哲学家饥饿的时候,视图拿起左右筷子,拿不到则等待,两根筷子全拿到了就可以开始进餐,吃完后放下筷子继续思考。
-
问题分析:
①关系分析:哲学家和其左右邻居对其中间的筷子是互斥访问。
②整理思路:一根筋的思路就是以互斥的模型依次让哲学家去拿左右筷子,但是这样可能会造成系统的死锁,可以采取的限制机制:- 最多只允许四个哲学家同时进餐
- 当且仅当一个哲学家左右两边啊筷子同时可用才允许他抓起筷子
- 对哲学家编号,奇数位哲学家先拿左边,偶数位则反之
③信号量设置(采取限制b):
信号类型 信号名称 信号作用 初值 含义 互斥数组 chopstick 互斥访问两个哲学之间的筷子 均为1 表示可用筷子数为1 互斥 mutex 将拿左右筷子的动作看做一个整体 1 左右筷子均满足才能拿筷子 3.程序描述
semaphore chopstick[5] = {1,1,1,1,1} semaphore mutex = 1; Pi(){ do{ P(mutex); P(chopstick[i]);//a P(chopstick[(i+1)%5]);//b V(mutex);//体会一下这里利用互斥信号量将取左右手筷子看做一个整体的操作 eat;//c V(chopstick[i]);//d V(chopstick[(i+1)%5]);//e think;//f }while(1); }
④.吸烟者问题
-
问题描述:一个系统有三个抽烟者和一个供应者,每个抽烟者各自拥有烟草、纸和胶水,他们从供应者处拿剩余的两种材料并卷烟;供应者每次随机将两种材料放在桌上,一直重复上述过程。
-
问题分析:
①关系分析:供应者和三个抽烟者各自为同步关系;三个抽烟者对抽烟这个行为是互斥关系②整理思路:我认为和生产消费者问题是大概差不多的,只不多这里生产者有多种提供可能,随机生成;每个消费者只消费自己指定的东西。
③信号量设置(采取限制b):
信号类型 信号名称 信号作用 初值 含义 同步 offer1 提供者与吸烟1号之间的同步信号 0 表示吸烟1可取的组合材料数目 同步 offer2 提供者与吸烟2号之间的同步信号 0 表示吸烟2可取的组合材料数目 同步 offer3 提供者与吸烟3号之间的同步信号 0 表示吸烟3可取的组合材料数目 互斥 finish 三个吸烟者对吸烟动作的互斥信号量 0 可以吸烟的人数 -
程序描述
int random semaphore offer1 = 0; semaphore offer2 = 0; semaphore offer3 = 0; semaphore finish = 0; process p1(){ while(1){ random = 随机产生的一个整数; random = random%3; if(random == 0) V(offer1); else if(random == 1) V(offer2); else V(offer3); 任意两种材料放在桌子上; P(finish); } } process Fi(){ while(1){ P(offeri); 拿相对应的材料,卷烟,抽掉; V(finish); } }
四.死锁
(一)死锁的概念
1.死锁的定义:(多道程序系统中出现的问题)多个进程因竞争资源而造成的一种僵局(互相等待),无外力作用,则进程无法向前推动。
E.g.每个进程占用着一部分资源,同时索取另一个进程所占用的资源。
2.死锁产生的原因
①系统资源的竞争:对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。
②进程推进顺序非法:这里指请求和释放资源
③死锁产生的必要条件:(同时满足)
a. 互斥条件:“排他性控制”——在一段时间内某资源只能被一个进程所占用
b.不可剥夺条件:某进程获得的资源只能由自己释放
c.请求和保持条件:进程自己保持了至少一个资源,但提出了新的资源请求,且该资源被其他进程占有,进程现已阻塞但仍然对占用的资源保持不放。
d.循环等待条件:存在一个进程资源的循环等待链
(二)死锁处理策略
核心思路:破坏死锁的必要条件的四个之一就可以破除死锁。
【图汇总】
死锁预防:
①破坏互斥条件:可行度低,允许系统资源可以共享使用,安全性不高
②破坏不剥夺条件:实现复杂,开销大;若进程因请求资源得不到满足死锁,则它应该将自己当前保持的所有资源释放,稍后再重新申请;可能导致之前工作实效,只适用于状态易于保存和恢复的资源。
③破坏请求和保持条件
预先静态分配方法,进程在运行前一次申请完它所需要的全部资源,资源不满足前,不投入使用。系统资源浪费严重,饥饿现象也可能发生。
④破坏循环等待条件:顺序资源分配法,每个进程都按照编号递增的顺序请求资源。
(三)死锁避免
1.系统安全状态
-
死锁避免的本质:在进程动态申请资源的过程中,系统进行资源分配之前,应该先计算此次资源分配的安全性,如果此次分配会使得系统进入不安全状态,则会让进程等待。
-
安全序列:系统中多个进程的一种推进序列,按照这种序列为每个进程分配所需要的资源,就可以满足进程对资源的需求且使每个进程都顺序地完成
-
安全状态:系统可以找到一个安全序列
不安全状态是死锁状态的一个必要非充分条件,进入了不安全状态有可能进入死锁【图示】
2.银行家算法
算法类比:
操作系统↔银行家;
操作系统管理的资源↔银行家管理的资金;
进程向操作系统请求分配资源↔用户向银行家贷款算法核心思想:
进程申请资源时,需要核对:
①该进程已申请到的资源+现在申请的资源≤进程对资源的最大需求
②系统现存的资源≥该进程对资源的最大需求量
两者不满足其一,则推迟分配
(1)数据结构描述
名称 | 结构 | 含义 |
---|---|---|
可利用资源矢量 | Available[m] | m类资源含有的个数 |
最大需求矩阵 | Max[n,m] | 每个进程对每类资源的需求量 |
分配矩阵 | Allocation[n,m] | 每个进程对于每类进程已经拥有的个数 |
需求矩阵 | Need[n,m] | 每个进程尚需的各类资源 |
Need = Max-Allocation
(2)银行家算法描述
(3)安全性算法
所谓安全性算法就是对前文描述的找安全序列的过程制定一个过程
五.后记
- 参考文档:《王道2019年操作系统考研指导》,本文系复习专业课时整理留作自用,若有问题,欢迎交流~
- 思维导图