进程管理学习1
3进程管理
进程的概念
程序是一个静态的概念
程序顺序执行特点:
-
顺序性
-
封闭性
-
可再现性
程序执行的环境和初始条件相同,当程序重复执行时,不论停顿与否,结果相同
为程序员检测和校正程序的错误带来了很大的方便。
多道程序系统特点:?1
-
用户随机
用户程序是随机的
-
资源共享
共享:软件资源、硬件资源
-
执行并发
一组进程的执行在时间上是重叠的
三个特点相互联系、相互依赖,是现代操作系统的重要特点。
可以并发
read (a) ;
read (b) ;
失去再现性
getaddr(top)
reladdr(blk)
如果执行并发的各程序段语句或指令满足Bernstein的三个条件,则认为并发执行不会对执行结果的封闭性和可再现性产生影响。
Bernstein三个条件,是指
并发进程的无关性
是进程与时间无关的一个充分条件Bernstein条件的一般形式为:
定义R(pi)={a1,a2,a3,...an};
W(pi)={b1,b2,b3,...bn};
(1) R(p1) ∩ W(p2) = ∅ 读后写
(2) R(p2) ∩ W(p1) = ∅ 写后读
(3) W(p1) ∩ W(p2) = ∅ 写后写
系统判定并发执行的各程序段是否满足Bernstein条件是相当困难的
利用前趋图,但给出任意时刻的前趋图实际上不可行。
so:引入进程
原因:程序 无法反映
操作系统所应该具有的程序段执行的 并发性、用户随机性,以及 资源共享 等特征。
进程:并发执行的程序 在执行过程中
分配和管理资源
的基本单位进程是竞争计算机系统资源的基本单位
进程和程序的区别和联系
-
进程是一个动态的概念
程序则是一个静态的概念
-
进程具有
并发
特征,而程序没有 -
进程是
竞争计算机系统资源
的基本单位 -
不同进程 可以包含 同一程序,只要该程序所对应的数据集不同。
进程的描述
进程的静态描述
系统中 描述进程存在
和 能够反映其变化
的 物理实体
组成:
1 程序段和数据段 是 进程完成所需功能 的 物质
基础
- 程序段:描述进程所要完成的功能
- 数据段(数据结构集):程序在执行中必不可少的
工作区
和操作对象
。
2 内容放在 外存
中,直到该进程执行时再调入内存
。
几个特点:
-
进程控制块(PCB)包含了有关进程的
描述信息
、控制信息
以及资源信息
还有 进程调度等待 所使用的
现场保护区
,是进程动态特征的集中体现四部分
-
系统根据PCB感知进程的存在 和 通过PCB 中所包含的各项变量的变化,掌握进程所处的状态以-->达到
控制进程活动
的目的进程的PCB是 系统感知进程的唯一实体,一个进程的PCB结构 ,全部或者部分
常驻内存
3 为了进程 制约关系 和 资源共享 关系
创建一个进程
首先创建PCB
完成其功能后
系统释放PCB
进程也随之消亡......
4 进程PCB包含的内容
-
描述信息
-名、号进程名、用户名(标识号)、家族关系(进程的隶属关系)
-
控制信息
-状态、优先级进程当前状态(初始、就绪、执行、等待、终止)
进程的优先级(进程占有处理机的重要依据),与之有关的PCB表项:
- 占有CPU时间
- 进程优先级偏移
- 占据内存时间
- 程序开始地址
- 各种计时信息(进程占有和利用资源的有关情况)
- 通信信息(执行过程中 本进程 与 其他进程 的 信息交换 情况)
-
资源管理信息
-资源PCB中最多的就是资源管理信息
相关存储器的信息、使用I/O设备的信息、相关文件系统的信息,例:
-
占用 内存大小 及其 管理用 数据结构指针
例:内存管理中所用到的进程页表指针等。
-
(复杂系统),对换和覆盖用的有关信息
例:兑换程序段的长度、兑换外存地址等(进程申请、释放内存 中用)
-
共享程序段的大小 及 起始 地址
-
I/O设备的设备号,传送的数据信息,缓冲区信息等
(进程申请、释放设备进行数据传输中使用)
-
指向文件系统的指针和有关标识等
进程可以使用这些信息对文件系统进行操作
-
-
CPU现场保护结构
-进程上下文-
当前进程 因
等待某个事件-->进入等待状态
某种时间发生-->被终止在处理机上的执行
为了以后该进程 在被打断处 恢复执行
需要保护当前进程的CPU现场(
进程上下文
) -
PCB中有专门的 CPU现场保护结构 ,以存储退出执行的进程现场数据。
-
※进程上下文:
进程执行过程中 顺序关联 的 静态描述。
包括
计算机系统中与执行该进程有关的各种寄存器的值、
程序段在经过编译之后形成的机器指令代码集(正文段)、
数据集及各种堆栈值和PCB结构。
有关
寄存器
和栈区
的内容是非常重要的,例
没有
程序计数器PC
和程序状态寄存器PS
cpu将无法知道 下条待执行指令的地址 和 控制有关操作
※进程控制块PCB
是 系统感知进程存在 的唯一实体。
-
通过对PCB的操作,系统为 有关进程分配资源
从而使得有关进程得以被调度执行;
-
完成进程所要求功能的 程序段的有关地址,
以及程序段在进程过程中,因某种原因被停止执行后的现场信息保存在PCB中;
-
当进程执行结束后,通过释放PCB,来释放进程所占有的各种资源。
PCB表往往要占据较大的存储空间(一般占几百到几千个字节)
为了减少PCB对内存的占用量,
只允许PCB中最常用的部分,如CPU现场保护、进程描述信息、控制信息等常驻内存
。
PCB 结构中的其他部分则存放于外存之中,待该进程将要执行时与其他数据一起装入内存。
进程队列
线性、链解、索引
1 线性方式
2 链接方式
3 索引方式
进程状态及其转换
知道了进程是个啥
那就来到了 进程有啥属性-->进程状态
基本状态:就绪、执行、等待
就绪状态
可以进一步分为
-
内存就绪状态
得到处理机后才能立即投入执行
-
外存就绪状态
只有先成为内存就绪状态后,才可能被调度执行
意义:提高了内存的利用效率,增加了系统开销和系统复杂性。
执行状态
单CPU系统中,任一时刻处于执行状态的进程只能有一个。
只有处于 就绪状态的进程 经 调度选中 之后才可进入执行状态。
-
用户执行状态
-
系统执行状态
把 用户程序 和 系统程序 区分开来,以利于程序的共享和保护
但:以增加系统复杂度和系统开销为代价
阻塞(等待)状态
进程因等待某个事件发生而 放弃处理机 进入等待状态。
可根据等待事件的种类而进一步划分为不同的子状态
例如内存等待、设备等待、文件等待和数据等待等
优势:系统控制简单,发现和唤醒相应的进程较为容易。
缺点:系统中设置过多的状态又会造成 系统参数和状态转换过程的增加。
进程控制
知道了进程是个啥
-->我们就要了解咋用、咋控制进程
进程控制概念
系统使用一些
具有特定功能的程序段
来创建、撤消进程 以及 完成进程 各状态间 的转换
从而达到 多进程 高效率并发执行 和协调、实现资源共享
的目的。
进程控制是 进程和处理机管理 的一个重要任务。
--原语的含义
系统态下执行的 某些 具有特定功能的程序段
分类
-
机器指令级
执行期间不允许中断
-
功能级
作为原语的程序段 不允许并发执行
原语都在系统态下执行,且都是为了完成某个系统管理所需要的功能、和被高层软件所调用。
如果控制的程序段并发执行,则会使得其执行结果失去封闭性和可再现性。
在操作系统中,通常把进程控制用程序段
做成原语。
用于进程控制的原语:创建原语、撤消原语、阻塞原语、唤醒原语
等。
进程的创建
1 由系统程序模块统一创建
-
在批处理系统中,由操作系统的
作业调度程序
为 用户作业 创建相应的进程
以完成 用户作业 所要求的功能。
-
由系统统一创建的进程之间的
关系是平等的
它们之间一般不存在资源继承关系。
2 由父进程创建
-
在层次结构的系统中,父进程创建子进程-->以完成并行工作。
-
在父进程创建的进程之间则
存在隶属关系
,且互相构成树型结构的家族关系。
属于某个家族的一个进程
可以继承其父进程所拥有的资源。
创建原语流程图
进程图(Process Graph)是描述进程族系关系的有向树
※UNIX/Linux系统创建进程示例
执行创建进程的系统调用(fork),主要操作过程有:
- 申请一个空闲的PCB
- 为新进程分配资源
将新进程的PCB初始化
- 将新进程加到
就绪队列
中
申请-->分配-->初始化-->列队
下面这个C程序展示了UNIX系统中
父进程创建子进程 及 各自分开活动的 情况
#include <stdio.h>
void main(int argc, char *argv[])
{
int pid;
/* fork another process */
pid = fork();
//创建了一个进程
if (pid < 0)
{ /* error occurred */
fprintf(stderr, "Fork Failed");
//创建进程失败
exit(-1);
}
else if (pid == 0)
{ /* child process */
//子进程-->调用execlp函数
execlp("/bin/ls", "ls", NULL);
}
else
{ /* parent process */
/* parent will wait for the child to complete */
//父进程等待子进程完成后,执行。
wait(NULL);
printf("Child Complete");
exit(0);
}
}
共性:
无论哪种创建方式,系统生成时,都
必须由操作系统
,创建一部分承担
系统资源分配和管理工作
的系统进程。无论哪种创建方式,都
必须调用创建原语
来实现创建原语扫描系统的PCB链表,
找到一定的PCB表后,填入调用者相关参数
形成代表进程的PCB结构
PS:参数:进程名,进程优先级P0,进程正文段机器地址d0,资源清单R0等。
进程撤销
- 该进程已完成所要求的功能而正常终止。
- 由于某种错误导致非正常终止。
- 祖先进程要求撤消某个子进程。
无论哪一种情况导致进程被撤消,进程都必须释放它所占用的各种资源和PCB 结构本身,以利于资源的有效利用。
注意事项:
-
祖先进程撤销某个子进程时,现审查子进程是否还有子孙进程
-
撤销原语,首先检查PCB进程链、或进程家族-->是否存在撤销的进程
-
如果找到了所要撤消的进程的PCB结构,
则撤消原语释放该进程所占有的资源之后,把对应的PCB结构,从进程链或进程家族中,摘下并返回给PCB空队列。
-
如果被撤消的进程有自己的子进程,
则撤消原语先撤消其子进程的 PCB结构,并释放子进程所占用的资源之后,再撤消当前进程的 PCB结构和释放其资源。
-
具体流程
进程的创建原语和撤消原语完成了进程从无到有,从存在到消亡的变化。被创建后的进程最初处于就绪状态,然后经调度程序选中后进入执行状态。
实现进程的执行状态到等待状态,又由等待状态到就绪状态转换的两种原语,分别为阻塞原语与唤醒原语。
进程阻塞与唤醒
阻塞原语在一个进程期待某一事件发生,但发生条件尚不具备时,被该进程自己调用来阻塞自己
。
阻塞进程时:
由于正处于执行状态,首先终端 处理机 和 保存该进程的CPU现场。
然后将被阻塞进程 置“阻塞”状态后,插入等待队列
再转进进程调度程序,选择新的就绪进程投入运行。
转进程调度程序是很重要的,否则,处理机将会出现空转而浪费资源。
具体流程
唤醒
当等待队列中的进程所等待的事件发生时,等待该事件的所有进程都将被唤醒。
方法:
-
由系统进程唤醒
:当由系统进程唤醒等待进程时,系统进程统一控制事件的发生,
并将“事件发生”这一消息,通知等待进程。
从而使得该进程,因等待事件已发生,而进入就绪队列。
-
由事件发生进程唤醒
:事件发生进程和被唤醒进程之间是合作关系。
唤醒原语既可被系统进程调用,也可被事件发生进程
调用。
调用唤醒原语的进程为称唤醒进程。
唤醒原语:
-
首先将被唤醒进程从相应的等待队列中摘下,将被唤醒进程
置为就绪状态
之后,送入就绪队列。 -
在把被唤醒进程送入就绪队列之后,唤醒原语既可以返回原调用程序,也可以转向进程调度,以便让调度程序有机会选择一个合适的进程执行。
唤醒原语具体流程
进程互斥
可以并发
read (a) ;
read (b) ;
失去再现性
getaddr(top)
reladdr(blk)
堆栈的取数和存数过程
getaddr(top)从给定的top所指栈中取出相应的内存数据块地址
procedure getaddr(top)
begin
local r
r ←(top)
top ← top-1
return(r)
end
reladdr(blk)则将内存数据块地址blk放入堆栈S中
procedure reladdr(blk)
begin
top ← top+1
(top)← blk
end
如果getaddr 和 reladdr程序段进行顺序执行,其执行结果具有封闭性和可再现性。
如果改变程序段getaddr和 reladdr的执行顺序或执行速度,可能得到不同的执行结果。
在某些情况下,程序的并发执行使得其执行结果不再具有封闭性和可再现性,且可能造成程序出现错误。
原因:两程序段共享资源堆栈S,从而使得执行结果受执行速度影响。
对策:控制和协调各程序段执行过程中的软、硬件资源的共享和竞争。
Bernstein于1966年提出了两相邻语句S1,S2可以并发执行的条件:
如果对于语句S1和S2,有
① R(S1)∩ W(S2)={∮},
② W(S1)∩ R(S2)={∮},
③ W(S1)∩ W(S2)={∮} 同时成立,则语句S1和S2是可以并发执行的。
如果并发执行的各程序段中语句或指令满足上述Bernstein 的三个条件,则认为并发执行不会对执行结果的封闭性和可再现性产生影响。
但是判断是相当困难的
进程互斥
一组并发进程中的一个或多个程序段,因共享某一公有资源
而导致
它们必须以一个不允许交叉执行
的单位执行。
原因:用户随机、资源共享、并发执行的结果,资源的竞争与共享制约所导致
临界区
不允许多个并发进程交叉执行的一段程序,称为临界部分或临界区
- 临界区是由属于
不同并发进程
的程序段、共享公用数据或公用数据变量而引起的。- 临界区
不可能用增加硬件
的方法来解决。- 临界区也可以被称为
访问公用数据的那段程序
。
间接制约
把那些不允许交叉执行的临界区,按不同的公用数据划分为不同的集合
。
以公用数据栈S划分的临界区集合{getspace,release},把这些集合成为类(class).
对类给定一个唯一的标识名,系统就会容易地区分它们。
定义:由于共享某一公有资源而引起的,在临界区内不允许并发进程交叉执行的现象,称为由共享公有资源而造成的对并发进程执行速度
的间接制约,简称间接制约
间接:指并发进程的速度,受公有资源制约,而不是直接制约的意思。
直接制约:并发进程互相共享对方私有资源。
直接制约,将使得各并发进程同步执行
受间接制约的类中各程序段在执行顺序上是任意的
。
对于每一类,系统应有相应的分配,和释放相应公有资源的管理办法
,以制约并发进程。
这就是互斥。
互斥
定义:一组并发进程中的一个或多个程序段,因共享某一¥公有¥资源,而导致它们必须以一个不允许交叉执行
的单位执行。
不允许两个以上并发进程同时进入临界区
原因:共享某一公有资源
并发进程互斥执行准则
-
不能假设各并发进程的相对执行速度
。即各并发进程
享有平等的、独立的竞争共有资源的权利
,且在不采取任何措施的条件下,在临界区内任一指令结束时,其他并发进程可以进入临界区。 -
并发进程中的某个进程不在临界区时,它
不阻止其他进程进入临界区
。 -
并发进程中的若干个进程申请进入临界区时,
只能允许一个进程进入
。 -
并发进程中的某个进程申请进入临界区时开始,
应在有限时间内得以进入临界区
。(不能某个进程一直等起来没完)
准则(1),(2),(3)是保证各并发进程
享有平等的、独立的竞争和使用公有资源的权利
,且保证每一时刻至多只有一个进程在临界区
。准则(4)则是并发进程不发生死锁的重要保证。否则,由于某个并发进程长期占有临界区,其他进程则因为不能进入临界区,而进入互相等待状态。
互斥的加锁实现
怎样实现并发进程的互斥:
- 临界区中的各个过程按不同的时间排列调用-不可行(违反执行准则1)
- 对临界区加锁-可行
设临界区的类名为S。设锁定位 key[S] 表示该锁定位属于类名为S的临界区。
加锁后的临界区程序描述如下:
lock(key[S])
〈临 界 区〉
unlock(key[S])
设key[S]=1时表示类名为S的临界区可用,
key[S]=0时表示类名为S的临界区不可用。
则,unlock(key[S])只用一条语句即可实现。即:key[S]←1(赋值)
※PS:lock(key[S])必须满足key[S]=0时,不允许任何进程进入临界区,
而key[S]=1时仅允许一个进程进入临界区的准则。
一种简便的实现方法是:
lock (x)=
begin local v
repeat
v←x
untilv=1
x←0
end
这种实现方法不能保证并发进程互斥执行所要求的准则(3-至多1个)
原因:当同时有几个进程调用lock(key[S])时,在x←0语句执行之前,可能已有两个以上的多个进程由于key[S]=1而进入临界区。
解决办法:在硬件中设置了“测试与设置“指令,保证第一步和第二步执行不可分离。
注意:在系统实现时锁定位key[S]总是设置在公有资源所对应的数据结构中的。
信号量和P、V原语
信号量(semaphore)
加锁的方法可以实现进程之间的互斥,但
- 影响系统的执行效率
- 在某些情况下出现不公平现象
进程PA和PB反复使用临界区的情况:
PA
A: lock(key[S])
<S>
unlock(key[S])
Goto A
PB
B: lock(key[S])
<S>
unlock(key[S])
Goto B
设进程PA已通过lock(key[S])过程而进入临界区。
进程PB将处于永久饥饿状态(starvation)。
只有在进程PA执行完unlock(key[S])过程之后、执行Goto A语句之前的瞬间发生进程调度,使进程PA把处理机转让给进程PB,进程PB才有可能得到执行。这种可能性非常小。
产生不公平现象的原因
- 一个进程能否进入临界区:依靠进程自己调用lock过程去测试相应的锁定位。
- 每个进程能否进入临界区:依靠自己的测试判断。没有获得执行机会的进程无法判断。
- 获得了测试机会的进程,又因需要测试而损失一定的CPU时间。
教室设置管理员的例子(统一检查哪些教室关门了,哪些没有关门)。既减少了学生多次来去教室检查门是否被打开的时间,又减少了因为学生自发地检查造成的不公平现象。
在操作系统中,这个管理员就是信号量。信号量管理相应临界区的公有资源,它代表可用资源实体。
锁(lock)和信号量(semaphore)的区别
- 对一段代码加锁相当于我每次想要执行该段代码时,都要判断该lock是不是为真(没锁上)啊,为真我才能执行,如果不为真,我就等待下次调度该代码时,
再次判断
lock是不是为真。 - 而对一段代码加信号量,其实本质上也是判断该信号量是不是为真啊,为真我才能执行,但是如果不为真,该进程就进入等待队列,等待下次调度该代码时,直接就可以执行了,而
不用再次判断
该信号量是否为真了,直接就可以进入临界区执行了。
所以他们之间的区别是什么呢?
- 加锁就相当于我每次从宿舍走到教室,发现教室被占用了,那好了,今天我就无法学习了,下次再来,教室又被占用了,因为可能出现我走在路上的时候,总是有人把教室占了,我就可能永远都用不到教室,还浪费了从宿舍走到教室的时间。
- 而信号量是相当于什么呢?相当于我去教室,但是教室被占了,好了,这时候教室管理员记下我的名字,当然也会记下别人的名字。当教室未被占用时,教室管理员按照名单上的顺序将教室依次分给指定的人。
信号量管理临界区的公有资源
荷兰科学家E.W.Dijkstra提出
信号量sem是一整数
Sem>=0:可供并发进程使用的资源实体数
Sem<0 :正在等待使用临界区的进程数
如何建立一个信号量
- 说明所建信号量所代表的意义
- 赋初值-->用于互斥的信号量sem的初值应该大于零
- 建立相应的数据结构,指向等待使用该临界区的进程
P,V原语
信号量的数值,仅能由P,V原语操作改变
类名为S的临界区 可以描述为
When S do P(sem)临界区V(sem) od
- Sem:信号量
- 一次P原语操作使得信号量sem减1
- 一次V原语操作将使得信号量sem加1
P原语操作的主要动作:
(1) sem减 1;
(2) 若sem-1 >=0 ,P原语返回进程继续执行;(就意味着还有资源)
(3) 若sem-1<0,进程阻塞并转进程调度。(意味着没有资源了)
V原语的操作主要动作:
(1) sem加1;
(2) 若sem+1 >0 ,进程继续执行;
(3) 若sem+1 <=0 唤醒一等待进程再返回或转进程调度
原语操作的功能框图如图所示
P原语更像是在找资源,
V原语更像是在释放资源。
左侧为p原语,右侧为v原语操作功能
用信号量和P,V原语来实现进程互斥
设
- sem 是用于互斥的信号量
- 初值为 1 表示没有并发进程使用该临界区
- 临界区置于P(sem)和V(sem)之间实现互斥。
用信号量实现并发进程PA,PB互斥
-
设 sem为互斥信号量,其取值范围为(1,0,-1)
sem=1:进程PA和PB都未进入临界区
sem=0:进程PA或PB已进入临界区
sem=-1:一个进程已进入临界区,而另一个进程等待进入临界区
-
描述:
PA: P(sem) <S> V(sem): : : PB: P(sem) <s> V(sem): : :
进程同步
由Dijkstra提出并解决的哲学家就餐问题是典型的同步问题。
该问题描述的是五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。
思路
因为是五位哲学家,并且每位哲学家的各自做自己的事情(思考和吃饭),因此可以创建五个线程表示五位哲学家,五个线程相互独立(异步)。并对五位哲学家分别编号为0~4。
同时,有五根筷子,每根筷子只对其相邻的两位哲学家是共享的,因此这五根筷子可以看做是五种不同的临界资源(不是一种资源有5个,因为每根筷子只能被固定编号的哲学家使用)。并对五根筷子分别编号为0~4,其中第i号哲学家左边的筷子编号为i,则其右边的筷子编号就应该为(i + 1) % 5。
因为筷子是临界资源,因此当一个线程在使用某根筷子的时候,应该给这根筷子加锁,使其不能被其他进程使用。
根据以上分析,可以使用pthread_create函数创建五个线程,可以使用pthread_mutex_t chops[5]表示有五根筷子,五个不同的临界资源,并用pthread_mutex_init(&chops[i], NULL);来初始化他们。
#define N 5
#define LEFT (i - 1) % N
#define RIGHT (i + 1) % N
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef struct
{ /* 定义结构型信号量 */
int value;
struct PCB *list;
} semaphore;
int state[N];
semaphore mutex = 1; /* 互斥进入临界区 */
semaphore s[N]; /* 每位哲学家一个信号量 */
void philosopher(int i)
{
while (1)
{
think(); /* 哲学家在思考问题 */
take_chopstick(i); /* 拿到两根筷子或者等待 */
eat(); /* 进餐 */
put_chopstick(i); /* 把筷子放回原处 */
}
}
void take_chopstick(int i)
{
P(mutex);
state[i] = HUNGRY;//1
test(i); /* 试图拿两根筷子 */
V(mutex);
P(s[i]);
}
void put_chopstick(int i)
{
P(mutex);
state[i] = THINKING;//0
test(LEFT); /* 查看左邻,现在能否进餐 */
test(RIGHT); /* 查看右邻,现在能否进餐 */
V(mutex);
}
void test(int i)
{
if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING)
{
//EATING = 2
state[i] = EATING;
V(s[i]);
}
}
这段伪代码的思路很明确,这个函数代表的是一个哲学家的活动,可以将其创建为五个不同的线程代表五位不同的哲学家
。每位哲学家先思考,当某位哲学家饥饿的时候,就拿起他左边的筷子,然后拿起他右边的筷子,然后进餐,然后放下他左右的筷子并进行思考。因为筷子是临界资源
,所以当一位哲学家拿起他左右的筷子的时候,就会对他左右的筷子进行加锁,使其他的哲学家不能使用,当该哲学家进餐完毕后,放下了筷子,才对资源解锁,从而使其他的哲学家可以使用。
这个过程看似没什么问题,但是当你仔细分析之后,你会发现这里面有一个很严重的问题,就是死锁,就是每个线程都等待其他线程释放资源从而被唤醒
,从而每个线程陷入了无限等待的状态。在哲学家就餐问题中,一种出现死锁的情况就是,假设一开始每位哲学家都拿起其左边的筷子,然后每位哲学家又都尝试去拿起其右边的筷子,这个时候由于每根筷子都已经被占用,因此每位哲学家都不能拿起其右边的筷子,只能等待筷子被其他哲学家释放。由此五个线程都等待被其他进程唤醒,因此就陷入了死锁。
进程同步
1 并发进程对公有资源的竞争
引起间接制约
,使得各并发进程互斥执行 -->源于资源共享
其他制约关系影响执行速度的例子
- 计算进程和打印进程共同使用同一缓冲区Buf
- 计算进程把计算结果放入
- Buf中打印进程则打印 Buf中的数据
不采取制约措施,两个进程的执行起始时间和执行速度都是彼此独立的
PC :
A: local Buf
Repeat
Buf← Buf
Until Buf=空
计算
得到计算结果
Buf ← 计算结果
Goto A
PP:
B: local Pri
Repeat
Pri ← Buf
Until Pri ≠ 空
打印 Buf中的数据
清除 Buf中的数据
Goto B
假定进程PC和PP对公有缓冲区Buf已经采取互斥措施
反复测试语句对CPU时间造成极大的浪费
-
原因:进程PC和PP的执行互相制约所引起的。PC的输出结果是PP的执行条件,
反过来,PP的执行结果也是PC的执行条件。
-
这种现象在操作系统和用户进程中大量存在,不同于互斥,进程互斥时执行顺序可以是任意的。
2 ※一组在异步环境下的并发进程,各自的执行结果互为对方的执行条件,
从而限制各进程的执行速度的过程称为并发进程间的直接制约
。 -->源于进程合作
异步环境:各起始时间的随机性和执行速度的独立性
解决方法:直接制约的进程,互相给对方进程,发送执行条件已经具备的信号
- 只要收到了制约进程发来的信号便开始执行
- 在未收到制约进程发来的信号时便进入等待状态
- 被制约进程省去对执行条件的测试
- 方法最为简单和直观
把异步环境下的一组并发进程,因
直接制约
而互相发送消息,从而进行互相合作、互相等待,
使得各进程按一定的速度执行的过程
称为进程间的同步。具有同步关系的一组并发进程称为
合作进程
,合作进程间互相发送的信号称为消息或事件
。
对一个消息或事件赋以唯一的消息名,用过程wait(消息名)
表示进程等待合作进程发来的消息
用过程signal(消息名)表示向合作进程发送消息
。
利用过程wait和signal描述上例中的同步关系:
(1) 设消息名Bufempty表示Buf空,消息名Buffull表示Buf中装满了数据。
(2) 初始化Bufempty=true,Buffull=false 。
PC:
A: wait(Bufempty) //等待Buf为空
计算
Buf ← 计算结果 //Buf进行赋值
Bufempty ← false //表示Buf非空
signal(Buffull) //表示buf中装满了数据
Goto A
PP:
B: wait(Buffull)//等待buf装满了数据
打印Buf中的数据
清除Buf中的数据
Buffull ← false//表示buf清空
signal(Bufempty)//表示buf空
Goto B
过程wait的功能是等待到消息名为true的进程继续执行,
而signal的功能则是向合作进程发送合作进程所需要的消息名,并将其值置为true
私用信号量
用信号量的方法实现进程的同步
把各进程之间发送的消息作为信号量看待,这里的信号量只与制约进程及被制约进程有关
,而不是与整组并发进程有关。因此,称该信号量为私用信号量(Private Semaphore)
一个进程Pi的私用信号量Semi,是从制约进程发送来的,进程Pi的执行条件所需要的消息。
与私用信号量相对应,称互斥时使用的信号量为公用信号量。
用P、V原语操作实现同步
- 为各并发进程设置私用信号量
- 为私用信号量赋初值
- 利用P,V原语和私用信号量规定各进程的执行顺序
缓冲区队列:
例:设进程PA和PB通过缓冲区队列,传递数据发送进程PA 和接收进程PB满足如下条件:
(1) 在PA至少送一块数据入一个缓冲区之前,PB不可能从缓冲区中取出数据;
(2) PA往缓冲队列发送数据时,至少有一个缓冲区是空的;
(3) 由PA发送的数据块在缓冲队列中按先进先出(FIFO)方式排列。
进程PA调用的过程deposit(data)和进程PB调用的过程remove(data)必须同步执行
过程 deposit(data)的执行结果是过程remove(data)的执行条件
当缓冲队列全部装满数据时,remove(data)的执行结果又是deposit(data)的执行条件。
※描述发送过程deposit(data)和接收过程remove(data):
(1) 设Bufempty为进程PA的私用信号量,Buffull 为进程PB的私用信号量;
(2) 令Bufempty的初始值为n(n为缓冲队列的缓冲区个数),Buffull 的初始值为0;
PA: deposit(data): begin local x //定位开始位置 P(Bufempty); //检查缓冲区队列缓冲区个数(还有没有空位) -1 按FIFO(先进先出)方式选择一个空缓冲区Buf(x); Buf(x)← data //数据上列 Buf(x)置满标记 V(Buffull)//Buffull+1 end PB: remove(data): Begin local x P (Buffull); //检查Buffull+1 表示要从中取出1个数 按FIFO方式选择一个装满数据的缓冲区Buf(x) data ← Buf(x) //从队列上取出一个值 Buf(x)置空标记 V (Bufempty) //Buffempty+1 end
PS:
局部变量x用来指明缓冲区的区号,给Buf(x)置标志位
是为了便于区别和搜索空缓冲区及非空缓冲区。
需要考虑互斥
生产者消费者问题
把并发进程的同步和互斥问题一般化,可以得到一个抽象的一般模型,即生产者-消费者问题。
把系统中使用某一类资源的进程称为该资源的消费者,
而把释放同类资源的进程称为该资源的生产者。
硬件资源:外设、内存及缓冲区等
软件资源:临界区、数据、例程等
PC进程相当于数据资源的生产者,而PP进程相当于消费者。
把一个长度为n的有界缓冲区(n>0)与一群生产者进程P1,P2,…,Pm和
一群消费者进程C1,C2,…,Ck联系起来(如图所示)。
生产者-消费者问题是一个同步问题。满足条件:
(1) 消费者想接收数据时,有界缓冲区中至少有一个单元是满的;
(2) 生产者想发送数据时,有界缓冲区中至少有一个单元是空的。
有界缓冲区是临界资源,各生产者进程和各消费者进程之间必须互斥执行。
定义信号量
:设公用信号量mutex保证生产者进程和消费者进程之间的互斥,
设信号量avail为生产者进程的私用信号量,信号量full为消费者进程的私用信号量。
赋初值:
- 信号量avail表示有界缓冲区中的空单元数,初值为n;
- 信号量full表示有界缓冲区中非空单元数,初值为0。
- 信号量mutex表示可用有界缓冲区的个数,初值为 1。
deposit(data):
begin
P(avail) //空单元数-1
P(mutex) //有界缓冲区-1
送数据入缓冲区某单元
V(full) //非空单元数+1
V(mutex) //有界缓冲区+1
end
remove(data):
begin
P(full) //非空单元数-1
P(mutex) //有界缓冲区-1
取缓冲区中某单元数据
V(avail) //空单元数+1
V(mutex) //有界缓冲区+1
end
一个过程中包含有几个公用、私用信号量,因此,需要注意P、V原语的操作次序
由于V原语是释放资源的,所以可以以任意次序出现。
P原语如果次序混乱,将会造成进程之间的死锁
进程通信
进程间传送数据
根据通信内容可以划分:
- 控制信息的传送(低级通信)
- 大批量数据传送(高级通信)
进程间同步或互斥,也是使用锁或信号量进行通信来实现
在单机系统中,进程间通信可分为4种形式:
(1) 主从式;
(2) 会话式;
(3) 共享存储区方式;
(4) 消息或邮箱机制。
几种通信方式都可用于大量数据传送。
主从式通信系统
主要特点是:
- 主进程可自由地使用从进程的资源或数据;
- 从进程的动作受主进程的控制;
- 主进程和从进程的关系是固定的。
例:终端控制进程和终端进程。
会话方式
通信进程双方可分别称为使用进程和服务进程
。
其中,使用进程调用服务进程提供的服务。它们具有如下特点:
- 使用进程必须得到服务进程的许可;
- 服务进程对所提供服务的控制由自身完成;
- 使用进程和服务进程在通信时有固定连接关系。
例:用户进程与磁盘管理进程之间的通信
共享存储区方式
不要求数据移动
两个需要互相交换信息的进程通过对同一共享数据区(shared memory)的操作
来达到互相通信的目的。
消息或邮箱机制
无论接收进程是否已准备好接收消息,发送进程都将把所要发送的消息送入缓冲区或邮箱。
消息的一般形式为4个部分组成。即:发送进程名、接收进程名、数据和有关数据的操作。
消息的组成
缓冲区或邮箱痛惜结构
- 只要存在空缓冲区或邮箱,发送进程就可以发送消息。
- 发送进程和接收进程之间无直接连接关系。
- 发送进程和接收进程之间存在缓冲区或邮箱用来存放被传送消息。
消息缓冲机制
发送进程在自己的内存空间设置一个发送区,用发送过程发送
接收进程则在自己的内存空间内设置相应的接收区,用接收过程接收消息
消息缓冲机制中的公用缓冲区必须满足条件:
- 禁止其他进程对该缓冲区消息队列的访问
- 当缓冲区中无消息存在时,接收进程不能接收到任何消息。
发送进程是否可以发送消息,由发送进程是否申请到缓冲区决定。
设公用信号量mutex 为控制对缓冲区访问的互斥信号量,其初值为1 。
设SM为接收进程的私用信号量,表示等待接收的消息个数,其初值为0 。
发送进程调用过程send(m)将消息m 送往缓冲区,
接收进程调用过程Receive(m)将消息m从缓冲区读往自己的数据区,
则Send(m)和Receive(n)可分别描述为:
Send(m):
begin
向系统申请一个消息缓冲区
P(mutex)
将发送区消息m送入新申请的消息缓冲区
把消息缓冲区挂入接收进程的消息队列
V(mutex)
V(SM)
end
Receive(n):
begin
P(SM)
P(mutex)
摘下消息队列中的消息n
将消息n从缓冲区复制到接收区
释放缓冲区
V(mutex)
end
消息队列是按接收进程排列
发送进程无法在Send过程用P操作判断信号量SM
邮箱通信
邮箱通信就是由发送进程申请建立一与接收进程链接的邮箱。发送进程把消息送往邮箱,接收进程从邮箱中取出消息,从而完成进程间信息交换。
设置邮箱的最大好处就是发送进程和接收进程之间没有处理时间上的限制。
邮箱由邮箱头和邮箱体组成。其中邮箱头描述邮箱名称、邮箱大小、邮箱方向以及拥有该邮箱的进程名等。邮箱体主要用来存放消息。
邮箱通信结构:
对于只有一发送进程和一接收进程使用的邮箱,则进程间通信应满足如下条件:
- 发送进程发送消息时,邮箱中至少要有一个空格能存放该消息。
- 接收进程接收消息时,邮箱中至少要有一个消息存在。
设发送进程调用过程 deposit(m)将消息发送到邮箱,接收进程调用过程remove(m)将消息m 从邮箱中取出。
信号量fromnum 为发送进程的私用信号量,信号量mesnum为接收进程的私用信号量。fromnum 的初值为信箱的空格数 n,mesnum 的初值为 0。则 deposit(m)和remove(m)可描述如下:
deposit(m):
begin local x
P(fromnum)
选择空格x
将消息m放入空格x中
置格x的标志为满
V(mesnum)
end
remove(m):
begin local x
P(mesnum)
选择满格x
把满格x中的消息取出放m中
置格x标志为空
V(fromnum)
end
调用过程deposit(m)的进程与调用过程remove(m)的进程之间存在着同步制约关系。