进程控制
# 进程控制
进程控制是进程管理中最基本的功能,主要包括创建新进程、终止已完成的进程、将因发生异常情况而无法继续运行的进程置于阻塞状态、负责进程运行中的状态转换等功能如当一个正在执行的进程因等待某事件而暂时不能继续执行时,将其转变为阻塞状态,而在该进程所期待的事件出现后,又将该进程转换为就绪状态等。进程控制一般是由OS的内核中的原语来实现的。
# 操作系统内核
现代操作系统一般将OS划分为若干层次,再将OS的不同功能分别设置在不同的层次中。通常将一些与硬件紧密相关的模块(如中断处理程序等)、各种常用设备的驱动程序以及运行频率较高的模块(如时钟管理、进程调度和许多模块所公用的一些基本操作),都安排在紧靠硬件的软件层次中,将它们常驻内存,即通常被称为的OS内核。这种安排方式的目的在于两方面:一是便于对这些软件进行保护,防止遭受其他应用程序的破坏;二是可以提高OS的运行效率。相对应的是,为了防止OS本身及关键数据(如PCB等)遭受到应用程序有意或无意的破坏,通常也将处理机的执行状态分成系统态和用户态两种。
- 系统态:又称为管态,也称为内核态。它具有较高的特权,能执行一切指令,访问所有寄存器和存储区,传统的OS都在系统态运行。
- 用户态:又称为目态。它是具有较低特权的执行状态,仅能执行规定的指令,访问指定的寄存器和存储区。一般情况下,应用程序只能在用户态运行,不能去执行OS指令及访问OS区域,这样可以防止应用程序对OS的破坏。
总体而言,不同类型和规模的OS,它们的内核所包含的功能间存在着一定的差异,但大多数OS内核都包含了以下两大方面的功能:
# 支撑功能
该功能是提供给OS其它众多模块所需要的一些基本功能,以便支撑这些模块工作其中三种最基本的支撑功能是:中断处理、时钟管理和原语操作。
- 中断处理。中断处理是内核最基本的功能,是整个操作系统赖以活动的基础,OS中许多重要的活动,如各种类型的系统调用、键盘命令的输入、进程调度、设备驱动等无不依赖于中断。通常,为减少处理机中断的时间,提高程序执行的并发性,内核在对中断进行“有限处理”后,使转入相关的进程,由这些进程继续完成后续的处理工作。
- 时钟管理。时钟管理是内核的一项基本功能,在OS中的许多活动都需要得到它的支撑,如在时间片轮转调度中,每当时间片用完时,便由时钟管理产生一个中断信号,促使调度程序重新进行调度。同样,在实时系统中的截止时间控制、批处理系统中的最长运行时间控制等,也无不依赖于时钟管理功能。
- 原语操作。 所谓原语( Primitive),就是由若干条指令组成的,用于完成一定功能的一个过程。它与一般过程的区别在于:它们是“原子操作( Action Operation)”。所谓原子操作是指,一个操作中的所有动作要么全做,要么全不做。换言之,它是一个不可分割的基本单位。 因此,原语在执行过程中不允许被中断。原子操作在系统态下执行,常驻内存在内核中可能有许多原语,如用于对链表进行操作的原语、用于实现进程同步的原语等。
# 资源管理功能
- 进程管理。在进程管理中,或者由于各个功能模块的运行频率较高,如进程的调度与分派、进程的创建与撤消等;或者由于它们为多种功能模块所需要,如用于实现进程同步的原语、常用的进程通信原语等。通常都将它们放在内核中,以提高OS的性能。
- 存储器管理。存储器管理软件的运行频率也比较高,如用于实现将用户空间的逻辑地址变换为内存空间的物理地址的地址转换机构、内存分配与回收的功能模块以及实现内存保护和对换功能的模块等。通常也将它们放在内核中,以保证存储器管理具有较高的运行速度。
- 设备管理。由于设备管理与硬件(设备)紧密相关,因此其中很大部分也都设置在内核中。如各类设备的驱动程序、用于缓和CPU与IO速度不匹配矛盾的缓冲管理、用于实现设备分配和设备独立性功能的模块等。
# 进程的创建
# 进程的层次结构
在OS中,允许一个进程创建另一个进程,通常把创建进程的进程称为父进程,而把被创建的进程称为子进程。子进程可继续创建更多的孙进程,由此便形成了一个进程的层次结构。如在UNⅨX中,进程与其子孙进程共同组成一个进程家族(组)了解进程间的这种关系是十分重要的。因为子进程可以继承父进程所拥有的资源,例如,继承父进程打开的文件,继承父进程所分配到的缓冲区等。当子进程被撤消时,应将其从父进程那里获得的资源归还给父进程。此外,在撤消父进程时,也必须同时撤消其所有的子进程。为了标识进程之间的家族关系,在PCB中设置了家族关系表项,以标明自己的父进程及所有的子进程。进程不能拒绝其子进程的继承权值得注意的是,在 Windows中不存在任何进程层次结构的概念,所有的进程都具有相同的地位。如果一个进程创建另外的进程时创建进程获得了一个句柄,其作用相当于一个令牌,可以用来控制被创建的进程。但是,这个句柄是可以进行传递的,也就是说,获得了句柄的进程就拥有控制其它进程的权力,因此,进程之间的关系不再是层次关系了,而是获得句柄与否、控制与被控制的简单关系。
# 进程图
为了形象地描述一个进程的家族关系而引入了进程图( Process Graph)。所谓进程图就是用于描述进程间关系的一棵有向树,如图2-13所示。图中的结点代表进程。在进程P创建了进程P之后,称P是P的父进程( ParentProcess),P是P的子进程( Progeny Process)这里可用一条由进程P指向进程P的有向边来描述它们之间的父子关系。创建父进程的进程称为祖先进程,这样便形成了一颗进程树,把树的根节点作为进程家族的祖先(Ancestor)。
# 引起创建进程的事件
为使程序之间能并发运行,应先为它们分别创建进程。导致一个进程去创建另一个进程的典型事件有四类:
- 用户登录。在分时系统中,用户在终端键入登录命令后,若登录成功,系统将为该用户建立一个进程,并把它插入就绪队列中。
- 作业调度。在多道批处理系统中,当作业调度程序按一定的算法调度到某个(些)作业时,便将它(们)装入内存,为它(们)创建进程,并把它(们)插入就绪队列中。
- 提供服务。当运行中的用户程序提出某种请求后,系统将专门创建一个进程来提供用户所需要的服务,例如,用户程序要求进行文件打印,操作系统将为它创建一个打印进程,这样不仅可使打印进程与该用户进程并发执行,而且还便于计算为完成打印任务所花费的时间。
- 应用请求。在上述三种情况下,都是由系统内核为用户创建一个新进程;而这类事件则是由用户进程自己创建新进程,以便使新进程以同创建者进程并发运行的方式完成 特定任务。例如,某用户程序需要不断地先从键盘终端读入数据,继而再对输入数据进行相应的处理,然后,再将处理结果以表格形式在屏幕上显示。该应用进程为使这几个操作能并发执行,以加速任务的完成,可以分别建立键盘输入进程、表格输出进程。
# 进程的创建(Creation of Process)
在系统中每当出现了创建新进程的请求后, OS便调用进程创建原语Creat按下述步骤创建一个新进程:
- 申请空白PCB,为新进程申请获得唯一的数字标识符,并从PCB集合中索取一个空白PCB.
- 为新进程分配其运行所需的资源,包括各种物理和逻辑资源,如内存、文件、1O设备和CPU时间等。这些资源或从操作系统或仅从其父进程获得。新进程对这些资源的需求详情一般也要提前告知操作系统或其父进程。例如,为新进程的程序和数据以及用户栈分配必要的内存空间时,操作系统必须知道新进程所需内存的大小:
- 对于批处理作业,其大小可在用户提出创建进程要求时提供;
- 若是为应用进程创建子进程,也应是在该进程提出创建进程的请求中给出所需内存的大小:
- 对于交互型作业,用户可以不给出内存要求而由系统分配一定的空间:如果新进程要共享某个已在内存的地址空间(即已装入内存的共享段),则必须建立相应的链接。
- 初始化进程控制块(PCB), PCB的初始化包括:
- 初始化标识信息,将系统分配的标识符和父进程标识符填入新PCB中;
- 初始化处理机状态信息,使程序计数器指向程序的入口地址,使栈指针指向栈顶;
- 初始化处理机控制信息,将进程的状态设置为就绪状态或静止就绪状态,对于优先级,通常是将它设置为最低优先级,除非用户以显式方式提出高优先级要求。
- 如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。
# 进程的终止
# 引起进程的终止事件(Termination of Process)
- 正常结束,表示进程的任务已经完成,准备退出运行。在任何系统中,都应有个用于表示进程已经运行完成的指示。在批处理系统中,通常会在程序的最后安排一条Holt指令,用于向OS表示运行已结束。当程序运行到Holt指令时,将产生一个中断,去通知OS本进程已经完成;在分时系统中,用户可利用 Logs off去表示进程运行完毕,此时同样可产生一个中断,去通知OS进程已运行完毕。
- 异常结束,是指进程在运行时发生了某种异常事件,使程序无法继续运行。常见的异常事件有:
- 越界错,这是指程序所访问的存储区,已越出该进程的区域;
- 保护错,指进程试图去访问一个不允许访问的资源或文件,或者以不适当的方式进行访问,例如,进程试图去写一个只读文件:
- 非法指令,指程序试图去执行一条不存在的指令。出现该错误的原因可能是程序错误地转移到数据区,把数据当成了指令:
- 特权指令错,指用户进程试图去执行一条只允许OS执行的指令:
- 运行超时,指进程的执行时间超过了指定的最大值;
- 等待超时,指进程等待某事件的时间超过了规定的最大值;
- 算术运算错,指进程试图去执行一个被禁止的运算,例如,被0除;
- I/O故障,这是指在I/O过程中发生了故障.
- 外界干预,是指进程应外界的请求而终止运行。这些干预有:
- 操作员或操作系统干预,指如果系统中发生了某事件,例如,发生了系统死锁,由操作员或操作系统采取终止某些进程的方式使系统从死锁状态中解救出来;
- 父进程请求,指当子进程已完成父进程所要求的任务时,父进程可以提出请求结束该子进程;
- 因父进程终止,指当父进程终止时,它的所有子进程也都应当结束,因此, OS在终止父进程的同时,也将它的所有子孙进程终止。
# 进程的终止过程
如果系统中发生了要求终止进程的某事件, OS便调用进程终止原语,按下述过程去终止指定的进程:
- 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态
- 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真.用于指示该进程被终止后应重新进行调度;
- 若该进程还有子孙进程,还应将其所有子孙进程也都予以终止,以防它们成为不可控的进程;
- 将被终止进程所拥有的全部资源或者归还给其父进程,或者归还给系统;
- 将被终止进程(PCB)从所在队列(或链表)中移出,等待其它程序来搜集信息。
# 进程的阻塞与唤醒
# 引起进程阻塞与唤醒的事件
有下述几类事件会引起进程阻塞或被唤醒:
- 向系统请求共享资源失败。进程在向系统请求共享资源时,由于系统已无足够的资源分配给它,此时进程因不能继续运行而转变为阻塞状态。例如,一进程请求使用打印机,由于系统已将打印机分配给其它进程,已无可以再可分配的打印机,这时请求者进程只能被阻塞,仅在其它进程释放出打印机时,请求进程才被唤醒。
- 等待某种操作的完成。当进程启动某种操作后,如果该进程必须在该操作完成之后才能继续执行,则应先将该进程阻塞起来,以等待操作完成。例如,进程启动了某IO设备如果只有在O设备完成了指定的O操作任务后进程才能继续执行,则该进程在启动了I设备后便应自动进入阻塞状态去等待。在⑩O操作完成后,再由中断处理程序将该进程唤醒。
- 新数据尚未到达。对于相互合作的进程,如果一个进程需要先获得另一进程提供的数据后才能对该数据进行处理,只要其所需数据尚未到达,进程使只有阻塞。例如,有两个进程,进程A用于输入数据,进程B对输入数据进行加工。假如A尚未将数据输入完毕,则进程B将因没有所需处理的数据而阻塞:一旦进程A把数据输入完毕,便可去唤醒进程B.
- 等待新任务的到达。在某些系统中,特别是在网络环境下的OS,往往设置一些特定的系统进程,每当这种进程完成任务后便把自己阻塞起来,等待新任务的到来。例如在网络环境中的发送进程,其主要任务是发送数据包,若已有的数据包已全部发送完成,而又无新的数据包发送,这时发送进程将把自己阻塞起来:仅当有新的数据包到达时,才将发送进程唤醒.
# 进程的阻塞过程
正在执行的进程,如果发生了上述某事件,进程便通过调用阻塞原语 block将自己阻塞。可见,阻塞是进程自身的一种主动行为。进入 block过程后,由于该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状态由“执行”改为阻塞,并将PCB插入阻塞队列。如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞队列。最后,转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换,亦即,保留被阻塞进程的处理机状态,按新进程的PCB中的处理机状态设置CPU的环境.
# 进程的唤醒过程
当被阻塞进程所期待的事件发生时,比如它所启动的O操作已完成,或其所期待的数据已经到达,则由有关进程(比如提供数据的进程)调用唤醒原语 wakeup,将等待该事件的进程唤醒。 wakeup执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中应当指出, block原语和 wakeup原语是一对作用刚好相反的原语。在使用它们时,必须成对使用,即如果在某进程中调用了阻塞原语,则必须在与之相合作的、或其它相关的进程中安排一条相应的唤醒原语,以便能唤醒被阻塞进程:否则,阻塞进程将会因不能被唤醒而永久地处于阻塞状态,再无机会继续运行.
# 进程的挂起与激活
# 进程的挂起
当系统中出现了引起进程挂起的事件时, OS将利用挂起原语suspend将指定进程或处于阻塞状态的进程挂起, suspend的执行过程是:首先检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪:对于活动阻塞状态的进程,则将之改为静止阻塞;为了方便用户或父进程考查该进程的运行情况,而把该进程的PCB复制到某指定的内存区域;最后,若被挂起的进程正在执行,则转向调度程序重新调度。
# 进程的激活过程
当系统中发生激活进程的事件时, OS将利用激活原语active,将指定进程激活。激活原语先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪:若为静止阻塞,便将之改为活动阻塞。假如采用的是抢占调度策略,则每当有静止就绪进程被激活而插入就绪队列时,便应检查是否要进行重新调度,即由调度程序将被激活的进程与当前进程两者的优先级进行比较,如果被激活进程的优先级低,就不必重新调度:否则,立即剥夺当前进程的运行,把处理机分配给刚刚被激活的进程。