======第二章进程管理======
Posted dearQiHao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了======第二章进程管理======相关的知识,希望对你有一定的参考价值。
在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单位的是进程。操作系统所具有的四大特征也都是基于进程而形成的,并可以从进程的观点来研究操作系统。显然,在操作系统中,进程是一个极其重要的概念。
2.1 进程的基本概念
在未配置OS的系统中,程序的执行方式是顺序执行,也就是说必须在一个程序执行完后,才允许另一个程序执行;在多道程序环境下,则允许多个程序并发执行。程序的这两种执行方式间有着显著的不同。也正是程序并发执行的这种特征,才导致了在操作系统中引入了进程的概念。
2.1.1 程序的顺序执行及其特征
- 程序的顺序执行
- 程序顺序执行时的特征
(1)顺序性:每一操作必须在上一个操作结束之后开始。
(2)封闭性:程序一旦开始执行,其执行结果不受外界因素影响。
(3)可再现性:只要程序执行时的环境和初始条件相同,当程序重复执行时,都将获得相同的结果。
2.1.2 前驱图
前驱图是一个有向无循环图,记为DAG,用于描述进程之间执行的前后关系。图中的每个节点可用于描述一个程序段或进程,乃至一条语句;结点间的有向边则用于表示连个结点之间存在的偏序或前驱关系->。
→={(Pi,Pj) | Pi must complete before Pj may start},如果(Pi,Pj)∈→,可写成 Pi→Pj,称 Pi 是 Pj 的直接前趋,而称 Pj 是 Pi 的直接后继。在前趋图中,把没有前趋的结点称为初始结点(Initial Node),把没有后继的结点称为终止结点(Final Node)。此外,每个结点还具有一个重量(Weight),用于表示该结点所含有的程序量或结点的执行时间。在图 2-1(a)和 2-1(b)中 分别存在着这样的前趋关系:
Ii→Ci→Pi
和
S1→S2→S3
对于图 2-2(a)所示的前趋图,存在下述前趋关系:
P1→P2,P1→P3,P1→P4,P2→P5,P3→P5,P4→P6,P4→P7,P5→P8,P6→P8,P7→P9,P8→P9
或表示为:
P={P1,P2,P3,P4,P5,P6,P7,P8,P9}
→={(P1,P2),(P1,P3),(P1,P4),(P2,P5),(P3,P5),(P4,P6),(P4,P7),(P5,P8),(P6,P8),
(P7,P9),(P8,P9)}
应当注意,前趋图中必须不存在循环,但在图 2-2(b)中却有着下述的前趋关系: S2→S3,S3→S2 显然,这种前趋关系是不可能满足的。
2.1.3 程序的并发执行及其特征
- 程序的并发执行
- 程序并发执行时的特征
(1)间断性:程序在并发执行时,由于他们共享系统资源,以及为完成同一项任务而相互合作,致使这些并发执行的程序之间,形成了相互制约的关系。相互制约将导致并发程序具有“执行——暂停——执行”这种间断性的规律。
(2)失去封闭性:程序在并发执行时,是多个程序共享系统中的各种资源,因而这些资源的状态将由多个程序来改变,致使程序的运行失去了封闭性。这样,某程序在执行时,必然会受到其它程序的影响。
(3)不可再现性:程序在并发执行时,由于失去了封闭性,也将导致其再失去可再现性。
2.1.4 进程的特征与状态
- 进程的特征和定义
(1)结构特征
进程印象:
PCB:通常的程序是不能并发执行的。为使程序(含数据)能独立运行,应为之配置一进程控制块,这个进程控制块叫做PCB。
程序段
数据段
在许多情况下所说的进程,实际上是指进城实体。例如,所谓创建进程,实质上是创建进程实体中的 PCB;而撤消进程,实质上是撤消进程的 PCB。
(2)动态性
进程的实质是进程实体的一次执行过程,因此,动态性是进程的最基本的特征。动态性还表现在:“它由创建而产生,由调度而执行,由撤消而消亡”。可见,进程实体有一定的生命期,而程序则只是一组有序指令的集合,并存放于某种介质上,其本身并不具有运 动的含义,因而是静态的。
(3)并发性
指多个进程实体同存于内存中,且能在一段时间内同时运行。并发性是进程的重要特征,同时也成为 OS 的重要特征。引入进程的目的也正是为了使其进程实体能和其它进程实体并发执行;而程序(没有建立 PCB)是不能并发执行的。
(4)独立性
在传统的 OS 中,独立性是指进程实体是一个能独立运行、独立分配资源和独立接受调 度的基本单位。凡未建立 PCB 的程序都不能作为一个独立的单位参与运行。
(5)异步性
指进程按各自独立的、 不可预知的速度向前推进,或说进程实体按异步方式运行。进程的定义:
(1)进程是程序的一次执行。
(2) 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
(3) 进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
在引入了进程实体的概念后,我们可以把传统 OS 中的进程定义为:“进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位”。
- 进程的三种基本状态
(1)就绪状态
当进程已分配到除 CPU 以外的所有必要资源后,只要再获得 CPU,便可立即执行,进程这时的状态称为就绪状。在一个系统中处于就绪状态的进程可能有个,通常将它们排成一个队列,称为就绪队列。
(2)执行状态
进程已获得 CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态;在多处理机系统中,则有多个进程处于执行状态。
(3)阻塞状态
正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态, 亦即进程的执行受到阻塞,把这种暂停状态称为阻塞状态,有时也称为等待状态或封锁状态。
致使进程阻塞的典型事件有:
请求 I/O,申请缓冲空间等。
通常将这种处于阻塞状态的进程也排成一个队列。有的系统则根据阻塞原因的不同而把处于阻塞状态的进程排成多个队列。
处于就绪状态的进程,在调度程序为之分配了处理机之后,该进程便可执行,相应地,它就由就绪状态转变为执行状态。正在执行的进程也称为当前进程,如果因分配给它的时间片已完而被暂停执行时,该进程便由执行状态又回复到就绪状态;
如果因发生某事件而使进程的执行受阻(例如,进程请求访问某临界资源,而该
资源正被其它进程访问时),使之无法继续执行,该进 程将由执行状态转变为阻塞状态。图 2-5 示出了进程的三种基本状态以及各状态之间的转换关系。
3. 挂起状态
1) 引入挂起状态的原因
在不少系统中进程只有上述三种状态,但在另一些系统中,又增加了一些新状态,最 重要的是挂起状态。引入挂起状态的原因有:
(1) 终端用户的请求。当终端用户在自己的程序运行期间发现有可疑问题时,希望暂时使自己的程序静止下来。亦即,使正在执行的进程暂停执行;若此时用户进程正处于就绪状态而未执行,则该进程暂不接受调度,以便用户研究其执行情况或对程序进行修改。我 们把这种静止状态称为挂起状态。
(2) 父进程请求。有时父进程希望挂起自己的某个子进程,以便考查和修改该子进程,或者协调各子进程间的活动。
(3) 负荷调节的需要。当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的进程挂起,以保证系统能正常运行。
(4) 操作系统的需要。操作系统有时希望挂起某些进程,以便检查运行中的资源使用情况或进行记账。
2)进程状态的转换
在引入挂起状态后,又将增加从挂起状态到非挂起状态的转换;或者相反。可有以下几种情况:
(1) 活动就绪→静止就绪。当进程处于未被挂起的就绪状态时,称此为活动就绪状态,表示为 Readya。当用挂起原语 Suspend 将该进程挂起后,该进程便转变为静止就绪状态,表示为 Readys,处于 Readys 状态的进程不再被调度执行。
(2) 活动阻塞→静止阻塞。当进程处于未被挂起的阻塞状态时,称它是处于活动阻塞状 态,表示为 Blockeda。当用 Suspend 原语将它挂 起后,进程便转变为静止阻塞状态,表示为Blockeds。处于该状态的进程在其所期待的事件 出现后,将从静止阻塞变为静止就绪。
(3) 静止就绪→活动就绪。处于 Readys 状 态的进程,若用激活原语 Active 激活后,该进 程将转变为 Readya 状态。
(4) 静止阻塞→活动阻塞。处于 Blockeds 状 态的进程,若用激活原语 Active 激活后,该进 程将转变为 Blockeda 状态。图 2-6 示出了具有挂起状态的进程状态图。
- 创建状态和终止状态
1)创建状态
先为一个新进程创建PCB,并填写必要的管理信息;
其次,把该进程转入就绪状态并插入就绪队列之中;
当一个新进程被创建时,系统已为其分配了 PCB,填写了进程标识等信息,但由于该进程所必需的资源或其它信息, 如主存资源尚未分配等,一般而言,此时的进程已拥有了自己的 PCB,但进程自身还未进入主存,即创建工作尚未完成,进程还不能被调度运行,其所处的状态就是创建状态。
引入创建状态,是为了保证进程的调度必须在创建工作完成后进行,以确保对进程控 制块操作的完整性。同时,创建状态的引入,也增加了管理的灵活性,操作系统可以根据系统性能或主存容量的限制,推迟创建状态进程的提交。对于处于创建状态的进程,获得了其所必需的资源,以及对其PCB初始化工作完成后,进程状态便可由创建状态转入就绪状态。
2)终止状态
进程的终止也要通过两个步骤:首先等待操作系统进行善后处理,然后将其 PCB 清零, 并将 PCB 空间返还系统。当一个进程到达了自然结束点,或是出现了无法克服的错误,或 是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止状态。进入终止态的进程以后不能再执行,但在操作系统中依然保留一个记录,其中保存状态码和一些计时统计数据,供其它进程收集。一旦其它进程完成了对终止状态进程的信息提取之后,操作系统将删除该进程。
图 2-7 示出了增加了创建状态和终止状态后,进程的三种基本状态及转换图衍变为五种状态及转换关系图。
图 2-8 示出了增加了创建状态和终止状态后,具有挂起状态的进程状态及转换图。
如图 2-8 所示,引进创建和终止状态后,在进程状态转换时,相比较图 2-7 所示的进程五状态转换而言,需要增加考虑下面的几种情况。
(1) NULL→创建:一个新进程产生时,该进程处于创建状态。
(2) 创建→活动就绪:在当前系统的性能和内存的容量均允许的情况下,完成对进程创 建的必要操作后,相应的系统进程将进程的状态转换为活动就绪状态。
(3) 创建→静止就绪:考虑到系统当前资源状况和性能要求,并不分配给新建进程所需资源,主要是主存资源,相应的系统进程将进程状态转为静止就绪状态,对换到外存,不再参与调度,此时进程创建工作尚未完成。
(4) 执行→终止:当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,进程即进终止状态。
2.1.5 进程控制块
- 进程控制块的作用
为了描述和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块 PCB,PCB是进程实体的一部分,是操作系统中最重要的记录型数据结
构。PCB 中记录了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。进程控制块的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程。或者说,OS 是根据 PCB 来对并发执行的进程进行控制和管理的。例如,当 OS 要调度某进程执行时,要从该进程的 PCB中查出其现行状态及优先级;在调度到某进程后,要根据其 PCB 中所保存的处理机状态信 息,设置该进程恢复运行的现场,并根据其 PCB 中的程序和数据的内存始址,找到其程序和数据;进程在执行过程中,当需要和与之合作的进程实现同步、通信或访问文件时,也都需要访问 PCB;当进程由于某种原因而暂停执行时,又须将其断点的处理机环境保存在 PCB 中。可见,在进程的整个生命期中,系统总是通过 PCB 对进程进行控制的,亦即,系统是根据进程的 PCB 而不是任何别的什么而感知到该进程的存在的。所以说,PCB 是进程
存在的惟一标志。
当系统创建一个新进程时,就为它建立了一个 PCB;进程结束时又回收其 PCB,进程于是也随之消亡。PCB 可以被操作系统中的多个模块读或修改,如被调度程序、资源分配程序、中断处理程序以及监督和分析程序等读或修改。因为 PCB 经常被系统访问,尤其是被运行频率很高的进程及分派程序访问,故 PCB 应常驻内存。系统将所有的 PCB 组织成若干个链表(或队列),存放在操作系统中专门开辟的 PCB 区内。例如在 Linux 系统中用task_struct 数据结构来描述每个进程的进程控制块,在 Windows 操作系统中则使用一个执行体进程块(EPROCESS)来表示进程对象的基本属性。 - 进程控制块中的信息
1)进程标识符:进程标识符用于唯一地标识一个进程。一个进程通常有两种标识符。
(1)内部标识符:在所有的操作系统中,都为每一个进程赋予了一个唯一的数字标识符,它通常是一个进程的序号。设置内部标识符的主要作用是为了方便系统使用。
(2)外部标识符:由创建者提供,通常是由字母数字组成,往往是由用户在访问该进程时使用。为了描述进程的家族关系,应设置 父进程标识以及 子进程标识。此外还可以设置用户标识,已指示拥有该进程的用户。
2)处理机状态
处理机状态信息主要是由处理机的各种寄存器中的内容组成的。处理机在运行时,许多信息都放在寄存器中。当处理机被中断时,所有这些信息都必须保存在 PCB 中,以便在 该进程重新执行时,能从断点继续执行。
这些寄存器包括:
① 通用寄存器,又称为用户可视寄存器,它们是用户程序可以访问的,用于暂存信息。
② 指令计数器,其中存放了要访问的下一条指令的地址;
③ 程序状态字 PSW,其中含有状态信息,如条件码、执行方式、中 断屏蔽标志等;④ 用户栈指针,每个用户进程都有一个或若干个与之相关的系统栈,用于存放过程和系统调用参数及调用地址,栈指针指向该栈的栈顶。
3)进程调度信息
在 PCB 中还存放一些与进程调度和进程对换有关的信息,包括:
① 进程状态,指明进程的当前状态,作为进程调度和对换时的依据;
② 进程优先级,用于描述进程使用处理机的优先级别的一个整数,优先级高的进程应优先获得处理机;
③ 进程调度所需的其它信息,它们与所采用的进程调度算法有关,比如,进程已等待 CPU 的时间总和、进程已执行的时
间总和等;
④ 事件,指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
4)进程控制信息
① 程序和数据的地址,指进程的程序和数据所在的内存或外存地(首)址,以便再调度到该进程执行时,能从 PCB 中找到其程序和数据;
② 进程同步和通信 机制,指实现进程同步和进程通信时必需的机制,如消息队列指针、信号量等,它们可能 全部或部分地放在 PCB 中;
③ 资源清单,即一张列出了除 CPU 以外的、进程所需的全部资源及已经分配到该进程的资源的清单;
④ 链接指针,它给出了本进程(PCB)所在队列中的下一个进程的 PCB 的首地址。
- 进程控制块的组织方式
1)链式方式
把具有同一状态的 PCB,用其中的链接字链接成一个队列。这样,可以形成就绪队列、若干个阻塞队列和空白队列等。对其中的就绪队列常按进程优先级的高低排列,把优先级高的进程的 PCB 排在队列前面。此外,也可根据阻塞原因的不
同而把处于阻塞状态的进程的 PCB 排成等待 I/O 操作完成的队列和等待分配内存 的队列等。图 2-9 示出了一种链接队列的 组织方式。
2)索引方式
系统根据所有进程的状态建立几张索引表。例如,就绪索引表、阻塞索引表等,并把各索引表在内存的首地址记录在内存的一些专用单元中。在每个索引表的表目中,记录具 有相应状态的某个 PCB 在 PCB 表中的地址。
图 2-10 示出了索引方式的 PCB 组织。
2.2 进程控制
进程控制是进程管理中最基本的功能。它用于创建一个新进程,终止一个已完成的进程,或终止一个因出现某事件而使其无法运行下去的进程,还可负责进程运行中的状态转换。进程控制一般是由 OS的内核中的原语来实现的。
原语是由若干条指令组成的,用于完成一定功能的一个过程。它与一般过程的区别在于:它们是“原子操作”。所谓原子操作,是指一个操作中的所有动作要么全做,要么全不做。换言之,它是一个不可分割的基本单位,因此,在执行过程中不允许被中断。原子操作在管态下执行,常驻内存。 原语的作用是为了实现进程的通信和控制,系统对进程的控制如不使用原语,就会造成其状态的不确定性,从而达不到进程控制的目的。
2.2.1 进程的创建
-
进程图
进程图是用于描述一个进程的家族关系的有向树,如图 2-11 所示。图中的结点(圆圈)代表进程。在进程 D 创建了进程 I 之后,称 D 是 I 的父进程,I 是 D 的子进程。 这里可用一条由父进程指向子进程的有向边来描述它们之间的父子关系。创建父进程的进程称为祖先进程,这样便形成了一棵进程树,把树的根结 点作为进程家族的祖先。
了解进程间的这种关系是十分重要的。因为子进程可以继承父进程所拥有的资源,例如,继承父进程打开的文件,继承父进程所分配到的缓冲区等。当子进程被撤消时,应将其从父进程那里获得的资源归还给父进程。此外,在撤消父进程时,也必须同时撤消其所有的子进程。为了标识进程之间的家族关系,在 PCB 中都设置了家族关系表项,以标明自己的父进程及所有的子进程。 -
引起创建进程的事件
(1)用户登录
(2)作业调度。在批处理系统中,当作业调度程序按一定的算法调度到某作业时,便将该作业装入内存,为它分配必要的资源,并立即为它创建进程,再插入就绪队列中。
(3)提供服务。当运行中的用户程序提出某种请求后,系统将专门创建一个进程来提供 用户所需要的服务
(4)应用请求。在上述三种情况下,都是由系统内核为它创建一个新进程;而第 4 类事 件则是基于应用进程的需求,由它自己创建一个新进程,以便使新进程以并发运行方式完成特定任务。 -
进程的创建
一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语Creat( )按下述步 骤创建一个新进程。
(1) 申请空白 PCB。
(2) 为新进程分配资源。
(3) 初始化进程控制块。PCB 的初始化包括:
① 初始化标识信息,将系统分配的标识符和父进程标识符填入新 PCB 中;
② 初始化处理机状态信息,使程序计数器指向程序的入口地址,使栈指针指向栈顶;
③ 初始化处理机控制信息,将进程的状态设置为就绪状态或 静止就绪状态,对于优先级,通常是将它设置为最低优先级,除非用户以显式方式提出高优先级要求。
(4) 将新进程插入就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。
2.2.2 进程的终止
- 引起进程终止的事件
(1)正常结束
(2)异常结束
(1) 越界错误。这是指程序所访问的存储区已越出该进程的区域。
(2) 保护错。这是指进程试图去访问一个不允许访问的资源或文件,或者以不适当的方式进行访问,例如,进程试图去写一个只读文件。
(3) 非法指令。这是指程序试图去执行一条不存在的指令。出现该错误的原因,可能是程序错误地转移到数据区,把数据当成了指令。
(4) 特权指令错。这是指用户进程试图去执行一条只允许 OS 执行的指令。
(5) 运行超时。这是指进程的执行时间超过了指定的最大值。
(6) 等待超时。这是指进程等待某事件的时间超过了规定的最大值。
(7) 算术运算错。这是指进程试图去执行一个被禁止的运算,例如被 0 除。
(8) I/O 故障。这是指在 I/O 过程中发生了错误等。
- 进程的终止过程
如果系统中发生了上述要求终止进程的某事件,OS 便调用进程终止原语,按下述过程 去终止指定的进程。
(1) 根据被终止进程的标识符,从 PCB 集合中检索出该进程的 PCB,从中读出该进程的状态。
(2) 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度。
(3) 若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防它们成为不可控的进程。
(4) 将被终止进程所拥有的全部资源,或者归还给其父进程,或者归还给系统。
(5) 将被终止进程(PCB)从所在队列(或链表)中移出,等待其他程序来搜集信息。
2.2.3 进程的阻塞与唤醒
- 引起进程阻塞和唤醒的事件
(1)请求系统服务。当正在执行的进程请求操作系统提供服务时,由于某种原因,操作系统并不立即满足该进程的要求时,该进程只能转变为阻塞状态来等待。
(2)请求某种操作。当进程启动某种操作后,如果该进程必须在该操作完成之后才能继续执行,则必须先使该进程阻塞,以等待该操作完成。
(3)新数据尚未到达。对于相互合作的进程,如果其中一个进程需要先获得另一(合作)进程提供的数据后才能 对数据进行处理,则只要其所需数据尚未到达,该进程只有(等待)阻塞。
(4)无新工作可做。系统往往设置一些具有某特定功能的系统进程,每当这种进程完成任务后,便把自己阻塞起来以等待新任务到来。
- 进程阻塞过程
正在执行的进程,当发现上述某事件时,由于无法继续执行,于是进程便通过调用阻 塞原语 block 把自己阻塞。可见,进程的阻塞是进程自身的一种主动行为。
进入 block 过程后,由于此时该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状 态由“执行”改为“阻塞”,并将 PCB 插入阻塞队列。如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列。
最后,转调度程序进行重新调度,将处理机分配给另一就绪进程并进行切换,保留被阻塞进程的处理机状态(在 PCB 中),再按新进程的 PCB 中的处理机状态设置 CPU 的环境。
- 进程唤醒过程
当被阻塞进程所期待的事件出现时,如 I/O 完成或其所期待的数据已经到达,则由关进程调用唤醒原语 wakeup( ),将等待该事件的进程唤醒。
唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其 PCB 中的现行状态由阻塞改为就绪,然后再将该 PCB 插入到就绪队列中。
应当指出,block 原语和 wakeup 原语是一对作用刚好相反的原语。因此,如果在某进程中调用了阻塞原语,则必须在与之相合作的另一进程中或其他相关的进程中安排唤醒原语,以能唤醒阻塞进程;否则,被阻塞进程将会因不能被唤醒而长久地处于阻塞状态,从 而再无机会继续运行。
2.2.4 进程的挂起与激活
- 进程的挂起
当出现了引起进程挂起的事件时,比如,用户进程请求将自己挂起,或父进程请求将自己的某个子进程挂起,系统将利用挂起原语 suspend( )将指定进程或处于阻塞状态的进程挂起。
挂起原语的执行过程是:首先检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪; 对于活动阻塞状态的进程,则将之改为静止阻塞。为了方便用户或父进程考查该进程的运行情况而把该进程的 PCB 复制到某指定的内存区域。最后,若被挂起的进程正在执行,则转向调度程序重新调度。
- 进程的激活过程
当发生激活进程的事件时,例如,父进程或用户进程请求激活指定进程,若该进程驻留在外存而内存中已有足够的空间时,则可将在外存上处于静止就绪状态的该进程换入内存。这时,系统将利用激活原语 active( )将指定进程激活。
激活原语先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;若为静止阻塞,便将之改为活动阻塞。假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,应检查是否要进行重新调度,即由调度程序将被激活进程与当前进程进行优先级的比较,如果被激活进程的优先级更低,就不必重新调度;否则,立即剥夺当前进程的运行,把处理机分配给刚被激活的进程。
进程同步
进程同步的主要任务是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合作,从而使程序的执行具有可再现性。
-
两种形式的制约
(1) 间接相互制约关系。同处于一个系统中的进程,通常都共享着某种系统资源,如共 享 CPU、共享 I/O 设备等。所谓间接相互制约即源于这种资源共享,例如,有两个进程 A 和 B,如果在 A 进程提出打印请求时,系统已将惟一的一台打印机分配给了进程 B,则此时进程 A 只能阻塞;一旦进程 B 将打印机释放,则 A 进程才能由阻塞改为就绪状态。
(2) 直接相互制约关系。这种制约主要源于进程间的合作。例如,有一输入进程 A 通过 单缓冲向进程 B 提供数据。当该缓冲空时,计算进程因不能获得所需数据而阻塞,而当进程 A 把数据输入缓冲区后,便将进程 B 唤醒;反之,当缓冲区已满时,进程 A 因不能再向缓冲区投放数据而阻塞,当进程 B 将缓冲区数据取走后便可唤醒 A。 -
临界资源
生产者-消费者问题是一个著名的进程同步问题。它描述的是:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有 n 个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。
尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
我们可利用一个数组来表示上述的具有 n 个(0,1,…,n-1)缓冲区的缓冲池。
用输入指针 in 来指示下一个可投放产品的缓冲区,每当生产者进程生产并投放一个产品后,输入 指针加 1;
用一个输出指针 out 来指示下一个可从中获取产品的缓冲区,每当消费者进程取 走一个产品后,输出指针加 1。
由于这里的缓冲池是组织成循环缓冲的,故应把输入指针加 1 表示成 in:= (in+1)mod n; 输出指针加 1 表示成 out:= (out+1) mod n。当 (in+1) mod n=out时表示缓冲池满;而 in=out 则表示缓冲池空。此外,还引入了一个整型变量 counter,其初始值为 0。每当生产者进程向缓冲池中投放一个产品后,counter 加 1;反之,每当消费 者进程从中取走一个产品时,使 counter 减 1。生产者和消费者两进程共享下面的变量:Var n,integer; type item=…; var buffer: array[0,1,…,n-1] of item; in,out: 0,1,…,n-1; counter: 0,1,…,n;
指针 in 和 out 初始化为 1。在生产者和消费者进程的描述中,noop 是一条空操作指令, while condition do no-op 语句表示重复的测试条件(condication),重复测试应进行到该条件变为 false(假),即到该条件不成立时为止。在生产者进程中使用一局部变量 nextp,用于暂时存放每次刚生产出来的产品;而在消费者进程中,则使用一个局部变量 nextc,用于存放每次要消费的产品。
producer: repeat
...
produce an item in nextp;
...
while counter=n do no-op;
buffer[in]:=nextp;
in:=in+1 mod n;
counter:=counter+1;
until false;
consumer: repeat
while counter=0 do no-op;
nextc:=buffer[out];
out:=(out+1) mod n;
counter:=counter-1;
consumer the item in nextc;
until false;
虽然上面的生产者程序和消费者程序在分别看时都是正确的,而且两者在顺序执行时其结果也会是正确的,但若并发执行时就会出现差错,问题就在于这两个进程共享变量 counter。生产者对它做加 1 操作,消费者对它做减 1 操作,这两个操作在用机器语言实现时, 常可用下面的形式描述:
register1:=counter; register2:=counter;
register1:=register1+1; register2:=register2-1;
counter:=register1; counter:=register2;
假设 counter 的当前值是 5。如果生产者进程先执行左列的三条机器语言语句,然后消 费者进程再执行右列的三条语句,则最后共享变量 counter 的值仍为 5; 反之,如果让消费 者进程先执行右列的三条语句,然后再让生产者进程执行左列的三条语句,则 counter 值也 还是 5,但是,如果按下述顺序执行:
register1:=counter; (register1=5)
register1:=register1+1; (register1=6)
register2:=counter; (register2=5)
register2:=register2-1; (register2=4)
counter:=register1; (counter=6)
counter:=register2; (counter=4)
正确的 counter 值应当是 5,但现在是 4。倘若再将两段程序中各语句
交叉执行的顺序改变,将可看到又可能得到 counter=6 的答案,这表明程序的执行已经失去了再现性。为了预防产生这种错误,解决此问题的关键是应把变量 counter 作为临界资源处理,令生产者进程和消费者进程互斥地访问变量 counter。
- 临界区
把在每个进程中访问临界资源的那段代码称为临界区。若能保证诸进程互斥地进入自己的临界区,便可实现诸进程对临界资源的互斥访问。
为此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看它是否正被访问。
如果此刻该临界资源未被访问,进程便可进入临界区对该资源进行访问,并设置它正被访问的标志;
如果此刻该临界资源正被某进程访问,则本进程不能进入临界区。
因此,必须在临界区前面增加一段用于进行上述检查的代码,把这段代码称为进入区。相应地,在临界区后面也要加上一段称为退出区的代码,用于将临界区正被访问的标志恢复为未被访问的标志。进程中除上述进入区、临界区及退出区之外的其它部分的代 码,在这里都称为剩余区。这样,可把一个访问临界资源的循环进程描述如下:
4. 同步机制应遵循的规则
为实现进程互斥地进入自已的临界区,可用软件方法,更多的是在系统中设置专门的 同步机构来协调各进程间的运行。所有同步机制都应遵循下述四条准则:
(1)== 空闲让进==。当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效地利用临界资源。
(2) 忙则等待。当已有进程进入临界区时,表明临界资源正在被访问,因而其它试图进 入临界区的进程必须等待,以保证对临界资源的互斥访问。
(3) 有限等待。对要求访问临界资源的进程,应保证在有限时间内能进入自己的临界区, 以免陷入“死等”状态。
(4) 让权等待。当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙 等”状态。
2.3.2 信号量机制
-
整型信号量
最初由 Dijkstra 把整型信号量定义为一个用于表示资源数目的整型量 S它与一般整型 量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation) wait(S)和 signal(S)来访问。很长时间以来,这两个操作一直被分别称为 P、V 操作。Wait(S)和 signal(S)操作可描述为:wait(S): while S<=0 do no-op; S:=S-1; signal(S): S:=S+1
wait(S)和 signal(S)是两个原子操作,因此,它们在执行时是不可中断的。当一个进程在修改某信号量时,没有其他进程可同时对该信号量进行修改。此外,在 wait 操作中,对 S 值的测试和做 S:=S-1 操作时都不可中断。
- 记录新型号量
在整型信号量机制中的 wait 操作,只要是信号量 S≤0,就会不断地测试。因此,该机 制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。但在采取了“让权等待”的策略后,又会出 现多个进程等待访问同一临界资源的情况。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量 value 外,还应增加一个进程链表指针 L,用于链接上述的所有等待进程。记录型信号量是由于它采用了记录型的数据结构而得名的。它所包含的上述两个数据项可描述为:
在记录型信号量机制中,S.value 的初值表示系统中某类资源的数目,因而又称为资源信号量。对它的每次 wait 操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源数减少一个,因此描述为 S.value:=S.value-1;当 S.value<0 时,表示该类资源已分配完毕,因此进程应调用 block 原语,进行自我阻塞,放弃处理机,并插入到信号量链表 S.L 中。
可见,该机制遵循了“让权等待”准则。此时 S.value 的绝对值表示在该信号量链 表中已阻塞进程的数目。对信号量的每次 signal 操作,表示执行进程释放一个单位资源,使系统中可供分配的该类资源数增加一个,故 S.value:=S.value+1 操作表示资源数目加 1。若加 1 后仍是 S.value≤0,则表示在该信号量链表中,仍有等待该资源的进程被阻塞,故还应 调用 wakeup 原语,将 S.L 链表中的第一个等待进程唤醒。如果 S.value 的初值为 1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量,用于进程互斥。
- AND型信号量
上述的进程互斥问题,是针对各进程之间只共享一个临界资源而言的。在有些应用场 合,是一个进程需要先获得两个或更多的共享资源后方能执行其任务。假定现有两个进程 A 和 B,他们都要求访问共享数据 D 和 E。当然,共享数据都应作为临界资源。为此,可为这两个数据分别设置用于互斥的信号量 Dmutex 和Emutex,并令它们的初值都是 1。相应地,在两个进程中都要包含两个对 Dmutex 和 Emutex 的操作,即
最后,进程 A 和 B 处于僵持状态。在无外力作用下,两者都将无法从僵持状态中解脱 出来。我们称此时的进程 A 和 B 已进入死锁状态。显然,当进程同时要求的共享资源愈多时,发生进程死锁的可能性也就愈大。
AND 同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部 地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源也不分配给它。
即对若干个临界资源的分配,采取原子操作方式:要么把它所请求的资源全部分配到进程,要么一个也不分配。由死锁理论可知,这样就可避免上述死锁情况的发生。为此,在 wait 操作中,增加了一个“AND”条件,故称为 AND 同步,或称为同时 wait 操作,即 Swait(Simultaneous wait)定义如下:
- 信号量集
在记录型信号量机制中,wait(S)或 signal(S)操作仅能对信号量施以加 1 或减 1 操作,意味着每次只能获得或释放一个单位的临界资源。而当一次需要 N 个某类临界资源时,便要进行 N 次 wait(S)操作,显然这是低效的。此外,在有些情况下,当资源数量低于某一下限 值时,便不予以分配。因而,在每次分配之前,都必须测试该资源的数量,看其是否大于其下限值。基于上述两点,可以对 AND 信号量机制加以扩充,形成一般化的“信号量集” 机制。Swait 操作可描述如下,其中 S 为信号量,d 为需求值,而 t 为下限值。
下面我们讨论一般“信号量集”的几种特殊情况:
(1) Swait(S,d,d)。此时在信号量集中只有一个信号量 S,但允许它每次申请 d 个资 源,当现有资源数少于 d 时,不予分配。
(2) Swait(S,1,1)。此时的信号量集已蜕化为一般的记录型信号量(S>1 时)或互斥信号 量(S=1 时)。 (3) Swait(S,1,0)。这是一种很特殊且很有用的信号量操作。当 S≥1 时,允许多个进程进入某特定区;当 S 变为 0 后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。
2.3.3 信号量的应用
-
利用信号量实现进程互斥
为使多个进程能互斥地访问某临界资源,只须为该资源设置一互斥信号量 mutex,并设其初始值为 1,然后将各进程访问该资源的临界区 CS 置于wait(mutex)和 signal(mutex)操作之间即可。这样,每个欲访问该临界资源的进程在进入临界区之前,都要先对 mutex 执行wait 操作,若该资源此刻未被访问,本次 wait 操作必然成功,进程便可进入自己的临界区,这时若再有其他进程也欲进入自己的临界区,此时由于对 mutex 执行 wait 操作定会失败,因而该进程阻塞,从而保证了该临界资源能被互斥地访问。当访问临界资源的进程退出临 界区后,又应对 mutex 执行 signal 操作,以便释放该临界资源。利用信号量实现进程互斥的进程可描述如下:
在利用信号量机制实现进程互斥时应注意,==wait(mutex)和 signal(mutex)==必须成对地出现。 缺少 wait(mutex)将会导致系统混乱,不能保证对临界资源的互斥访问;而缺少 signal(mutex)将会使临界资源永远不被释放,从而使因等待该资源而阻塞的进程不能被唤醒。 -
利用信号量实现前驱关系
还可利用信号量来描述程序或语句之间的前趋关系。设有两个并发执行的进程 P1 和 P2。 P1 中有语句 S1;P2 中有语句 S2。我们希望在 S1 执行后再执行 S2。为实现这种前趋关系,我们只须使进程 P1 和 P2 共享一个公用信号量 S,并赋予其初值为 0,将 signal(S)操作放在语句 S1 后面;而在 S2 语句前面插入 wait(S)操作,即
在进程 P1 中,用S1;signal(S);
在进程 P2 中,用wait(S);S2;
由于 S 被初始化为 0,这样,若 P2 先执行必定阻塞, 只有在进程 P1 执行完 S1;signal(S);操作后使 S 增为 1 时,P2 进程方能执行语句 S2 成功。同样,我们可以利用信号 量,按照语句间的前趋关系(见图 2-12),写出一个更为复
杂的可并发执行的程序。
图 2-12 示出了一个前趋图,其中 S1,S2,S3,…,S6 是最简单的程序段(只有一条语句)。 为使各程序段能正确执行,应设置若干个初始值为“0”的信号量。如为保证 S1→S2,S1→S3 的前趋关系,应分别设置信号量 a 和 b,同样,为了保证 S2→S4,S2→S5,S3→S6,S4→S6 和 S5→S6,应设置信号量 c,d,e,f,g。
Var a,b,c,d,e,f,g:semaphore: =0,0,0,0,0,0,0;
begin
parbegin
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
2.3.4 管程机制
虽然信号量机制是一种既方便、又有效的进程同步机制,但每个要访问临界资源的进程都必须自备同步操作 wait(S)和 signal(S)。这就使大量的同步操作分散在各个进程中。这不仅给系统的管理带来了麻烦,而且还会因同步操作的使用不当而导致系统死锁。这样, 在解决上述问题的过程中,便产生了一种新的进程同步工具——管程(Monitors)。
- 管程的定义
利用共享数据结构抽象地表示系统中的共享资源,而把对该共享数据结构实施的操作定义为一组过程,如资源的请求和释放过程 request 和 release。进程对共享资源的申请、释 放和其它操作,都是通过这组过程对共享数据结构的操作来实现的,这组过程还可以根据资源的情况,或接受或阻塞进程的访问,确保每次仅有一个进程使用共享资源,这样就可以统一管理对共享资源的所有访问,实现进程互斥。
代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,共同构成了一个操作系统的资源管理模块,我们称之为管程。管程被请求和释放资源的进程所调用。Hansan 为管程所下的定义是:“一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据”。
由上述的定义可知,管程由四部分组成:
① 管程的名称;
② 局部于管程内部的共享数据结构说明;
③ 对该数据结构进行操作的一组过程;
④ 对局部于管程内部的共享数据设置初始值的语句。
图 2-13 是一个管程的示意图。
管程的语法描述如下:
需要指出的是,局部于管程内部的数据结构,仅能被局部于管程内部的过程所访问, 任何管程外的过程都不能访问它;反之,局部于管程内部的过程也仅能访问管程内的数据结构。由此可见,管程相当于围墙,它把共享变量和对它进行操作的若干过程围了起来, 所有进程要访问临界资源时,都必须经过管程(相当于通过围墙的门)才能进入,而管程每次 只准许一个进程进入管程,从而实现了进程互斥。
管程是一种程序设计语言结构成分,它和信号量有同等的表达能力,从语言的角度看, 管程主要有以下特性:
(1) 模块化。管程是一个基本程序单位,可以单独编译。
(2) 抽象数据类型。管程中不仅有数据,而且有对数据的操作。
(3) 信息掩蔽。管程中的数据结构只能被管程中的过程访问,这些过程也是在管程内部
定义的,供管程外的进程调用,而管程中的数据结构以及过程(函数)的具体实现外部不可见。
管程和进程不同,主要体现在以下几个方面:
(1) 虽然二者都定义了数据结构,但进程定义的是私有数据结构 PCB,管程定义的是公共数据结构,如消息队列等;
(2) 二者都存在对各自数据结构上的操作,但进程是由顺序程序执行有关的操作,而管程主要是进行同步操作和初始化操作;
(3) 设置进程的目的在于实现系统的并发性,而管程的设置则是解决共享资源的互斥使 用问题;
(4) 进程通过调用管程中的过程对共享数据结构实行操作,该过程就如通常的子程序一样被调用,因而管程为被动工作方式,进程则为主动工作方式;
(5) 进程之间能并发执行,而管程则不能与其调用者并发;
(6) 进程具有动态性,由“创建”而诞生&以上是关于======第二章进程管理======的主要内容,如果未能解决你的问题,请参考以下文章
(王道408考研操作系统)第二章进程管理-第三节9:读者写者问题
(王道408考研操作系统)第二章进程管理-第三节3:实现进程互斥的硬件方法
(王道408考研操作系统)第二章进程管理-第三节8:经典同步问题之吸烟者问题
(王道408考研操作系统)第二章进程管理-第一节1:进程PCB及其特征