说明: 全文大部分都是《操作系统-精髓与设计原理 第八版》的原文,自己做了一些删改,使其更易于理解。
如果说操作系统是围绕进程这一概念创建的,有些言过其实,但进程在操作系统中的地位也至少是举足轻重的。
计算机的目的是要完成任务,而任务的完成需要一个或者多个进程的执行。操作系统维护了这些进程的运行,因为进程之间不但需要配合和调度,也需要使用计算机系统资源(操作系统抽象了计算机的系统资源,如文件系统,内存,网络接口等等),使用相同的资源可能就会产生冲突,操作系统就需要调节。在第5节,你将看到围绕进程实现的各种不同的操作系统的方法。
主要内容:
可以把进程视为由一组元素组成的实体,进程的两个基本元素是:
如果进程处在运行之中,又由以下元素来表征:
以上信息存放在一个称为进程控制块的数据结构中,在后面我们将会看到进程控制块远不止只有这些信息。进程控制块由操作系统创建和管理。
进程由程序代码及其相关数据以及进程控制块组成,称为进程映像。
进程控制块包含了进程的充分信息,计算机中断后,会把程序计数器和处理器寄存器保存到进程控制块的相应位置,如果不修改进程状态,再次运行的时候,程序计数器和处理器寄存器值恢复。
在本节中我们将通过对进程状态的描述和探索,来建立一个进程行为模型,并为操作系统实现提供系统方法。
我们知道进程要么正在执行要么不在执行,所以我们可以构建最简单的模型,进程可处于两种状态:运行态和未运行态。那么我们就知道了,操作系统必须要以某种方式来表示每个进程,使得操作系统可以跟踪到它,这里给出了一个简单的模型:
图中队列元素指向某个进程,等待着被调度执行。这只是最简单的操作系统进程控制模型,我们知道这样的模型是远远不够的,在对两状态模型进行改进之前,我们来了解一下进程的创建和终止,因为无论使用哪种模型,进程的生存其都是围绕着进程的创建和终止。
关于创建:
进程的创建过程:
以上两点构成了进程的创建过程,详细过程可以查看4.2。
触发进程创建的事件:
关于终止:
任何一个计算机都必须为进程提供表示其完成的方法。比如用户结束一个应用程序,将会给操作系统发出一个服务请求,以终止发出请求的进程。进程除了正常完成终止外,还包括
程序内部故障条件和错误导致进程终止:
外部干涉终止:
如果所有进程都做好了执行的准备,那么两状态进程模型给出的排队原则是有效的,但是我们知道
所以我们将两状态进程模型中的非运行态分为就绪态和阻塞态,此外还增加了两个很有用的状态,他们一共组成了以下五个状态:
这里再给给出图中未画出的两种状态转换说明:
对五状态进程模型的再改进:
既然有了阻塞态,为了更加高效的进行进程调度,我们可以维护两个队列:就绪队列和阻塞队列。进入系统的每个进程都放置在就绪队列中,当操作系统选择另一个进程运行时,将从就绪队列中进行选择。对于无优先级的方案,这可以是一个简单的先进先出队列。当一个正在运行的进程被移出处理器时,它根据情况要么终止,要么放置在就绪或阻塞队列中。最后,当一个事件发生时,所有位于阻塞队列中等待该事件的进程都被放到就绪队列中。
但是这里我们可以非常容易的想到在阻塞队列中当一个事件发生时,操作系统必须扫描整个阻塞队列,搜索那些等待该事件的进程。在大型操作系统中,队列中可能有几百甚至几千个进程,此时拥有多个队列将会很有效,一个事件可以对应一个队列。因此,事件发生时,相应队列中的所有进程都将转换到就绪态。
依照此思想,我们还可以维护多个就绪队列,每个优先级一个队列,将会带来很大的便利。操作系统很容易就可确定哪个就绪进程具有最高优先级且等待时间最长。
交换的需要
前面介绍的三个基本状态(就绪态、运行态和阻塞态)提供了一种为进程行为建立模型并指导操作系统实现的系统方法。许多实际的操作系统都是按照这三种状态具体构建的。
但是,我们可以证明向模型中增加其他状态也是合理的。为了说明加入新状态的好处,考虑一个未使用虚存的系统,每个被执行的进程必须完全载入内存。
回忆可知,机器变得复杂的原因是,I/O活动远慢于计算速度使得单道程序系统中的处理器大多数时间处于空闲状态。此时,内存中保存有多个进程,当一个进程被阻塞时,处理器可移向另一个进程,但由于处理器远快于I/0,会出现内存中的所有进程都在等待I/O的现象。因此,即便是多道程序设计,处理器多数时间仍可能处于空闲状态。
解决方案之一是扩充内存来容纳更多的进程,但这种方法有两个缺点。首先是内存的价格问题,当内存大小增加到兆位及千兆位时,价格也会随之增加;其次是程序对内存空间需求的增长速度要快于内存价格的下降速度。因此,更大的内存往往会导致更大的进程而非更多的进程。
解决方案之二是交换,即把内存中某个进程的一部分或全部移到磁盘中。当内存中不存在就绪态的进程时,操作系统就把被阻塞的进程换出到磁盘中的挂起队列(suspend queue),即临时从内存中“踢出”的进程队列。操作系统此后要么从挂起队列中取出另一个进程,要么接受一个新进程的请求,将其放入内存运行。
要使用前面介绍的交换,在进程行为模型中必须增加另一个状态:挂起态。当内存中的所有进程都处于阻塞态时,操作系统可把其中的一个进程置为挂起态,并将它转移到磁盘,此时内存所释放的空间就可被调入的另一个进程使用。操作系统执行换出操作后,将进程取到内存中的方式有两种:接纳一个新近创建的进程,或调入一个此前挂起的进程。显然,操作系统倾向于调入一个此前挂起的进程,并为它提供服务,而非增加系统的总负载数。
但这一推理也带来了一个难题,即所有已被挂起的进程都处于阻塞态。显然,这时把被阻塞的进程取回内存没有任何意义,因为它仍然未做好执行的准备。这时的进程状态转换为:
但是,由于每个挂起的进程最初都阻塞在某个特定的事件上,因此发行该事件时,进程将不再阻塞而可以继续执行,因此我们可以使用两个挂起态,分别为就绪/挂起态,阻塞/挂起态。这时的进程状态转换为:
在查看包含两个新挂起态的状态转换图前,必须注意迄今为止的论述都假设未使用虚存,进程要么都在内存中,要么都在内存外。使用虚存中,可能会执行只有部分内容在内存中的进程,若访问的进程地址不在内存中,则将进程的相应部分调入内存。使用虚存看上去不需要显式交换,因为通过处理器中的存储管理硬件,任何进程中的任何地址都可移入或移出内存。然而,若活动进程很多,且所有的进程都有一部分在内存中时,则可能会导致虚存系统崩溃。因此,即使是在虚存系统中,操作系统也需要不时地根据执行情况完全显式地换出进程。
这里就以下几个状态转换做出说明,其余请读者自行理解:
以上讨论的是内存的原因来挂起进程,实际上它还有以下以及其他多种原因来挂起进程,因此你也可以知道使用了挂起态的更多好处:
以上就是我们对进程状态的描述,以及我们最后构造出的五状态+两挂起态进程模型。操作系统可以依靠这个模型来构建系统。那么操作系统要控制进程在这些状态之间转换,需要哪些信息?接下来我们就将讨论这一基本问题,要控制进程并管理资源,操作系统需要哪些信息,即进程的描述。
操作系统为了管理进程和资源,必须掌握每个进程和资源的当前状态。普遍采用的方法是,操作系统构造并维护其管理的每个实体的信息表。图3.11给出了这种方法的大致范围,即操作系统维护的4种不同类型的表:内存、I/O、文件和进程。尽管不同操作系统的实现细节不同,但所有操作系统维护的信息基本都可以分为这4类。
操作系统在管理和控制进程时,首先要知道进程的位置,其次要知道进程的属性(如进程ID、进程状态)。
进程位置
进程位置在处理进程位置和属性问题前,首先要解决一个更基本的问题:进程的物理表示是什么?进程最少必须包括一个或一组被执行的程序,而与这些程序相关联的是局部变量、全局变量和任何已定义常量的数据单元。因此,一个进程至少应有足够的内存空间来保存其的程序和数据。此外,程序的执行通常涉及用于跟踪过程调用和过程间参数传递的栈。最后,还有与每个进程相关的许多属性,以便操作系统控制该进程。通常,属性集称为进程控制块(process control block)。程序、数据、栈和属性的集合称为进程映像(process image)。这就解决了进程的物理内容是说明,也就才能在存储设备上有位置这一概念。
进程映像的位置取决于所用的内存管理方案。在最简单的情形下,进程映像保存在相邻的内存块中或连续的内存块中。存储块位于外存(通常是磁盘)中,因此在操作系统管理进程时,其进程映像至少应有一部分位于内存中。而要执行该进程,则必须将整个进程映像载入内存中或至少载入虚存中。因此,操作系统需要知道每个进程在磁盘中的位置,并知道每个进程在内存中的位置。这在涉及到虚拟内存的时候更加复杂。
图3.11(上图)给出了位置信息的结构。有一个主进程表,每个进程在主进程表中都有一个表项,每个表项至少包含一个指向进程映像的指针。如果进程映像包括多个块,则这一信息直接包含在主进程表中,或通过交叉引用内存表中的表项得到。当然,这种描述是一般性描述,不同操作系统会按自身的方式来组织位置信息。
进程属性
由之前的叙述我们知道,进程属性保存在进程控制块之中。这里我们就简要的介绍操作系统将会用到的信息。
进程控制块信息分为三类:
下图是虚拟内存中的进程映像,虽然这里看起来是连续的,但是实际上可能并非如此,但是对于程序编写者而言,这样理解进程的地址结构就可以了,其他的操作系统会帮我们搞定。
此外我们可以看到在进程控制块中还有数据结构信息这一项,因此,可以使用队列实现进程控制块的链表,如下图所示,当然这种链表不是唯一的,例如你还可以将阻塞态的进程分别挂到不同事件阻塞的队列上:
进程控制块的保护
进程控制块的作用进程控制块是操作系统中最重要的数据结构。每个进程控制块都包含操作系统所需进程的所有信息。实际上,操作系统中的每个模块,包括那些涉及调度、资源分配、中断处理、性能监控和分析的模块,都能读取和修改它们。我们可以说资源控制块集合定义了操作系统的状态。
这就带来了一个重要的设计问题。操作系统中的很多例程需要访问进程控制块中的信息。直接访问这些表并不困难。每个进程都有一个唯一的ID号,它可用作进程控制块的指针表的索引。困难不是访问而是保护,具体表现为两个问题:
这些问题可要求操作系统中的所有例程都通过一个处理程序例程来解决,即处理程序例程的任务仅是保护进程控制块,且是读写这些块的唯一仲裁程序,而不是简单的直接进行读写。使用这类进程时,需要在性能和其他系统软件对其的信任度之间进行折中。
大多数处理器至少支持两种执行模式。某些指令只能在特权模式下运行,包括读取或改变诸如程序状态字之类的控制寄存器的指令、原始I/O指令和与内存管理相关的指令。另外,部分内存区域仅能在特权模式下访问。
非特权模式通常称为用户模式(user mode),因为用户程序通常在该模式下运行;特权模式称为系统模式(system mode)、控制模式(control mode)或内核模式(kernel mode),内核模式指的是操作系统的内核,它是操作系统中包含重要系统功能的部分。操作系统内核的典型功能是:
典型情况下,当用户调用一个操作系统服务或中断来触发系统例程的执行时,执行模式将被置为内核模式;而当从系统服务返回到用户进程时,执行模式则置为用户模式。不同的模式可以通过CPU寄存器来指示。
操作系统基于某种原因(之前说过)决定创建一个新进程时,会按如下步骤操作:
表面上看,进程切换很简单。在某个时刻,操作系统中断一个正在运行的进程,将另一个进程置于运行模式,并把控制权交给后者。然而,这会引发若干问题。首先,什么事件触发了进程的切换?其次,必须认识到模式切换与进程切换间的区别;最后,要实现进程切换,操作系统须对其控制的各种数据结构做些什么?
进程切换可在操作系统从当前正运行进程中获得控制权的任何时刻发生,也就是控制权在操作系统手中,就可能发生进程切换。下表给出了可能把控制权交给操作系统的事件。
大多数操作系统都会区分两种系统中断:一种称为中断,另一种称为陷阱。前者与当前正运行进程无关的某种外部事件相关,如完成一次I/O操作;后者与当前正运行进程产生的错误或异常条件相关,如非法的文件访问。
中断
对于普通中断(interrupt),控制权首先转给中断处理器,中断处理器完成一些基本的辅助工作后,再将控制权转给与已发生的特定中断相关的操作系统例程。示例如下:
陷阱
对于陷阱(trap),操作系统则确定错误或异常条件是否致命。致命时,当前正运行进程置为退出态,并切换进程;不致命时,操作系统的动作将取决于错误的性质和操作系统的设计,操作系统可能会尝试恢复程序,或简单地通知用户。操作系统可能会切换进程,或继续当前运行的进程。
系统调用
最后,操作系统可被来自正执行程序的系统调用(supervisor call)激活。例如,正运行用户进程执行了一个请求I/O操作的指令(如打开文件),这时该调用会转移到作为操作系统代码一部分的一个例程。使用系统调用时会将用户进程置为阻塞态。
综上所述我们知道中断不是进程主动发生的,而陷阱和系统调用是进程主动产生的。在这里我在网上查阅了一些资料,这里的陷阱又像是有一点异常的意思在里面,而系统调用又使用了陷阱,总之概念似乎有点混淆,不过对于中断倒是没有异议。读者可以结合下面这篇博客来理解陷阱和系统调用。
https://www.cnblogs.com/broglie/p/5463359.html
当进程切换的时候,需要进行执行模式的切换。执行模式的切换离不开中断。当出现中断的时候,处理器会做如下工作:
处理器现在继续取指阶段,并取中断处理程序的第一条指令来服务该中断。此时,将已中断进程的上下文保存到已中断程序的进程控制块中。
现在的问题是,保存的上下文包括哪些内容?答案是,它必须包含中断处理程序可能改变的所有信息,以及恢复被中断程序时所需要的所有信息。因此,必须保存称为处理器状态信息的进程控制块部分,包括程序计数器、其他处理器寄存器和栈信息。
进程控制块中的其他信息如何处理?若中断之后切换到另一个应用程序,则需要做一些工作。但在多数操作系统中,中断发生后并不一定进行进程切换。可能的情况是,执行中断处理程序后,继续执行正运行的进程。此时,所要做的工作是发生中断时保存处理器状态信息,并在控制权返回给该程序时恢复这些信息。保存和恢复功能通常由硬件实现。那么需要进程切换时,就需要做以下的工作了。
进程状态的变化显然,模式切换与进程切换是不同的。模式切换可在不改变运行态进程的状态的情况下出现。此时保存上下文并在以后恢复上下文仅需很少的开销。但是,若当前正运行进程将转换为另一状态(就绪、阻塞等),则操作系统必须使环境产生实质性的变化。完整的进程切换步骤如下:
因此,涉及状态变化的进程切换与模式切换相比,要做的工作更多。
操作系统有两个特殊事实:
如果操作系统仅是像其他程序那样由处理器执行的一组程序,那么操作系统是一个进程吗?如果是,如何控制它?这些有趣的问题使得人们提出了大量的设计方法。图3.15中给出了当代操作系统中使用的各种方法。接下来,我们一一讲解。
在许多老操作系统中,传统且通用的一种方法是在所有进程外部执行操作系统内核【见图3.15(a)】。采用这种方法时,若当前正运行进程被中断或产生一个系统调用,则会保存该进程的模式上下文,并将控制权转交给内核。操作系统本身具有控制过程调用和返回的内存区域与系统栈。操作系统可执行任何预期的功能,并恢复被中断进程的上下文,恢复中断用户进程的执行;操作系统也可保存进程的模式上下文,并继续调度和分派另一个进程,但是否这样做取决于中断的原因和当前的情况。
无论哪种情况,关键都是进程这一概念仅适用于用户程序,而操作系统代码则是在特权模式下单独运行的实体。
较小计算机(PC、工作站)的操作系统通常采用另一种方法,即在用户进程的上下文中执行所有操作系统软件。此时,操作系统是用户调用的一组例程,它在用户进程的环境内执行并实现各种功能,如图3.15(b)所示。任何时刻操作系统都管理着n个进程映像,这些进程映像就变成了下图所示的样子:
发生中断、陷阱或系统调用时,处理器置于内核模式,控制权转交给操作系统。要把控制权从用户程序转交给操作系统,需要保存模式上下文并切换模式,再切换到一个操作系统例程,但此时仍然是在当前的用户进程内继续执行,不需要切换进程,只是在同一进程中切换模式。
操作系统完成操作后,需要继续运行当前的进程,则会切换模式以在当前进程内恢复已中断的程序。这种方法的关键优点是:中断一个用户程序,使用某些操作系统例程,然后恢复用户程序,所有这些都不会招致两次进程切换的惩罚。
然而,若确认将出现进程切换而非返回到先前正执行的程序,则控制权会传递给一个进程切换例程,进程切换例程是否在当前进程中执行,则取决于系统的设计。然而,在某些特殊情况下,当前进程必须置于非运行态,而另一个进程则指定为正运行进程。此时,将执行视为发生在所有进程外部逻辑上最为方便。
图3.15(c)所示的另一种方法是把操作系统作为一组系统进程来实现。类似于其他方法,该软件是在内核模式下运行的内核的一部分。但在这种情况下,主要的内核功能被组织为独立的进程。同样,此时存在一些在任何进程之外执行的进行切换代码。
这种方法有几个优点。首先,它利用了鼓励使用模块化操作系统的程序设计原理,可使模块间的接口最小且最简单。其次,有些非关键操作系统功能可简单地用独立的进程来实现,例如前面提及的监视各种资源(处理器、内存、通道)利用率和系统中用户进程进展状态的程序。因为这种程序不向任何活动进程提供特殊的服务,因此只能被操作系统调用。作为一个进程,这一功能可以任何指定的优先级在分派器的控制下与其他进程交替执行。第三,把操作系统作为一组进程来实现时,在多处理器或多机环境中很有用,因此此时为提高性能,有些操作系统服务可传送到专用的处理上执行。
UNIX System V使用了一种对用户可见的简单但功能强大的进程机制。UNIX采用了图3.15(b)中的模型,在该模型中操作系统的大部分都在用户进程环境内执行。UNIX使用了两类进程,即系统进程和用户进程。系统进程在内核模式下运行,执行操作系统代码来实现管理功能和内部处理,如内存空间的分配和进程交换;用户进程则在用户模式下运行并执行用户程序和实用程序,在内核模式下运行并执行属于内核的指令。当产生异常(错误)、发生中断或用户进程发出系统调用时,用户进程可进入内核模式。
UNIX操作系统中共有9种进程状态,如表3.9所示。图3.17(基于【BACH86】中的图形)是相应的状态转换图,它与之前的五状态+两挂起进程模型类似,两个UNIX休眠态对应于其中的两个阻塞态。两个进程模型的主要不同之处如下:
和之前说到的五状态+两挂起进程模型对应总结如下:
UNIX中的进程是一组相当复杂的数据结构,这些数据结构为操作系统提供管理进程和分派进程所需的全部信息。表3.10概括了进程映像中的元素,这些元素分为三部分:用户级上下文、寄存器上下文和系统级上下文。
UNIX中的进程创建是由内核系统调用fork()实现的。一个进程发出一个fork请求时,操作系统执行如下功能:
所以子进程和父进程除了进程标识符和共享内存外,完全一致
所有这些工作都在父进程的内核模式下完成。内核完成这些功能后,可继续分派器例程工作一部分的如下三种操作之一:
很难想象在这种创建进程的方法中,父进程和子进程都执行相同的代码。区别在于:从fork调用返回时,测试返回参数。若值为零,则它是子进程,此时可转移到相应的用户程序中继续执行;若值非零,则它是父进程,此时继续执行主程序。
两个特殊进程
UNIX中有两个独特的进程。进程0是一个特殊的进程,它是在系统启动时创建的。实际上,它是启动时加载的一个预定义数据结构,是交换程序进程。此外,进程0产生称为初始进程的进程1,进程1是系统中所有其他进程的祖先。当新的交互用户登录到系统时,进程1会为该用户创建一个用户进程。随后,用户进程创建构成分支树的子进程,因此任何应用程序都由一组相关的进程组成。
现代操作系统中最基本的构件是进程,操作系统的基本功能是创建、管理和终止进程。当进程处于活跃状态时,操作系统必须设法使每个进程都分配到处理器执行时间,并协调它们的活动、管理有冲突的请求、给进程分配系统资源。
关于进程管理
要执行进程管理功能,操作系统必须维护每个进程的描述(或进程映像),包括执行进程的地址空间和一个进程控制块。进程控制块含有操作系统管理进程需要的全部信息,包括进程的当前状态、分配给进程的资源、优先级和其他相关数据。
关于进程状态
在整个生命周期内,进程总是在一些状态之间转换。最重要的状态有就绪态、运行态和阻塞态。就绪态进程是指当前未执行但已做好执行准备的进程,只要操作系统调度到它,它就会立即执行;运行态进程是指当前正被处理器执行的进程,在多处理器系统中会有多个进程处于这种状态;阻塞态进程是指正在等待某一事件完成(如一次I/O操作)的进程。
关于进程切换
正运行进程可被进程外发生并被处理器识别的中断事件打断,或被执行操作系统的系统调用打断。不论在哪种情况下,处理器都会执行一次模式切换,将控制权转交给操作系统例程。操作系统完成必需的操作后,可以恢复被中断的进程或切换到其他进程。