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. 进程的特征与状态
进程(动态)是进程实体(静态)的运行过程,是系统进行资源分配和调度的一个独立单位。
(1)进程的特征:
①结构特征:进程实体由进程控制块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)管程的特性:
①局部于管程内部的数据结构,仅能被管程内部的过程所访问,任何管程外的过程都不能访问它。局部于管程内的过程也仅能访问管程内的数据结构。
②一个进程只有通过调用管程内的过程才能进入管程访问共享数据。
③每次仅允许一个进程在管程内执行某个内部进程,即进程互斥地通过调用内部过程进入管程。其它想进入管程的过程必须等待,并阻塞在等待序列。
从程序设计语言角度来看,管程有模块化、抽象数据类型、信息隐蔽的特点。
来源:oschina
链接:https://my.oschina.net/u/1398794/blog/363286