第三讲 进程

1,502 阅读28分钟

进程的非正式定义非常简单:进程就是运行中的程序。

程序本身是没有生命周期的,它只是存在磁盘上面的一些指令(也可能是一些静态数据)。是操作系统让这些字节运行起来,让程序发挥作用。

即使可以利用的CPU只有一个,操作系统也支持(伪)并发操作的能力。操作系统将一个单独的CPU变换成多个虚拟的CPU。

进程

操作系统为正在运行的程序提供的抽象,就是所谓的进程(process)。正如我们上面所说的,一个进程只是一个正在运行的程序。在任何时刻,我们都可以清点它在执行过程中访问或影响的系统的不同部分,从而概括一个进程。

所有现代的计算机经常会在同一时间做许多事情。举个例子。先考虑一个网络服务器(阿里巴巴)。从四面八方进入大量网页请求。当一个请求进入时,服务器检查其需要的网页是否在缓存中。如果是,则把网页发送出去。如果不是,则启动一个磁盘请求以获取网页。然而,从CPU角度来看,磁盘请求需要漫长的时间。当等待磁盘请求完成时,其他更多的请求将会进入。如果有多个磁盘存在,会在满足第一个请求之前就接二连三地对其他的磁盘发出一些或所有的请求。很明显,需要一些方法去模拟并控制这种并发。进程(特别是线程)在这里就可以产生作用。

事实表明,人们常常希望同时运行多个程序。比如:在使用计算机或者笔记本的时候,我们会同时运行浏览器、邮件、游戏、音乐播放器,等等。实际上,一个正常的系统可能会有上百个进程同时在运行。如果能实现这样的系统,人们就不需要考虑这个时候哪一个CPU 是可用的,使用起来非常简单。所有的这些活动需要管理,于是一个支持多进程的多道程序系统就很有用了。

在任何多道程序设计系统中,CPU由一个进程快速至另一个进程,使每个进程各运行几十或几百个毫秒。严格地说,在某一个瞬间,CPU只能运行一个进程但在1秒钟期间,它可能运行多个进程,这样就产生并行的错觉

并发: 一段时间内,有多个进程在执行。如果时间短, 给人感觉是同时执行。

并行: 一个时刻,有多个进程在执行。

单个CPU、多个CPU都可以实现并发

单个CPU,没法实现并行。因为在某一个时刻,只能执行一个进程。

为了执行其他程序,CPU必须要做切换。这样在一段短的时间内,可以多个进程在执行,这叫并发。

只有在多个CPU的时候,多核处理器, 就会达到在同一个时刻,有多个进程在执行。这叫并行。

进程的组成

  • 程序的代码;
  • 程序处理的数据;
  • 程序计数器中的值,指示下一条将运行的指令;
  • 一组通用的寄存器的当前值,堆,栈;
  • 一组系统资源(如打开的文件)

进程的特点

  • 动态性:可动态地创建,结果进程;
  • 并发性:进程可以被独立调度并占用处理机运行;(并发:一段,并行:一时刻)
  • 独立性:不同进程的工作不相互影响;(页表是保障措施之一)
  • 制约性:因访问共享数据 / 资源或进程间同步而产生制约。

进程模型

在进程模型中,从概念上说,每个进程拥有它自己的虚拟CPU。当然,实际上真正的CPU在各进程之间来回切换。但为了理解这种系统,考虑在(伪)并行情况下运行的进程集,要比我们试困跟踪CPU如何在程序间来回切换简单得多。这种快速的切换称作多道程序设计。

在图2-1a中我们看到,在一台多道程序计算机的内存中有4道程序。

在图2-16中,这4道程序披抽象为4个各自拥有自己控制流程(即每个程序自己的逻轩程序计数器)的进程,并且每个程序都独立地运行。当然,实际上只有一个物理程序计数器,所以在每个程序运行时,它的逻我程序计数器被装入实原的程序计数器中。当该程序执行结束(或暂停执行)时,物理程序计数器被保存在内存中该进程的逻辑程序计数器中。

在图2-1c中我们看到,在观察足够长的一段时间后,所有的进程都运行了,但在任何一个给定的瞬间仅有一个进程真正在运行。

1.png

假设只有一个CPU。然而,逐渐这个假设就不为真了,因为新的芯片经常是多核的,包含2个、4个或更多的CPU。现在,一次只考虑一个CPU会更简单一些。因此,当我们说一个CPU只能真正一次运行一个进程的时候,即使有2个核(或CPU),每一个核也只能一次运行一个进程。

由于CPU在各进程之间来回快速切换,所以每个进程执行其运算的速度是不确定的。而且当同一进程再次运行时,其运算速度通常也不可再现。所以,在对进程编程时决不能对时序做任何确定的假设。

进程和程序的区别

进程和程序间的区别是很微妙的,但非常重要。

用一个类比可以使我们更容易理解这一点。想象一位有一手好厨艺的计算机科学家正在为他的女儿烘制生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需的原料:面粉、鸡蛋、糖、香草汁等。在这个比隃中,做蛋糕的食谱就是程序(即用适当形式描述的算法),计算机科学家就是处理器(CPU),而做蛋糕的各种原料就足输人数据。进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的,总和。

现在假设计算机科半家的儿子哭着跑了进来,说他的头被一只蜜蜂蛰了。计算机科学家就记录下他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本总救手册,按照共中的指示处理盐伤。这里,我们看到处理机从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗收治),每个进程拥有各自的程序(食谈和念救手册)。当蜜蜂蛰伤处理完之后,这位计算机科学家又回来做蛋糕,从他离开时的那一步继续做下去。

截屏2024-04-06 12.19.43.png

这里的关键思想是:一个进程是某种类型的一个活动,它有程序、输人、输出以及状态。单个处理器可以被若干进程共享,它使用某种调度算法次定何时停止一个进程的工作,并转而为另一个进程提供服务。

值得注意的是,如果一个程序运行了两遍,则算作两个进程。例如,我们可能经常两次去启动同一个字处理软件,或在有两个可用的打印机的情况下同时打印两个文件。像"两个进程恰好运行同一个程序",其实它们是不同的进程。操作系统能够使它们共享代码,因此只有一个副本放在内存中。

进程的实现

为了实现进程模型,操作系统维护着一张表格(一个结构数组),即进程表(process table)。每个进程占用一个进程表项。(这个进程表项也称为进程控制块-PCB)

进程控制块PCB是操作系统能够支持多进程和提供多处理的关键工具。PCB包含了进程状态的重要信息。

  • 进程ID: 跟这个进程相关的唯一标识符,用来区别其他进程。
  • 进程状态: 状态可以包括新的、就绪、运行、等待、停止等。
  • 程序计数器: 进程将要执行的下个指令的地址。
  • CPU寄存器: 根据计算机体系结构的不同,寄存器的类型和数量也会不同。它们包括累加器、堆栈指针、通用寄存器和其他条件码信息寄存器。
  • CPU调度信息: 包括进程优先级、调度列队的指针和其他调度参数。
  • 内存管理信息: 包括基地址和界限地址寄存器的值、页表或段表。
  • 记账信息: CPU时间、实际使用时间、时间期限、记账数据、作业或进程数量。
  • I/O状态信息: 包括显示的I/O请求、分配给进程的I/O设备(例如磁带驱动器)和被进程使用的文件列表(也叫打开文件列表)

1.png

进程控制块PCB包含了充分的信息,这样就可以中断一个进程的执行,并且在后来恢复执行进程时就好像进程未被中断过。当进程中断时,操作系统会把程序计数器和处理器寄存器(上下文数据)保存到进程控制块中的相应位置,进程状态也被改变为其他的值,例如限塞态或就绪态。现在操作系统可以自由地把其他进程设置为运行态,把其他进程的程序汁数器和进程上下文数据加载到处理器奇存器中,这样其他进程就可以开始执行了。 因此,可以说进程是由程序代码和相关数据还有进程控制块组成。对于一个单CPU计算机,在任何时间都最多只有一个进程在执行。正在运行的这个进程的状态为运行态。

PCB的组织方式

  • 链表:同一状态的进程其PCB成一链表,多个状态对应多个不同的链表。 各状态的进程形成不同的链表:就绪链表,阻塞链表

  • 索引表:同一状态的进程归入一个index表(由index指向PCB),多个状态对应多个不同的index表 各状态的进行形成不同的索引表:就绪索引表,阻塞索引表

1.png

进程切换

1.png

2.png

3.png

4.png

创建进程

操作系统需要有一种方式来创建进程。一些非常简单的系统,即那种只为运行一个应用程序设计的系統(例如,微波炉中的控制器),可能在系统启动之时,以后所需要的所有进程都已存在。然而在通用系統中,需要有某种方法在运行时按需要创建或撤销进程。 有4种主要事件导致进程的创建:

  • 1、系统初始化。
  • 2、执行了正在运行的进程所调用的进程创建系统调用。
  • 3、用户请求创建一个新进程。
  • 4、一个批处理作业的初始化。

启动操作系统时,通常会创建者干个进程。其中有些是前台进程,也就是同用户(人类)交互井替他们完成工作的那些进程。其他的是后台进程,这些进程与特定的用户没有关系,相反,却具有某专门的功能。例如,设计一个后台进程来接收发来的电子邮件,这个进程在一天的大部分时间都在睡眠但是当电子邮件到达时就突然被唤醒了。也可以设计另一个后台进程来接收对该机器中Web页面的访请求,在请求到达时唤醒该进程以便服务该请求。停留在后台处理诸如电子邮件、Web页面、新闻、打印之类活动的进程称为守护进程(dlaemon)。在大型系统中通常有很多守护进程。在UNIX中,可以用ps程序列出正在运行的进程,在Windows中,可使用任务管理器。

除了在启动阶段创建进程之外,新的进程也可以以后创建。一个正在运行的进程经常发出系统调用,以便创建一个或多个新进程协助其工作。在所要从事的工作可以容易地划分成若干相关的但没有相互作用的进程时,创建新的进程就特别有效果。例如,如果有大量的数据要通过网络调取并进行顺序处理,那么创建一个进程取数据,并把数据放人共享缓冲区中,而让第二个进程取走数据项并处理之,应该比较容易。在多处理机中,让每个进程在不同的CPU上运行会使整个作业运行得更快。

在交互式系统中,键入(登录)一个命令或者点(双)击一个图标就可以启动一个程序。这两个动作中的任何一个都会开始一个新的进程,并在其中运行所选择的程序。在基于命令行的UNIX系统中运行程序X,新的进程会从该进程接管开启它的窗口。在Microsoft Windows中,多数情形都是这样的,在一个进程开始时,它并没有窗口,但是它可以创建一个(或多个)窗口。在UNIX和Windows系统中,用户可以同时打开多个窗口,每个窗口都运行一个进程。通过鼠标用户可以选择一个窗口井且与该进程交互,例如,在需要时提供输人。

最后一种创建进程的情形仅在大型机的批处理系统中应用。用户在这种系统中(可能是远程地)提交批处理作业。在操作系统认为有资源可运行另一个作业时,它创建一个新的进程,并运行其输入队列中的下一个作业。

从技术上看,在所有这些情形中,新进程都是由于一个已存在的进程执行了一个用于创建进程的系统调用而创建的。这个进程可以是一个运行的用户进程、一个由键盘或鼠标启动的系统进程或者一个批处理管理进程。这个进程所做的工作是,执行一个用于创建新进程的系统调用。这个系统调用通知操作系统创建一个新进程,并且直接或间接地指定在该进程中运行的程序。

在UNIX 系統中,只有一个系统调用可以用来创建新进程:fork。这个系统调用会创建一个与调用进程相同的副本。在调用了fork后,这两个进程(父进程和子进程)拥有相同的存储映像、同样的环境字符串和同样的打开文件。这就是全部情形。通常,子进程接着执行execve或一个类似的系统调用。以修改其映像并运行一个新的程序。例如,当一个用户在shell中键入命令sort时,shell就创建一个子进程,然后,这个子进程执行sort。之所以要安排两步建立进程,是为了在(fork之后单在execve之前允许该子进程处理其文件描述符,这样可以完成对标准输人,标准输出和标难出错的重定向。

进程的终止

进程在创建之后,它开始运行,完成其工作。但永恒是不存在的,进程也一样。这个新的进程迟早会终止,通常由下列条件引起:

  • 1、正常退出(自愿的)。
  • 2、出错退出(自愿的)。
  • 3、严重错误(非自感)。
  • 4、被其他进程杀死(非自愿)。

多数进程是由于完成了它们的工作而终止。当编译器完成了所给定程序的编译之后,编译器执行一个系统调用,通知操作系统它的工作已经完成。在UNIX中该调用是exit,而在Windows中,相关的调用是ExitProcess。 面向屏幕的程序也支持自愿终止。字处理软件、Internet浏览器和类似的程序中总有个供用户点击的因标或菜单项,用来通知进程删除它所打开的任何临时文件,然后终止。

进程终止的第二个原因是进程发现了严重错误。例如,如果用户键人命令cc foo.c 要编译程序foo.c,但是该文件并不存在,于是编译器就会退出。在给出了错误参数时,面向屏幕的交互式进程通常井不退出。相反,这些程序会弹出一个对话框,并要求用户再试一次。

进程终止的第三个原因是由进程引起的错误,通常是由于程序中的错误所致。例如,执行了一条非法指令、引用不存在的内存,或除数是零等。有些系统中(如UNIX),进程可以通知操作系统,它希望自行处理某些类型的错误,在这类错误中,进程会收到信号(被中断),而不是在这类错误出现时终止。

第四种终止进程的原因是,某个进程执行一个系统调用通知操作系统杀死某个共他进程。在UNIX 中,这个系统调用是kill。在Win32中对应的函数是TerminateProcess。在这两种情形中,“杀手”都必 须获得确定的投权以便进行动作。在有些系统中,当一个进程终止时,不论是自愿的还是其他原因,由该进程所创建的所有进程也一律立即被杀死。不过,UNIX和Windows都不是这种工作方式。

进程的状态

重要的是要认识到: 一次只有一个进程可在CPU上运行。

尽管一个进程是一个独立的实体,有其自己的程序计数器和内部状态,但进程之间经常需要相互协作。一个进程的结果可能作为另一个进程的输入。在shell命令: cat chapter1 chapter2 chapter3 | grep tree中,第一个进程运行cat,将三个文件连接并输出。第二个进程运行grep,它从输入中选择所有包含单词tree的那些行。根据这两个进程的相对速度(这取决于这两个进程的相对复杂度和各自所分配到的CPU时间),可能会发生这种情况: grep准备就绪可以运行,但输入还没有完成。于是必须阻塞grep,直到输入到来。

进程的三种基本状态

进程在生命结束前,处于且仅处于三种基本状态之一。

  • 1、运行态(该时刻进程实际占用CPU)
  • 2、就绪态(可运行,但因为其他进程正在运行而暂时停止)
  • 3、阻塞态(除非某种外部事件发生,否则进程不能执行)。I/O操作完成前,就处于这种状态。

进程的三种状态之间有四种可能的转换关系。

1.png

  • 运行->阻塞。操作系统发现进程不能继续运行下去时,发生转换1。在UNIX,当一个进程从管道或设备文件(例如终端)读取数据时,如果没有有效输入存在,则进程会被自动阻塞。可能请求了一个无法立即得到的资源,如文件或虚拟内存中的共享区域。也可能需要进行某些初始化的工作,如I/O操作所遇到的情况,并且只有在该初始化动作完成后才能继续运行。
  • 转换2和3是由进程调度程序引起的,进程调度程序是操作系统的一部分,进程甚至感觉不到调度程序的存在。
  • 运行->就绪。系统认为一个运行进程占用处理器的时间已经过长,决定让其他进程使用CPU时间时,会生生转换2。
  • 就绪->运行。在系统已经让所有其他进程享有了它们应有的公平待遇而重新轮到第一个进程再次占用CPU运行时、会发生转换3。还有很多其他原因,例如操作系统给不同的进程分配不同的优先级。假设, 进程A在一个给定的优先级运行,且具有更高优先级的进程B正处于阻塞态。如果操作系统知道进程B等待的事件已经发生了,则将B进程转换到就绪态。然后因为优先级的原因中断进程A的执行,将CPU分配给进程B,我们说操作系统抢占了进程A。
  • 调度程序的主要工作就是决定应当运行哪个进程、何时运行及它应该运行多长时间,这是很重要的一点,我们将在本章的后面部分进行讨论。已经提出了许多算法,这些算法力图在整体效率和进程的竞争公平性之间取得平衡。我们将稍后部分研究其中的一些问题。
  • 阻塞->就绪。当进程等待的一个外部事件发生时(如一些输入到达),则发生转换4。如果此时没有其他进程运行。 则立即触发转换了,该进程便开始运行。否则该进程将处于就绪态,等待CPU空闲井且轮到它运行。

进程的五种状态模型

继续添加其他两种状态。

  • 4、创建态(刚刚创建的进程,操作系统还没有把它加入到可执行进程组中。通常是进程控制块已经创建但还没有加载到内存中的新进程)
  • 5、退出态,也称终止态(操作系统从可执行进程组中释放出的进程,或者是因为它自身停止了,或者是因为某种原因被取消)

1.png

  • 空->新建。创建执行一个程序的新进程。
  • 运行->退出。如果当前正在运行的进程表示自己已经完成或取消,则它会被操作系统终止。
  • 就绪->退出。为了清楚起见, 状态图中没有表示这种转换。在某些系统中,父进程可以在任何时刻终止一个子进程。如果一个父进程终止时,与该进程相关的所有进程都将被终止。

可能出现的排队规则,有两个队列: 就绪队列和阻塞队列。

单一阻塞队列 当操作系统选择另一个进程运行时,将从就绪队列中选择。对于没有优先级的方案,这可以是一个简单的先进先出队列。当一个正在运行的进程被移出处理器时,它根据情况或者或者被终止,或者被放置在就绪或阻塞队列中。最后,当一个事件发生时,所有位于阻塞队列中等待这个事件的进程都被转换到就绪队列中。

1.png

多条阻塞队列 另一种方案,意味者当一个事件发生时,操作系统必须扫描整个阻塞队列,搜索那些等待该事件的进程。在大型操作系统中,队列中可能有儿百甚至几千个进程,因此,拥有多个队列将会很有效,一个事件可以对应一个队列。那么,当事件发生时,相应事件中的所有进程都转换到就绪态。

1.png

最后还有一种改进方案是,按照优先级方案分派进驻,维护多个就绪队列(每个优先级一个队列),将会带来更多便利。操作系统可以很容易地确定哪个就绪进程具有最高的优先级且等待时间最长。

上下文切换(进程切换)

切换CPU到另一个进程需要保存当前进程状态和恢复另一个进程的状态,这个任务称为上下文切换

从表面看,进程切换的功能是很简单的。在任一时刻,一个正在运行的进程被中断,操作系统指定另一个进程为运行态,并把控制权交给这个进程。但是这会引发若干问题。首先,什么事件触发进程的切换?另一个问题是必须认识到模式切换进程切换之间的区别。最后,为实现进程切换,操作系统必须对它控制的各种数据结构做些什么?

何时切换迸程

进程切换可以在操作系统从当前正在运行的进程中获得控制权的任何时刻发生。可能把控制权交给操作系统的事件如下图。

1.png

首先考虑系统中断。实际上,大多数操作系统区分两种类型的系统中断。一种称为中断,另一种称为陷阱。前者与当前正在运行的进程无关的某种类型的外部事件相关,如完成一次IO操作; 后者与当前正在运行的进程所产生的错误或异常条件相关,如非法的文件访问。对于普通中断,控制首先转移给中断处理器,它做一些基本的辅助工作,然后转到与已经发生的特定类型的中断相关的操作系统例程。参见以下例子:

时钟中断:操作系统确定当前正在运行的进程的执行时间是否已经超过了最大允许时间段(时间片,即进程在被中断前可以执行的最大时间段),如果超过了,进程必须切换到就绪态,调入另一个进程。

I/O中断:操作系统确定是否发生了I/O活动。如果I/O活动是一个或多个进程正在等待的事件,操作系统就把所有相应的阻塞态进程转换到就绪态(阻塞/挂起态进程转换到就绪/挂起态),操作系统必须决定是维续执行当前处于运行态的进程,还是让具有高优先级的就绪态进程抢占这个进程。

内存失效:处理器访问一个虚拟内存地址,且此地址单元不在内存中时,操作系统必须从外存中把包含这个引用的内在块(页或块)调入内存中。在发出调人内存块的I/O请求之后,操作系统可能会执行一个进程切换,以恢复另一个进程的执行,发生内存失效的进程被置为阻塞态,当想要的块调入内存中时,该进程被置为就绪态。

对于陷阱、操作系统确定错误或异常条件是否是致命的。如果是,当前正在运行的进程被转换到退出态,并发生进程切换;如果不是,操作系统的动作取决于错误的种类和操作系统的设计,其行为可以是试图恢复或通知用户,操作系统可能会进行一次进程切换或者继续执行当前正在运行的进程。

最后,操作系统可能被来自正在执行的程序的系统调用激活。例如,一个用户进程正在运行,并且正在执行一条请求 I/O 操作的指令,如打开文件,这个调用导致转移到作为操作系统代码一部分的一个例程上执行。通常,使用系统调用会导致把用户进程置为阻塞态。

模式切换

在中断阶段,处理器检查是否发生了中断,这通过中断信号来表示。如果没有未处理的中断,处理器继续执行取指令周期,即取当前进程的下一条指令。如果存在一个未处理的中断,处理器需要做如下工作:

  • 1、把程序计数器置成中断处理程序的开始地址。
  • 2、从用户态切换到内核态,使得中断处理代码可以包含有特权的指令。

处理器现在继续取指阶段,并取中断处理程序的第一条指令,它将给中断提供服务。此时,被中断的进程上下文保存在被中断程序的进程控制块中。

现在的问题是,保存的上下文环境包括什么?答案是它必须包括所有中断处理可能改变的信息和恢复被中断程序时所需要的信息。因此.必须保存称做处理器状态信息的进程控制块部分,这包括程序计数器、其他处理器寄存器和栈信息。

还需要做些其他工作吗?这取决于下一步会发生什么。中断处理程序通常是执行一些与中断相关的基本任务的小程序。例如,它重置表示出现中断的标志或指示器。可能给产生中断的实体I/O模块发送应答。它还做一些与产生中断的事件结果相关的基本辅助工作。例如,如果中断I/O事件有关,中断处理程序将检查错误条件:如果发生了错设.中断处理程序给最初请求I/O操作的进程发一个信号。如果是时钟中断。处理程序将控制移交给分派器,当分配给当前正在运行进程的时间片用尽时,分派器将控制转移给另一个进程。

进程控制块中的其他信息如何处理?如果中断之后是切换到分一个应用程序,则需要做一些工作。但是,在大多数操作系统中,中断的发生井不足必须伴随着进程切换的。可能是中断处理器执行之后,当能正在运行的进程继续执行。在这种情况下,所需要做的是当中断发生时保存处理器状态信息,当控制返回给这个程序时恢复这些信息,在典型情况下,保存和恢复功能由硬件实现。

进程状态的变化

显然,模式切换与进程切换是不同的。发生模式切换可以不改变正处于运行态的进程状态。

在这种情况下,保存上下文环境和以后恢复上下文环境只需要很少的开销。但是,如果当前正运行的进程被转换到另一个状态(就绪、阻塞等),则操作系统必须使其环境产生实质性的变化。 完整的进程切换步骤如下:

  • 1、保存处理器上下文环境,包括程序计数器和其他寄存器。
  • 2、更新当前处于运行态进程的进程控制块,包括将进程的状态改变到另一状态(就绪态/阻塞态、就绪/挂起态或退出态)。还必须更新其他相关城,包括离开运行态的原因和记账信息)
  • 3、將进程的进程控制块移到相应的队列(就绪、在事件i处阻塞、就绪/挂起)。
  • 4、选择另一个进程执行
  • 5、更新所选择进程的进程控制块,包括將进程的状态变为运行态。
  • 6、更新内存管理的数据结构,这取决于如何管理地址转换。
  • 7、恢复处理器在被选择的进程最近一次切换出运行状态时的上下文环境,这可以通过载入程序计数器和其他寄存器以前的值来实现。

因此,进程切换涉及状态变化,因而比模式切换需要做更多的工作。

1.png

参考资料

进程线程

进程和线程

Chapter7 进程和线程

进程管理与单处理器调度