3.1 进程的概念
现代操作系统的重要特点是:
·程序并发执行 执行并发
·系统资源共享 资源共享
·用户随机地使用系统 用户随机
操作系统的重要任务之一:使用户充分、有效地利用系统资源。
3.1.1 程序和执行
Program 程序:描述计算机所要完成的具有独立功能的,并在时间上按严格次序前后相继的操作序列,是静态的概念。
程序体现了编程人员要求计算机完成所要求功能时应该采取的顺序步骤。
多程序系统:批处理系统、分时系统、实时系统以及网络与分布式系统等,都需要计算机同时处理多个具有独立功能的程序。
多程序情境下程序执行环境的变化:
·独立性:每道程序都是逻辑上独立的,它们之间不存在逻辑上的制约关系。
·随机性:在多道程序环境下,特别是在多用户环境下,程序和数据的输入与执行开始时间都是随机的。
·资源共享:资源共享将导致对进程执行速度的制约。
程序的执行:
·程序的顺序执行:具有独立功能的程序独占处理机直至最终结束的过程。
Repeat
IR ← M[pc]; //IR指令寄存器,M为存储器
pc ← pc+1; //PC程序计数器
Execute (instruction in IR);
Until CPU halt
程序顺序执行的特点:顺序性(状态转移过程,上一条指令执行结束是下一跳指令执行开始的充分必要条件)、封闭性(最终结果由初始条件给出,不受外界因素影响)、可再现性(执行结果与速度无关)。
·程序的并发执行:一组在逻辑上互相独立的程序或程序段,在执行过程中其执行时间在客观上互相重叠,即一个程序段的执行尚未结束,另一个程序段的执行已经开始的执行方式。
Eg: 1.多道程序系统的多道程序并发执行;
2.程序段种包含了可同时执行或顺序颠倒的代码段。
S0
Cobegin
P1;P2;... Pn ;
Coend
Sn
并发vs并行:并发指交替做不同事情,并行指同时做不同事情。
并发执行的条件:Bernstein——不能对同一组变量同时读写,不能同时写
① R(S1) ∩ W(S2)= Ø
② W(S1) ∩ R(S2)= Ø
③ W(S1) ∩ W(S2)= Ø
则语句S1和S2是可以并发执行的。
程序的并发执行必然导致资源共享和资源竞争,从而改变程序的执行速度。
如果并发执行的程序段不按照特定的规则和方法进行资源共享和竞争,其执行结果将不可避免地失去封闭性和可再现性。
由于程序的顺序性、静态性以及孤立性,用程序段作为描述其执行过程和共享资源的基本单位既增加操作系统设计和实现的复杂性,也无法反映操作系统应该具有的程序段执行的并发性、用户随机性,以及资源共享等特征。——总结一句话:程序不适合作为描述执行过程和资源共享的单位。
3.1.2 进程的定义
Process 进程:一个具有独立功能的程序在处理机执行过程和分配资源的基本单位。
进程vs程序:
·进程是一个动态概念,程序是一个静态概念。程序是指令的有序集合,没有任何执行的含义。进程强调执行过程,它动态地被创建,并被调度执行后消亡。
·进程具有并发特征。进程具有独立性(执行)和异步性(执行速度)。
·进程是竞争计算机系统资源的基本单位,从而其并发性受到系统的制约。
·不同的进程可以包含同一程序,只要该程序所对应的数据集不同。
3.2 进程的描述
进程的静态描述:描述进程存在和能够反映其变化的物理实体。
3 part:进程控制块PCB,有关程序段和该程序段操作的数据集。
系统根据PCB感知进程的存在和通过PCB中所包含的各项变量的变化。
由于进程的PCB是系统感知进程的唯一实体,因此一个进程的PCB结构都是全部或部分常驻内存的。
进程的程序部分描述进程所要完成的功能。
数据集是程序在执行时必不可少的工作区和操作对象。
在大部分多道操作系统中,这两部分内容放在外存中,直到该进程执行时再调入内存。
3.2.1 进程控制块
进程与PCB:在创建一个进程时,首先创建其PCB,然后根据PCB中的信息对进程实施有效管理和控制。当一个进程完成其功能之后,系统释放PCB,进程也随之消亡。
PCB的结构:
·描述信息
① 进程名或进程标识号。
② 用户名或用户标识号。
③ 家族关系:每一个进程必有一个父进程;可以有0个或者多个子进程。
Linux:init命令
·控制信息
① 进程当前状态*5:初始(创建)状态、就绪状态、执行(运行)状态、等待(阻塞)状态、终止状态。
② 进程优先级:a)占有CPU时间;b)进程优先级偏移;c)占据内存时间等。
③ 程序开始地址
④ 各种计时信息:进程占有和利用资源的有关情况。
⑤ 通信信息:执行过程中与其他进程的信息交换情况。
·资源管理信息——5、8、9章
包括有关存储器的信息、使用输入输出设备的信息、有关文件系统的信息等。
① 占用内存大小及其管理用数据结构指针,例如后述内存管理用到的进程页表指针等。
② 在某些复杂系统中,还有对换或覆盖用的有关信息,如对换程序段长度,对换外存地址等。在进程申请、释放内存时使用。
③ 共享程序段大小及起始地址。
④ 输入输出设备的设备号,所要传送的数据长度、缓冲区地址、缓冲区长度及所用设备的有关数据结构指针等。在进程申请释放设备进行数据传输中使用。
⑤ 指向文件系统的指针及有关标识等。
·CPU 现场保护结构
用于存储退出执行时的进程现场数据,常用的现场信息包括通用寄存器的内容、控制寄存器(如PSW寄存器)的内容、用户堆栈指针、系统堆栈指针等。
占内存与解决方法:一个PCB表一般占几百到几千个字节(Linux系统的每个PCB约占1KB多的内存空间)。只允许PCB中最常用的部分,如CPU现场保护、进程描述信息、控制信息常驻内存。PCB 结构中的其他部分则存放于外存之中,待该进程将要执行时与其他数据一起装入内存。
PCB的组织方式:
·线性方式——组织在一张线性表中。
该方式实现简单、开销小,但每次查找时都需要扫描整张表,因此适合进程数目不多的系统。
·链接方式——相同状态链接成一个队列。
就绪队列往往按进程的优先级将PCB从高到低进行排列。
阻塞状态进程的PCB根据其阻塞原因的不同,排成多个阻塞队列,如等待I/O操作完成的队列和等待分配内存的队列等。
·索引方式——建立索引表。
例如,就绪索引表、阻塞索引表等,各索引表在内存的首地址记录在内存的一些专用单元中。
3.2.2 进程上下文
Context 进程上下文:是一个抽象的概念,包含进程执行过的、执行时的以及待执行的指令和数据,是存储在指令寄存器、堆栈(调用子程序的返回点和参数等)、状态字寄存器等中的内容。
·上文:已执行过的进程指令和数据。
·正文:正在执行的进程指令和数据。
·下文:待执行的进程指令和数据。
UNIX System V进程上下文分类:
·用户级上下文:由进程的用户程序段部分编译而成的用户正文段、用户数据、用户栈等组成。
·寄存器上下文:由程序寄存器PC(下条指令地址)、处理机状态字寄存器PS(硬件状态)、栈指针(下一项地址)和通用寄存器的值(参数)组成。
·系统级上下文:分为静态部分与动态部分。
3.2.3 进程上下文切换
辨析:在不同的进程之间切换,而不是统一进程的过程。
3.2.4 进程空间与大小
进程空间or虚空间:进程有一个自己的地址空间。
进程空间大小:只与处理机的位数有关。
Eg: 16位长处理机的进程空间大小为216,
而32位长处理机的进程空间大小为232。
进程空间的用途:程序的执行在进程空间内进行。用户程序、进程的各种控制表格等都按一定的结构排列在进程空间中。
在UNIX以及Linux等操作系统中,进程空间被划分为用户空间和系统空间两大部分。用户程序在用户空间内执行,操作系统内核程序在系统空间内执行。上图。
3.3 进程状态及其转换
3.3.1 进程状态
5种状态:
初始状态:创建进程申请资源无法满足时(例如系统没有足够的内存,使进程无法装入其中),此时创建工作尚未完成,进程无法被调度运行。
就绪状态:进程得到除CPU以外其他资源,需等待调度程序为其分配处理机。
内存就绪状态
外存就绪状态
执行状态:实际占用CPU正在运行;如果分配给进程的时间片用完,则被剥夺处理机暂停执行,转为就绪状态。
用户执行状态
系统执行状态
等待(阻塞)状态:因为外部事件(例如等待输入数据)而暂时不能占有处理机,待相关事件发生后才可能运行,等待被唤醒。
内存等待状态
设备等待状态
文件等待状态
数据等待状态
外存等待状态
终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。
3.3.2 进程状态转换
进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件发生变化和转换。
进程的状态转换除了要使用不同的控制过程,有时还要借助于硬件触发器才能完成。
挂起:进程暂时被淘汰出内存。
引入“挂起”操作,进程可能发生以下状态转换:
(1) 活动就绪→静止就绪。
(2) 活动阻塞→静止阻塞。
(3) 静止就绪→活动就绪。
(4) 静止阻塞→活动阻塞。
3.4 进程控制
进程和处理机管理的一个重要任务是进程控制。
进程控制:系统使用一些具有特定功能的程序段来创建、撤消进程以及完成进程各状态间的转换,从而达到多进程高效率并发执行和协调、实现资源共享的目的。
原语:系统态下执行的某些具有特定功能的程序段。
机器指令级原语:执行期间不允许中断,如物理学中的原子一样,是一个不可分割的基本单位。
功能级原语:作为原语的程序段不允许并发执行。
这两类原语都是为了完成某个系统管理所需要的功能和被高层软件所调用。
若:创建和撤消进程的程序段并发执行,结果失去封闭性和可再现性。
所以:操作系统中,通常把进程控制用程序段做成原语。
用于进程控制的原语有:创建原语、撤消原语、阻塞原语、唤醒原语等。
3.4.1 进程的创建与撤销
进程创建:
·由系统程序模块统一创建
·由父进程创建
特点:进程平等,创建原语实现,子进程继承父进程资源,由进程链储存树形家族结构,创建系统进程来分配系统资源和管理工作。
流程:PCB链表查空、填参、加入就绪队列、入进程链。
由初始状态到就绪状态。
进程撤销:
·该进程已完成所要求的功能而正常终止。
·由于某种错误导致非正常终止。
·祖先进程要求撤消某个子进程。
特点:撤销则释放占用的各种资源和PCB结构本身;祖先进程撤销子进程需要审核它是否还有别的子进程,都释放(no祖先悖论)。
流程:查进程链、判断PCB有无子进程、释放资源和PCB结构。
3.4.2 进程的阻塞与唤醒
执行状态和等待状态与等待状态和就绪状态之间的转换。
进程阻塞:一个进程期待某一事件发生,但发生条件尚不具备时,被该进程自己调用阻塞原语来阻塞自己,进程从执行状态变为等待状态。转进程调度很重要,否则处理机将会出现空转而浪费资源。
进程唤醒:等待队列中的进程等待的事件发生时,等待该事件的所有进程都将被唤醒,进程从等待状态变为就绪状态。最后都要转进程调度。
2 ways:系统进程唤醒;事件发生进程唤醒。
3.5 进程互斥
由于资源有限,进程并发执行过程中存在资源竞争与共享。(少数决游戏:主角和他人竞争、同时和他人共享资源。)
3.5.1 资源共享所引起的制约
Critical region临界区:不允许多个并发进程交叉执行的一段程序称为临界部分(critical section)。
临界区是由属于不同并发进程的程序段共享公用数据或公用数据变量而引起的,临界区即是访问公用数据的那段程序。(临界区不可能用增加硬件的方法来解决。)
设计算进程PA, PB共享内存MS。MS分为系统区、进程工作区和数据区。数据区划分成大小相等的块,每个块中既可能有数据,也有可能没有数据。系统区主要是堆栈S,存放空数据块的地址。
令getspace为获取空数据块过程,release(ad)为释放数据块过程。
getspace: begin local g; g←stack[top]; top←top-1; end | release(ad):begin top←top+1; stack[top]←ad; end |
Why冲突?线执行release一句,再执行getspace,最后执行release后一句。
间接制约:由于共享某一公有资源而引起的在临界区内不允许并发进程交叉执行的现象,称为由共享公有资源而造成的对并发进程执行速度的间接制约。
“间接”二字主要是指各并发进程的速度受公有资源制约,而不是进程间直接制约(同步)的意思。
受间接制约的类中各程序段执行顺序是任意的。
Class 类:把不允许交叉执行的临界区按不同的公用数据划分为不同的集合。以公用数据栈S划分的临界区集合是{getspace,release}。
上述例子:when〈类名〉do〈临界区〉od
互斥:一组并发进程中的一个或多个程序段,因共享某一公有资源而导致它们必须以一个且不允许交叉执行的单位执行。不允许两个以上的共享该资源的并发进程同时进入临界区。
纯过程(纯代码,可重入代码):在执行过程中不改变过程自身代码的一类过程。
纯过程各并发进程可以同时访问它。
把一个过程作成纯过程可便于多个进程共享,但由于编制纯过程必须对有关变量和工作区作相应的处理,从而其执行效率往往会受到一定的影响。
并发进程互斥执行准则:
·不能假设各并发进程的相对执行速度。
·并发进程中的某个进程不在临界区时,它不阻止其他进程进入临界区。
·并发进程中的若干个进程申请进入临界区时,只能允许一个进程进入。
·并发进程中的某个进程申请入临界区时开始,应在有限时间内进入临界区。
3.5.2 互斥的加锁实现
临界区加锁:当某个进程进入临界区之后,它将锁上临界区,直到它退出临界区时为止。并发进程在申请进入临界区时,首先测试该临界区是否是上锁的。如果该临界区已被锁住,则该进程要等到该临界区开锁之后才有可能获得临界区。
设临界区的类名为S。为了保证每一次临界区中只能有一个程序段被执行,又设“锁定位”为key[S],表示该“锁定位”属于类名为S的临界区。
lock(key[S]) 〈临 界 区〉 unlock(key[S]) |
设key[S]=1时表示类名为S的临界区可用,key[S]=0时表示类名为S的临界区不可用。
·unlock(key[S])只用一条语句即可实现:
key[S]←1; |
·lock(key[S])的一种实现方法是:
lock(key[s]):begin local v; repeat v←key[s]; until v=l; key[s]←0; end |
不能保证并发进程互斥执行的准则(3)。硬件设置“测试与设置”指令,保证第一步和第二步执行不可分离。
3.5.3 信号量和P、V原语
加锁的方法可以实现进程之间的互斥,但存在影响系统可靠性和执行效率的问题。例如:循环测试锁定位将损耗较多的CPU计算时间。
PA A:lock(key[S]) <S> unlock(key[S]) Goto A | PB B:lock(key[S]) <S> unlock(key[S]) Goto B |
以上是不公平现象。一个进程能否进入临界区依靠进程自己调用lock过程去测试相应的锁定位。每个进程能否进入临界区是依靠自己的测试判断!
Semaphore 信号量:
操作系统中,信号量sem是一整数。
·sem≥0时,代表可供并发进程使用的资源实体数;
·sem≤0时,表示正在等待使用临界区的进程数。
用于互斥的信号量sem的初值应该大于零。
建立一个信号量步骤:
·说明所建信号量代表的意义;
·为信号量赋初值;
·建立相应数据结构以便指向那些等待使用该临界区的进程。
P、V原语操作:信号量的数值仅能由P、V原语操作改变。
sem是与临界区内所使用的公用资源有关的信号量。一次P原语操作使得信号量sem减1,而一次V原语操作将使得信号量sem加1。
临界区描述:
When S do P(sem); <临界区> V(sem); od |
当某个进程正在临界区内执行时,其他进程如果执行了P原语操作,则该进程在等待队列中等待有其他进程做V原语操作释放资源后进入临界区,这时P原语的执行才算真正结束。当多个进程执行P原语未通过而进入等待状态之后,如有某进程作了V原语操作,则等待进程中的一个可以进入临界区,但其他进程必须等待。
3.5.4 用P、V原语实现进程互斥
利用P、V原语和信号量,可以解决并发进程的互斥问题,而且不会产生使用加锁法解决互斥问题时所出现的问题。
设信号量sem是用于互斥的信号量,且其初值为1表示没有并发进程使用该临界区。只要把临界区置于P(sem)和V(sem)之间,即可实现进程间的互斥。
用信号量实现并发进程PA,PB互斥的描述如下:
·设sem为互斥信号量,其取值范围为(1,0,-1)。
sem=1表示进程PA和PB都未进入类名为S的临界区;
sem=0表示进程PA或PB已进入类名为S的临界区;
sem=-1表示进程PA和PB中,一个进程已进入临界区,而另一个进程等待进入临界区。
·描述:
PA: P(sem) 〈S〉 V(sem): … | PB: P(sem) 〈S〉 V(sem): … |
3.5.5 进程互斥例子!——重点看
Word文档中的内容。
1.一条河上架设了一座只允许单向通过桥,且任意时刻桥上最多可以有N个人,过河的人只能沿着桥向前走而不能向后退,请设计利用PV原语实现多人顺利过河的算法。
解:把桥上可容纳的人的数量看作是公共资源,过桥的人互斥地使用该资源。
Semaphore semBridge=N; //信号量semBridge表示桥上还可以容纳的人数,初值为N
Guoqiao(void)
{ P(semBridge);
Walkthrough(); //过桥
V(semBridge);
}
2.一条河上架设了一座桥,且任意时刻桥上最多可以有N个人,过河的人只能沿着桥向前走而不能向后退。过河时,只要对岸无人过桥,就可以过桥,但不允许河两岸的人同时过桥。请设计利用PV原语实现两个方向多人顺利过河的算法。(假定人源源不断地到达)
将过桥的两个方向称为正方和反方:
首先想第一个过桥人的情况,如果正反双方都要过桥,那么第一个上桥的那个人就获得对桥的控制权,从而使另一方必须等待。
其次,假定正方人在过桥,因为桥上最多可以有N个人,所以正方过桥时需要控制在N个人之内。
最后,最后一个离开桥的人要释放对桥的控制权。需要注意的是,等待过桥的任何一个人都可能是第一个上桥的人,正在过桥的这些人中任何一个都可能是最后一个离桥的人。
为此,必须解决以下4个问题:
1)如何确定是第一个上桥的人和最后一个离桥的人?
2)如何获得和释放对桥的控制权?
3)谁获得、释放对桥的控制权?
4)如何控制桥上的人数最多为N个人?
1)如何确定是第一个上桥的人和最后一个离桥的人?
为了确定第一个上桥的和最后一个离桥的人,设置一个计数器counter初值为0,只要上桥一个人就令其加一、只要下桥一个人就令其减一。如果counter等于1就说明这是第一个上桥的人,如果counter等于0说明这是最后一个离桥的人。
确定第一个上桥的和最后一个离桥的人是针对某个特定的过桥方向,过桥的人都要操作计时器,且任意时刻只能有一个人操作计时器,因此计时器counter是同方向过桥的人的公共资源,即需要为正方和反方分别设置计时器counterA和counterB。
给counterA设置一个信号量semCounterA,初值为1。给counterB设置一个信号量semCounterB,初值为1。
2)如何获得和释放对桥的控制权?
正反双方都要过同一个桥,所以桥是正方双方过桥的人的公共资源,设置信号量semBridge表示对桥的控制权,初值为1。P(semBridge)获得对桥的控制权,V(semBridge)释放对桥的控制权。
3)谁获得、释放对桥的控制权?
每一个上桥的人都执行P(semBridge)来获得上桥资格,每一个离桥的人都执行V(semBridge)来释放资源,是否可行?
每一方过桥的人数事先是不固定的(尽管不能超过N,但是并不强制规定一定要是N个人),所以不能设置semBridge的初始值为N,否则可能出现2种情况:正方上桥的人不足N人,执行P原语后semBridge仍然非负,这样反方的人执行P原语后也上了桥,违反了“不允许河两岸的人同时过桥”的条件;正方上桥的人恰好为N个人,执行N次P原语后semBridge为0,这样反方的人执行P原语后进入等待状态,一旦正方有一人下桥执行V原语释放semBridge,反方的人就可以获得上桥资格,但此时正方的人尚未全部下桥,违反了“不允许河两岸的人同时过桥”的条件。
正确的策略:第一个上桥的人执行P(semBridge),同方向最后一个离桥的人执行V(semBridge)。这样就能保证一旦正方的人上桥,除非正方最后一个人下桥,否则反方人员必须等待。
即正方第一个人执行P(semBridge)后,第二个人、第三个人……直接上桥。反方要上桥时,反方的第一个上桥人必须先对计数器的信号量semCounterB执行P原语,之后counter值为1后才可以对信号量semBridge执行P原语进入等待,反方向的第二个人、第三个人……依次对semCounterB执行P原语进入等待。
4)如何控制桥上的人数最多为N个人?
把桥上可容纳的人的数量看作是公共资源,过桥的人互斥地使用该资源。设置一个控制人数的信号量semMax,初值为N。这个信号量可以作为正方双方的共用信号量,因为对它的操作是在获取桥的控制权之后,所以不会发生冲突。
如果使用计数器counterA和counterB的计数功能限制人数呢?counterA和counterB可以计数,但不能约束超出负载量的过桥人等待。
counterA=0,counterB=0; //设置正反双方的计数器并赋初值
//设置信号量
semaphore semCounterA=1, //信号量semCounterA表示是否正方在过桥
semCounterB=1, //信号量semCounterB表示是否反方在过桥
semBridge=1, //信号量semBridge表示桥是否可用
semMax=N,//信号量semMax表示桥上还可以容纳的人数
void directA(void) //正方过桥
{
//判断是否是第一个上桥人,若是则锁住桥
P(semCounterA);
counterA++;
if(counterA==1)
P(semBridge);
V(semCounterA);
//控制人数不超过N
P(semMax);
Walkthrough(); //过桥
V(semMax);
//离桥时减去人数,若是最后一个离桥则释放桥
P(semCounterA);
counterA--;
if(counterA==0)
V(semBridge);
V(semcounterA);
}
//反方向与正方向同理
void directB(void){
/*....*/
}
以上例子中,假设正方向的人开始过桥,并且过桥的人有无穷个人,那么反方向的人永远也无法获得过桥机会。因为不存在最后一个人来释放桥。
一种可能的修改:一旦对方等待过桥的人数超过N个人,正在过桥方向的新到达的过桥者必须等待,直到对方的等待过桥者有N个人过完桥为止。