操作系统——进程管理
进程
1)顺序性
顺序执行
2)封闭性
不受外界因素影响
3)可再现性
当初始条件和执行环境相同,重复执行会得到相同的结果
1)Bernstein条件
p1,p2两个程序,满足,R(p1) && W(p2) = R(p2) && W(p1) = W(p1) && W(p2) = 空集时,可以并发执行
1)定义
- 程序在处理器上的一次执行过程
- 可以和别的进程并行执行的计算
- 程序在一个数据集合上的运行过程,系统进行资源分配和调度的一个独立单位
- 一个数据结构及能在其上进行操作的一个程序
- 一个程序关于某个数据集合在处理器上顺序执行所发生的活动
2)特征
- 动态性。表示在处理器上的一次执行过程
- 并发性。
- 独立性。能独立运行的基本单位
- 异步性。
- 结构特征。进程控制快(Process Control Block,PCB),每个进程都由程序段、数据段和进程控制快组成
3)组成
- 进程控制快(PCB)
- 程序段
- 数据段
- 进程标识符(PID)
- 进程当前状态
- 进程队列指针。指向下一个PCB的地址
- 程序和数据地址
- 进程优先级
- CPU保护现场。因某种原因释放处理器时,CPU现场信息会保留当前的信息,以便下次调用时继续
- 通信信息
- 家族联系。与父子进程相关
- 占有资源清单
4)进程和作业的区别
作业的流程为:作业提交>作业收容>作业执行>作业完成。
而进程是已经提交完毕的作业的执行过程
1)五种基本状态
- 就绪状态
- 执行状态
- 阻塞状态
- 创建状态
- 结束状态
2)状态之间的转换(如图)

- 状态转换并非都是主动的,很多情况下都是被动的,只有从执行状态到阻塞状态属于程序的自我行为(因事件而主动调用阻塞源于),其他都是被动的
1)进程的创建
①创建的原因有
- 用户登录。
- 作业调度。
- 请求服务。由于进程的需要,由其自身创建一个新进程并完成特定任务
②创建原语
- 向系统申请PCB、指定唯一PID
- 为进程分配必要的资源
- PCB初始化。填入信息
- PCB插入就绪队列
2)进程的撤销
- 从PCB集合中找到被撤销的PCB
- 若被撤销的进程处于执行状态,则应立即停止该进程的执行,设置重新调度标志以便进程撤销后将处理器分配给其他进程
- 另一种策略,撤销所有子孙进程。
- 回收被撤销进程所占有的资源或归还给父进程,或归还给系统,最后回收它的PCB
3)进程的阻塞
阻塞即:由执行状态变为阻塞状态,由进程自身调用阻塞原语去完成,主要操作如下
- 停止进程运行。因为此刻在执行状态,所以要中断处理器
- 保存该进程的CPU现场,以便以后再次为执行状态时继续运行
- 将进程状态改变为阻塞状态,将改进程插入到等待队列当中
- 转到进程调度程序,调度下一个新的进程投入运行
4)进程的唤醒
唤醒即:由阻塞状态变为就绪状态,由另一个发现者进程调用唤醒原语实现,主要操作如下
- 将进程从等待队列中溢出
- 状态改为就绪状态,并插入就绪队列
5)进程的切换
指处理器从一个进程运行到另一个进程运行的过程,步骤如下:
- 保存处理及上下文,包括程序计数器和其他寄存器
- 更新PCB信息
- 将PCB移入相应的队列。就绪队列或阻塞队列
- 选择另一个进程执行,更新其PCB
- 更新内存管理的数据结构
- 恢复处理器上下文
1)共享存储器系统
向系统申请建立一个共享存储区,并指定共享存储区的关键字
2)消息传递系统
- 直接通信。将发送的消息挂在接收进程的消息还早消息缓冲队列中
- 间接通信。寻找一个中间介质,称之为信箱。通过信箱通信
3)管道通信系统
以字符流形式将大量数据送入管道。
线程
1.线程的定义
1)是进程内的一个执行单元,比进程更小
2)是进程内的一个可调度实体
3)是程序(或进程)中相对独立的一个控制流序列
4)线程本身不能单独运行,只能包含在进程中,只能在进程中执行。
2.线程的特点
1)资源
只拥有运行时必不可少的资源。如:程序计数器、一组寄存器和栈。但可以和同属一个进程的其他线程共享进程拥有的全部资源。
2)多线程
多线程是指在一个进程中有多个线程,这些线程共享该进程资源。
3.线程的实现
1)内核级线程
即:操作系统内核完成的线程
2)用户级线程
即:用户级别完成的线程。例如java、Android中学习并使用到的多线程
线程与进程的比较
1.调度
1)传统操作系统
拥有资源和独立调度的基本单位都是进程。
2)现代操作系统
独立调度的基本单位是线程、拥有资源的基本单位是进程。
3)过程
同一进程中,线程切换不会引起进程切换。
不同线程中,线程切换会引起进程切换。
2.拥有资源
3.并发性
有线程的并发性会更好更强
4.系统开销
有线程的操作系统中,调度产生的系统开销会减少
用户与系统之间的多线程模型
多个用户线程对应一个系统线程。
1)优点
线程在用户空间进行管理,效率较高
2)缺点
只要有一个线程阻塞,整个进程就会被阻塞。
即使一有多处理器,该进程的若干用户线程也只能在一个处理器中运行
一个用户线程对应一个系统线程
1)优点
一个线程阻塞时,不影响其他线程
在多处理器上可以多个线程进行并行
2)缺点
创建一个用户线程是需要创建一个相应的系统线程
多个用户线程对应多个系统线程
1)优点
比以上两种模型都会更好。实现真正意义上的并行执行
处理器调度
1)高级调度
又称为:宏观调度、作业调度、长程调度。主要从外存上处于后备状态的作业中选择一个或多个作业分配资源
作业调度的运行频率较低,通常为几分钟一次
每次执行作业调度时,调度需要解决两个问题。
- 必须决定操作系统可以接纳多少个作业
- 必须决定接纳哪些作业
涉及到的算法
- 先来先服务
- 短作业优先
2)中级调度
又称为:中程调度、交换调度。目的是提高内存利用率和系统吞吐量。
主要任务:将处于外存对换区中具备条件的进程调入内存,修改状态为就绪状态,挂在就绪队列上。将在内存中暂时不能运行的进程挂到外存,称为挂起状态。
主要涉及内存管理与扩充
3)低级调度
又称为:微观调度、进程调度、短程调度。
主要任务:从就绪队列中选取一个进程,将处理器分配给它。
进程调度的运行频率很高,一般隔几十毫秒要运行一次。
①与高级调度之间的区别
|
作业调度(高级调度) |
进程调度(低级调度) |
|
为进程被调用做准备 (为作业创建进程) |
进度调度使进程被调用 (进程被执行) |
|
作业调度次数多 |
作业调度次数少 |
|
调度频率低 |
调度频率高 |
|
作业调度可以没有 |
进程调度必须有 |
1)CPU利用率
字面意思,懂得都懂
2)系统吞吐量
表示单位时间内CPU完成作业的数量。
3)响应时间
主要面向用户,在用户接受的时间内做出相应。称之为响应时间。
4)周转时间
周转时间 = 完成时间 - 提交时间
平均周转时间 = (周转时间1 + 周转时间2 + ...) / n
带权周转时间 = 周转时间 / 运行时间
平均带权时间 = (带权周转时间1 + 带权周转时间2 + ...) / n
进程调度
- 记录系统中所有进程有关情况以及状态特征
- 选择获得处理器的进程
- 处理器分配
- 进程运行结束
- 进程因某种原因。由运行状态变为阻塞状态。如:I/O请求、P操作、阻塞原语等。
- 执行完系统调度等系统后返回用户进程。可以看做系统进程执行完毕
- 抢占调度方式中,具有更高优先级的进程要求使用处理器,则会使当前正在处理的进程进入就绪队列
- 分时系统中,分配给该进程的时间片用完
- 处理中断的过程中
- 在操作系统内核程序临界区中。理论上这种情况会加锁。
- 其他需要完全屏蔽中断的原子操作过程中。
1)抢占方式(可剥夺)
指当一个进程正在处理器上执行时,若有某个优先级更高的进程进入就绪队列,则会被优先级更高的抢占。
2)非抢占方式(不可剥夺)
同上述例子。只不过,即使有优先级更高的,也不能抢占。
常见调度算法(重点)
简称FCFS(first come first service),是最简单的调度算法。按先后次序。
现在已经很少用来作为主要调度算法,但常常结合其他调度方法策略中体现。例如:优先级相同时,才采用该调度算法。
简称SJF(Shortest Job First),把处理器分配给最快完成的作业(或进程)。
长作业会产生“饥饿现象”
把处理器分配给优先级最高的进程。算法核心是,如何确定进程的优先级
1)静态优先
即:优先级确定之后,在整个进程运行期间不再改变
- 按进程类确定。系统进程高于用户进程。
- 按作业资源要求确定。进程申请资源越多,估计的运行时间越长,优先级则越低。
- 按用户类型和要求确定。用户收费标准越高,则越快。
2)动态优先
即:会随着运行的时间而动态进行变化。
当出现优先级相同时,会采用先来先服务或短作业优先的策略。
注意:这里配合调度方法可以分成抢占式和非抢占式两种。
- 根据占有CPU时间长短决定。一个进程占用CPU时间越长,则优先级越低。反之,则优先级越高
- 根据等待CPU时间的长短决定。一个进程等待CPU时间越长,则优先级越高。反之,则优先级越低
设置一个时间片段,“进程”们轮流调用,用完时间片段后转给下一个进程。
1)时间片设置太长
使得每一个进程都能一次性完成。就变相转变成先来先服务调度算法。
2)时间片设置太短
使得处理器在进程之间频繁切换,会浪费很多的时间。
3)设置时间片长短的参考指标
-
系统响应时间 。分时系统必须满足系统对响应时间的要求。
- 其中响应时间与时间片的公式为:
- T(响应时间) = N(就绪队列中的进程数) × q(时间片大小)
- 可以看出,系统响应时间与时间片成正比
- 就绪队列中的进程数目。与时间片成反比
- 系统的处理能力。系统处理能力越强,单位时间内可处理的命令越多,时间片就可以越小。
综合了先来先服务与短作业优先两种调度算法。响应比越高,则优先级越高。
公式:响应比 = (作业等待时间 + 估计运行时间) / 估计运行时间
由于需要计算每个后备的响应比,增加了系统的开销
设置多个队列。每个队列可以采用不同的调度算法。
例如:为交互任务设置一个就绪队列,该队列采用时间片轮转调度方式。再为批处理任务设置一个就绪队列,该队列采用先来先服务的调度算法。
由时间片轮转和优先级调度的综合与发展。
设置多个队列,优先级依次下降。优先级越高的队列时间片越短。
举例:设置三个队列,若第一个队列中进程未运行完成,则置于第二个队列中。在最后一个队列中使用时间片轮转方式。在第一个队列为空时,有新的进程进入系统时,采用抢占式,放下当前工作,调回第一个队列中运行。队列中的每个进程服从先来先服务调度算法。
同步与互斥
1.概念
1)制约关系
- 间接相互制约(互斥)
如果资源不允许两个进程同时访问,则为互斥。基本形式为“进程——资源——进程”
同类进程即为互斥
- 直接相互制约(同步)
某个进程得不到另一个进程的信息则不能继续下去,则为同步。基本形式为“进程——进程”
不同类进程即为制约
2)临界资源与临界区
同时仅允许一个进程使用的资源称为临界资源。
临界资源分成四部分,如下:
- 进入区。检查是否可以进入临界区,如果可以,通常会设置相应的“正在访问临界区”标志。
- 临界区。进程中用于访问临界资源的的代码,又称临界段。
- 退出区。将“正在访问临界区”的标志去除。
- 剩余区。除去以上三部分的剩余部分。
易错点区分:
- 临界区不是临界资源所在地址。临界资源是要进程“互斥”访问。因为需要对使用临界资源的进程进行管理,也就产生了临界区的概念。
- 每个临界区代码可以不相同。对资源内部的操作与互斥同步管理是无关的。
3)互斥的概念和要求
为了禁止两个进程同时访问临界区,需要遵循以下的准则。
- 空闲让进。
- 忙则等待。
- 有限等待。保证有限的时间内进入自己的临界区。
- 让权等待。因某些原因不能访问临界区时,应该将处理器让给其他进程。
互斥实现
假设有两个进程,他们执行一个无限循环程序,每次使用资源一个有限的时间间隔,让他们互斥访问。这里两个进程分别为p0和p1
1)使用一个变量进行控制
int trun = 0;
p0: {
do {
// 检查turn的值,直到为0时才进行下去
while(turn != 0);
// 访问临界区
进程p0的临界区代码CS0
// 退出区
trun = 1;
// 访问玩临界区后继续进行自己的其他代码
进程p0的其他代码
} while(ture)
}
p1: {
do {
// 检查turn的值,直到为0时才进行下去
while(turn != 1);
// 访问临界区
进程p1的临界区代码CS1
// 退出区
trun = 0;
// 访问玩临界区后继续进行自己的其他代码
进程p1的其他代码
} while(ture)
}
缺点:不能保证空闲让进这一点。
举例:如果p0需要连续访问两次。在p1访问前,p0都不能访问到第二次
2)使用数组进行控制
enum boolean{false, true};
boolean flag[2] = {false, true};
p0: {
do {
/* 检查flag[1]的值,flag[1]为真的时候
说明p1正在访问临界区,此时p0就不能访问*/
while(flag[1]);
// 进入区
flag[0] = true;
// 访问临界区
进程p0的临界区代码CS0
// 退出区
flag[0] = false;
// 访问玩临界区后继续进行自己的其他代码
进程p0的其他代码
} while(ture)
}
p1: {
do {
/* 检查flag[0]的值,flag[0]为真的时候
说明p0正在访问临界区,此时p1就不能访问*/
while(flag[0]);
// 进入区
flag[1] = true;
// 访问临界区
进程p1的临界区代码CS0
// 退出区
flag[1] = false;
// 访问玩临界区后继续进行自己的其他代码
进程p1的其他代码
} while(ture)
}
缺点:不能保证忙则等待这一点。
举例:如果两个进程同时未进入临界区,然后同时进入临界区,则会出现问题。
3)使用数组进行控制(访问前设置标记)
enum boolean{false, true};
boolean flag[2] = {false, true};
p0: {
do {
// 进入区
flag[0] = true;
/* 检查flag[1]的值,flag[1]为真的时候
说明p1正在访问临界区,此时p0就不能访问*/
while(flag[1]);
// 访问临界区
进程p0的临界区代码CS0
// 退出区
flag[0] = false;
// 访问玩临界区后继续进行自己的其他代码
进程p0的其他代码
} while(ture)
}
p1: {
do {
// 进入区
flag[1] = true;
/* 检查flag[0]的值,flag[0]为真的时候
说明p0正在访问临界区,此时p1就不能访问*/
while(flag[0]);
// 访问临界区
进程p1的临界区代码CS0
// 退出区
flag[1] = false;
// 访问玩临界区后继续进行自己的其他代码
进程p1的其他代码
} while(ture)
}
缺点:不能保证有限等待这一点。
举例:如果两个进程同时未进入临界区,然后同时进入临界区,则会出现问题。
4)使用数组和变量进行控制
enum boolean{false, true};
boolean flag[2] = {false, true};
int turn;
p0: {
do {
// 进入区
flag[0] = true;
// 此时p0未进入临界区,仍然允许p1进入临界区(进入区)
turn = 1;
/* 检查flag[1]的值,flag[1]为真的时候,说明p1希望可以访问临界区,
,turn=1表示,p1可以访问临界区,满足两条件时p0就不能访问*/
while(flag[1] && turn == 1);
// 访问临界区
进程p0的临界区代码CS0
// 退出区
flag[0] = false;
// 访问玩临界区后继续进行自己的其他代码
进程p0的其他代码
} while(ture)
}
p1: {
do {
// 进入区
flag[1] = true;
// 此时p1未进入临界区,仍然允许p0进入临界区(进入区)
turn = 0;
/* 检查flag[0]的值,flag[0]为真的时候,说明p0希望可以访问临界区,
,turn=0表示,p0可以访问临界区,满足两条件时p1就不能访问*/
while(flag[0] && turn == 0);
// 访问临界区
进程p1的临界区代码CS0
// 退出区
flag[1] = false;
// 访问玩临界区后继续进行自己的其他代码
进程p1的其他代码
} while(ture)
}
完美解决!
1)中断屏蔽
2)硬件指令
3)优点
- 适用范围广。适用于任何数目的进程
- 简单。标志设置简单,含义明确,容易验证正确性
- 支持多个临界区。当一个进程内有多个临界区时,只需为每个临界区设立一个布尔变量
4)缺点
- 不能实现“让权等待”
- 会使一些进程一直选不上,产生“饥饿”现象
信号量
1)原语(代码以二元组数据结构为基础)
PV操作为原子操作,不会被打断。必须一次性执行完。
struct semaphore {
int count;
queueType queue;
}; // 二元组
//P操作
wait (semaphore s) {
s.count--;
// 当count<0时 表示的是,正在阻塞的进程的数量
if(s.count < 0)
{
阻塞该进程;
将该进程插入等待队列s.queue
}
}
//V操作
signal (semaphore s) {
s.count++;
if(s.count <= 0)
{
从等待队列s.queue取出第一个进程P
将P插入就绪队列
}
}
2)分类
- 整型信号量。若进行P操作时无可用资源,则进程会持续对该信号量进行测试,存在“忙等”现象,未遵循“让权等待”原则。
- 记录型信号量。添加了链表结构,用于链接所有等待该资源的进程。若进程无资源可用,则进行自我阻塞,放弃处理器,加入等待该资源的等待链表队列中。
3)应用
- 实现进程同步,若想要S1运行在S2之前。
semaphore N = 0;
p1() {
...
//运行p1代码内容
S1;
//进行V操作
V(N);
...
}
p2() {
...
//进行P操作
P(N);
//运行p2代码内容
S2;
...
}
- 实现进程互斥
semaphore N = 1;
p1() {
...
//进行P操作
P(N);
p1临界区代码;
//进行V操作
V(N);
...
}
p2() {
...
//进行P操作
P(N);
p2临界区代码;
//进行V操作
V(N);
...
}
1)生产者-消费者问题
生产者与消费者共享一个有界缓冲区,生产者向其中投入产品,消费者从中取走产品。
例如:
- 输入时,输入进程是生产者,计算进程是消费者。
- 输出时,计算进程是生产者,打印进程是消费者。
为解决上述问题,需设置两个信号量,分别说明空(empty)缓冲区数目和满(full)缓冲区数目。其中empty初始值为N,full初始值为0。同步程序如下:
// 满缓冲区
Semaphore full = 0;
// 空缓冲区
Semaphore empty = n;
// 互斥信号量
Semaphore mutex = 1;
Producer() {
while(true) {
生产出产品;//nextp为临时缓冲区
// 申请空缓冲区
P(empty);
// 申请使用缓冲池
P(mutex);
将产品放入缓冲池
// 释放缓冲区
V(mutex);
// 增加一个满缓冲区
V(full);
}
}
Consumer() {
while(true) {
Produce in nextp;//nextp为临时缓冲区
// 申请满缓冲区
P(full);
// 申请使用缓冲池
P(mutex);
将产品放入缓冲池
// 释放缓冲区
V(mutex);
// 增加一个空缓冲区
V(empty);
消费掉产品
}
}
顺序不可颠倒,如果生产者先P(mutex),会产生死锁现象。举例:当缓冲区满时,生产者再次生产。就会出现死锁。
2)读者-写者问题
需满足的基本条件
- 任意多个读者可以同时读这个文件
- 一次只能有一个写者可以往文件中写(写者必须互斥)
- 如果一个写者正在操作,禁止任何读进程和写进程访问,
(1)读者优先算法
semaphore rmutex = 1; // 保证读者互斥
semaphore mutex = 1; // 保证数据写时互斥
int readcount = 0; //记录读者的数量
reader() {
while(true) {
// 申请读者使用权
P(rmutex);
// 如果没有读者,则直接获取读取权利,阻止写入
if(readcount == 0)
P(mutex);
readcount++;
// 释放读者使用权,可以让其他读者使用
V(rmutex);
读取操作
// 申请读者使用权
P(rmutex);
readcount--;
// 没有读者了,此时允许写入
if(readcount == 0)
V(mutex);
// 释放读者使用权,可以让其他读者使用
V(rmutex);
}
}
writer() {
while(true) {
P(mutex);
写操作
V(mutex);
}
}
(2)公平情况算法
semaphore rmutex = 1; // 保证读者互斥
semaphore wmutex = 1; // 保证写者互斥
semaphore mutex = 1; // 保证数据写时互斥
int readcount = 0; //记录读者的数量
reader() {
while(true) {
//查看是否有写者存在
P(wmutex);
// 申请读者使用权
P(rmutex);
// 如果没有读者,则直接获取读取权利,阻止写入
if(readcount == 0)
P(mutex);
readcount++;
// 释放读者使用权,可以让其他读者使用
V(rmutex);
// 释放写者使用权
V(wmutex);
读取操作
// 申请读者使用权
P(rmutex);
readcount--;
// 没有读者了,此时允许写入
if(readcount == 0)
V(mutex);
// 释放读者使用权,可以让其他读者使用
V(rmutex);
}
}
writer() {
while(true) {
// 是否有其他写者存在
P(wmutex);
P(mutex);
写操作
V(mutex);
V(wmutex);
}
}
(3)写者优先算法
semaphore rmutex = 1; // 保证读者互斥
semaphore wmutex = 1; // 保证写者互斥
semaphore mutex = 1; // 保证数据写时互斥
semaphore readable = 1; //表示当前是否有写者
int readcount = 0, writecount = 0; //记录读者、写者的数量
reader() {
while(true) {
//查看是否有写者存在,若没有则占用
P(readable);
// 申请读者使用权
P(rmutex);
// 如果没有读者,则直接获取读取权利,阻止写入
if(readcount == 0)
P(mutex);
readcount++;
// 释放读者使用权,可以让其他读者使用
V(rmutex);
// 释放readable,允许其他读者或写者占用
V(readable);
读取操作
// 申请读者使用权
P(rmutex);
readcount--;
// 没有读者了,此时允许写入
if(readcount == 0)
V(mutex);
// 释放读者使用权,可以让其他读者使用
V(rmutex);
}
}
writer() {
while(true) {
// 占用wmutex,准备修改writecount
P(wmutex);
// 若为第一个写者,则阻止后续读者进入
if(writecount == 0)
P(readable);
writecount++;
// 允许其他写者修改writecount
V(wmutex);
P(mutex);
写操作
V(mutex);
P(wmutex);
writecount--;
if(writecount == 0)
V(readable);
V(wmutex);
}
}
3)哲学家进餐问题
描述:五个哲学家绕一张圆桌吃饭,每两个哲学家之间放一根筷子,哲学家的动作包括思考和进餐。进餐时需要同时拿起左边和右边的筷子,思考时则将筷子放回。
错误解法:
semaphore Fork[5] = {1, 1, 1, 1, 1};
philosopher (int i) { // i = 1, 2, 3, 4, 5
while(true) {
思考;
// 拿起左边筷子
P(Fork[i % 5]);
// 拿起右边筷子
P(Fork[(i + 1) % 5]);
想吃饭;
进餐;
// 放下左边筷子
V(Fork[i % 5]);
// 放下右边筷子
V(Fork[(i + 1) % 5]);
}
}
当所有哲学家同时左手拿起筷子时,会产生死锁。
解决方法:
- 最多允许4个哲学家同时进餐
- 哲学家左右两边筷子同时可用时,才拿起筷子
- 奇数号哲学家拿起左边筷子,偶数号哲学家拿起右边筷子
第三种解法:
semaphore Fork[5] = {1, 1, 1, 1, 1};
philosopher (int i) { // i = 1, 2, 3, 4, 5
while(true) {
思考;
想吃饭;
// 奇数号哲学家
if(i % 2 != 0) {
// 拿起左边筷子
P(Fork[i % 5]);
// 拿起右边筷子
P(Fork[(i + 1) % 5]);
进餐;
// 放下左边筷子
V(Fork[i % 5]);
// 放下右边筷子
V(Fork[(i + 1) % 5]);
} else {
// 拿起右边筷子
P(Fork[(i + 1) % 5]);
// 拿起左边筷子
P(Fork[i % 5]);
进餐;
// 放下右边筷子
V(Fork[(i + 1) % 5]);
// 放下左边筷子
V(Fork[i % 5]);
}
}
}
4)理发师问题
理发店有一位理发师、一张理发椅以及多个凳子。代码介绍理发过程。
第一种,正常流程。
int waiting = 0;
// 用户互斥操作waiting
semaphore mutex = 1;
// 理发椅信号量
semaphore bchair = 1;
// 等待椅信号量
semaphore wchair = 1;
// 同步理发师和顾客的信号量
semaphore ready = finish = 1;
barber() {
while(true)
{
// 有顾客坐在理发椅上准备好了
P(ready);
理发;
// 理发完成
V(finish);
}
}
customer() {
// 申请使用waiting变量
P(mutex);
if(waiting <= n)
{
waiting++;
V(mutex);
} else {
V(mutex);
离开;
}
// 申请等候椅子
P(wchair);
// 申请理发椅
P(bchair);
// 申请完理发椅后,需要释放等候椅子
V(wchair);
// 准备就绪
V(ready);
// 未完成理发
P(finish);
// 理完发后,释放理发椅子
V(bchair);
//等待人数对应减少
P(mutex);
waiting--;
V(mutex);
}
第二种,将理发椅、凳子、顾客统一成一个变量。理发师:只要有顾客就不断理发。
int chairs = n + 1; // 等待的椅子和理发椅数量
// 操作chairs
semaphore mutex = 1;
// 等待理发的顾客数量 初始值为0
semaphore ready = 0;
// 理发师初始状态为空闲
semaphore finish = 1;
barber() {
while(true)
{
// 有顾客坐在理发椅上准备好了
P(ready);
理发;
P(mutex);
chairs++;
V(mutex);
// 理发完成
V(finish);
}
}
customer() {
// 申请使用chairs变量
P(mutex);
if(chairs > 0)
{
chairs--;
V(mutex);
V(ready);
P(finish);
} else {
// 无空余的椅子
V(mutex);
离开;
}
}
管程
1)基本特征
- 局部在管程的数据只能被局部管程访问
- 进程只能通过管程内的过程,才能进入管程访问共享数据
- 每次只允许一个进程在管程内执行内部过程,其他进程若想访问则需要排队
死锁(重中之重)
多个进程因为竞争资源处于永久阻塞状态,无外力作用,将无法进行下一步操作。
- 参与死锁的进程至少有两个
- 每个参与死锁的进程均等待资源
- 参与死锁的进程中至少有两个进程占有资源
- 死锁进程是系统中当前进程集合的一个子集
资源是否可以剥夺,取决于资源本身的性质。
1)可剥夺资源
占有者进程需要使用该资源,另一个进程可以强行抢过来自己用。
2)不可剥夺资源
占有者进程使用着该资源不会呗其他进程抢夺。
- 互斥条件。即:排他性,在一段时间内某种资源仅为一个进程所占有。
- 不剥夺条件。进程所获得的资源在未使用完毕之前,都不能被其他进程抢走。
- 请求与保持条件。在请求资源的同时,已有资源一直保持持有。
- 环路等待条件。存在一种进程资源的循环等待链,而链中的每一个进程已经获得的资源同时被链中的下一个进程所请求。
1)鸵鸟算法
对死锁视而不见,不理睬。
2)预防死锁
通过设置某些条件,破坏产生死锁的4个必要条件其中一个或多个来预防死锁的产生。
3)避免死锁
用某种方法防止系统进入不安全状态,从而避免死锁的产生。
4)检测及解除死锁
通过检测发现死锁,采用某种措施解除死锁。
1)互斥条件
允许多个进程同时访问资源。但是本身很多资源都是不能同时访问的。就有问题。
2)不剥夺条件
策略:在某个进程得不到应得到的资源时,释放自身所有资源。详细的算法特别复杂,还可能会导致运行到一半的程序又得重新再运行一遍。
3)请求与保持条件
策略:采用静态预先分配方法。运行时需要的资源全部到位才运行,否则就不运行。这样会降低资源的利用率。
4)环路等待条件
为资源编号,获取资源时必须按顺序获取,若请求了第一个资源,则后面请求资源的时候只能往后请求资源。例如:打印机为1,磁带机为2。则申请过打印机资源后,只能申请磁带机资源。
1)安全状态与不安全状态
容易混淆的错误:
- 不安全状态不是指系统已经产生了死锁。不安全状态是指系统可能发生死锁的状态,并不意味着系统已经发生死锁。
- 处于不安全状态的系统不会必然导致死锁。
2)银行家算法
- 从进程状态考虑,死锁处于等待状态,饿死处于忙等状态。
- 死锁进程等待的是永远不会释放的资源。饿死等待的是会释放但是不会分配给自己的资源
- 死锁一定发生了循环等待,饿死则没有。资源分配图可以检测死锁,但是不会检测饿死
- 死锁一定涉及多个进程,而饿死或被饿死的进程可能只有一个。
浙公网安备 33010602011771号