进程管理
进程
进程是程序的一次执行
是一个程序及其数据在处理机上顺序执行时所发生的活动
是具有独立功能的程序在一个数据集合上的一次运行过程
是系统进行资源分配和调度的一个基本单位
是PCB结构、程序和数据的集合
设备分配只针对现有进程,不会创建进程
进程的特征:
- 动态性:进程的实质是程序的一次执行过程,因此,动态特征是进程最重要的特征
- 并发性:没有为之建立进程的程序是不能并发执行的,仅当为之建立一个进程后才能参加
并发执行 - 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
- 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可
预知的速度向前推进 - 结构特征:为了控制和管理进程,系统为每个进程设立一个进程控制块--PCB
进程与程序的区别:
- 程序是进程的静态文本,进程是执行程序的动态过程
- 进程与程序之间不是一一对应的,同一程序同时运行于若干不同的数据集合上,它将属于若干个不同的进程;
一个进程可以执行多个程序 - 程序可作为软件资源长期保存,进程只是一次执行过程,是暂时的
- 进程是系统分配调度的独立单位,能与其他进程并发执行
进程状态及其演变
进程执行的间断性,决定了进程可能具有多种状态
基本状态
运行的进程可能具有就绪、执行、阻塞三种基本状态
当进程已分配到除CPU以外的所有必要资源时,它便处于就绪状态,一旦获得CPU,便立即执行,进入执行状态
正在执行的进程,由于发生某个事件而暂时无法执行时,便放弃处理机而进入阻塞状态
由于执行的进程变为阻塞状态后,调度程序立即把处理机分配给另一个就绪进程(因此,阻塞进程的事件消失后,进程不会立即恢复到执行状态,而转变为就绪状态,重新等待处理机)
创建和终止
为了管理的需要,还存在着两种比较常见的进程状态,即创建状态和终止状态
创建状态:
引起创建的事件:
- 用户登录
- 作业调度:为被调度的作业创建进程
- 提供服务:要求打印
- 应用请求
创建一个进程一般要通过两个步骤:
- 首先,为一个新进程创建PCB,并填写必要的管理信息;
- 其次,把该进程转入就绪状态并插入就绪队列之中。
当一个新进程被创建时,系统已为其分配了PCB,填写了进程标识等信息,但由于该进程所必需的资源或其它信息,如主存资源尚未分配等,一般而言,此时的进程已拥有了自己的PCB,但进程自身还未进入主存,即创建工作尚未完成,进程还不能被调度运行,其所处的状态就是创建状态。
引入创建状态,是为了保证进程的调度必须在创建工作完成后进行,以确保对进程控制块操作的完整性。同时,创建状态的引入,也增加了管理的灵活性,操作系统可以根据系统性能或主存容量的限制,推迟创建状态进程的提交。对于处于创建状态的进程,获得了其所必需的资源,以及对其PCB初始化工作完成后,进程状态便可由创建状态转入就绪状态。
终止状态:
引起终止的事件:
- 正常结束
- 异常结束
- 外界干预
- 系统管理员kill
- 父进程终止
- 父进程请求
进程的终止也要通过两个步骤:
- 首先等待操作系统进行善后处理
- 然后将其PCB清零,并将PCB 空间返还系统
当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止状态
进入终止态的进程以后不能再执行,但在操作系统中依然保留一个记录,其中保存状态码和一些计时统计数据,供其它进程收集。一旦其它进程完成了对终止状态进程的信息提取之后,操作系统将删除该进程。
阻塞和唤醒
阻塞是进程自身的一种主动行为
a. 调用block原语
b. 停止执行,修改PCB进入阻塞队列(一个或多个
唤醒由其他相关进程完成
a. wakeup原语
b. 修改PCB进入就绪队列
挂起
为了系统和用户观察分析的需要,还引入了挂起操作,与挂起对应的是激活操作
当进程被挂起,便会进入静止状态:正在执行,便会暂停执行,处于就绪状态则不接受调度
引入挂起状态的原因有:
- 终端用户的请求:当终端用户在自己的程序运行期间发现有可疑问题时,希望暂时使自己的程序静止下来。亦即,使正在执行的进程暂停执行;若此时用户进程正处于就绪状态而未执行,则该进程暂不接受调度,以便用户研究其执行情况或对程序进行修改。我们把这种静止状态称为挂起状态。
- 父进程请求:有时父进程希望挂起自己的某个子进程,以便考查和修改该子进程,或者协调各子进程间的活动。
- 负荷调节的需要:当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的进程挂起,以保证系统能正常运行。
- 操作系统的需要:操作系统有时希望挂起某些进程,以便检查运行中的资源使用情况或进行记账。
在引入挂起状态后:
- 活动就绪→静止就绪:当进程处于未被挂起的就绪状态时,称此为活动就绪状态,表示为Readya
当用挂起原语Suspend 将该进程挂起后,该进程便转变为静止就绪状态,表示为Readys,处于Readys状态的进程不再被调度执行 - 活动阻塞→静止阻塞:当进程处于未被挂起的阻塞状态时,称它是处于活动阻塞状态,表示为Blockeda。当用Suspend原语将它挂起后,进程便转变为静止阻塞状态,表示为Blockeds。处于该状态的进程在其所期待的事件出现后,将从静止阻塞变为静止就绪
- 静止就绪→活动就绪:处于Readys 状态的进程,若用激活原语Active 激活后,该进程将转变为Readya 状态
- 静止阻塞→活动阻塞:处于Blockeds 状态的进程,若用激活原语Active 激活后,该进程将转变为Blockeda 状态
五个进程状态的转换
- NULL→创建:一个新进程产生时,该进程处于创建状态
- 创建→活动就绪:在当前系统的性能和内存的容量均允许的情况下,完成对进程创建的必要操作后,相应的系统进程将进程的状态转换为活动就绪状态
- 创建→静止就绪:考虑到系统当前资源状况和性能要求,并不分配给新建进程所需资源,主要是主存资源,相应的系统进程将进程状态转为静止就绪状态,对换到外存,不再参与调度,此时进程创建工作尚未完成
- 执行→终止:当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,进程即进终止状态
PCB
为了描述和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录型数据结构
PCB 的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程
- 作为独立运行基本单位的标志
- 能实现间断性的运行方式
- 提供进程管理所需要的信息
- 提供进程调度所需要的信息
- 实现与其他进程的同步与通信
PCB中的信息:
- 进程标识符:用于惟一地标识一个进程,一个进程通常有两种标识符:
- 内部标识符,在所有的操作系统中,都为每一个进程赋予了一个惟一的数字标识符,
它通常是一个进程的序号。设置内部标识符主要是为了方便系统使用。 - 外部标识符,它由创建者提供,通常是由字母、数字组成,往往是由用户(进程)在访问该进程时使用。为了描述进程的家族关系,还应设置父进程标识及子进程标识。此外,还可设置用户标识,以指示拥有该进程的用户。
- 内部标识符,在所有的操作系统中,都为每一个进程赋予了一个惟一的数字标识符,
- 处理机状态:主要是由处理机的各种寄存器中的内容组成的。处理机在运行时,许多信息都放在寄存器中。当处理机被中断时,所有这些信息都必须保存在PCB 中,以便在该进程重新执行时,能从断点继续执行。这些寄存器包括
- 通用寄存器,又称为用户可视寄存器,它们是用户程序可以访问的,用于暂存信息,在大多数处理机中,有 8~32 个通用寄存器,在RISC 结构的计算机中可超过100 个
- 指令计数器,其中存放了要访问的下一条指令的地址
- 程序状态字PSW,其中含有状态信息,如条件码、执行方式、中断屏蔽标志等
- 用户栈指针,指每个用户进程都有一个或若干个与之相关的系统栈,用于存放过程和系统调用参数及调用地址,栈指针指向该栈的栈顶。
- 进程调度信息:在 PCB中还存放一些与进程调度和进程对换有关的信息,包括
- 进程状态,指明进程的当前状态,作为进程调度和对换时的依据
- 进程优先级,用于描述进程使用处理机的优先级别的一个整数,优先级高的进程应优先获得处理机
- 进程调度所需的其它信息,它们与所采用的进程调度算法有关,比如,进程已等待CPU的时间总和、进程已执行的时间总和等
- 事件,指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
- 进程控制信息:
- 程序和数据的地址,指进程的程序和数据所在的内存或外存地(首)址,以便再调度到该进程执行时,能从PCB中找到其程序和数据
- 进程同步和通信机制,指实现进程同步和进程通信时必需的机制,如消息队列指针、信号量等,它们可能全部或部分地放在PCB 中
- 资源清单,即一张列出了除CPU 以外的、进程所需的全部资源及已经分配到该进程的资源的清单
- 链接指针,它给出了本进程(PCB)所在队列中的下一个进程的PCB的首地址。
PCB的组织方式
在一个系统中,通常可拥有数十个、数百个乃至数千个PCB。为了能对它们加以有效的管理,应该用适当的方式将这些PCB组织起来。目前常用的组织方式有以下两种。
- 线性方式:将系统种所有PCB都组织在一张线性表中,将该表首地址存在内存的一个专用区域
实现简单,开销小,但是每次都需要扫描整张表,适合进程数目不多的系统 - 链接方式:把同一状态的PCB链接成一个队列,形成就绪队列、若干个阻塞队列和空白队列等
对其中的就绪队列常按进程优先级的高低排列,优先级高排在队前,此外,也可根据阻塞原因的不同而把处于阻塞状态的进程的PCB排成等待I/O 操作完成的队列和等待分配内存的队列等 - 索引方式:系统根据所有进程的状态建立几张索引表,例如就绪索引表、阻塞索引表等,并把各索引表在内存的首地址记录在内存的一些专用单元中,在每个索引表的表目中,记录具有相应状态的某个PCB在PCB址
进程的控制
进程控制是进程管理最基本的功能,主要包括创建新进程,终止已完成的进程,将发生异常的进程置于阻塞状态,进程运行中的状态转换等
进程创建
参数:进程标识、优先级、进程起始地址、CPU初始状态、资源需求等等
创建进程的过程:
- 创建一个空白PCB
- 为新进程分配所需资源
- 初始化PCB
- 标识信息,将系统分配的标识符和父进程标识符填入新PCB
- 处理机状态信息,使程序计数器指向程序入口地址,使栈指针指向栈顶
- 处理机控制信息,将进程设为就绪/静止状态,通常设为最低优先级
- 如果就绪队列能接纳,则插入
进程终止
进程终止的时机/时间:
- 正常结束
- 异常结束
- 越界错,访问的存储区越出该进程的区域
- 保护错,试图访问不允许访问的资源,或以不适当的方式访问(写只读)
- 非法指令,试图执行不存在的指令(可能是程序错误地转移到数据区,数据当成了指令)
- 特权指令出错,用户进程试图执行一条只允许OS执行的指令
- 运行超时,执行时间超过指定的最大值
- 等待超时,进程等待某件事超过指定的最大值
- 算数运算错,试图执行被禁止的运算(被0除)
- I/O故障
- 外界干预
- 操作员或OS干预(死锁)
- 父进程请求,子进程完成父进程指定的任务时
- 父进程终止,所有子进程都应该结束
终止过程:
- 根据被终止进程的标识符,从PCB集合中检索出该PCB,读取进程状态
- 若处于执行状态:立即终止执行,置调度标志为true,指示该进程被终止后重新调度
- 若进程有子孙进程:将其所有子孙进程终止
- 全部资源还给父进程/OS
- PCB从所在队列/链表中移出
进程阻塞
阻塞的时机/事件
- 请求共享资源失败,系统无足够资源分配
- 等待某种操作完成
- 新数据尚未到达(相互合作的进程)
- 等待新任务
阻塞过程:进程通过block
进程唤醒
原语wakeup,和阻塞成对使用
唤醒过程:先把被阻塞的进程从该事件阻塞队列移出,将其PSB状态改为就绪,再插入就绪队列
进程同步
制约关系
- 资源共享关系(间接制约
- 需要互斥的访问临界资源
- 相互合作关系(直接制约
临界资源:一次只允许一个进程访问的资源
- 引起不可再现性是因为临界资源没有互斥的访问
临界区
while(1){
entry; //进入区
critical; //临界区
exit; //退出区
}
各进程应互斥进入相关临界区,所谓临界区是指一段代码,一段程序
同步机制应该遵循:
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待:不能进入临界区的执行进程放弃cpu执行权
整形信号量
信号量机制是一种进程间的低级通信方式
s是一个整形量,除了初始化外,仅通过两个原子操作wait(s)和signal(s)访问(也叫P,V操作)
wait(s){
while(s<=0);
s--;
}
signal(s){
s++;
}
互斥关系:A,B共享一个缓冲区,互斥
PA(){
while(1){
wait(s);
// 临界区
signal(s);
// 剩余区
}
}
PB(){
while(1){
wait(s);
// 临界区
signal(s);
// 剩余区
}
}
main(){
s=1; //init
// begin
PA();
PB();
// end
}
前驱关系:P1,P2同步,P2→P1
PA(){
P(s);
a1;
}
PB(){
a2;
V(s);
}
总结:
- 互斥的模式:PV总是成对出现在同一进程中;
- 同步的模式:PV总是成对出现在不同进程中;
- 前驱关系:有多少前驱关系设置多少个信号量,初值为0;有多少前驱做多少P操作,有多少后继结点做多少V操作,无前驱不做P操作。
信号量表示的是临界资源数
初值为2,表示初始时有2个可用的资源,若现在为-1,说明这两个可用资源已经被占用了,而且有一个进程在等待资源
初值为3,表示初始时有3个可用的资源,若现在为1,表示有两个资源被进程访问了,可用资源变为1,没有进程会等待
为了使两个进程同步运行,至少2个信号量
例题
1.桌上有一空盘,允许存放一只水果,爸爸可向盘内放苹果或桔子,儿子专等吃桔子,女儿专等吃苹果
semaphore s,so,sa=1,0,0;
father(){
while(1) {
wait(s);
// 将水果放入盘中;
if(放入的是桔子)
then signal(so);
else signal(sa);
}
}
son(){
while(1){
wait(so);
// 从盘中取出桔子
signal(s);
// 吃桔子
}
}
daughter() {
while(1){
wait(sa);
// 从盘中取出苹果
signal(s);
// 吃苹果
}
}
main()
{
// cobegin
father();
son();
daughter();
// coend
}
2.某寺庙,有小、老和尚若干,由小和尚提水入缸供老和尚饮用,水缸可容10桶水,水取自同一个井中,水井窄,每次只能容一个桶取水,水桶总数3个,每次入缸取水桶仅为1桶,且不可同时进行,试给出有关取水,入水的算法
mutexj = 1;
mutexg = 1;
empty = 10;
full = 0;
count = 3;
old(){
while(1) {
wait(full); //缸中有无水
wait(count); //有无桶
wait(mutexg); //取水前
Fetch from gang;
signal(mutexg);
signal(count);
signal(empty); //通知小
}
}
little(){
while(1) {
wait(empty); //缸中有无空
wait(count); //有无桶
wait(mutexj); //取水前
Fetch from well;
signal(mutexj);
wait(mutexg); //倒水前
pour;
signal(mutexg);
signal(count);
signal(full); //通知老
}
}
记录型信号量
整形信号量中S<=0就会不断地测试,未遵循让权等待,而是处于忙等
解决方案:建立一个进程链表list,连结所有等待该类资源的进程
typedef struct{
int value;
struct process_control_block *list
}semaphore;
wait(semaphore *S){
S->value--;
if(S->value < 0)
block(S->list);
}
signal(semaphore *S){
S->value--;
if(S->value <= 0)
wakeup(S->list);
}
- S->value>0时,表示系统中可用资源的数目
- 当S->value<0时,S->value的绝对值表示阻塞进程的数目
- 如果S->value的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量
AND信号量
要么全分配,要么一个也不分配
不用时有可能发生死锁
Swait(s1,s2,…,sn){
while(1){
if (s1>=1& …&sn >=1){
for(i=1;i<=n;i++) si--;
break;
}
else{
//将进程放入与找到的第一个si<1的si相关的阻塞队列中
//并将该进程的程序计数设置为swait操作的开始
}
}
}
Ssignal(s1,s2,…,sn){
while(1){
for (i=1;i<=n;i++){
si++;
//将与si关联的队列中等待的所有进程都移动到就绪队列中
}
}
}
信号量集
为提高效率而对AND信号量的扩充
允许一次申请多种资源多个
ti为分配下限值,Si>=ti则不分配,di为该进程需求值
Swait(S1, t1, d1, …, Sn, tn, dn){
while(1){
if(Si>=ti& … &Sn>=tn)
for (i=1;i<=n;i++)
Si=Si-di;
else{
//将进程放在Si<ti的第一个Si的阻塞队列中
//并将该进程的程序计数设置为swait操作的开始
}
}
}
Ssignal(S1, d1, …, Sn, dn){
while(1){
for (i=1;i<=n;i++) {
Si =Si+di;
//将与si关联的队列中等待的所有进程都移动到就绪队列中
}
}
}
Swait(S,d,d):允许每次申请d个资源,少于d不分配
Swait(S,1,1):S>1记录型信号量,S=1互斥形信号量
Swait(S,1,0):可控开关,S>=1时允许同时进入,S<1时不允许
经典进程同步问题
生产者-消费者*
定义数据结构
int n;
typedef item = xxx; //产品类型
item buffer [n]; //缓冲池
int in = 0,out = 0;
int counter = 0; //产品数
缓冲池满时,生产者等待;为空时,消费者等待
记录型信号量
semaphore mutex=1,empty=n,full=0
item buffer[n]; //缓冲池
int in = 0,out = 0;
int main()
{
cobegin:
producer();
consumer();
coend
}
producer(){
while(1){
…
produce an item in nextp;
…
wait(empty);
wait(mutex);
buffer[in]=nextp;
in=(in+1)%n;
signal(mutex);
signal(full);
}
}
consumer(){
while(1) {
wait(full);
wait(mutex);
nextc=buffer[out];
out=(out+1)%n;
signal(mutex);
signal(empty);
consumer the item in nextc;
}
}
P操作的顺序至关重要,顺序不当可能导致死锁(有缓冲池使用权,无缓冲)
V操作的顺序无关紧要
当缓冲区只有一个时,mutex可省略
AND信号量
semaphore mutex=1,empty=n,full=0;
item buffer[n];
int in=0,out=0;
main(){
cobegin
producer();
consumer();
coend
}
producer()
{
while(1)
{
...
produce......;
...
Swait(empty,mutex);
buffer[int]=nextp;
in = (in+1)mod n;
Ssignal(mutex,full);
}
}
consumer(){
while(1){
Swait(full,mutex);
nextc=buffer[out];
out=(out+1)%n;
Ssignal(mutex,empty);
consumer......;
}
}
哲学家进餐
5个哲学家围坐,用5只筷子吃面,筷子交替摆放
记录型信号量
设5个信号量表示5只筷子
AND信号量
同上
读-写*
读进程可共享对象,写进程不可
设整形变量readcount读者数,wmutex读写互斥,rmutex互斥访问
记录型信号量:
semaphore rmutex=1,wmutex=1;
int readcount=0;
int main(){
cobegin:
reader();
writer();
coend;
}
reader(){
while(1){
wait(rmutex);
if(readcount==0) wait(wmutex);//无人读才能写
readcount++;
signal(rmutex);
...
read......
...
wait(rmutex);
readcount--;
if(readcount==0) signal(wmutex);
signal(rmutex);
}
}
writer(){
while(1){
wait(wmutex);
...
write......
...
signal(wmutex);
}
}
写优先
semaphore rmutex=1,wmutex=1,s=1;
int readcount=0;
int main(){
cobegin:
reader();
writer();
coend;
}
reader(){
while(1){
wait(s);//!
wait(rmutex);
if(readcount==0) wait(wmutex);//无人读
readcount++;
signal(rmutex);
signal(s);//!
...
read......
...
wait(rmutex);
readcount--;
if(readcount==0) signal(wmutex);
signal(rmutex);
}
}
writer(){
while(1){
wait(s);//!
wait(wmutex);
...
write......
...
signal(wmutex);
signal(s);//!
}
}
信号量集
#define RN 20//最大读者数
semaphore L=RN,mx=1;
int main(){
cobegin:
reader();
writer();
coend;
}
reader(){
while(1){
swait(L,1,1);
swait(mx,1,0);
...
read......
...
wait(rmutex);
ssignal(L,1);
}
}
writer(){
while(1){
swait(mx,1,1);
swait(L,RN,0);
...
write......
...
ssignal(mx,1);
}
}
管程
将同步操作的机制和临界资源结合到一起,避免了要使用临界资源的进程自备同步操作
管程:一个数据结构和能为并发进程所执行的一组操作
包括:1. 局部对于管程的共享变量,2. 对该数据结构操作的一组过程,3. 对局部管程数据设初值
Monitor m_name{ //管程名
variable declarations; //共享变量说明
cond declarations; //条件变量说明
public: //能被进程调用的过程
void P1(…); //对数据结构的操作过程
{}
void P2(…);
{}
...
void Pn(…);
{}
...
{
//管程主体
//初始化代码
}
}
生产者-消费者
建立管程PC,包括:
- 二过程:put(item)过程;get(item)过程;
- 一变量:count>=n时满,<=0时空
- 初始值:in=out=count=0
Monitor PC{
item buffer[N];
int in out;
condition notfull,notempty;
int count;
public:
void put(item x){
if(count>=N)cwait(notfull);
buffer[in]=x;
in=(in+1)%N;
count++;
csignal(notempty);
}
void get(item x){
if(count<=0)cwait(notempty);
x=buffer[out];
out=(out+1)%N;
count--;
csignal(notfull);
}
}
void producer(){
item x;
while(1){
//produce
PC.put(x);
}
}
void consumer(){
item x;
while(1){
PC.get(x);
//consume
}
}
原文地址:https://www.cnblogs.com/zhxmdefj/p/12059174.html