不管是工作面试还是应试考试,进程都是操作系统中非常重要的一环,因为进程是操作系统进行资源分配的基本单位,同时进程也是相对于操作系统来说可以独立运行的基本单位,本文将向大家详细介绍进程的前世今生以及风流韵事。 本文主要内容如下:
我们首先来分析一下为什么操作系统要引入进程这个概念,即进程从哪来?
一般来说,操作系统按照是否支持并发可分为以下两类:
- 早起的计算机大多数都是单道批处理系统,即每一个时刻最多只能有一道用户程序被装入内存中执行、其他用户程序只能等待,这样的机制使得计算机的资源得不到充分利用、系统吞吐率低下。
- 后来的多道程序系统中正式支持多道用户程序的同时执行,使得系统资源的利用率得到了大幅提高。
这两种不同的程序运行机制有着很大的不同,尤其是资源分配方面,在单道批处理系统中,每一时刻运行的用户程序最多只有一个,这时资源很好分配,只需要把系统所有资源都分配给正在运行的那一个用户程序即可。可是多道程序系统呢?同一时刻可能有多个用户程序都在运行,如何合理地将有限的系统资源分配给这些用户程序?以什么单位进行资源的分配?为了解决这一系列的问题,引入了操作系统中进程的概念,所以,在对进程做深入了解之前,我们先介绍一下程序的顺序执行和并发执行,从这两种不同的程序执行方式中寻找进程的来源。
前趋图实际上是一个有向无环图(DAG,Directed Acyclic Graph
),描述的是多个进程的执行顺序,如下图所示:
在前趋图中,每个结点代表一个进程(如上面的4个圆),箭头表示了进程之间的执行顺序,比如上图所示的前趋图表示要求ABCD
四个进程需要按照$A \to B \to C \to D$的顺序进行执行。
前趋图可以使用式子进行抽象表示,比如上图表示的进程间关系可以描述为: $$ A \to B,B\to C,C\to D \space 或\ (A,B),(B,C),(C,D) $$ 注意,前趋图中不能有循环,即回路,否则必然会出现冲突,比如像下图:
具有回路,边1要求A在B之前执行,边2要求B在A之前执行,这显然永远不可能实现,即具有冲突。
程序顺序执行很好理解,就是多个程序按照指定顺序串行进行执行,反映到前趋图上就是:
表示ABCD四个程序会串行顺序进行执行。
我们可以发现程序顺序执行具有以下几个特点:
- 顺序性:处理机严格按照指定的顺序执行
- 封闭性:程序会在封闭的环境下运行,运行时独占系统资源,结果不受其他程序干扰
- 可再现性:只要程序执行时的环境与初始条件相同,不管执行多少次程序,最后的结果一定是一样的
程序顺序执行导致计算机资源利用率过低,解决办法就是引入程序并发执行,但是很明显不是所有程序都可以并发执行,如果两个程序具有前趋关系,这两个程序一定不可能并发执行。
我们举个例子,比如有4个应用程序,每一个应用程序所做的工作如下:
S1:
a:=y+2
S2:
b:=y+4
S3:
c:a+b
S4:
d:=c+b
很明显,在执行S3
之前必须知道a
和b
的值即S1
和S2
必须执行完成,在执行S4
的时候必须知道c和``b
即S3
和S2
必须完成,而S1
和S2
之间没有前趋关系,可以并发执行以提高效率,这样抽象出来前趋图为:
可以看到,S1和S2就可以并发执行,这样可以提高系统的资源利用率。
那么并发执行有什么特点呢?总结一下大概有3条:
-
间断性:比如上面的例子,S1和S2是并发执行的,有可能S1执行了1s就执行完成了,但是S2需要执行3s才能完成,由于S1和S2都完成的前提下S3才能运行,所以会导致S2等待S1执行完成,即S2需要暂停,这就导致了并发程序具有“执行——暂停——执行”这种间断性的活动规律。
-
失去封闭性:当多个程序进行并发执行时,每一个程序都有可能影响系统的资源状态,进而间接影响到其他正在运行的程序的状态,比如S1运行的时候由于运算量太大导致占用系统资源过渡,进而S2没有可用资源,S2就必须暂停等待系统的空闲资源,即S1的运行将S2的状态从运行中影响到等待中。
-
不可再现性:由于并发程序运行时失去了封闭性,所以不能保证程序运行的可再现性,即同一个程序多次执行的结果可能不一致,比如有程序A和程序B并发执行,二者共同操作变量N,A的操作是:
N=N+1;
,B的操作是Printf(N);N=0;
,即:由于并发执行时程序的运行速度受系统资源影响(没有封闭性导致的),所以有可能发生的情况有:
如上图所示,可以看到,由于并发,会产生多种可能的代码执行顺序,进而产生多种执行结果,即程序的运行结果不可再现(不要杠精上线说上图2、3两个执行顺序执行结果一样哦)。
看到这里有的读者可能会有疑问,既然都不能保证运行结果的唯一性,程序的并发执行还有意义吗?是的,我们一定是想让我们的程序在执行的时候可以产生唯一结果、不受其他程序影响、而且具有可再现性,所以我们就需要解决这个问题,聪明的读者已经猜出来如何解决了,是的,就是进程。
本节主要介绍进程的定义。
首先再明确一下引入进程的目的:使程序可以并发执行,对并发执行的程序加以描述和控制。
其次我们我们来看一下进程的组成部分:
可以看到,进程实体(进程映像,简称进程)由程序段、相关的数据段、**进程控制块(PCB
)**三部分组成,程序段、相关的数据段都很好理解,那什么是进程控制块呢?
为了使参与并发执行的每个程序(含数据)都能独立运行,在操作系统中必须为之配置一个专门的数据结构,称之为进程控制块(Process Control Block,PCB
),系统利用PCB
来描述进程的基本情况和活动过程,进而控制和管理进程。
另外提醒一点,一般情况下,所谓的创建进程实际上指的是创建进程实体中的PCB,而撤销进程实际上指的是撤销进程的PCB。
现在我们可以对进程做一个正式的定义了:进程是进程实体的运行过程,是系统进行资源分配和调度的独立单位。
要明确,进程和应用程序是两个截然不同的概念,进程有PCB,而应用程序没有,此外,进程还具有以下特征(附有普通应用程序和进程的区别):
在上一节我们提到进程具有动态性的特点,即进程就有一定的声明周期,那么在进程声明周期的不同阶段进程会处于不同的状态,一般来说,进程的具有两类、五个状态,如下图所示:
进程有三种基本状态:
- 就绪(Ready)状态:进程获取了除CPU资源之外的所有必须资源后的状态(如果系统中有多个进程都处于就绪状态,通常按照一定的优先策略将他们排成一个队列,该队列称为就绪队列)
- 执行(Running)状态:进程已经获取了所有资源(包括CPU资源),当前正在运行。
- 阻塞状态(Block):正在执行的进程由于某些事件(比如I/O请求、申请缓冲区失败等)暂时无法继续执行的状态,也称进程的执行受到阻塞(阻塞状态、等待状态、封锁状态),当有多个进程处于阻塞状态时,也会将这些阻塞状态的进程排成一个或多个阻塞队列。
那么进程的这三种状态时如何转化的呢?一图胜千言,直接看图:
除了以上的三种基本状态,进程还具有一下两种常见的状态:
- 创建状态
- 终止状态
创建进程是一个很复杂的过程,要经历多个步骤:
- 申请空白PCB
- 向PCB填写用于控制和管理进程的信息
- 为进程分配运行时所必须的资源
- 将进程转为就绪状态
- 将进程插入就绪队列
那么这么多步能保证一次立马顺序执行完吗?答案当然是不能,由于系统资源的动态性,创建进程的过程有可能出现暂停、等待,但是此时进程的创建还没有完成,即进程还不能被调度运行,此时的进程处于的状态就是创建状态。
通过创建状态的引入,操作系统可以根据系统性能或主存容量的限制推迟新进程的提交(创建状态),而当进程获取了所有必须资源后,即可由创建状态转入就绪状态。
进程的终止也是一个较为复杂的过程,要经历两步:
- 等待操作系统进行善后处理
- 将PCB清零、PCB空间返还系统
进程进入终止状态后不能再继续执行,但在操作系统中依然保留一个记录,其中保存状态码和一些计时统计数据,供其他进程收集。一旦其他进程完成了对其信息的提取,操作系统即删除该进程,即将PCB清零,将空白PCB返还给操作系统。
下图展示了引入创建状态和终止状态后的进程状态转换图:
在许多操作系统中,还可以对进程执行挂起操作,该操作作用于某个进程时,该进程将被挂起,意味着此时该进程处于静止状态,如果进程正在执行,它将暂停执行,若原本处于就绪状态,则该进程此时暂不接受调度。与挂起操作相对应的是激活操作。
挂起操作主要发生在以下几个场景:
- 终端用户需要
- 父进程的请求
- 负荷调节的需要
- 操作系统的需要
挂起操作一般是通过Suspend原语进行实现的,而激活操作是通过Active原语进行实现的。
引入了挂起和激活操作之后衍生出了以下几种状态:
而这些状态之间的转换关系可用下图表示:
如上图是操作系统控制表的一般结构,此处我们重点介绍一下操作系统所管理的进程表,即进程控制块PCB。
总的来说,进程控制块(Process Control Block)的作用是:使一个在多道程序环境下不能独立运行的程序(含数据)成为一个能独立运行的基本单位、一个能与其他进程并发执行的进程。具体地,可将PCB的作用细分为一下几部分:
从PCB的作用我们可以看出,PCB的的很多功能都是基于PCB中存储的信息才能实现的,那么PCB中都存储有哪些信息呢?
简单来说PCB中主要包括四方面信息:
在一个操作系统中,可能同时存在很多个PCB(成百上千个),操作系统是以何种方式管理、组织这些PCB的?目前常用的组织方式有以下三种:
所谓的进程控制,其实主要指的是以下几部分:
而这些功能一般是借助操作系统内核中的原语来进行实现的,我们先来了解一下操作系统内核。
什么是操作系统内核:现代操作系统一般将OS划分为不同的层次,然后将OS的不同功能别别放置到不同的层次中。通常将一些与硬件紧密相关的模块(如中断处理程序)、各种常用设备的驱动程序、运行频率较高的模块(如时钟管理)都安排在紧靠硬件的软件层中,将他们驻留在内存中,即通常所说的OS内核。
将OS内核部分驻留在内存中主要是出于两个目的:
- 便于对内核进行保护、防止遭受其他软件的破坏
- 提高OS的运行效率
为了防止OS本身及关键数据(如PCB)被其他程序破坏,将处理机的执行状态分为两种:
- 系统态:又称管态,也称内核态,拥有执行一切指令的权限
- 用户态:又称目态,具有较低特权,仅能执行特定的指令
关于操作系统内核的功能我们也需要有一定的了解和认知,这里总结如下图:
在操作系统中,允许一个进程创建另一个进程,被创建的进程是儿子,创建儿子的进程是父亲,而儿子有可创建新进程,这时候就有了孙子,子子孙孙无穷尽也,由此便形成了一个进程的层次结构(类似家族族谱的树形结构)。
进程之间的这种层次结构是十分重要的,比如子进程可以继承父进程的资源,父进程被撤销时一定也会撤销子进程,为了表示这种进程之间的结构关系,在PCB中设置了家族关系表项,以标明自己的父进程和所有的子进程。
注意:windows系统不存在进程之间的父子关系,所有进程都是平等的。
进程图没什么复杂的,就和家谱图一毛一样,主要是为了表示各个进程之间的父子关系,比如:
什么情况下会进行进程的创建呢?大致分为以下四类:
其中黄色的三类是系统内核为用户创建新进程,第四类是用户进程自己创建新进程。
进程创建就四步:
引起进程终止的事件主要有三类,分别如下:
当系统发生了要求终止进程的某事件,OS将调用进程终止原语,按照以下步骤进行进程的终止:
正在执行的进程,如果发生了上述引起进程阻塞的事件,便会通过调用阻塞原语block将自己阻塞(即进程的阻塞是一个主动行为),然后:
- 立即停止执行
- 将进程的现行状态改为阻塞
- 将PCB插入阻塞队列
- 转调度程序,重新进行调度,将处理机分配给另一就绪执行的进程,并进行切换
进程唤醒是通过wakeup原语来进行实现的,过程如下:
- 将待唤醒的进程从阻塞队列中移出
- 将其PCB状态由阻塞改为就绪
- 将其PCB插入就绪队列中
注意:block和wakeup必须成对使用。
当进程中出现了引起进程挂起的事件后,OS将利用挂起原语suspend将指定进程挂起,执行过程如下:
- 检查进程的当前状态,如果处于活动就绪状态,将其置为静止就绪
- 如果处于活动阻塞状态,将其置为静止阻塞
- 为了方便用户或父进程考察该进程的运行情况,将该进程的PCB复制到某指定的内存区域
- 若被挂起的程序正在执行,转向调度程序重新调度
进程的激活是通过激活原语active实现的,过程如下:将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;若为静止阻塞,便将之改为活动阻塞。假如采用的是抢占调度策略,则每当有静止就绪进程被激活而插入就绪队列时,便应检查是否要进行重新调度,即由调度程序将被激活的进程与当前进程两者的优先级进行比较,如果被激活进程的优先级低,就不必重新调度,否则,立即剥夺当前进程的运行,把处理机分配给刚刚被激活的进程。