2.1 进程的基本概念
算法
1. 程序的顺序执行及其特征数据结构
特征:顺序性、封闭性、可再现性并发
2. 程序的并发执行及其特征异步
(1)特征:间断性、失去封闭性、不可再现性模块化
(2)程序并发执行的条件——Bernstein条件:函数
设有读集R(p_i )={a_1,a_2…a_m}和写集W(p_i )={b_1,b_2…b_n},若程序知足如下三个条件,则程序能够并发且具备可再现性(i≠j):R(p_i )∩W(p_j )=∅|R(p_j )∩W(p_i )=∅|W(p_i )∩W(p_j )=∅学习
3. 进程的特征与状态测试
进程(动态)是进程实体(静态)的运行过程,是系统进行资源分配和调度的一个独立单位。this
(1)进程的特征:spa
①结构特征:进程实体由进程控制块PCB(Process Control Block)、程序段、相关的数据段组成
②动态性(最基本特性)③并发性④独立性⑤异步性
(2)进程的状态:
①三种基本状态:就绪态、运行态、阻塞态
(进程状态的转换并不是均可逆,阻塞态没法转换为执行态;只有执行态变为阻塞态是主动的,其他都是被动的;进程在某一时刻仅有一种状态)
②挂起态
引发挂起态的缘由:终端用户的要求、父进程的请求、负荷调节的须要、操做系统的须要
进程状态的转换
活动就绪->静止就绪
活动阻塞->静止阻塞
静止就绪->活动就绪
静止阻塞->活动阻塞
③建立态和终止态
建立态:仅仅为一个进程建立PCB(未分配资源,进程建立工做还没有完成)
终止态:当一个进程到达了天然结束点,或发生了没法克服的错误,或被操做系统所终结,或被其余拥有终止权的进程所终结,该进程进入终止态。
4. 进程控制块
(1)进程控制块的做用:
PCB是记录操做系统所需的、用于描述进程的当前状况以及控制进程运行的所有信息的数据结构。PCB是进程存在的惟一标识,它常驻内存。系统将全部的PCB组织成若干链表或队列,存放在操做系统专门开辟的PCB区内。
(2)进程控制块中的信息:
①进程标识符PID(内部标识符提供给系统,外部标识符提供给用户)
②处理机状态:包含通用寄存器、指令寄存器、程序状态字、用户栈指针等寄存器中的内容
③进程调度信息:包含进程状态、进程优先级、进程调度所需其余信息,事件(阻塞的缘由)
④进程控制信息:包含程序和数据地址、进程同步和通讯机制、资源清单、连接指针
(3)进程控制块的组织方式:连接方式、索引表方式
5. 进程和程序的关系
(1)进程是动态的,程序是静态的
(2)进程是暂时的,程序是永久的
(3)进程包括程序、数据、PCB
(4)进程可建立其它进程,而程序不能造成新的程序
(5)进程具备并行性
6. 进程与做业的关系
(1)做业是用户向计算机提交任务的任务实体。在用户向计算机提交做业后,系统将其放入外存中的等待队列等待执行;而进程则是完成用户任务的执行实体,是向系统申请资源的基本单位。
(2)一个做业可有多个进程组成,且至少有一个进程组成,但一个进程不能构成多个做业。
(3)做业的概念主要用于批处理系统中,而进程的概念则用在几乎全部的多道程序系统中。
2.2 进程控制
进程控制是进程管理中最基本的职能,它用于建立/终止一个进程,还可负责进程运行中的状态转换。进程控制通常由OS的内核中的原语(原语由若干条指令构成,用于完成必定功能的一个过程,它是原子操做,是不可分割的基本单位,即一个操做中的全部动做要么全作,要么全不作,执行过程不容许被中断,在管态下运行,常驻内存)实现。
1. 进程的建立
(1)引发进程建立的事件:用户登陆、做业调度、提供服务、应用请求
(2)进程的建立:调用建立原语Create( )建立新进程
①申请PCB②为新进程申请资源(若资源不足,则进入阻塞态)③初始化PCB(初始化标识信息、处理机状态信息、处理机控制信息,优先级通常设置为最低)④将新进程插入就绪队列
2. 进程的终止
(1)引发进程终止的事件:正常结束、异常结束(越界错误、保护错、非法指令、特权指令错、运行超时、等待超时、算术运算错、I/O故障等)、外界干预(操做员或操做系统干预、父进程请求,父进程终止)
(2)进程的终止:
①根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程状态
②若被终止进程正处于执行状态,应当即终止该进程的执行,并置调度标志为真。
③若该进程还有子进程也应终止
④被终止进程的全部资源或归还父进程,或归还操做系统。
⑤将被终止进程从链表或队列中移除,等待其余程序来搜集信息。
3. 进程的阻塞与唤醒(进程的自主行为)
(1)引发进程阻塞或唤醒的事件:请求系统服务、启动某个操做、新数据还没有到达、无新工做可作
(2)进程阻塞过程:进程经过调用block原语阻塞本身,即若处于执行态,则终止并将PCB插入具备相同事件的阻塞队列,再由转调度程序从新调度,将处理机分配给另外一进程并进行切换(执行态->阻塞态)。
(3)进程唤醒过程:发现者进程经过wakeup原语唤醒被阻塞的进程,即首先把阻塞的进程从等待该事件的阻塞队列中移除,将其PCB的现行状态由阻塞改成就绪,而后再将该进程的PCB插入到就绪队列中(阻塞态->就绪态)。
4. 进程的挂起与激活
(1)进程的挂起:调用suspend原语挂起进程
(2)进程的激活:调用active原语激活进程,即首先将进程从外存调入内存,更改该进程的运行状态。再根据调度策略,检查是否要进行从新调度。
5. 进程的切换
保存处理机上下文->更新PCB->将PCB移入相应的队列->选择另外一进程执行,更新其PCB->更新内存管理的数据结构->恢复处理机上下文
2.3 进程同步
1. 进程同步的基本概念
多个相互合做的进程在一些关键点上可能须要等待或相互交换信息的相互制约关系。
(1)两种形式的制约关系:间接制约关系(系统资源的共享)——互斥,直接制约关系(进程之间的合做)——同步
(2)临界资源和临界区:
临界资源:在一段时间内仅容许一个用户访问的资源
临界区:每一个进程中访问临界资源的那段代码
进入区(检查是否能够进入临界区,设置标志)
临界区(访问临界资源)
退出区(消除标志)
剩余区
(3)同步机制应遵循的基本原则:空闲让进,忙则等待,有限等待、让权等待
2. 互斥实现方法
(1)软件方法
在进入区设置和检查一些标志来标明是否有进程在临界区中,若是已有进程在临界区,则在进入区经过循环检查进行等待,进程离开临界区后则在退出区修改标志。
(1) 算法一:单标志法。
该算法设置一个公用整型变量turn,用于指示被容许进入临界区的进程编号,即若turn=0,则容许P0进程进入临界区。该算法可确保每次只容许一个进程进入临界区。但两个进程必须交替进入临界区,若是某个进程再也不进入临界区了,那么另外一个进程想再次进入临界区,则它将没法进入临界区(违背“空闲让进”)。
int turn=0; P_0:{ do{ while(turn!=0); critical section; turn=1; remainder section; }while(true) } P_1:{ do{ while(turn!=1); critical section; turn=0; remainder section; }while(true) }
(2) 算法二:双标志法先检查。
该算法的基本思想是在每个进程访问临界区资源以前,先查看一下临界资源是否正被访问,若正被访问,该进程需等待;不然,进程才进入本身的临界区。为此,设置了一个数据flag[i],如第i个元素值为false,表示Pi进程未进入临界区,值为TRUE,表示Pi进程进入临界区。这个算法解决了“空闲让进”的问题,但违背了“忙则等待”原则。
enum boolean{true,false}; boolean flag[2]{false,false}; P_0:{ do{ While(flag[1]); flag[0]=true; critical section; flag[0]=false; remainder section; }while(true) } P_1:{ do{ While(flag[0]); flag[1]=true; critical section; flag[1]=false; remainder section; }While(true) }
(3) 算法三:双标志法后检查。
算法二是先检测对方进程状态标志后,再置本身标志,因为在检测和放置中可插入另外一个进程到达时的检测操做,会形成两个进程在分别检测后,同时进入临界区。为此,算法三釆用先设置本身标志为true后,再检测对方状态标志,若对方标志为true,则进程等待;不然进入临界区。此算法能够有效防止两进程同时进入临界区,但存在两进程都进不了临界区的问题,违背了“有限等待”的原则。
enum boolean{true,false}; boolean flag[2]{false,false}; P_0:{ do{ flag[0]=true; While(flag[1]); critical section; flag[0]=false; remainder section; }while(true) } P_1:{ do{ flag[1]=true; While(flag[0]); critical section; flag[1]=false; remainder section; }while(true) }
(4)算法四:Peterson’s Algorithm。
为了防止两个进程为进入临界区而无限期等待,又设置变量turn,指示不容许进入临界区的进程编号,每一个进程在先设置本身标志后再设置turn 标志,不容许另外一个进程进入。这时,再同时检测另外一个进程状态标志和不容许进入标志,这样能够保证当两个进程同时要求进入临界区,只容许一个进程进入临界区。此算法能够彻底正常工做,利用flag[]解决临界资源互斥问题,利用turn解决“饥饿现象”。
enum boolean{true,false}; boolean flag[2]{false,false}; int turn; P_0:{ do{ flag[0]=true; turn=1; While(flag[1]&&turn==1); critical section; flag[0]=false; remainder section; }while(true) } P_1:{ do{ flag[1]=true; turn=0; While(flag[0]&&turn==0); critical section; flag[1]=false; remainder section; }while(true) }
(2) 硬件办法
本节对硬件实现的具体理解对后面的信号量的学习颇有帮助。计算机提供了特殊的硬件指令,容许对一个字中的内容进行检测和修正,或者是对两个字的内容进行交换等。经过硬件支持实现临界段问题的低级方法或称为元方法。
(1) 中断屏蔽方法
当一个进程正在使用处理机执行它的临界区代码时,要防止其余进程再进入其临界区访问的最简单方法是禁止一切中断发生,或称之为屏蔽中断、关中断。由于CPU只在发生中断时引发进程切换,这样屏蔽中断就能保证当前运行进程将临界区代码顺利地执行完,从而保证了互斥的正确实现,而后再执行开中断。其典型模式为:
…
关中断;
临界区;
开中断;
…
这种方法限制了处理机交替执行程序的能力,所以执行的效率将会明显下降。对内核来讲,当它执行更新变量或列表的几条指令期间关中断是很方便的,但将关中断的权力交给用户则很不明智,若一个进程关中断以后再也不开中断,则系统可能会所以终止。
(2) 硬件指令方法
①TestAndSet指令:这条指令是原子操做,即执行该代码时不容许被中断。其功能是读出指定标志后把该标志设置为真。指令的功能描述以下:
boolean TestAndSet(boolean *lock){ boolean old; old = *lock; *lock=true; return old; }
能够为每一个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用,初值为false。在进程访问临界资源以前,利用TestAndSet检查和修改标志lock;如有进程在临界区,则重复检查,直到进程退出。利用该指令实现进程互斥的算法描述以下:
while TestAndSet (& 1 ock); // 进程的临界区代码段; lock=false; // 进程的其余代码
②Swap指令:该指令的功能是交换两个字节的内容。其功能描述以下。
Swap(boolean *a,boolean *b){ boolean temp; Temp=*a; *a = *b; *b = temp; }
注意:以上对TestAndSet和Swap指令的描述仅仅是功能实现,并不是软件实现定义,事实上它们是由硬件逻辑直接实现的,不会被中断。
应为每一个临界资源设置了一个共享布尔变量lock,初值为false;在每一个进程中再设置一个局部布尔变量key,用于与lock交换信息。在进入临界区以前先利用Swap指令交换lock 与key的内容,而后检查key的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。利用Swap指令实现进程互斥的算法描述以下:
key=true; while(key!=false) Swap(&lock,&key); // 进程的临界区代码段; lock=false; // 进程的其余代码;
硬件方法的优势:适用于任意数目的进程,不论是单处理机仍是多处理机;简单、容易验证其正确性。能够支持进程内有多个临界区,只需为每一个临界区设立一个布尔变量。
硬件方法的缺点:进程等待进入临界区时要耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而致使“饥饿”现象。
3. 信号量(Semaphore)机制
(1)整型信号量(存在“忙等”问题,未遵循“让权等待”准则):即整型信号量被定义为一个用于表示资源数目的整型量S,它除初始化外,仅能经过两个原子操做wait(S)和signal(S)来访问,即PV操做。
wait(S){ while (S<=0); S=S-1; } signal(S){ S=S+1; }
在wait操做中只要信号量S<=0,就会不断进行测试,所以该机制未遵循“让权等待”的准则,存在“忙等”的状态。
(2)记录型信号量:
数据结构:
typedef struct{ int value; struct process *L; } semaphore;
wait(S)和signal(S)操做:
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为资源信号量:当|S.value|<=0时表示尚有等待该资源的进程被阻塞,应唤醒;当S.value的初值为1时,表示只容许一个进程访问临界资源,此时的信号量变为互斥信号量。
(3)AND型信号量
将进程在整个运行过程当中所须要的全部资源,一次性分配给进程,待进程使用完后一块儿释放,只要尚有一个资源未能分配给进程,其余全部可能为之分配的资源也不分配给它。
同时wait操做(Simultaneous wait,又称ADD同步):
void Swait(semaphore S1,semaphore S2,…,semaphore Sn,int n){ if (S1>1 && … && Sn>1 ){ for(int i=1;i<=n;i++){ Si - -; } } else{ place the process in the waiting queue associated with first Si found with Si<1,and set the program count of this process to the beginning of Swait operation } } void Ssignal(semaphore S1,semaphore S2,…,semaphore Sn,int n){ for (int i=1;i<=n;i++){ Si++; Remove all the process waiting in the queue associate with Si into ready queue } }
(4)信号量集
Swait操做(S为信号量,d为需求值,t为下限值)
void Swait(semaphore S1,semaphore d1,semaphore t1,…,semaphore Sn,semaphore dn,semaphore tn,int n){ if(Si>t1 && … &&Sn>tn){ for (int i=1;i<=n;i++){ Si-=di; } } else{ place the process in the waiting queue associated with first Si found with Si<ti,and set the program count of this process to the beginning of Swait operation } } void Ssignal(semaphore S1,semaphore d1…,semaphore Sn,semaphore dn,int n) for (int i=1;i<=n;i++){ Si+=di; Remove all the process waiting in the queue associate with Si into ready queue } }
4. 信号量的应用
(1)利用信号量实现进程互斥
semaphore S = 1; //初化信号量 P1 ( ) { … P(S); // 准备开始访问临界资源,加锁 进程P1的临界区 V(S); // 访问结束,解锁 … } P2( ) { … P(S); //准备开始访问临界资源,加锁 进程P2的临界区; V(S); // 访问结束,解锁 … }
(2)利用信号量实现同步
semaphore S = 0; //初始化信号量 P1 ( ) { … x; //语句x V(S); //告诉进程P2,语句乂已经完成 } P2(){ … P(S) ; //检查语句x是否运行完成 y; // 检查无误,运行y语句 … }
(3)利用信号量实现前驱关系
semaphore al=a2=bl=b2=c=d=e=0;//初始化信号量 S1(){ ... V(al); V(a2) ; //S1已经运行完成 } S2(){ P(a1); //检查S1是否运行完成 ... V(bl); V(b2); // S2已经运行完成 } S3(){ P(a2); //检查S1是否已经运行完成 ... V(c); //S3已经运行完成 } S4(){ P(b1); //检查S2是否已经运行完成 ... V(d); //S4已经运行完成 } S5(){ P(b2); //检查S2是否已经运行完成 ... V(e); // S5已经运行完成 } S6(){ P(c); //检查S3是否已经运行完成 P(d); //检查S4是否已经运行完成 P(e); //检查S5是否已经运行完成 ...; }
分析进程同步和互斥问题的方法步骤:
1) 关系分析。找出问题中的进程数,而且分析它们之间的同步和互斥关系。同步、互斥、前驱关系直接按照上面例子中的经典范式改写。
2) 整理思路。找出解决问题的关键点,而且根据作过的题目找出解决的思路。根据进程的操做流程肯定P操做、V操做的大体顺序。
3) 设置信号量。根据上面两步,设置须要的信号量,肯定初值,完善整理。
5. 管程机制
(1)管程:表明共享资源的数据结构,以及对该共享数据结构实施操做的一组过程所组成的资源管理程序,共同构成了一个操做系统的资源管理模块。
(2)管程的组成:管程的名称、局部于管程内部的共享数据结构的说明、对该数据结构进行操做的一组过程、对于局部于该管程内部的共享数据设置初值的语句
(3)管程的语法描述:
type monitor_name=MONITOR; <共享变量说明> define <(能被其余模块引用的)过程名列表> use<(要调用的本模块外定义的)过程名列表> procedure<过程名>(<形式参数表>) begin …… end; …… function<函数名>(<形式参数表>):值类型: begin …… end; …… begin <管程的局部数据初始化语句序列> end
为了解决一个进程调用了管程,在管程中被阻塞或挂起,使其它进程没法进入管程,而被迫等待的问题,引入了条件变量condition,它也是抽象数据类型,其形式为Var x,y:condition;每一个条件变量保存了一个链表,用于记录因该条件变量而阻塞的全部进程,同时提供x.wait和x.signal两个操做。
①x.wait:正在调用管程的进程因x条件而被阻塞或挂起,则调用x.wait将本身插入到x条件的等待队列上,并释放管程,直到x条件变化。
②x.signal:正在调用管程的进程发现条件x发生了变化,则调用x.signal,从新启动一个因x条件而阻塞或挂起的进程。
此时有两个进程在管程内,有两种解决方式:
①P等待,直至Q离开管程或等待另外一条件
②Q等待,直至P离开管程或等待另外一条件
Hoare采用了第一种,Hansan选择了两者的折衷。
(4)管程的特性:
①局部于管程内部的数据结构,仅能被管程内部的过程所访问,任何管程外的过程都不能访问它。局部于管程内的过程也仅能访问管程内的数据结构。
②一个进程只有经过调用管程内的过程才能进入管程访问共享数据。
③每次仅容许一个进程在管程内执行某个内部进程,即进程互斥地经过调用内部过程进入管程。其它想进入管程的过程必须等待,并阻塞在等待序列。
从程序设计语言角度来看,管程有模块化、抽象数据类型、信息隐蔽的特色。