(1)顺序性(2)封闭性(3)可再现性
(1)间断性(2)失去封闭性(3)不可再现性
进程实体(进程映像):程序、数据、PCB、栈
为了让程序能够并发才建立进程,没有建立PCB的程序不是进程,更不能并发执行。创建进程就是创建进程的PCB,撤销进程就是撤销进程的PCB
进程的三状态模型:(就绪态、执行态、阻塞态)
引入挂起状态后的五状态模型:(活动就绪态、静止就绪态、活动阻塞态、静止阻塞态、执行态)
“活动”--没有挂起,“静止”--挂起
引入创建状态和中止状态后的七状态模型:(活动就绪态、静止就绪态、活动阻塞态、静止阻塞态、执行态、创建态、终止态)
记录了用于描述进程的当前情况以及控制进程运行的全部信息
当OS要调度某进程执行时,要从该进程的PCB中查出当前状态和优先级;
在调度到某进程后,要根据其PCB中所保存的处理器状态信息,设置该进程恢复运行的现场,并根据其PCB中的程序和数据的内存地址,找到其程序和数据;
进程在执行过程中,当需要和与之合作的进程实现同步、通信或访问文件时,也都需要访问PCB;当进程由于某种原因而暂停执行时,又须将其断点的处理器环境保存在PCB中。
(1)进程标识符:内部标识符(纯数字,给系统看)、外部标识符(字母、数字,创建者提供)
(2)处理器状态:处理器的寄存器中的内容
(3)进程调度信息:与进程调度和进程对换有关的信息,如1)进程状态 2)进程优先级 3)事件 等
(4)进程控制信息:
1)程序和数据的地址
2)进程同步和通信机制,如信号量
3)资源清单(除CPU以外的)
4)指向本进程所在队列的下一个进程的PCB的首地址
(1)链接方式(2)索引方式
进程管理中最基本的概念,包含进程的创建、终止、阻塞与唤醒、挂起与激活。
(1)用户登录(分时系统中为终端用户创建进程)
(2)作业调度(调度到某作业时,为它创建进程、分配资源(除CPU)、插入到就绪队列中)
(3)提供服务(进程请求OS为它创建一个新进程完成任务)
(4)应用请求(进程自己为自己创建一个子进程)
(1)申请PCB,获得唯一数字标识符;
(2)分配资源(主要是为程序、数据、用户栈分配空间);
(3)初始化PCB:标识符、处理器状态信息(PC指向程序入口地址,st指向栈顶)、处理器控制信息(优先级、进 程状态)
(4)插入到就绪队列中
(1)正常结束
(2)异常结束(越界、访问权限、超时、算术运算......)
(3)外界干预(OS或人为,父进程终止,父进程请求终止子进程......)
(1)获取PCB中的进程状态
(2)若处于执行态,则终止进程,设调度标志为真
(3)终止所有后代进程
(4)将自己和后代进程的所有资源归还给父进程 或 OS
(5)将PCB从所在链表移出
(1)请求系统服务(如printer)
(2)启动某种操作(如I/O)
(3)等待数据或请求
进程无法继续执行,把自己阻塞。若还在执行,则停止执行并把PCB中的状态改为阻塞态,保留处理器上下文,插入到对应事件的阻塞队列。调度程序把处理器分配给下一个就绪进程,按新进程的PCB设置处理器上下文。
将被阻塞的进程从阻塞队列中移出,PCB中的状态改为就绪,并加入就绪队列。
wait(S): while S<=0 do no-op;
S:=S-1;
signal(S): S:=S+1;
type semaphore = record
value: integer;
L: list of process;
end
procedure wait(S)
var S: semaphore;
begin
S.value:=S.value-1;
if S.value<0 then block(S.L);
end
procedure signal(S)
var S: semaphore;
begin
S.value:=S.value+1;
if S.value<=0 then wakeup(S.L);
end
Swait(S1,S2, ..., Sn)
if S1>=1 ... and Sn>=1 then
for i:=1 to n do
Si:=Si-1;
endfor
else
把当前进程放入第一个满足条件Si<1的资源Si对应的阻塞队列中,PC设为
Swait操作的开始
endif
Ssignal(S1, S2, ..., Sn)
for i:=1 to n do
Si:=Si+1;
把Si对应的阻塞队列的中的进程全部释放到就绪队列中
endfor;
Swait(S1,t1,d1 ..., Sn,tn,dn)
if S1>=t1 ... and Sn>=tn then
for i:=1 to n do
Si:=Si-di;
endfor
else
把当前进程放入第一个满足条件Si
整型信号量(互斥信号量)的两个应用:
1. 进程互斥
2. 描述各个进程语句间的前驱关系
1. 要实现多个进程对某一资源互斥访问的话,只需为该资源设置一个信号量,初始值为1. 然后,将各个进程的临界区代码放在wait(mutex) (p操作) 和 signal(mutex) (v操作) 之间即可。
示例:(类 pascal 语法作伪代码挺好,但是缩进销魂,还是放图片比较好)
2. 描述前驱关系的话,两个进程P1和P2,它们之中分别有语句S1和S2。这两个进程并发执行,如果想要语句S2在S1之后执行,那么只需设一个互斥信号量a,令其初始值为0.
P1: S1; signal(a)
P2: wait(a); S2
这样就可以了,更复杂的前驱关系(用一张图表示),可以由此拓展,比如一条边设一个信号量就可以了。
详细例题:参考文献[1] P54 --利用信号量实现前驱关系
引入原因:使用信号量的机制后,每个进程需要自备同步操作 (p、v操作 或说 wait(S)、signal(S) 操作),管理起来很麻烦,而且容易导致死锁。
管程概念:将计算机的资源映射成数据结构,将资源的请求和释放等操作映射成数据结构上的操作。则共享数据结构与其操作就构成了“管程”(OS的资源管理程序之意)。
严格来讲,管程的组成为:名称、数据结构、数据结构的操作、字段初始化。
进程要访问临界资源的时候,就需要进入该资源对应的管程,每次只允许一个进程进入管程。
管程特点:1. ADT(抽象数据类型)2. 封装(encapsulation)3. 模块化,管程实际上是OS的一个程序模块
管程和进程的区别:
(1)进程定义的是私有数据结构PCB;管程定义的是公共数据结构(别的进程可以进入管程调用数据结构的操作)
(2)进程做顺序执行操作;管程做同步操作和初始化操作
(3)进程的存在是为了并发;管程的存在是为了解决共享资源的互斥访问问题
(4)进程主动工作;管程被动工作(被调用)
(5)进程间能并发执行,管程不能与其调用者并发执行(管程是被调用的模块,当然不能独立执行)
(6)进程有生死,因“创建“而生,因”撤销“而死;而管程无所谓生死,它只是系统的一个模块
管程相对于信号量:自信号量到管程,pv操作并没有消失,而是从各进程中集中到管程里了(实际上是集中到了数据结构的操作方法里)
说了那么多,管程是如何实现多进程共享资源的互斥访问的?--通过条件变量的pv (wait、signal)操作
设 (1) 某资源 (2) 条件变量X (3) 因X而阻塞的进程队列
进程要请求某资源的时候,进入管程,如果因X条件需要被阻塞或挂起,则调用X.wait将自己插到X条件的阻塞队列后,同时释放管程。此时,其他进程可以进入管程,如果新的进程发现X条件发生了变化,则调用X.signal,从阻塞队列中唤醒一个因为X条件而阻塞的进程。若队列为空,则啥也不做 (这和信号量中的signal操作不同,信号量中的signal操作总会改变资源的数目,而管程中条件变量的signal操作却可以啥都不做) 。
以上,还产生了一个新的问题--当进程B发现条件X变化,唤醒进程A之后,由于B已经在管程里了,而A理应也要进入管程了。这就有几个简单的策略,比如让A等待,B做完事或B因为等待另一条件而阻塞,然后把管程给A;也可以反过来,还可以折衷。
信号量解法:
设在生产者和消费者之间的缓冲池有n个缓冲区,可以利用互斥信号量mutex实现诸进程对缓冲池的互斥使用。利用资源信号量empty、full分别表示缓冲池中空缓冲区和满缓冲区的数量。
1)记录型型信号量解法:
Var mutex, empty, full: semaphore:=1,n,0;
buffer:array[0,...,n-1] of item;
in, out: integer:=0, 0;
begin
parbegin
proceducer: begin
repeat
produce an item nextp;
wait(empty);
wait(mutex);
buffer(in):=nextp;
in:=(in+1) mod n;
signal(mutex);
signal(full);
until false;
end
consumer: begin
repeat
wait(full);
wait(mutex);
nextc=buffer(out);
out:=(out+1) mod n;
signal(mutex);
signal(empty);
consume the item in nextc;
until false;
end
parend
end
2)AND型信号量解法:
可以同时对多种资源作一个单位的操作,代码比1)要简洁一些。
Var mutex, empty, full:semaphore:=1,n,0;
buffer:array[0,...,n-1] of item
in out: integer:=0,0;
begin
parbegin
producer: begin
repeat
produce an item in nextp;
Swait(empty,mutex);
buffer(in):=nextp;
in:=(in+1) mod n;
Ssignal(mutex,full);
until false;
end
consumer: begin
repeat
Swait(full,mutex);
nextc:=buffer(out);
out:=(out+1) mod n;
Ssignal(mutex,empty);
consume the item in nextc
until false;
end
parend
end
3)管程解法:
建立管程,主要是数据结构的field和operation,定义两个operation: put(item) 生产者通过这个操作把产品放到缓冲池中,利用count来表示缓冲池的产品数目,当count>=n时--缓冲池已满,生产者等待。get(item) 消费者通过这个操作从缓冲池中取出一个产品,当count<=0时,表示缓冲池中没有产品了,消费者等待
建立管程如下:
type producer-consumer=monitor
Var in,out,count: integer; //字段 (field)
buffer: array[0,...,n-1] of item;
notfull, notempty:condition; //条件变量
procedure entry put(item) //put operation
begin
if count>=n then notfull.wait;
buffer(in):=nextp;
in:=(in+1) mod n;
count:=count+1;
if notempty.queue then notempty.signal;
end
procedure entry get(item) //get operation
begin
if count<=0 then notempty.wait;
nextc:=buffer(out);
out:=(out+1) mod n;
count:=count-1;
if notfull.queue then notfull.signal;
end
begin in:=out:=0; //字段 (field) 初始化
count:=0;
end
在生产者和消费者的进程中调用管程:
producer: begin
repeat
produce an item in nextp;
PC.put(item);
until false;
end
consumer: begin
repeat
PC.get(item);
consume the item in nextc;
until false;
end
Var chopstick: array[0,...,4] of semaphore;
repeat
wait(chopstick[i]);
wait(chopstick[(i+1)mod 5]);
...
eat;
...
signal(chopstick[i]);
signal(chopstick[(i+1)mod 5]);
...
think;
until false;
Var chopstick array of semaphore:=(1,1,1,1,1);
process i
repeat
think;
Swait(chopstick[i],chopstick[(i+1)mod 5]);
...
eat;
...
Ssignal(chopstick[i],chopstick[(i+1)mod 5]);
until false;
Var rmutex, wmutex: semaphore:=1,1;
Readcount: integer:=0;
begin
parbegin
Reader: begin
repeat
wait(rmutex);
if readcount=0 then wait(wmutex);
Readcount:=Readcount+1;
signal(rmutex);
...
perform read operation;
...
wait(rmutex);
readcount:=readcount-1;
if readcount=0 then signal(wmutex);
signal(rmutex);
until false;
end
writer: begin
repeat
wait(wmutex);
perform write operation;
signal(wmutex);
until false;
end
parend
end
Var RN integer;
L, mx: semaphore:=RN,1;
begin
parbegin
reader: begin
repeat
Swait(L,1,1);
Swait(mx,1,0);
...
perform read operation;
...
Ssignal(L,1);
until false;
end
writer: begin
repeat
Swait(mx,1,1; L,RN,0);
perform write operation;
Ssignal(mx,1);
until false;
end
parend
end
关键:
内核在内核空间为每一个内核支持线程设置一个线程控制块,内核通过这个来调控它们
优点:
1)在多处理器系统中,同一进程中的内核支持线程可以并行执行(由内核调度)。
2)若进程中的一个线程被阻塞了,内核可以调度进程中的其他线程占用CPU来运行,也可运行其他进程中的线程。
3)此种线程具有很小的数据结构和堆栈,线程间的切换迅速,开销小。
4)内核本身也可以采用多线程技术,提高系统的执行速度和效率。
缺点:
对用户的线程切换来说,因为用户的线程运行在用户态,切换时需要从用户态转到内核态进行,开销大。(纯内核支持线程的系统,其线程调度和管理是在内核实现的。)
系统在创建一个新进程的时候,为它分配一个任务数据去PTDA(Per Task Data Area),含若干线程控制块(TCB)空间。每一个TCB可保存线程标识符、优先级、线程运行的CPU状态。虽然这些信息与用户级线程TCB的信息相同,但却是保存在内核空间中的。
内核支持线程的创建、撤销与进程的相似。有的实现中撤销一个线程并不立即回收其资源和TCB。以后要再创建一个新线程时,可以直接利用已被撤销但仍然葆有资源、TCB的线程作为新线程。
[1] 计算机操作系统(第三版)汤小丹 梁红兵 哲凤屏 汤子瀛 编著
[2] 操作系统--精髓与设计原理(第七版)William Stallings 著;陈向群 陈渝 译