§3 进程管理
C1 进程
1)并发
:同一时间两个进程都在运行或者阻塞。可能在不同处理机
并行
:同一时间两个进程都在运行。- 特点:
- 间断性:执行过程中可能阻塞挂起
- 非封闭性:多个进程共享资源,可能相互影响
- 不可再现性:执行结果依赖于调度执行次序
- 并发条件:Bernstein条件(充分条件):不发生读写冲突
- 为 的读写集合
- $R(S_i)\cap W(S_j) = W(S_i)\cap R(S_j) = W(S_i)\cap W(S_j)\varnothing ,i\neq j $
- 计算题-并发利用率:分母使用所有进程结束(IO结束)执行的时间,设备也一样
2)进程
:程序在指定数据集上的一个动态执行过程
-
特点:
- 动态性:动态创建和结束
- 并发性:多个进程存在与内存中,能在一段时间内同时运行
-
独立性:传统OS中进程是独立运行的基本单位
-
异步性/制约性:由于共享数据/资源而相互制约。执行时间不可预计
-
进程与程序
-
区别:
进程 程序 代码端+数据段+PCB 算法+数据结构 动态 静态 暂存 永久 -
联系:一个程序对应多个进程,一个进程可能包含多个程序(多对多)
-
-
进程优点:提高效率;缺点:增加空间、时间开销和系统复杂度
3)控制结构:进程控制块
(PCB)。PCB是进程存在的唯一标志
- 组成:
- 进程标识信息,如进程ID,产生者(用户,父进程)标识。操作系统给每个进程赋予一个UID(子进程同父进程),每个组分配GID
- 处理机状态信息:
- 用户可见寄存器:可用数据/地址寄存器
- 控制和状态寄存器:PC,PSW
- 进程控制信息:
- 存储管理:堆、栈、代码段指针
- 进程所用资源:打开的系统资源,如文件
- 进程优先级
- 进程互斥、同步使用的信号量。
- PCB管理所用的链接域
- 其它信息:运行时间等
- PCB组织管理:多个链表将同一状态的进程链接,便于动态修改,实质上是带优先级的队列。
- 控制实现:
原语
:若干指令组成指令序列。在内核态原子执行,常驻内存。 - Linux中PCB为task_struct
4)生命周期
-
创建:
-
时机:系统初始化,用户请求服务,程序调用和批处理作业
-
fork()
系统调用:创建子进程。返回值在子进程中为0,父进程中为子进程的pid。为子进程复制父进程的页面,或者使用写时复制机制。vfork()
直接共享父进程的资源,一般要执行exec()
载入程序。父子进程使用不同地址空间。while(TURE){ type_promopt();//显示提示符 read_command(commands,parameters); if(fork()!=0){ //父进程 waitpid(-1,&status, 0); // -1表示等待退出,status得到执行情况 // 第三参数可以选择一些特殊功能 /* 此处父进程代码主要是配合子进程exit()使用。 回收一些子进程难以执行回收的资源,尚未回收 但已执行退出的子进程处于僵尸状态 */ }else{ //子进程 execve(command,parameters,0); //执行命令,**覆盖掉**进程原来的代码,堆栈 } }
-
Windows在创建进程时分配不同地址空间,POSIX使用写时复制机制
-
-
等待:由于等待系统服务、数据或是某种操作,进程自身发出阻塞
-
唤醒:资源到位后,由OS或其他进程唤醒,进入就绪态
-
终止:正常退出或错误退出(自行),致命错误或其他进程所杀(强制,使用kill)
- 有些系统中会"诛杀"所有创建的子进程,Unix和Windows不会
4)状态:由调度程序处理中断,启动和终止进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JJ2h7FZ3-1590851691325)(E:\Typora笔记\文件图片\进程状态空间.png)]
-
挂起:进程保存到外存以腾出空间,分阻塞挂起与就绪挂起
- 阻塞到阻塞挂起:没有进程就绪,或就绪进程需要更多内存
- 就绪到就绪挂起:高优先级阻塞(OS认为很快就绪)和低优先级就绪进程同时存在时,挂起就绪进程
- 运行到就绪挂起:高优先级的阻塞挂起进程进入就绪挂起
- 阻塞挂起到就绪挂起
-
激活:
- 就绪挂起到就绪:没有就绪进程或挂就绪起进程优先级高于就绪进程时
- 阻塞挂起到阻塞:高优先级阻塞挂起(认为很快就绪)且内存允许时
5)层次结构:
- POSIX中所有进程都来自根进程init,进程组织为进程树
- Windows中没有层次,因为父进程可将创建子进程时获得的"句柄"(可控制子进程)交给其他进程
6)多道程序设计中: 为道数, 为I/O概率,CPU利用率为
C2 线程
1)线程:共享进程数据和空间的并行执行流程
2)作用:
- 共享同一地址空间和所有数据,解决进程间阻塞限制
- 相对于进程可以更快地创建、撤销和切换
- 提升进程内多个任务并行度
3)线程也有状态变化,控制结构TCB记录独享PC,Reg,栈,状态
4)进程与线程:
- 一个线程的错误可能引发所有线程崩溃。故进程用于安全性高的应用,线程用于性能要求高的应用。同时,线程引入设计复杂度问题。例如子进程是否继承所有线程,通常在继续执行时复制所有线程,在exec另一程序时只创建一个线程。
- 进程作为资源分配单位,线程是CPU调度单位
- 线程粒度小,并行度更高,切换更快。
- 共享资源,容易协作同步。
- 线程不能单独执行,但是每个线程都有程序出入口和执行序列,组成进程。
5)POSIX线程API: pthread
调用 | 功能 |
---|---|
pthread_create | 创建线程 |
pthread_exit | 结束调用的线程 |
pthread_join | 阻塞直到指定进程退出 |
pthread_yield | 释放CPU以执行另一个线程 |
pthread_attr_init | 创建并初始化一个线程属性结构 |
pthread_attr_destroy | 删除线程属性结构(如优先级) |
5)实现:
用户级线程
:使用用户线程库函数管理,内核将其视为单线程进程处理。每个进程维护线程表TCB进行管理。- 优点:
- 可用于不支持线程技术的系统。只需要线程库函数集(称运行时系统)
- 使用库函数统一API,易于移植
- 线程间切换不需要用户/核心态切换,不需要刷新Cache,在本地执行故快
- 由应用控制切换,易于优化
- 缺陷:
- 若发起系统调用,所有线程将被阻塞。对于CPU密集型任务,不需要多线程,而IO密集型又常常阻塞。Unix系统中对于阻塞的补救方式是先使用检查命令检查系统调用是否可能导致阻塞,若可能阻塞则暂时不进行系统调用,切换到另一个线程。这类检查函数称"包装器"。
- 某一线程引发的缺页中断也会使得所有被线程阻塞在磁盘IO
- 无法使用时钟中断机制退出线程,需要线程自行放弃。
- 时间片按进程分配,每个线程得到的时间片少,执行慢
- 内核只能按进程单位分配处理器,所以多处理器无法并发处理多线程
- 优点:
内核线程
:由内核维护线程表和进程表。- 缺点:
- 需要系统调用/内核切换来创建终止切换线程,开销大
- 涉及进程调度和线程调度,系统更复杂
- Windows NT、Windows 2000/ XP
- 缺点:
- 混合实现:用户级线程和内核级线程彼此多路复用
轻量级进程
:由内核支持的用户线程,单独的内核线程支持一个或多个用户进程。被内核单独调度,可并行运行在多处理器间。- Solaris 2
- 调度程序激活机制:模拟内核线程的功能并保证用户线程的性能
- 虚拟处理器:内核为每个进程提供一定数量虚拟处理器(多处理器系统中可以是真实处理器),运行时系统进行分配。内核也可以回收处理器。
上行调用
:内核在检测到阻塞时,或者阻塞的资源到位时,跳到指定地址唤醒运行时系统**(违背OS分层结构**)- 中断处理:CPU陷入内核态,若中断与被中断进程相关,恢复时运行时系统选择执行新就绪线程,原线程或新建线程
- Linux不确切区分进程与线程,将线程视为进程的另一个执行上下文
4)用户线程调度每次只能调度同一进程线程,而内核线程可以调度不同进程线程
5)弹出式线程:服务请求到来时,在内核新建一个进程来处理而不是恢复一个已存在进程,创建快捷,提供响应。因在内核态,能够访问所有资源,但出错也会带来更大损害。
6)单线程代码的多线程化:
-
全局变量、共享变量的使用会阻碍多线程化
-
线程安全:多个并发进程调用总是产生正确结果
-
可重入过程:某过程执行过程中,暂停执行另一任务,并二次调用该过程。若二次调用和先前执行都得到正确的结果,则可重入。
- 不在函数内部使用静态或者是全局变量
- 不返回静态或全局数据,数据产生都由调用者提供
- 使用本地数据,拷贝全局变量以保护全局变量
- 不调用不可重入函数
可重入 线程安全
//不可重入,线程安全 thread_local int temp; int add(int a){ temp = a; return temp + 10; }
-
信号管理:保证信号(如键入)被指定的线程处理而不产生干扰
-
堆栈管理:确保不发生堆栈溢出
C3 进程调度
1)调度分级:
- 高级调度:宏观调度,对作业调度。以分钟,小时,天为单位。
- 中级调度:内外存交换
- 低级调度:CPU资源调度,分抢占式和非抢占式,以毫秒为单位
2)时机:
调度时机:进程创建,阻塞,终止,就绪,时钟中断。
切换时机:只要OS取得对CPU的控制,进程切换就可能发生。系统调用,自陷,中断
2)过程:
- 保存处理器上下文(PC和REG)
- 使用新状态和相关信息更新PCB
- 将进程移入对于状态调度队列
- 选择一个新线程
- 更新新线程PCB
- 将新线程PCB装入CPU上下文,更新MMU
3)调度性能准则:
-
周转时间:完成时刻-提交时刻——批处理系统
-
平均周转时间
-
带权平均周转时间:每个作业的T/Ts求和÷作业数
-
长作业:FCFS < HRRF < RR,受运行时间影响
短作业:RR < HRRF <FCFS,受等待时间影响
-
-
吞吐量:总作业数÷总时间——批处理系统
- 由于并行处理和等待,平均周转时间不是吞吐量的倒数
-
响应时间:用户请求提交到首次执行。——分时系统
-
截止时间:开始截止时间和完成截止时间——实时系统
-
优先级:关键任务是否达到更好指标
-
公平性:不因作业或进程本身特性而使指标恶化
-
处理机利用率——大中型系统
-
资源的均衡利用率——大中型系统
5)批处理系统调度算法:
-
先来先服务(FCFS):按就绪顺序运行,阻塞/终止时让出。
- 有利于长作业及CPU密集型作业
-
短作业优先(SJF):预计执行时间短的非抢占执行
- 优点:减少平均周转时间、带权周转时间;提高吞吐量
- 缺点:长作业饥饿;作业运行时长不确定导致调度困难;不能按紧迫程度调整优先级
-
最短剩余时间(SRTF):实时按剩余时间最短抢占
- 缺点:长作业饥饿
-
高响应比(HRRF):选择响应比高的进程执行
-
响应比: RP = 1 + 已等待时间/要求运行时间
- 优点:短作业容易得到高响应比;不会发生饥饿
- 缺点:计算响应比的开销大,性能略差于SJF
6)交互式系统调度:
-
时间片轮转(RR):按FCFS排队,按时间片轮转。时间片用完时被中断抢占。可出让时间片。
- 优点:提高并发性和响应时间、资源利用率
- 时间片长度:
- 过长退化为FCFS;响应时间(T=N*q)长
- 过短导致上下文切换开销增加;一次作业需要多次轮转,周转时间变长
- 就绪进程数和时间片长度反比
- 时间片长度应当使得请求通常在一个时间片内处理完。否则响应时间,平均周转,平均带权周转时间将会延长;需要考虑CPU执行速度
- Linux使用动态用户优先级分配5-800ms时间片
-
优先级算法:
-
抢占和非抢占;动态、静态优先级
-
动态:每执行一个时间片就降低优先级;等待时间长的优先级高
-
多级队列算法:
- 多个就绪队列,就绪队列(按进程性质和类型)又分多个子队列
- 进程首次进入固定一个队列,后续可动态调整所在队列
- 不同队列可有优先级,时间片长度和调度策略
-
多级反馈队列算法:时间片轮转+优先级
- 设置多个就绪队列。
- 优先级递减。
- 优先级低的队列时间片长。
- 高优先级队列为空才执行低优先级队列的进程。执行过程中新的高优先级任务抢占CPU,被抢占任务会放在原队列末尾。
- 最低级队列使用时间片轮转。其它使用FCFS
- I/O型每次进入高优先级,随后阻塞,计算型每次降低优先级,两者之间的放在原队列。(为适应不同时期特点,I/O完成提升优先级,时间片用完减低优先级)
- 优点:提高吞吐量并缩短平均周转时间(短进程优先);提升I/O设备利用率和响应时间(I/O进程优先);不必估计进程执行时间,动态调节
- 设置多个就绪队列。
-
8)实时系统:按是否需要绝对满足截止时间分软硬实时系统。进程行为通常可预测
- 实时调度:
- 要求了解就绪时间,开始/截止,处理时间,资源需求,绝对或相对优先级
- 抢占式
- 快速中断响应
- 快速任务分派:使用小调度单位(线程)
- 假设任务集已知,运行时间不变,任务间独立运行,不计切换时间,CPU利用率: ,ci为执行时间,Ti为任务周期,Di为截止时间,通常Ti=Di
- 静态表调度:对所有周期性任务分析预测,事先确定调度方案。无灵活性,适合完全固定的任务场景。
- 单调速率调度(RMS):
- 单处理器最优静态调度
- 可调度任务集(充分条件):
- 任务周期小则优先级高。相同优先级将随机选取。
- 静态优先级;抢占
- 最早截止时间优先(EDF):
- 绝对截止时间早则优先级高
- 可调度任务集(充要条件):
- 每个截止时间节点/结束任务时进行调度
- 动态优先级;抢占
- 最低松弛度优先算法(LLF):
- 任务紧急则优先级高
- 松弛度 = 进程截止时间 - 当前时间 - 本身剩余运行时间
- 调度时机:某进程松弛度为0时抢占
- 可调度任务集(充要条件):
9)多处理机调度:
- 注意整体运行效率(而不是个别处理机利用率)、线程间互斥,以线程为单位
- 非对称式多处理系统(ASMP):各个处理器地位不同
- 主-从处理器系统,主处理机管理公共就绪队列,分派进程给从处理机
- 各个处理机有固定分工,如IO、OS系统功能、应用程序
- 潜在不可靠性:主机故障则系统崩溃
- 对称式多处理系统(SMP):每个处理机地位相同
- 集中控制:分静态和动态调度
- 静态:每个CPU有一个就绪队列,调度开销小,可能负载不均
- 动态:公共就绪队列,每次分配给空闲CPU
- 分散控制:自调度
- 公共就绪队列。但有变型:Mach OS将局部和全局相结合,局部就绪队列线程优先调度
- 不需要专门的处理机进行分派,每个处理机选择适当进程执行。每个处理机可使用单处理机的调度技术,易于移植。
- 存在队列同步开销,缓存更新开销(阻塞恢复后可能不在原处理机运行,需要更新Cache),线程协作开销(合作的几个线程间阻塞而被换下)
- 成组调度:一个进程中一组线程每次同时分派到一组处理机上执行,在剥夺处理机时也成组剥夺
- 避免线程相互等待造成资源浪费,提高并行度和吞吐量
- 每次调度完成多个线程分派,系统内线程总数相同同时减少调度次数
- 处理机时间分配:按进程为单位分配或按线程为单位。按进程分比较浪费
- 专用处理机调度:每个线程固定分配一个CPU直到其执行完成
- 缺点:线程阻塞时CPU闲置
- 优点:线程执行时不需要切换,开销减小;适用于CPU数量众多的高度并行系统,单个CPU利用率已不太重要
- 集中控制:分静态和动态调度
12)优先级反转:低优先级进程持有锁后被抢占,使高优先级进程被低优先级进程阻塞,而低优先级进程又无法被调度解锁。
-
用户级线程没有这个问题,同一进程中低优先级线程运行时高优先级线程不会就绪
-
优先级置顶:加锁的进程不允许被抢占,临界区短时可行
-
优先级继承:高优先级进程被低优先级进程持有的锁阻塞,提升低优先级进程优先级,使其在下一次调度中尽快完成。
13)Linux处理机调度:
- 2.4:FIFO队列;不可抢占(不能响应实时任务);优先I/O进程;SMP环境下多个处理器使用同一队列,负载均衡简单,但进程在多个CPU间切换Cache效率降低;每次遍历队列查找可用进程,O(n)
- 2.6:O(1)调度,每个CPU有一个自己的运行队列,分expired和active。查位图找就绪任务。140个动态优先级,0-99为实时优先级。优先级高时间片长。
- 2.6:SD调度器:粗细粒度时间片,粗粒度时间片用完降级,细粒度时间片用完轮转,使得I/O型在高优先级队列停留时间长。
- 2.6:RSDL调度器:完全公平。组时间配额(tg,优先级队列上所有进程总时间),优先级时间配额(tp,小于进程时间片),tp用完单个进程按SD算法降级,tg用完整个队列所有进程降级
- 2.6.23:CFS调度器。模拟完全理想多任务处理器。
- 权重:进程参照nice因子(-20~+19)决定的优先级,权重使用一些经验数字转换
- 虚拟运行时间:vt,与权重反比,实际运行时间正比
- 完全公平:不跟踪进程休眠时间,不区分交互式进程,所有进程统一对待。既定时间内每个进程都获得公平的CPU占用时间
- 按vt构造红黑树维护就绪进程(所以最小的在最左下节点)
C4 进程同步
1)进程同步:执行次序上协调
- 间接制约:资源共享
- 直接制约:进程合作
2)并发错误:
- 典型:
- 数据竞争:多个进程无保护地同时访问共享变量,其中至少一个写
- 原子性违反:一组操作不允许分开执行
- 死锁:进程集合中所有进程都在等待集合中其它进程
- 特点:不可再现
- 原因:
- 不同环境输入不同(传感器)
- 不同条件API返回不同(系统时间)
- 不同调度次序
- 不同中断时间
- 不同的共享内存的顺序
- 执行重放技术:记录一次执行的所有执行顺序和数据
- 原因:
4)互斥:
-
临界:互斥保护的共享资源称临界资源。访问临界资源的程序片段称临界区。
-
要求:排队进入
- 在临界区空闲时,有且只有一个进程进入临界区
- 适应任意CPU数量和速度
- 临界区外的进程不能妨碍其他进程进入临界区
- 一个进程不在临界区外无限等待
-
实现:
-
中断屏蔽:进入临界区时关闭中断,包括时钟中断,停止切换进程
- 仅关闭了一个CPU,仍未解决多核情形
- 给用户进程关闭中断的权限不安全
- 通常用于内核处理
※ 共享锁变量:锁的读取和设置需要是原子操作
-
硬件指令操作锁
- TSL: lock读入寄存器后将lock置位,检查寄存器,锁内存总线。释放时lock复位
- XCHG: 原子地交换两个位置的内容,原理相同
-
严格轮换:turn为0时进程0可进入,退出设置为1,进程1同理。只适合两个进程,同时严格轮换,相互钳制。违反临界区外进程不能阻碍其它进程进入临界区的规则 -
Peterson算法:
int turn; int interested[2] void enter(int this){ int other = 1-this; interested[this]= true; turn = this; while(turn == this && interested[other] == TRUE); } void leave(int this){ interested[this] = FALSE; }
- 只适合2进程
- 对于非抢占式CPU,可能不公平。
※ 硬件指令和Peterson的共同缺点:忙等+存在优先级反转。可能使用sleep和wakeup补救,但要保证判断condition后睡眠这一操作原子(错误的方法:睡眠前检查睡眠允许位,多进程合作需要增加更多允许位)
-
Bakery Algorithm
- 进入临界区时拿一个不重复的序号。按照序号分配了先后顺序
-
信号量
机制:-
使用S表示当前可用资源数:
S = 1
:二元互斥;S > 1
:同步通讯。 -
P\down
和V\up
为原子操作,分别为分配或阻塞、释放并唤醒 -
实现:整数+一个队列
- 单CPU:中断屏蔽后测试信号量
- 多CPU:锁变量(硬件实现)保证只有一个CPU检查信号量
-
在一些系统中信号量可能为负,用于表示等待进程数
-
信号量可以实现一切同步需求
#汇合操作:a1先于b2,b1先于a2 S1 = S2 = 0 void threadA(){ a1(); V(S1); P(S2); a2(); } void threadB(){ b1(); V(S2); P(S1); b2(); } #屏障同步Barrie mutex = 1 保护count bar = 0 屏障 count = 0 void Barrie(){ P(mutex); ++count; if(count == n) V(bar) #第n个进程打开屏障 V(mutex); P(bar) #在屏障前等待 P(mutex); --count; if(count != 0) V(bar)#唤醒下一个进程 V(mutex); }
-
-
信号量集:进程同时需要多个共享资源
-
and型:每种资源都只需要一个。一齐分配、释放。操作SP、SV
SP时各个信号量的次序不重要,只是影响进入哪个阻塞队列(进入第一个信号量小于1的信号量的等待队列)
-
一般型:每种资源需要不少于一个。SP(S1,t1,d1;…;Sn,tn,dn),SV(S1,d1;…;Sn,dn)
t为需求量(s>=t才分配),d为分配量(分配时s = s - d)
(S,1,0):可控开关,S=0时禁止任何进程进入临界区
(S,1,1):互斥信号量
-
-
管程
:集中临界区控制操作,封装了同步机制,由用户编写(高级同步原语)- 信号量加重了编程负担,同步操作分散在各进程中,使用不当可导致死锁或逻辑错误。
- 实现:函数库
- 缺点:依赖于编译器支持,与特定语言相关。(C,Java);不适用于分布式系统。仅用于单核/多核共享内存系统
-
C5 进程间通信
1)
-
低级通信:只传递状态和整数值(控制信息)。传送信息量小;效率低;v编程复杂
-
高级通信:传送任意数量数据
3)管道、命名管道:半双工通讯(数据单向流动)
-
匿名管道:
|
-
半双工:可双向传输,但是不能同时传输。数据单向流动,双向通讯时需要建立2个管道
-
只能用于有亲缘关系的进程间
-
单独构成独立文件系统:管道对于两队的进程是一个特殊文件,只存在于内存中,不属于任何文件系统
-
数据先进先出
-
-
命名管道:
mkfifo()
mkfifo fifotest cat < fifotest show > fifotest
- 无亲缘关系可传递
- 使用一个
FIFO文件
通讯,受文件系统管理
4)信号:通知接受进程有某事件发生
5)消息队列:消息的链表。克服了信号信息量小,管道只接受无格式字节流以及缓冲区大小受限等障碍
6)共享内存:进程共同访问同一块内存空间。
- 同一物理内存映射到多个进程各自的进程地址空间
- 多个进程共享同一块内存区域,需要同步机制约束,结合其它通讯机制(信号量)保证安全
- 效率高,最快(解决复制开销)
7)信号量:进程间/线程间同步
8)套接字:通过网络接口用于不同机器间。也可用于本机进程间通讯
9)消息传递过程:
-
通讯原语。send(destination,&message) receive(source,&message)
-
调用:阻塞(传递中线程阻塞)/非阻塞
-
主要问题:
- 信息丢失和延迟(TCP协议)
- 编址问题
-
过程:
- 陷入内核
- 消息复制到缓冲区
- 消息加入接收进程消息队列
- 消息复制取回
-
Send (dst, mess){ P(buf_empty) P(lock_buf) buf = getBuf() V(lock_buf) bcopy(buf,mess) P(lock_point) insert() V(lock_point) V(buf_full) } ?send的原语实现?
C6 经典同步问题
1)生产者消费者问题:
void Consumer(){
P(empty)
consume()
V(full)
}
void Producer(){
P(full)
produce()
V(empty)
}
2)读写锁问题:
-
开关灯模式:读者优先,写者饿死
room_empty = 1; mutex = 1; readnum = 0; void Writer{ P(room_empty) write() V(room_empty) } void Reader{ #light.lock(){ P(lock_readnum) readnum++; if(readnum == 1) P(room_empty)#第一个读者开灯 V(lock_readnum) #} read() #light.unlock(){ P(lock_readnum) readnum--; if(readnum == 0) V(room_empty)#最后一个读者关灯 V(lock_readnum) #} } #一般信号量机制描述 max_read = n can_read = 1 void Writer(){ SP(can_read,1,1;max_read,n,0) write() SV(can_read,1) } void Reader(){ SP(max_read,1,1;can_read,1,0) read() SV(max_read,1) }
-
闸机模式:读写公平算法
#关键:使用两个锁,turnstile实现轮流通过,can_write用于等待场内清空 can_write = turnstile = 1 Writer(){ P(turnstile) P(can_write) wirte() V(turnstile) V(can_read) } Reader(){ P(turnstile) V(turnstile) P(lock_readnum) ++readnum; if(1 == readnum) P(can_write) V(lock_readnum) read() P(lock_readnum) --readnum; if(0 == readnum) V(can_write) V(lock_readnum) }
3)理发师问题:
semaphore customers = 0
semaphore barbers = 0
semaphore mutex = 1
int waiting = 0
理发师:
while True:
P(customers)
P(mutex)
--waiting
V(mutex)
V(barbers)
cut()
顾客:
while True:
P(mutex)
if waiting < CHIRS:
++waiting
V(mutex)
V(customers)
P(barbers)
cut()
else:
V(mutex)
4)服务员问题:
int next = 0
semaphore mutex = 1
semaphore cnt = 0
Customer_i:
V(cnt)
Server_i:
while True:
P(mutex)
P(cnt)
++next
V(mutex)
serve()
5)水分子构建:
mutex = 1 #保护计数器
Oxygen:
P(mutex)
oxygen += 1
if hydrogen >= 2 :
V(hydrogenQ)
V(hydrogenQ)
hydrogen -= 2
oxygen -= 1
else:
V(mutex)
P(oxygenQ)
bond()
barrier()
V(mutex)
Hydrogen:
P(mutex)
hydrogen += 1
if hydrogen >= 2 and oxygen >= 1
V(hydrogenQ)
V(hydrogenQ)
hydrogen -= 2
V(oxygenQ)
oxygen -= 1
else:
V(mutex)
P(hydrogenQ)
bond()
barrier()
4)死锁:进程集中所有的进程都在等待该集合中其它进程才能引发的事件
-
必要条件:
-
互斥条件:存在互斥资源
- 允许共享/假脱机技术
-
保持和请求条件:已经获得资源的线程请求新的资源
- 请求资源之前先释放已持有资源
- 全部资源到位再给予资源
- 缺陷:
- 难预测资源请求
- 资源利用率低
- 降低并发性
-
不剥夺条件:进程资源无法抢占->抢占
- 临时资源,即消息、中断信号等,是不可剥夺资源
-
环路等待条件:存在等待环形链
-
资源有序分配,实现分类编号,对多种资源的请求按序号递增排列请求
(同种多个仍然可能死锁)
-
限制了请求顺序,增加开销和资源占用时间
-
-
-
处理方式:
- 无视:在死锁发生概率小,影响低时可以考虑
- 检测:
- 单资源:资源分配图
- 多资源:分配向量
- 预防:破环死锁条件(静态)
- 避免:资源分配前检测是否安全(动态)。需要获取额外信息
-
死锁检测:
- 资源分配图:
- 每类资源只有一个的检测
- 按资源请求和释放的序列对图进行操作,每部操作后检测是否有环
- 资源向量(矩阵)算法:
- 每类资源多个的检测
- $\sum ^n _ {i=1} C_{i j} + A_j = E_j $ C:进程i分配到资源j,A:余量,E:总量
- 检测方式:
- 1°寻找进程Pi ,其请求 R行各分量小于 A各分量
- 2°释放进程i的Ci资源(加入A),标记完成
- 3°重复1° 2°,若有未标记进程,说明死锁
- 资源分配图:
-
死锁恢复:
-
资源抢占法:挂起占有资源的进程,释放其资源
-
杀死进程:杀死一个/若干(环内/外)进程,直到搭配循环
-
回滚法:设置检查点,回滚到未占用资源的检查点状态,将资源分给其它资源
常用于容错
-
-
死锁预防:破坏4个条件
-
死锁避免:前提是需要知道进程需要的所有资源
-
安全状态:所有进程突然请求最多资源,依然有调度次序使得每个进程运行完
-
银行家算法:
- 可用资源向量Available
- 最大需求矩阵Max
- 分配矩阵Allocation
- 需求矩阵Need = Max - Allocation
if Request[i]<Need[i]: if Request[i]<Available[i] && safe():#模拟分配后是否安全 alloc() update() else: wait() else: raise Error #超出其宣布的最大值
-
安全性的检测:同分配向量法,R替换为Need即可
work = Available # 工作向量,表示可提供资源数 finish = [] # 表示是否有足够资源分配给进程 for i in range(n): # 初始化为不可分配 finish.append(False) while True: for i in range(m): if !Finish[i] and Need[i] <= Work[i]: work += allocation Finish[i] = True if i == m: break for i in range(m): if !Finish[i]: unsafe = True
-
允许互斥,部分分配,不可抢占,提升了资源利用率
-
难以得知最大需求,现实不可行
-
资源分配图化简法:
- 当进程Pi有请求边,先变成分配边(满足其请求),若所有请求满足,释放其资源(删除所有分配边)。重复执行,若可完全化简,则无死锁。
-