进程
进程
进程的概念
进程是程序的一次执行过程。进程映像由程序段、相关数据段和PCB(进程控制块)三部分组成。
- PCB 记录了进程描述信息(PID、UID)、进程拥有的资源(内存、I/O设备等)和进程运行情况(CPU使用时间等)。
- 数据段 包含运行过程中产生的各种数据。
- **程序段 ** 程序指令,多个进程可以运行同一个程序。
进程的特征
- **动态性 **
- **并发性 **
- **独立性 **
- **异步性 **
- **结构性 **
进程的状态和转换
- **运行态 ** 进程正在处理机上运行。就绪态可转换为运行态。
- **就绪态 ** 进程缺少处理机资源。创建态、运行态(所分配处理机的时间片结束)、阻塞态(等待的某一事件发生)可转换为就绪态。
- **阻塞态(等待态) ** 进程需要其他资源(除处理机之外)或等待某一事件(I/O操作的完成)。运行态可转换为阻塞态。进程的阻塞是进程自身的主动行为。
- **创建态 ** 进程正在被创建,尚未转到就绪态。
- **结束态 ** 进程正在从系统消失。进程需要结束时,需要先转换为结束态,然后再进一步进行资源释放和回收工作。
进程的通信
- **共享存储 ** 进程之间利用一块直接访问的共享空间进行写读操作实现信息的交换。
- 消息传递 进程之间利用操作系统提供的消息传递方法(发送消息和接收消息两个原语)进行通信。
- 管道通信 进程之间利用一个共享文件(pipe文件)进行通信。
处理机调度
三级调度
作业调度
作业调度就是内存与辅存之间的调度,对每个作业只调入一次,调出一次
中级调度(内存调度)
暂时不能运行的进程从内存调至外存等待或者把具备运行条件的进程重新调入内存
低级调度(进程调度)
按照某种方法和策略从就绪队列中选取一个进程,把处理器分配给它。
这三种调度方式的频率从大到小:低级调度 > 中级调度 > 作业调度。
主要区别是调度的起始位置和终止位置不同,获得计算机资源不同,
并且进程调度是最基本的。
进程调度方式
非剥夺式调度
直至某进程完成或者因某事件进入阻塞态,才把处理机分配给其他进程
剥夺式调度
更为紧迫的进程会遵循一定的原则抢占处理机
调度算法
先来先服务(FCFS)
属于不可剥夺算法,利于CPU繁忙型作业,不利于I/O繁忙型作业,利于长作业
短作业优先(SJF)
对短作业进行优先调度。对长作业不利,有可能导致饥饿现象,但其的平均等待时间、平均周转时间最少。
优先级调度
选择优先级较高的作业调入内存或者把处理机分配给优先级较高的进程。优先级设置原则如下:系统进程 > 用户进程 交互型进程 > 非交互型进程 i/o型进程 > 计算型进程
高响应比优先调度
响应比 = (等待时间 + 要求服务时间)/ 要求服务时间,利于短作业,综合了FCFS和SJF算法
时间片轮转算法
主要适用于分时系统,时间片大小对系统性能的影响很大
多级反馈队列调度算法
多级反馈队列算法综合了时间片轮转和优先级调度算法,设置了多个就绪队列,并赋予各队列中时间片大小不同。优先级越大的队列时间片越小。优点利于短作业,周转时间较短,并且不会长期得不到处理。
进程同步
临界资源
进入区 检查是否能进入临界区,若能进入,则上锁
临界区 访问临界资源的代码
退出去区 解锁
剩余区 代码的其他部分
四个准则
- 空闲让进 临界区空闲时,可以允许进程进入临界区
- 忙则等待 当临界区已有进程时,其他试图进入临界区的进程必须等待
- 有限等待 保证试图进入临界区的进程在有限时间内进入临界区
- 让权等待 不能进入临界区时,应立即释放处理器
临界区互斥的基本方法
软件方法
- 单标志法 设置一个公用的整形变量turn,指示被允许进入临界区的进程编号,违背空闲让进原则
p0进程:
while(turn != 0);
critical section;
turn = 1;
reminder section;
p1进程:
while(turn != 1);
critical section;
turn = 0;
reminder section;
当p0进程退出临界区后,p1不进入临界区,此时虽然临界区处于空闲状态,但p0进程却不能进入临界区
- 双标志先检查法 每个进程访问临界资源之前,先查看临界资源是否正被访问,若没有进行访问,再上锁
p0进程:
while(flag[1]); (1)
flag[0] = true; (3)
critical section;
flag[0] = false;
reminder section;
p1进程:
while(flag[0]); (2)
flag[1] = true; (4)
critical section;
flag[1] = false;
reminder section;
在进程发生切换,即执行(1)(2)(3)(4)顺序时,会违背忙则等待原则
- 双标志后检查法 先进行上锁,再查看临界资源是否被访问
p0进程:
flag[0] = true; (1)
while(flag[1]); (3)
critical section;
flag[0] = false;
reminder section;
p1进程:
flag[1] = true; (2)
while(flag[0]); (4)
critical section;
flag[1] = false;
reminder section;
在进程发生切换,即执行(1)(2)(3)(4)顺序时,会违背空闲让进原则和有限等待原则
- Peterson’s Algorithnm 进程会先设置自己的标志,然后设置turn标志,然后检查临界区是否被访问和不允许进入标志
p0进程:
flag[0] = true;turn = 1 (1)
while(flag[1] && turn == 1); (3)
critical section;
flag[0] = false;
reminder section;
p1进程:
flag[1] = true;turn = 0 (2)
while(flag[0] && turn == 0); (4)
critical section;
flag[1] = false;
reminder section;
综合了单标志检查法和双标志后检查法,利用flag解决临界资源的互斥问题和利用turn解决了饥饿现象,遵循了空闲让进,忙则等待,有限等待原则,但不遵循让权等待原则
让权等待是指进不了临界区资源则释放处理机资源,而不是处于while循环。
硬件屏蔽方法
- **中断屏蔽方法 **
关中断
临界区
开中断
简单高效,但不适用于多处理机,只适用于操作系统内核进程。
- **硬件指令方法 **
例如TestAndSet指令,这条指令是原子操作,即执行代码时不允许被中断,还有Swap等指令。
信号量
信号量机制是一种功能较强的机制,可用来解决互斥与同步的问题。
- 整数型信号量
wait(S){
while(S <= 0);
S = S -1;
}
signal(S)
{
S = S + 1;
}
整数量S是用于表示资源数目,从wait操作可以看出,整数型信号量没有遵循让权等待原则
- **记录型信号量 **
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);
}
}
S.value < 0 表示资源已经分配完,则调用block原语使进程自我阻塞,放弃处理机,因此遵循了让权等待原则
使用信号量机制可以解决进程的同步问题和互斥问题。在同步问题中,若某个行为要用到某资源,则需要在这个行为之前P这个资源(即wait);若某个行为会提供某种资源,则在这个行为之后需要V这个资源(即signal)。在互斥问题中,P,V操作要紧夹互斥资源的那个行为,中间不能有其他冗余代码。
管程
有共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序称为管程
管程由4部分组成:
- 管程的名称
- 局部于管程内部的共享数据结构数据说明
- 对该数据结构进行操作的一组过程
- 对局部于管程内部的共享数据设置初始值的语句
管程把共享资源的操作封装起来,管程内的共享数据结构只能被管程内的过程所访问。也就是说一个进程只能通过调用管程内的过程才能进入管程访问共享资源。并且每次只允许一个进程进入管程。
monitor Demo{
共享数据结构S;
condition x;
init_code(){...} //初始资源数
take_away()
{
if(S < 0) x.wait(); // 资源不够,在条件变量x上阻塞等待
资源足够的处理过程
}
give_back()
{
归还资源,做一系列的处理
if(有进程等待) x.signal(); // 唤醒阻塞进程
}
}
生产者-消费者问题
问题:一组生产者和一组消费者进程共享一个初始为空,大小为n的缓冲区,只有缓冲区没有满时,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区没有为空时,消费者才能取出消息,否则必须等待。而且缓冲区是临界资源,只能允许一个生产者放入消息,或一个消费者取出消息。
从问题可以看出缓冲区是临界资源,所以缓冲区只能允许互斥访问,存在互斥关系;同时生产者和消费者还存在同步的关系。
semaphore mutex = 1; // 用于互斥
semaphore empty = n; //空闲缓冲区
semaphore full = 0; //满缓冲区
producer(){
while(1){
produce a item;
P(empty); //需要用到empty的资源,P一下
p(mutex); //进入临界区
add item to buffer; //将数据放入缓冲区
V(mutex); //离开互斥区
V(full); //满缓冲区加一
}
}
consumer(){
while(1){
P(full); //需要用到full的资源,P一下
p(mutex); //进入临界区
remove an item to buffer; //将数据放入缓冲区
V(mutex); //离开互斥区
V(empty); //空缓冲区加一
comsume this item;
}
}
实现互斥的P操作要在实现同步的P操作之后。因为在缓存区满的情况下,若生产者先执行P(mutex),则生成者在执行P(empty)时则会阻塞,而消费者希望取出消息将生产者唤醒,当执行P(mutex)会被阻塞,从而陷入了死锁。
读者-写者问题
问题描述:有读者和写者两组进程同时共享一个文件,允许多个读进程同时访问共享文件,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能发生数据错误。
从问题可以看出读进程之间没有互斥的关系,写进程与写进程是互斥的,写进程与读进程也是互斥的。
读进程之间没有互斥关系,所以需要一个计数器来记录读进程此时访问文件的个数,并且对于计数器的访问也要是互斥的,否则可能会导致计数器的值发生错误。
int count = 0;
semaphore mutex = 1;
semaphore rw = 1;
writer(){
while(1)
{
p(rw);
writing;
v(rw);
}
}
reader(){
while(1)
{
p(mutex); //对计数器进行互斥访问
if(count == 0) { p(rw); }//若读进程此时访问文件数为0,则P(rw),阻塞写进程访问文件
count++;
v(mutex);
reading;
p(mutex);
count--;
if(count == 0) { v(rw); }//若无读进程访问文件,则允许写进程写
v(mutex);
}
}
当一直有读进程访问文件时,会导致写进程长时间等待且存在写进程“饿死”的情况。为了避免写进程“饿死”的情况,应当在有写进程请求访问的时候,禁止后续读进程的请求。所以再增加一对信号量。
int count = 0;
semaphore mutex = 1;
semaphore rw = 1;
semaphore w = 1; //用于读-写公平
writer(){
while(1)
{
p(w)
p(rw);
writing;
v(rw);
v(w);
}
}
reader(){
while(1)
{
p(w); //若有写进程请求,此时会阻塞读进程
p(mutex); //对计数器进行互斥访问
if(count == 0) { p(rw); }//若读进程此时访问文件数为0,则P(rw),阻塞写进程访问文件
count++;
v(mutex);
v(w);
reading;
p(mutex);
count--;
if(count == 0) { v(rw); }//若无读进程访问文件,则允许写进程写
v(mutex);
}
}
哲学家进餐问题
问题描述:一张圆桌上有5位哲学家,每两位哲学家之间有一根筷子,哲学家有思考和进餐两种状态,当哲学家饥饿时,会试图拿起左,右两根筷子(一根一根地拿)进餐,若只拿起一根筷子,则必须等待。可以看出5名哲学家与左右邻居对其中间筷子的访问是互斥关系。
semaphore chopstick[5] = {1,1,1,1,1};
semaphore mutex = 1;
Pi(){
do{
P(mutex);
P(chopstick[i]); //拿起左边筷子
P(chopstick[(i + 1) % 5]); //拿起右边筷子
V(mutex);
eating;
V(chopstick[i]);
V(chopstick[(i + 1) % 5]);
thinking;
}while(1);
}
因为有5根筷子,所以5个临界资源。为了每位哲学家只拿起最右边的筷子或者只拿起最左边的筷子导致死锁。设置了mutex信号量实现第一位哲学拿筷子时,直接拿起两根筷子,从而避免死锁。
死锁
定义
死锁:指多个进程因竞争资源而造成的僵局(互相等待),若无外力作用,这些进程都无法推进执行。
死锁:至少是两个进程一起死锁,死锁进程处于阻塞态
饥饿:可以只有一个进程饥饿,饥饿进程可能阻塞也可能就绪
死循环:可能只有一个进程发生死循环,死循环进程可上处理机
产生原因
- 对不可剥夺资源的竞争
- 进程推进顺序非法,即请求和释放资源的顺序不当
死锁产生的必要条件
- **互斥条件 ** 一段时间内某资源仅为一个进程所占有
- 不剥夺条件 进程所获得的资源不能被其他进程强行夺走,只能由自己来主动释放
- 请求并保持条件 一个进程申请资源得不到满足时处于等待资源的状态且不释放已占资源
- 循环等待条件 存在一个进程环路,其中每一个进程已获得的资源同时被下一个进程所请求
注意:发生死锁时一定有循环等待,但发生循环等待未必死锁
死锁的处理策略
资源分配策略 | 各种可能模式 | |
---|---|---|
死锁预防 | 保守,资源闲置 | 一次请求所有资源,资源剥夺,资源按序分配 |
死锁避免 | 预防和检测的折中 | 寻找可能的安全顺序 |
死锁检测 | 宽松,只要允许就分配资源 | 定期检查死锁是否发生 |
死锁预防
- **破坏互斥条件 **
如SPOOLing技术,把独占设备在逻辑上改造成共享设备。 - **破坏不剥夺条件 **
常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源。 - 破坏请求并保持条件
采用预先静态分配的方法,即在进程运行前一次性申请完所需的资源,直至进程运行完毕后才主动释放资源。会导致系统资源被严重浪费。 - 破坏循环并等待条件
采用资源有序分配法。给资源进行编号,规定每个进程只能按编号递增的顺序请求资源。但限制了新类型设备的增加以及造成资源的浪费。
死锁避免
死锁避免同样属于事先预防策略。死锁避免是在资源动态分配的过程中,防止系统进入不安全状态,以避免发生死锁。安全状态是指系统能按某种进程推进顺序对每个进程分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可以顺利地完成。
注意:并非所有的不安全状态都是死锁状态,但当系统进入不安全状态后,便有可能进入死锁状态。只要系统处于安全状态,系统便可避免进入死锁状态
银行家算法
安全序列 指的是如果系统按照这种序列分配资源,则每个进程都能顺利完成,只要找出一个安全序列,系统就是安全状态。安全序列可能有多个。如果分配资源后,系统找不出任何一个安全序列,系统就进入了不安全状态。
如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁。发生死锁时一定处于不安全状态。
通过在资源分配前判断这次分配后是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这是银行家算法的核心思想。
数据结构
- 可利用资源向量Availabel 长度为m个元素的数组Available表示可用的每类资源数目。
- 最大需求矩阵Max n * m 矩阵,表示各进程对每类资源的最大需求。
- 分配矩阵Allocation n * m 矩阵,表示各进程已拥有每类资源的数量。
- Need矩阵 n * m 矩阵,表示各进程最多还需要多少资源,为Max - Allocation。
- Request矩阵 表示此次申请的每类资源数目。
银行家算法描述
- 判断此次申请的每类资源的数目是否大于各进程对每类资源的最大需求,若大于,则出错。
- 检查系统还可用的每类资源的数目是否满足此次申请的每类资源的数目。
- 满足则系统试探把资源分配给进程,并修改各类数据结构
- Available = Available - Request
- Allocation = Allocation + Request
- Need = Need - Request
- 系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。若安全,才正式将资源分配给此次申请的进程。
安全性算法
- 检查当前剩余资源是否满足每个进程对各类的资源的最大需求,若满足,则把此进程加入安全序列
- 对此进程的已拥有资源进行回收释放。
- 重复上面的步骤,直至每个进程加入安全序列,如果进程不能全部加入安全序列,则此次资源分配后,系统处于不安全状态。
死锁检测和解除
资源分配图
系统死锁可用资源分配图来描述。圆圈代表一个进程,框代表一类资源,框中的一个圆代表一类资源中的一个资源。从进程到资源的有向边称为请求边;从资源到进程的边称为分配边。
如图,进程P1已获得两个R1资源,又请求一个R2资源;进程P2已获得一个R1资源和一个R2资源,又请求一个R1资源
死锁定理
系统为死锁的条件是当且仅当系统状态的资源分配图是不可简化的,该条件为死锁定理。
简化资源分配图的步骤为:
(1)找出既不阻塞又不孤点的进程Pi(上图为P1,因为P1所申请的R2资源小于等于系统已有的R2空闲的资源,所以P1能继续运行直至完成),消去Pi所有请求边和分配边。
(2)进程Pi释放资源,可以唤醒某些等待这些资源而阻塞的进程。循环往复使用(1)的方法,若能消去图中的所有边,则称该图是完全可简化的。
如下是资源分配图的简化
死锁解除
- **资源剥夺法 **
挂起某些死锁进程,并抢占它们资源分配给其他死锁进程。需要防止被挂起的进程长时间得不到资源而处于资源匮乏的状态 - 撤销进程法
强制撤销部分或全部死锁进程并剥夺它们的资源。撤销原则可以按照进程优先级和撤销进程代价高低来进行 - 进程回退法
让一个或多个进程回退到足以回避死锁的地步。进程回退时自愿释放资源而非被剥夺。