[TOC]
程序执行
前趋图
前趋图(Procedence Graph),是指一个有向无循环图, 用于描述程序执行先后顺序。
- 图中每个节点可用来表示一个进程或程序段甚至一条语句
- 节点间的有向边表示两个节点之间存在的偏序(Partial Order)或前趋关系(Precedence Relation),使用
->
表示 - 没有前趋的节点为_初始节点_,没有后继节点为_终止节点_
- 重量:用来表示该节点所含有的程序量或程序的执行时间。
- 不允许出现循环,必然会产生不可能出现的前趋关系
通过前趋图可以帮助我们分析那些程序可以同步执行。
顺序执行
一个应用程序由若干个程序段组成,每个程序段由多条语句组成,为了完成特定的功能,他们在执行时,都需要按照某种先后次序顺序执行,仅当前一程序段执行完毕后,才运行后一程序段。
举个例子:输入->计算->输出,这三步操作必须要按照先后顺序执行才能保证功能的正确性。
S1: a:=x;S2: a:=a+1;S3:Print(a)
特征:
- 顺序性:指处理机严格地按照程序所规定的顺序执行,即每一操作必须在下一个操作开始之前结束
- 封闭性:指程序在封闭的环境下运行,即程序运行时独占全机资源,资源的状态(除初始状态外)只有本程序才能改变它,程序一旦开始执行,其执行结果不受外界因素影响。
- 可再现性:只要程序执行时的环境与初始条件相同,当程序重复执行时,都可获得相同的结果。
总结:
- 资源利用率低
- 方便调试和校正程序
并发执行
前提:只有不存在前趋关系的程序之间才有可能并发执行
举个例子~有4个语句S1: a:=x+2;S2: b:=y+4;S3: c:=a+b;S4: d:=c+6
分析后得到对应如下有向图:
在一段时间内,S1和S2可以同时执行,那么S1和S2可以并发执行。
特征:
- 间断性:因资源共享性,并发执行的程序之间出现相互制约的关系,会出现 “执行-暂停-执行”
- 失去封闭性:并发执行的程序会共享系统资源,而这些资源的状态也由这些程序来控制,致使其中任一程序在运行时,其环境都必然会受到其他程序的运行。例如:当处理机被分配给某个进程运行时,其他程序必须等待。
- 不可再现性:由于失去了封闭性,其计算结果与程序之间的竞态结果相关,虽然执行的初始条件和环境相同,但结果可能会不同,如
S1: n:=n+1; S2: Print(n);n:=0
其执行结果预期执行顺序息息相关。
特点:
- 提高了系统的吞吐量和资源利用率
- 资源出现竞态
进程
为了使参与并发执行的每个程序(含数据)都能独立执行,在操作系统中为之配置一个专门的数据结构,称为进程控制块(Process Control Block,PCB)。由程序段、相关数据段和PCB构成了进程实体。
创建进程,就是创建进程实体中的PCB,撤销进程,就是撤销进程的PCB。
PCB中存放了进程标识符、进程运行的当前状态、程序和数据的地址以及关于该程序运行时的CPU环境信息。
进程的定义
进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调用的一个独立单元。(_区别于线程_)
特征:
- 结构:由程序段、数据段以及PCB构成
- 动态性:_进程的最基本特征_。 进程的实质是进程实体的执行过程。具有一定的生命周期。
- 并发性:多个进程实体同存于内存中,且能在一段时间内同时运行。
- 独立性:进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。
- 异步性:进程按异步方式运行,引入进程同步机制,来保证进程并发执行的结果可再现性。
进程的基本状态和转换
- 创建态:进程所需的资源尚未分配,不能被调度运行
- 就绪态(Ready):进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行。如果系统中有许多处理就绪态的进程,通常将它们按一定的策略(如优先级策略)排成一个队列,即就绪队列
- 执行态(Running):进程已获得CPU,正在执行的状态。
- 阻塞态(Block):正在执行的进程由于发生某事件,暂时无法继续执行的状态,此时引起进程调度,OS将CPU分配给另一个就绪态进程,而让受阻进程进入暂停状态。_如果系统中有多个阻塞状态的进程排成一个队列,即阻塞队列_。
- 终止态:不能再被执行,但在操作系统中保留一个记录,其中保持状态码和一些计时统计数据,供其他进程使用。一旦其他进程完成了对其信息的提取后,操作系统将删除该进程。
进程状态转换图:
进程控制块
进程控制块:是进程实例的一部分,拥有描述进程情况及控制进程运行所需的全部信息的记录性数据结构
- 进程存在的唯一标志
- 操作系统控制和管理并发执行的进程的依据
- 常驻内存并存放于操作系统的PCB区
作用:使一个多道程序环境下不能独立运行的程序(含数据)称为一个能独立运行的基本单位,一个能与其他进程并发执行的进程。
- 作为独立运行基本单位的标志。系统时根据PCB来感知进程的。
- 能实现间断性运行方式。当进程因阻塞而暂停运行时,在PCB中保留有自己运行时的CPU现场信息,再次被调度运行时,需要恢复其CPU现场信息。
- 提供进程管理所需要的信息。(PCB中含有程序和数据在内存或外存中的始址指针、进程所需的全部资源等)
- 提供进程调度所需要的信息。(PCB中含有进程状态的信息)
- 实现与其他进程的同步和通信。
进程控制块中的信息
-
进程标识符,用于唯一表示一个进程,标识符分为两类:
- 外部标识符:描述了进程的家族关系,有父/子进程标识、用户标识
- 内部标识符:为了方便系统对进程的调用,每一个进程拥有一个唯一的数字标识符。通常是进程的序号
- 处理机状态
- 进程调度信息,包括进程状态、进程优先级、进程调度所需的信息、事件(阻塞原因)
- 进程控制信息,包括程序和数据的指针、进程同步和通信机制、资源清单、链接指针(本进程所在队列中下一个进程的PCB首地址)
进程控制块的组织方式
- 线性方式:系统中所有的PCB都存在一张线性表中,将该表的首地址存放在内存中的PCB区。
特点:实现简单、开销小
缺点:进程数目大的系统,需要整表扫描。
- 链接方式:将具有相同状态进程的PCB分别通过PCB中的链接字连接成一个队列。
- 索引方式:根据所有进程状态的不同,建立多张索引表,在每个索引表中,记录具有相应状态的某个PCB在PCB表中的地址。
进程控制
进程创建/终止
创建进程的事件:(前三种为系统内核创建,第四种为用户进程创建)
- 用户登录:分时系统中,验证为合法的终端用户登录
- 作业调度:批处理系统中作业调度程序调度到某作业
- 提供服务:运行中的用户程序提出某种请求
- 应用请求:基于应用程序的需要由其自身创建新进程
创建进程原语:Create()
- 分配标识符,并申请空白进程控制块
- 为新进程的程序和数据以及用户栈分配必要的内存空间
- 初始化进程控制块(自身/父进程标识符、处理机状态/调度信息)
- 将新进程插入到就绪进程队列
终止进程的事件:
- 正常结束:批处理系统中Halt,,分时系统中的LogsOff
- 异常结束;越界错误/保护错、特权指令错误、非法指令错误、运行超时、等待超时、算术运算错误、 I/O故障
- 外界干预:操作或操作系统干预、父进程请求/终止
终止进程原语:Terminate()
- 检索被终止进程PCB,读取进程状态
- 若正处于执行状态,应立即中止执行并设置调度标志为true,以调度新进程
- 终止子孙进程
- 资源归还,移除被终止进程PCB,等待其他程序查询利用
进程阻塞/唤醒
一对一的单向操作
阻塞进程的事件:
- 向系统请求共享资源失败
- 等待某种操作的完成
- 新数据尚未到达
- 等待新任务的到达
阻塞进程原语:Block()
- 立即停止执行,将PCB中的现行状态由“执行”为“阻塞”,并将它插入到对应的阻塞队列中
- 转调度程序进程重新调度,将处理机分配给另一就绪进程,并进行切换
唤醒进程的事件:
- 系统服务满足
- 操作完成
- 数据到达
- 新任务到达
唤醒原语:Wakeup()
- 把被阻塞进程从等待该事件的阻塞进程队列中移除,将其PCB的现行状态由“阻塞”改为“就绪”,将该进程插入到就绪队列
进程挂起/激活
挂起进程原语:Suspend()
- 检查被挂起进程现行状态并修改,插入静止队列
- 复制PCB到指定域
- 若被挂起进程正在执行,则有调度程序重新调度
激活进程原语:Activate()
- 检查被挂起进程现行状态并修改,插入就绪队列
- 如有新锦成进入就绪队列且采用了抢占式调度策略,则检查和决定是否重新调度。
进程的层次结构
在操作系统中,运行一个进程创建另一个进程,就产生的父子进程
父进程:创建进程的进程,撤销父进程是,同时撤销其所有子进程。
子进程:被进程创建的进程,可以集成父进程所拥有的资源,当子进程被撤销是归还从父进程处获取的资源。
通过进程树,可实现进程的精细化管理。
window中不存在任何进程层级结构的概念,所有的进程都拥有相同的地位。一个进程A创建了另一个进程B,将获得一个句柄,可以用来控制被创建的进程B,这个句柄可以进行传递,获得了句柄的进程都可以控制进程B。
进程同步
进程同步机制的主要任务,是对多个相关进程在执行次序上进行协调,使并发执行的进程之间能按照一定的规则(或时序)共享资源,并能很好的相互合作,从而是程序的执行具有可再现性。
进程并发制约关系:
1、 间接相互制约关系--资源共享关系--互斥机制
- 多个进程彼此无关,完全不知道或间接感知其他进程的存在
- 系统必须保证进程能互斥的访问临界资源
- 系统资源应统一分配,不允许用户进程直接获取
2、 直接相互制约关系--相互合作关系
- 系统应保证相互合作的进程在执行次序上的协调和防止与事件有关的差错--同步机制
临界资源
在一段时间内,只能被一个进程所访问的资源。如物理设备、变量等。
此例中变量n为临界资源
临界区
每个进程中访问相同临界资源的代码段
作用:保证进程之间互斥进入自己的临界区是实现他们对临界资源的互斥访问的充要条件
个人理解,就是将使用到临界资源的影响放大,整体控制。
begin
parbegin
pi;
pj;
parend
end
begin
repeat
...
进入区
临界区
退出区
...
until false;
end
进入区:对临界资源进行检查;若临界资源正在被本进程访问,则本进程不能访问临界区。否则,本进程可以进入临界区,并设置正被访问标志。
退出区:用于将临界区正被访问的标志恢复为未被访问。
进程同步机制基本原则
- 空闲让进:当无进程进入临界区时,表明临界资源证处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效的利用临界资源。
- 忙着等待:当已有进程进入临界区时,表明临界资源正在被访问,其他进视图进入临界区的进程必须等待,比保证对临界资源的互斥访问。
- 有限等待:对要求访问临界资源的进程,应保证在有限时间内能进入自己的临界区,避免死等。
- 让权等待:当进程不能进入自己的临界区是,应立即释放处理机,避免忙等。
进程同步机制
对于临界区进行管理时,可以将标志看做一个锁。“锁开”进入,“锁关”等待,初始锁是打开的。每个要进入临界区的进程必须先对锁进行测试,当锁未开时,则必须等待,直至锁被打开。反之,当锁是打开的时候,则应立即把锁关上,以阻止其他进程进入临界区。
为防止多个进程同时测试到锁为打开的情况,测试和关锁操作必须是连续的,不允许分开。
关中断
在进入锁测试前关闭中断,直到完成锁测试并上锁后才能打开中断。 缺点:
- 滥用关中断可能导致严重后果
- 关中断时间过长,会影响系统效率
- 不适用于多CPU系统,一个处理器上关中断并不能防止进程在其他处理器上执行相同的临界代码。
利用Test-and-Set指令实现互斥
通过设置布尔变量lock状态切换实现互斥。 lock初始值为FALSE,表示该临界资源空闲。 进程在进入临界区之前,首先用TEST指令测试lock,如果为FALSE,表示没有进程在临界区内,准许进入,并将TRUE值赋予lock,等效于关闭了临界资源,使任何进程都不能进入临界区,否则必须循环测试直到lock为TRUE
利用swap指令实现互斥
称为对换指令,用于交换另个字的内容
为每个临界资源设置一个全局布尔变量lock,其初值为false,在每个进程中在利用局部布尔变量key.
总结:利用硬件指令能有效地实现进程互斥,但当临界资源忙绿是,其它访问进程必须不断的进行测试,处于忙等状态,不符合 “让权等待” 原则,照成处理机时间的浪费,同时也很难将他们用于解决复杂的进程同步问题。
信号量机制
整型信号量
定义一个用于表示资源数目的整形量S,除初始化外,只能执行原子操作 wait(S)和signal(S)。
信号量S,初始值大于0
P原语,表示减少信号量,原子操作
V原语,标识增加信号量,原子操作
wait(S): while S <= 0 do no-op; S:=S-1;
signal(S): S:=S+1;
只要是信号量S<=0,就会不断地测试。因此该机制没有遵循“让权等待”准则,而是使进程处于“忙等”的状态。
记录型信号量
记录型信号量机制采取了“让权等待”策略,是一种不存在“忙等”现象的进程同步机制。记录型信号量时由于它采用了记录型数据结果而得名的。在信号量机制中,除了需要一个用于代表资源数目数的整型变量value外,还需要一个进程链表指针L用于链接所有等待的进程。
信号量中包含一个表示资源数目的整型变量value,还包含了一个进程指针链表list,用于记录所有等待的进程。结构如下:
typedf struct{
int value;
struct process_control_block *list
} semaphore;
wait(S)和signal(S)操作:
wait(semaphore *S){
S->value--;
if(S->value<0) block(S->list);
}
signal(semaphore *S){
s->value++;
if(s->value<=0) wakeup(S->list);
}
P原语操作的动作是:
(1)S减1;
(2)若S减1后仍大于或等于零,则进程继续执行;
(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。
V原语操作的动作是:
(1)S加1;
(2)若相加结果大于零,则进程继续执行;
(3)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。
s->value初始值表示了当前资源信号量,s->value<0时,表示资源已分配完毕,进入阻塞状态,其绝对值表示了当前阻塞的进程个数。
申请和释放资源只能对一个临界资源进行处理,无法处理同一进程需要多个资源的情况
AND型信号量
将进程在整个运行过程中所需的所有资源,一次性全部分配给进程(原子操作),待进程使用完毕后再一起释放。只要尚有一个资源未能分配给进程,其他所有可能为之分配的资源也不分配。---可以防止死锁,不同进程互相持有对方所需资源
Swait操作 关键在于 s1.value>=1&&s2.value>=1...and sn.value>=1 时才可以分配资源,分配后进行信号量减一;否则,任意一个资源信号量<1,则阻塞该进程,将该进程挂在第一个不能满足的临界资源的阻塞队上(没有必要所有临界资源都去打标的),并重新申请。
procedure Swait(s1,s2,..sn)
var s1,s2,...sn:semphore;
begin
if s1.value>=1 and ... and sn.value>=1
then for i:1 to n do
si.value:=si.value-1;
else blockProcessAndRestPc(sfirst)
end
而我们如何去释放信号量呢
Var FLAG:boolean:=false
for i:=1 to n do
if(si.value<1){
FLAG:=true;
break;
}
endfor;
if(FLAG){
ResetPC();
block(si.L)
}else{
for i:1 to n do
si.value=si.value-1
endfor;
}
Ssignal操作 遍历所用的所有资源,信号量加一,同时唤醒其阻塞队列中的第一个。
procedure Ssignal(s1,s2,..sn)
var s1,s2,...sn:semphore;
begin
for i:1 to n do
si.value:=si.value+1;
wakeUp(si.L)
endfor;
end
总结:整型、记录型、AND型信号量机制只能对资源信号量每次申请一个和释放一个的操作,低效。
信号量集机制
原因:
1.需要满足一对一个资源申请N的单位
2.某些情况下,为了确保系统的安全性,当所申请的某临界资源数量小于下限值时,不予分配。
在一次P/V原语操作中完成申请和释放,将信号量si的测试值改为ti。即要求si>=ti,否则不在分配。一旦允许分配,进程对该资源的需求之为di,表示对资源的占有量,进行si=si-di操作,而不是简单的si=si-1操作。这就是一般化的信号量集机制。
Swait操作:Swait(S1,t1,d1,...,Sn,tn,dn)
S1:某临界资源 t1:该资源分配的下限值 d1:该资源的需求值
procedure Swait(S1,t1,d1,...,Sn,tn,dn)
var s1,s2,...sn:semphore;
t1,t2,...tn,d1,d2,...dn:integer;
begin
if s1.value>=t1 and ... and sn.value>=tn
then for i:1 to n do
si.value:=si.value-di;
else blockProcessAndRestPc(sfirst)
end
Ssignal操作:Ssignal(S1,d1,...Sn,dn)
procedure Ssignal(s1,d1,..sn,dn)
var s1,s2,...sn:semphore;
d1,d2,...dn:integer;
begin
for i:1 to n do
si.value:=si.value+di;
wakeUp(si.L);
endfor;
end
需要注意的时,资源释放同时唤醒队列时需要结合当前的释放量d是否满足当前阻塞队列中的进程的资源需求量d
几种特殊情况:
- Swait(S,d,d)
- Swait(S,1,1)-->记录型信号量
- Swait(S,1,0)-->可控开关,当s大于1时,允许多个进程进入特定区;反之,阻止任何进程进入特定区。
总结:是对AND型信号量的扩充
信号量的应用
基于信号量机制解决进程并发问题
实现互斥
设置互斥信号量 mutex,初始值为1,范围(-1,0,1)
- 当mutex=1,两个进程为进入需要互斥的临界区
- 当mutex=0,表示有一个进程进入临界区运行,另一个进程必须等待,挂入阻塞队列
- 当mutex=-1,表示有一个进程正在临界区运行,另一个进程因等待而阻塞在信号量队列中,需要被当前已在临界区运行的进程退出时唤醒。
代码:
# 主程序
Var mutex:semphore:=1;
begin
parbegin
process1;
process2;
parend
end
# 子程序1
process1:
begin
repeat
...
wait(mutex);
临界区
signal(mutex);
until false;
end
# 子程序2
process2:
begin
repeat
...
wait(mutex);
临界区
signal(mutex);
until false;
end
wait(mutex)和signal(mutex)必须成对出现。
实现进程间的协作
前趋图:
针对每一个前趋关系,设计一个协同信号量a,并赋予初值0,将signal(a)操作放在S1后面,将wait(a)操作放在S2前面。
综上:
Var a,b,c,d,e,f,g:semphore:=0,0,0,0,0,0,0
begin
pargin
begin S1;signal(a);signal(b);end;
begin wait(a);S2;signal(c);signal(d);end;
begin wait(b);S3;signal(e);end;
begin wait(c);S4;signal(f);end
begin wait(d);S5;signal(g);end;
begin wait(e);wait(f);wait(g);S6;end;
parend
end
管程
为什么要引入管程?
每个要访问临界资源的进程都必须自备同步操作wait(S)和signal(S),使得大量的同步操作分散在各个进程中。会给系统的管理带来麻烦,还会因同步操作的使用不当导致死锁。
代表共享资源的数据结构以及有对该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块--管程;
- 管程被请求和释放资源的进程所调用。
通过将表征共享数据的数据结构以及对数据结构操作的一组过程,包括同步机制,都集中封装在一个对象内部,隐藏的实现细节。有以下好处:
- 封装与管程内部的数据结构仅能被封装于管程内部的过程所访问,任何管程外的过程都不能访问
- 封装与管程内部的过程只能访问过程内部的数据结构。
- 所有进程访问临界资源时,都只能通过管程间接访问
- 管程每次只准许一个进程进入管程,执行管程内部的过程--从而实现了进程互斥
特点:
- 模块化--是一个基本程序单元,可以单独编译
- 抽象数据结构
- 信息遮蔽
与进程的不同:
- 进程定义的是私有数据结构PCB,管程定义的是公共数据结构
- 进程是由顺序程序执行有关操作,管程主要进行同步操作和初始化操作
- 进程的目的在于实现系统的并发,管程主要用来解决共享资源的互斥
- 管程被进程调用,属于被动工作,进程为主动工作
- 进程之间能并发执行,管程不能与其调用者并发
- 进程具有动态性,管程则是操作系统的一个资源管理模块
总结:管程可以抽象为一个临界资源,由进程进行请求和释放管程,来间接达到操作临界资源的目的。