进程的描述与控制
前趋图和程序执行
前趋图
前趋图是一个有向无环图(DAG),用于描述进程之间执行的先后的关系
结点:描述一个程序段或进程,或一条语句
**有向边:**结点之间的偏序或前序关系“——>”,Pi——>Pj,则Pi是Pj的直接前趋,Pj是Pi的直接后继
**注意:**前趋图中必须不存在循环
程序的顺序执行
一个程序由若干个程序段组成,而这些程序段的执行必须是顺序的,这种程序执行的方式就称为程序的顺序执行。
特征:
- 顺序性:处理机的操作严格按照程序所规定的顺序执行
- 封闭性:程序一旦开始执行,其计算结果不受外界因素的影响
- 可再现性:程序执行的结果与它的执行速度无关(即与时间无关),而只与初始条件有关
程序的并发执行
特征:
- 间断性:在多道程序设计的环境下,程序的并发执行,以及为完成一项任务而相互合作,这些程序之间要共享系统的资源,形成了相互制约的关系。相互制约导致并发程序具有“执行—暂停—执行”这种间断性的活动规律。
- 失去封闭性:程序在并发执行时,系统的资源状态由多道程序来改变,程序运行失去封闭性。一程序的运行受到其他程序的影响。
- 不可再现性:程序在并发执行时,多次运行初始条件相同的同一程序会得出不同的运行结果。例:共享公共变量的两个程序,它们执行时可能产生不同结果。
进程的描述
进程的特征和定义
进程:程序关于某个数据集合的一次执行过程
特征:
- 结构特征:进程控制块(PCB) + 程序 + 数据 = 进程实体
- 动态性——最基本的特征:进程:进程实体的一次执行过程,有生命周期。程序:程序是一组有序指令的集合,是静态概念
- 并发性
- 独立性
- 异步性:进程按各自独立的、不可预知的速度向前推进
###进程的三种基本状态 - 就绪状态:进程已获得除CPU之外的所有必需的资源,一旦得到CPU控制权,立即可以行。
- 运行状态: 进程已获得运行所必需的资源,它的程序正在处理机上执行
- 阻塞状态:正在执行的进程由于发生某事件而暂时无法执行时,便放弃处理机而处于暂停状态,称该进程处于阻塞状态或等待状态
挂起状态
引起挂起状态的原因:
- 终端用户的请求
- 父进程请求
- 负荷调节的需要
- 操作系统的需要
进程状态的转换:
引入挂起状态后,增加了挂起状态(静止状态)到非挂起状态(活动状态)的转换,或者相反 - 活动就绪–>静止就绪
- 活动阻塞–>静止阻塞
- 静止就绪–>活动就绪
- 静止阻塞–>活动阻塞
进程管理中的数据结构
进程控制块(PCB)的作用
存放进程管理和控制信息的数据结构称为进程控制块。它是进程管理和控制的最重要的数据结构,在创建时,建立PCB,并伴随进程运行的全过程,直到进程撤消而撤消。PCB就象我们的户口。
PCB是进程存在的唯一标志。
系统的所有PCB组织成链表或队列,常驻内存的PCB区。
进程控制块中的信息
进程标示符
每个进程都必须有一个唯一的标识符
内部标示符
外部标示符
处理机状态:
处理机状态信息主要由处理机的各种寄存器中的内容组成。处理机运行时的信息存放在寄存器中,当被中断时这些信息要存放在PCB中。
-
通用寄存器
-
指令计数器
-
程序状态字
-
PSW用户栈指针
进程调度信息: -
进程状态
-
进程优先级
-
进程调度所需的其他信息
-
事件
进程控制信息: -
程序和数据的地址
-
进程通信和同步机制
-
资源清单
-
链接指针
进程控制块的组织方式: -
线性方式
-
链接方式:吧具有相同状态的PCB通过其中的链接字链接成一个队列
-
索引方式:系统根据所有进程的状态建立几张索引表,吧各个表的内存首地址记录在内存的专用单元中。索引表的表目中记录了相应状态的某个PCB在PCB表中的地址。
进程控制
进程图:
引起创建进程的事件:
多道程序环境中,只有进程才能在系统中运行:
用户登录、作业调度、提供服务、应用请求
进程的创建:
调用进程创建原语Create()创建新进程
进程创建的过程:
申请空白PCB——为新进程分配资源——初始化进程控制块——将新进程插入就绪队列
进程的终止:
1、引起终止的事件:
正常结束、异常结束(越界错误、非法指令等)、外界干预(操作员或者操作系统干预、父进程请求、父进程终止)
2、进程终止的过程:
找出被终止进程的PCB ——
若进程状态为运行态,置CPU调度标志为真——
若其有子孙进程,终止其子孙进程并回收其资源——
回收终止进程的资源 ——
回收终止进程的PCB
进程的阻塞与唤醒
- 引起进程阻塞和唤醒的事件
1)请求系统服务 2)启动某种操作
3)新数据尚未到达 4)无新工作可做 - 进程阻塞过程
调用阻塞原语阻塞自己;
将PCB中的状态改为阻塞,并加入阻塞队列;
转进程调度。 - 进程唤醒过程
把阻塞进程从等待该事件的阻塞队列中移出;
置进程状态为就绪态,将PCB插入到就绪队列中。
阻塞原语与唤醒原语作用相反,成对使用
原语:
原语是在操作系统中调用核心层子程序的指令。. 与一般广义指令的区别在于它是不可中断的,而且总是作为一个基本单位出现。. 它与一般过程的区别在于:它们是“ 原子操作 (primitive or atomic action)”。. 所谓原子操作,是指一个操作中的所有动作要么全做,要么全不做。. 换言之,它是一个不可分割的基本单位,因此,在执行过程中不允许被中断。. 原子操作在管态下执行,常驻内存。. 原语的作用是为了实现进程的通信和控制,系统对进程的控制如不使用原语,就会造成其状态的不确定性,从而达不到 进程 控制的目的
进程的挂起与激活
- 进程的挂起过程
检查被挂起进程的状态:
若处于活动就绪,则改为静止就绪;
若处于活动阻塞,则改为静止阻塞;
若挂起的进程正在执行,则重新进行进程调度。 - 进程的激活过程:
- 激活原语先将进程从外存调入内存;
- 检查该进程的状态:
若为静止就绪,则改为活动就绪;
若处于静止阻塞,则改为活动阻塞。
进程同步
概念:
-
进程间两种形式的制约关系
(1) 间接相互制约关系 — 源于资源共享
(2) 直接相互制约关系 — 源于进程合作 -
临界资源 — 互斥访问
-
临界区
临界区:每个进程中访问临界资源的那段代码
访问临界区的程序设计为:
对欲访问的临界资源进行检查,
若此刻未被访问,设正在访问的标志 ……进入区
访问临界资源 ……临界区
将正在访问的标志恢复为未被访问的标志 ……退出区
其余部分 ……剩余区 -
同步机制应遵循的规则
空闲让进
忙则等待
有限等待
让权等待
信号量机制
1、整型信号量:
定义:整型量,除初始化外,仅能通过两个原子操作来访问
P操作:wait(S)😛(S)
while(S<=0)do no-p;
S--;
V操作 signal(S):V(S)
S++;
PV操作是原子操作不可中断
semaphore mutex =1;
begin
parbegin
process 1: begin
repeat
wait(mutex);
critical section
signal(mutex);
remainder section
until false;
end
process 2: begin
repeat
wait(mutex);
critical section
signal(mutex);
remainder section
until false;
end
parend
缺点:没有遵循让权等待原则
2、记录性信号量:
引入整型变量value(代表资源数目)、进程链表List (链接所有等待进程)
记录型数据结构:
typedef struct{
int value;
struct process_control_block * list;
} semaphore;
缺点:共享的资源越多,死锁的可能性越大
3、AND型信号量
生产者消费者问题
empty的初始值应该为缓存区的最大容量,当empty的值>0的时候生产者才可以进行放入操作,full的初始值应该是零,在刚开始时候缓存区的产品为零,消费者是无法取走产品的,而信号量mutex的值应该为1,通过它实现了生产者间,消费者间互斥
本情景是生产者间消费者间互斥的使用缓存区
每个程序中的多个wait操作的顺序不能颠倒。应该先执行对资源信号量的wait操作,再执行对互斥信号量的wait操作,否则可能会引起进程的死锁!
通过AND信号量解决
Int in=0, out= 0;
semaphore mutex=1,empty=n,full=0;
Void producer(){
do{
Swait(empty, mutex);
buffer[in]=nextp;
in=(in+1) mod n;
Ssignal(mutex, full);
}while(TRUE)
}
Void consumer(){
do{
Swait(full, mutex);
nextc:=buffer[out];
out=(out+1) mod n;
Ssignal(mutex, empty);
}while(TRUE)
}
Void main(){
cobegin
proceducer();
consumer();
coend
}
通过信号量集解决
Int in=0, out= 0;
semaphore mutex=1,empty=n,full=0;
Void producer(){
do{
…
Swait(empty,1,1; mutex,1,1);
buffer[in]=nextp;
in=(in+1) mod n;
Ssignal(mutex,1; full,1);
}while(TRUE)
}
Void consumer(){
do{
Swait(full,1,1; mutex,1,1);
nextc:=buffer[out];
out=(out+1) mod n;
Ssignal(mutex,1; empty,1);
}while(TRUE)
}
Void main(){
cobegin
proceducer();
consumer();
coend
}
哲学家进餐问题:
五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。
可见:相邻两位不能同时进餐;
最多只能有两人同时进餐
如果使用记录型信号量解决
semaphore stick[5]={1,1,1,1,1};
do{
wait(stick[i]);
wait(stick[(i+1)%5]);
吃;
signal(stick[i]);
signal(stick[(i+1)%5]);
i++;
}while(TRUE);
可见当五个哲学家都拿左边筷子时,就会将筷子全部拿完,而无法进行拿右边筷子,造成了进程的死锁
所以第一种方法就是让哲学家同时拿到左右筷子,吃完后再同时释放两根筷子,这样就避免了死锁问题
semaphore stick[5]={1,1,1,1,1};
do{
Swait(stick[i],stick[(i+1)%5]);
吃;
signal(stick[i],stick[(i+1)%5]);
i++;
}while(TRUE);
但是:当题目要求,欸!我就是让你用记录性信号量来实现则,将哲学家分为奇偶,奇数哲学家先拿左边的再拿右边的,偶数哲学家先拿右边的后拿左边的就可以完美的实现
semaphore chopstick[5]={1,1,1,1,1};
第i 位哲学家的活动可描述为:
do{
if i mod 2=1 {
wait(chopstick[ i ]);
wait(chopstick[ ( i +1) mod 5] )
}
else
{
wait(chopstick[ ( i +1) mod 5] );
wait(chopstick[ i ])
}
eat;
signal(chopstick[ i ]);
signal(chopstick[(i +1)mod 5]);
…
think;
}while(TRUE)
情景三:读者写者问题(敲黑板划重点!!!)
可以看出:允许多个进程同时读一个共享对象,但不允许一个Writer进程和其他Reader进程或Writer进程同时访问共享对象
“读者——写者问题”是保证一个Writer进程必须与其他进程互斥地访问共享对象的同步问题。
最终实现的就是:读读共享,写写互斥,读写互斥
互斥信号量wmutex: 实现Reader与Writer进程间在读或写时的互斥整型变量Readcount: 表示正在读的进程数目;
由于只要有一个Reader进程在读,便不允许Writer进程写。 ∴仅当Readcount=0,即无Reader进程在读时,Reader才需要执行Wait(wmutex)操作。若Wait(wmutex)操作成功,Reader进程便可去读,相应地,做Readcount+1操作。
同理,仅当Reader进程在执行了Readcount减1操作后其值为0时,才需执行signal(wmutex)操作,以便让Write进程写。
互斥信号量rmutex: Reader进程间互斥访问Readcount
semaphore rmutex=1, wmutex =1;
int readcount =0;
Void Reader(){
do{
wait(rmutex);
if (Readcount==0)
wait(wmutex);
Readcount ++;
signal(rmutex);
…
读;
…
wait(rmutex);
Readcount - -;
if (Readcount==0)
signal(wmutex);
signal(rmutex);
}while(TRUE);
}
Void writer(){
do{
wait(wmutex);
写;
signal(wmutex);
}while(TRUE);
}
Void main(){
Cobegin
reader(); writer();
Coend
}
利用信号量集机制实现
int RN;
semaphore L=RN, mx= 1;
Void reader() {
do{
Swait(L, 1, 1);
Swait(mx, 1, 0);
…
读;
…
Ssignal(L, 1);
}while(TRUE)
}
Swait(mx, 1, 0)语句起着开关的作用。只要无writer进程进入写,mx=1,reader进程就都可以进入读。但只要一旦有writer进程进入写时,mx=0,则任何reader进程就都无法进入读。
Void writer(){
do{
Swait(mx, 1, 1; L, RN, 0);
写;
Ssignal(mx, 1);
}while(TRUE)
}
Swait(mx, 1, 1; L, RN, 0)语句表示仅当既无writer进程在写(mx=1),又无reader进程在读(L=RN),writer进程才能进入临界区写。
Void main(){
cobegin
reader(); writer();
Coend
}