提纲

  • 什么是进程
  • 进程的状态
  • 进程描述
  • 进程状态的控制

    什么是进程

    首先看看对程序的认知,程序是被编译好用来实现一定功能可以被执行的二进制文件,是一个静态概念
    相对的进程则是运行中的程序,是计算机上执行的程序实例,是一个动态的概念
    这就好比面向对象中的类与对象,程序是类,进程则是类实例化的对象
    比如一台计算机上安装了QQ这个程序,可以同时打开两个QQ实例,分别登录不同的账号,这两个QQ的实例就是进程
    那么可以把进程看作是程序代码+数据组成的实体,同一程序的不同进程的程序代码是相同的,但是所保存的数据集却是不同的。
    不过仅仅依靠程序本身所带有的属性项来区别进程显然是不够的,进程还被附加上一些特有的属性
标识符 跟这个进程相关的唯一标识符,用于区别其他进程
状态 因为系统的资源分配等原因,进程会处于不同的状态,当进程取得CPU控制权的时候,他就处于执行态
优先级 相对于其他进程的优先级
程序计数器 程序中即将被执行的下一条指令的地址
内存指针 包括程序代码和进程相关数据的指针,还有和其他进程共享内存块的指针
上下文数据 进程执行时处理器寄存器中的数据
I/O状态信息 包含显式的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
审计信息 可包括处理器时间总和,使用的时钟数总和,时间限制,审计号等

这部分信息被存放在一个称为进程控制块(PCB)的数据结构中,该控制块由操作系统创建和管理。
这些信息保证了进程是可中断的,并且能够按照原样恢复原进程的执行,就像未被中断过一样。

进程的状态

从CPU的角度看,它在指令序列中按某种顺序执行指令,这个顺序根据程序计数器(PC)的变化来指示。而PC可能根据不同情况而指向内存中不同进程不同部分的程序代码。
考虑一个简单的例子,图中粗略的给出了三个进程在内存中的分布。此外还有一个分派器,此处的作用是使CPU从一个进程切换到另外一个进程。
image.png
假设一个内存地址存放一条CPU指令,操作系统最多允许一个进程连续执行6个指令周期(6条指令)
image.png
如上图,进程A执行6条指令后,因为超时,CPU控制权转到分派器,执行部分进程切换所需指令。进程切换完毕,控制权转到进程B,执行四条指令后,因为进程B发起I/O请求,这时I/O操作无法及时执行完成,进程B不能做下一步动作,控制权再次给回分派器,进程切换,将控制权交给进程C,进程C执行6个指令周期后,转交控制权,切换进程,回到进程A,接着进程A被暂停时的状态继续执行,超时转交控制权,此时进程B可能仍在等待I/O操作的完成,因此控制权仍然交给进程C,进程C继续执行,超时,再进行后续操作。

在任意时刻,一个进程或者在执行,或者没有执行。因此可以建立一个简单模型,在这一过程中就可以抽象出进程的两种状态,运行态与未运行态。

image.png
当操作系统创建一个新进程时,该进程以未运行状态加入到系统中,操作系统知道这个进程的存在,并正在等待执行机会,当正在运行的进程不时被中断,分派器将选择一个新的进程运行,前一个进程从运行态转换到未运行态,被选择的进程转到运行态。
从这个简单的模型中可以意识到操作系统的一些设计元素,必须用某种方式来表示每个进程,使得操作系统能够跟踪它,也就是说,必须有一些与进程相关的信息,包括进程在内存中当前状态和位置,即前面所提到的进程控制块(PCB),未运行态的进程必须保持在某类型的队列中,等待合适的执行时机。可以用下图描述分派器的行为。
image.png
关于进程派生
一般而言,进程都是由操作系统创建的,这个过程是对用户以及应用程序都是透明的,但是允许一个进程引发另外一个进程的创建,这也是十分有用的。
例如,服务器有一个进程用来监听来自客户端的请求,每当有客户端发来请求,都能创建一个新的进程用来处理这个请求,而监听进程则继续监听其他请求。使得服务器能够同时处理来自多个客户端的服务请求。而如果不能创建请求,那么当监听到一个来自客户端的请求的之后,监听进程就必须自己去完成这个客户端的请求,完成之后,才能继续监听来自其他客户端的请求。这样就不可避免的会导致客户端请求的丢失。虽然可以通过创建多个监听进程来应对,相比一个监听进程显然是更消耗资源的。
因此由进程引发新进程的创建是十分有用的。当操作系统为另外一个进程显示的请求创建一个进程时,这个动作称为进程派生,发起创建请求的进程为父进程,被创建的进程为子进程。

关于进程终止
进程在执行过程中,进程可能会出现各种各样的情况导致进程的终止,如算数错误,内存越界,无效指令,父进程终止等。

现在回过头来再来看前面的两种状态,举个例子,考虑这样一种情况,一个进程一次最多执行6条指令,进程A,B,C。
从进程A的第一条指令开始执行,超时之后,分派器将CPU控制权交给进程B,进程A入队列等待下一次执行机会。进程B开始执行,执行四条指令之后,等待I/O操作的完成,分派器把控制权交给C,而B进队列,C超时之后,进队列,控制权又交回A,A继续执行,超时,这时候,按顺序当然是应该将控制权交给B,但是很不幸,B还在等待I/O操作的完成,这时候在分配给B显然是不合理的,这会造成处理器的空闲。因此不能只将哪个进程在队列中待得最长作为处理器分派的依据,而应该考虑整个队列中没被I/O等操作阻塞的能直接运行的进程。因此很自然的一种解决方案就是将非运行态再次细分为就绪态和阻塞态。
就绪态,即是进程已经做好准备,有机会即可运行
阻塞态,进程在某些事件发生前不能执行,如I/O等待操作完成,或是等待消息。
此外,还有两种额外的状态,新建态和退出态
试想这样一个情形,现在有一台电脑的内存条8G,运行了一些必要的程序之后,占用7G,这时候我还想再开个微信,很不巧,假设微信的常驻进程需要的内存大于1G,这时候显然是无法载入微信的进程到主存中,但是又确实有新建这样一个进程的需求,怎么办呢?我们可以先为即将创建的微信进程的信息(PCB)载入主存,但是微信程序代码本身不载入,等到某个进程终止结束,内存空闲出足够的空间时,再将程序代码载入内存,并为相关数据分配空间。这样一种状态就可以称为新建态
再讨论一下退出态,有这么一类程序,它用于监控其他程序的执行状态,如Windows的任务管理器。如果进程一直存在,任务管理器就可以持续监控其他进程状态信息,但是监控这些信息是需要时间的,也就是,进程的实时状态信息到达任务管理器是有延迟的,如果进程终止时,系统直接删除的话,进程终止前一小段时间的状态信息是无法被任务管理器的提取的,要是因为错误终止,也难以收集错误信息。因此很有必要延迟进程在系统中删除的时间,等到所有信息都被任务管理器提取,该进程将被从系统中删除。这样一种状态称为退出态
至此所讨论的进程完整的生存模型如下
image.png
再次回到前面所述的简单例子,如何在考虑到阻塞和就绪态的情况下合理分派CPU给合适的进程。
只用一个队列的话,显然是不合适的。一个不够再加一个,分为就绪队列和阻塞队列,当一个事件发生时,阻塞队列中的正在等待该事件的进程转换到就绪队列中去,分派器直接从就绪队列中分派即可。但是这需要扫描整个阻塞队列,在大型操作系统中,队列中可能有几百几千个进程,这显然是耗时的。因此可以继续增加队列,将阻塞队列按事件分,一个队列对应一个事件,一个事件发生时,只需找到与那个事件对应的队列,将其中的所有进程转为就绪队列。
还有一个改进就是按照优先级的方案分派进程,维护多个就绪队列,每个优先级对应一个队列,操作系统可以很简便的确认最需要处理器的那个进程

上述模型已经很完善了,不过还没完,再看看下面的描述
假设时间回到10多年前,那时候的内存还不足1G,假设一台电脑有512MB的内存,现在放了几个进程在上面跑,跑着跑着一些进程阻塞了,比如等待用户输入,这时CPU转移到另外一批进程,但是跑着跑着这批进程也阻塞了,因为CPU太快了,前一批的进程等待的事件还没发生,这一批又被阻塞了,CPU快到所有进程都在等待I/O操作的情况都很常见。那CPU又闲下来了。但是实际上还有很多任务要处理,只是因为内存瓶颈,阻塞的进程把内存塞满了,也没办法载入新的进程到内存中给CPU去处理。
一种解决方案就是加内存啊,内存不够,加钱上更大的内存,但是这种方案有两个缺陷,一个是成本会增加。再者,内存价格下降的速度比程序对内存空间需求增长的速度往往是要慢的,因此更大的内存可能不会导致更多的进程,而是更大的进程。
另外一种方案就是交换,把主存中某个进程的一部分或全部内容转移到磁盘中。当主存中没有处于就绪态的进程时,操作系统就把阻塞的进程换到磁盘的”挂起队列”中,这是暂时保存从主存中”驱逐”出的进程队列。在此之后,操作系统可能会取出挂起队列中的一个进程或是新建一个进程载入主存中运行。
不过值得注意的是,交换操作也是一种I/O操作,但是因为磁盘I/O一般是计算机中最快的I/O所以交换一般会提高性能。
那么对于上述所说的被挂起的进程就又成为另外一种状态,称为挂起态。

这时候,进程的生存模型就是这样的了
image.png

现在有这么一种情形,当一个进程被挂起,而主存正好有空闲,操作系统可以有两个选择,一是把被挂起的进程取回主存继续执行,而是创建新进程执行。显然,通常倾向于取回挂起的进程,而不是新建进程增加系统中的负载总数。但是这也带来一个问题,所有挂起的进程都是被阻塞的,将阻塞进程取回主存没意义啊,因为它每准备好执行。不过挂起态中的每一个进程都是阻塞在一个特定事件上,当这个事件发生,进程就不阻塞了。因此上述模型还有所欠缺,因此考虑两个因素,进程是否在等待一个事件以及进程是否被换出主存。
两两组合,就形成四种状态

  • 就绪态
  • 阻塞态
  • 阻塞/挂起态
  • 就绪/挂起态

即合理的生存模型如下
image.png

挂起态的产生除了解决前面所述问题外,同时还附带了其他几个用途

  • 比如当操作系统发现或怀疑一些进程有问题时可以挂起进程。
  • 当用户怀疑某个运行的程序有缺陷,可以挂起进程并进行调试,检查和修改程序之后再恢复运行。
  • 当有一个用于收集和记录信息的后台程序,用户希望能快速打开或关闭时,可以选择挂起。
  • 父进程可能希望在子进程出现错误时挂起子进程,收集错误信息,调查错误原因。

进程描述

先看看操作系统的控制结构
操作系统为了管理进程和资源,必须掌握关于每个进程和资源当前状态的信息。一种普遍的方法就是,操作系统维护它所管理的每个实体的信息表
如图所示,操作系统维护四类实体的信息表。
image.png
这里主要讨论操作系统维护的进程表

进程的控制