操作系统之进程·概论

进程介绍

进程是操作系统提供了最古老也是最重要的抽象概念之一,他们将一个单独的cpu变换成多个虚拟的cpu,使得每个程序以为自己独自占用所有的计算机资源。进程的传统定义是:对正在运行程序的抽象。进程是程序的活动,有输入、输出以及状态,一个进程就是一个正在执行程序的实例。包括程序计数器、寄存器。

不过随着计算机的发展,多线程的出现,计算机实际运行和调度的对象的不再是进程。现代进程逻辑上不再保存当前运行的程序的状态,例如程序计数器、寄存器、堆栈指针等。这些和运行中的程序相关的保存在线程中,当然进程保存线程共享的数据,如内存信息、文件描述符。进程更多的表示多个线程的公共资源,所以以前的定义越来越不准确。进程现代的定义是:进程是程序的容器。进程不再是运行的程序,真正运行在cpu上的程序序列是线程。不过linux关于线程的实现很特别,以上的概念不完全适用于linux系统。

 

 

进程的创建和撤销

操作系统需要有一个方式来创建进程,一些简单的系统,在启动时,以后所有需要运行的进程都已存在,(比如空调中的系统),不过在通用的系统中,需要某种方法创建和撤销进程

会有几种情况导致进程的创建:

  1. 系统的初始化
  2. 正在运行的程序执行创建进程的系统调用
  3. 用户请求打开一个程序

操作系统启动时,会创建一些进程,一些是前台进程,一般与用户交互并且完成工作。其他是后台进程,这些于特定的用户无关,一般具有专门的功能。如web服务器,接受访问请求。linux启动时,会建立一个init进程,进程id为1,负责启动其他所有的进程。

新的进程也可以以后创建,一个正在运行的程序发出一个创建进程的系统调用,创建一个新的进程协助其工作。用户打开一个程序本质上也是一个和用户交互的程序执行创建进程。例如linux的shell程序接收命令后创建一个子进程。

在交互式系统中,用户通过双击或者命令可以启动一个程序,这个动作会开始一个新的进程,并在其中运行所选择的程序。


进程创建之后,开始运行,完成其工作。但永恒是不存在的,爱情也是如此,迟早会结束,一般有以下一个原因:

  1. 正常退出
  2. 错误退出
  3. 被其他进程杀死
  4. 宕机

多数进程是由于完成了他们的工作而终止,程序在完成工作后执行一个系统调用,通知系统他们的工作已经完成。在《现代操作系统》中,退出原因有个出错退出,比如程序接受到的参数不符合程序的要求,或者数据库访问失败,程序打印错误后,执行退出进程系统调用。如图:

我个人把出错退出归类到正常退出中,因为这些错误是程序有预见的,并且进行了错误处理,然后主动选择退出了。而且这些错误都是由程序自己规定的错误,比如上图中我在程序中没有规定updaddd是个合法的输入参数。或者规定数据库访问失败时选择退出而不是其他选择。

  一些进程在运行时引起了系统规定的错误,比如访问了不该访问了地址,执行了无权执行的指令,除零错误等。一般情况系统会直接终止进程,但有些操作系统中,进程可以通知系统,希望自己来处理某些类型的错误,在发生这些错误时,进程会收到信号,而不是直接终止进程。

进程也可以执行一个系统调用终止其他进程,在unix中,这个系统调用时kill,windows中对应的函数是TerminateProcess。

 

进程状态

每个进程是一个独立的实体,有其自己的内部状态。当一个进程在逻辑上不能继续运行时,它就会阻塞,例如等待可以使用的输入。还有另一种情况:一个概念上能运行的进程被迫停止,因为操作系统调度另一个进程占用了CPU。这两种情况完全不同。第一中情况是程序自身的原因,第二种则是系统技术上的原因引起的。

《现代操作系统》

如图显示进程的三种状态:

  1. 运行态(进程实际占用cpu)
  2. 就绪态(可运行,但因为其他进程在运行而暂时停止)
  3. 阻塞态(等待某些外部事件的发生,否则进程不能运行)

前两种在逻辑上是类似的。处于中两种状态的进程都可以运行,只是对于第二种状态暂时没有分配给它CPU。第三种状态与前两种完全不同,处于该状态的进程即便CPU空闲也无法运行。

进程的三种状态如图有四种转换类型:

进程发生阻塞,系统判断进程无法进程下去,发生1转换。

进程运行一段时间后,系统认为该让其它进程运行,发生转换2。同时系统选择一个就绪的进程分配给它CPU,发生转换3。转换2、3都是调用程序引起的。

当发生一个外部事件,产生一个中断,在阻塞队列的一个进程可能状态就变为了就绪状态。

但是转换5也是有可能发生的,即阻塞态到运行态的转换,这取决于具体系统的调度程序的实现。发生中断后,中断服务程序结束后,是选择立即重启被阻塞的进程,还是继续运行当前进程,还是选择其他进程,取决于调度程序。

进程的实现

为了实现进程模型,操作系统维护一张表格,即进程表。该表项包含了进程状态的重要信息,包括程序计数器、堆栈指针、内存分配情况、打开的文件信息、账号和调度信息,还有进程由运行态转换到就绪态或阻塞态必须保存的信息,从而保证该进程随后能再次启动,就像从未被中断过一样。

不过随着现代系统普遍引进线程的概念,已经操作系统对内核线程的实现,一些本来保存在进程控制块的信息现在转移到了线程控制块中,如程序计数器、堆栈指针。(这些我们在关于线程的部分在讨论)

与每一个I/O类关联的是一个称作中断向量的列表,它包含了中断服务程序的入口地址。假如一个磁盘发生了中断,用户的进程正在运行,则中断硬件将程序计数器、程序状态字、寄存器压入堆栈,计算机然后跳转到中断向量所指示的地址。以上是硬件完成的工作,然后软件,接管了一切剩余的工作。

中断过程:

  1. 硬件将程序计数器、程序状态字压入堆栈
  2. 硬件从中断向量装入新的程序计数器
  3. 汇编语言过程保存寄存器值,设置新的堆栈
  4. C中断服务例程运行
  5. 调度程序决定下一个将运行的进程
  6. C过程返回至汇编代码,汇编语言过程开始运行新的当前进程

硬件将cpu寄存器信息保存到堆栈后(第一步),硬件跳转到中断服务程序中(第二步)。所有的中断都是从保存寄存器开始的,硬件只是保寄存器临时压入堆栈中,需要中断程序自己进行后续处理。对当前进程而言,通常是保存到进程表项中。随后会从堆栈中删除有中断硬件机制存入堆栈的那部分信息,并将堆栈指针指向一个由进程处理程序所使用的临时堆栈。这些操作无法用C语言这一类高级语言描述,所以这些操作通过一个短小的汇编语言例程来完成,通常该例程可以供所有的中断使用,因为中断程序关于保存寄存器的工作都是完全一样的(第三步)。当该例程完成后,它调用一个C过程处理某个特定中断类型剩下的工作(第四步)。在完成有关工作后一些进程状态可能变成了就绪状态,接着调用调度程序,决定随后运行哪个进程(第五步)。随后将控制转给一段汇编代码,为当前的进程装入寄存器以及内存映射并启动该进程运行(第六步)。

当然各个具体操作系统的某些细节会有所不同。一个进程在执行过程中可能被中断数千次,但关键是每次中断后,被中断的程序都返回到中断发生之前完全相同的状态。

多道程序设计

由于程序在运行过程中,可能会发生很多次阻塞。如果在阻塞发生后系统可以调用另一个进程运行,则可以提高CPU的利用率。多道程序设计就是为了处理这种情况:同时有多个进程在运行,多个进程切换使用cpu。在一些进程阻塞的情况下,就绪的进程可以使用cpu资源,提高了资源的利用率。

参考文献:

【1】《现代操作系统》

【2】《操作系统概念》

【3】《深入理解计算机系统》

猜你喜欢

转载自blog.csdn.net/u013259665/article/details/85152672