进程管理(一)

进程概述

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

进程状态:

简而言之,进程可以处于以下3 种状态之一:

  1. 运行(running):在运行状态下,进程正在处理器上运行。这意味着它正在执行
    指令。
  2. 就绪(ready):在就绪状态下,进程已准备好运行,但由于某种原因,操作系统
    选择不在此时运行。
  3. 阻塞(blocked):在阻塞状态下,一个进程执行了某种操作,直到发生其他事件
    时才会准备运行。一个常见的例子是,当进程向磁盘发起I/O 请求时,它会被阻塞,
    因此其他进程可以使用处理器。
image-20230316220801940

虚拟化CPU

虽然只有少量的物理CPU 可用,但是操作系统如何提供几乎有无数个CPU 可用的假象?

操作系统通过虚拟化(virtualizing)CPU 来提供这种假象。通过让一个进程只运行一个时间片,然后切换到其他进程,操作系统提供了存在多个虚拟CPU 的假象。这就是时分共享(time sharing)CPU 技术,允许用户如愿运行多个并发进程。潜在的开销就是性能损失,因为如果CPU 必须共享,每个进程的运行就会慢一点。

要实现CPU 的虚拟化,要实现得好,操作系统就需要一些低级机制以及一些高级智能。我们将低级机制称为机制(mechanism)。机制是一些低级方法或协议,实现了所需的功能。例如,我们稍后将学习如何实现上下文切换(context switch),它让操作系统能够停止运行一个程序,并开始在给定的CPU 上运行另一个程序。所有现代操作系统都采用了这种分时机制。

时分共享(time sharing)是操作系统共享资源所使用的最基本的技术之一。通过允许资源由一个实体使用一小段时间,然后由另一个实体使用一小段时间,如此下去,所谓的资源(例如,CPU 或网络链接)可以被许多人共享。时分共享的自然对应技术是空分共享,资源在空间上被划分给希望使用它的人。例如,磁盘空间自然是一个空分共享资源,因为一旦将块分配给文件,在用户删除文件之前,不可能将它分配给其他文件。

受限直接执行

在构建这样的虚拟化机制时存在一些挑战:

  1. 第一个是性能:如何在不增加系统开销的情况下实现虚拟化?
  2. 第二个是控制权:如何有效地运行进程,同时保留对CPU 的控制?控制权对于操作系统尤为重要,因为操作系统负责资源管理。如果没有控制权,一个进程可以简单地无限制运行并接管机器,或访问没有权限的信息。

因此,在保持控制权的同时获得高性能,这是构建操作系统的主要挑战之一。

解决方案:

为了使程序尽可能快地运行,操作系统开发人员想出了一种技术——我们称之为受限的直接执行(limited direct execution)。这个概念的“直接执行”部分很简单:只需直接在CPU上运行程序即可。

因此,当OS 希望启动程序运行时,它会在进程列表中为其创建一个进程条目,为其分配一些内存,将程序代码(从磁盘)加载到内存中,找到入口点(main()函数或类似的),跳转到那里,并开始运行用户的代码。

但是,这种方法在我们的虚拟化CPU 时产生了一些问题。

  • 第一个问题很简单:如果我们只运行一个程序,操作系统怎么能确保程序不做任何我们不希望它做的事,同时仍然高效地运行它?
  • 第二个问题:当我们运行一个进程时,操作系统如何让它停下来并切换到另一个进程,从而实现虚拟化CPU 所需的时分共享?

问题1:受限制的操作:

硬件通过提供不同的执行模式来协助操作系统。在用户模式(user mode)下,应用程序不能完全访问硬件资源。在内核模式(kernel mode)下,操作系统可以访问机器的全部资源。还提供了陷入(trap)内核和从陷阱返回(return-from-trap)到用户模式程序的特别说明,以及一些指令,让操作系统告诉硬件陷阱表(trap table)在内存中的位置。

问题2:在进程之间切换:

如果操作系统没有在CPU 上运行,那么操作系统显然没有办法采取行动。因此,我们遇到了关键问题:如何重获CPU 的控制权

协作方式:等待系统调用

在协作调度系统中,OS 通过等待系统调用,或某种非法操作发生,从而重新获得CPU 的控制权。

非协作方式:操作系统进行控制

事实证明,没有硬件的额外帮助,如果进程拒绝进行系统调用(也不出错),从而将控制权交还给操作系统,那么操作系统无法做任何事情。事实上,在协作方式中,当进程陷入无限循环时,唯一的办法就是使用古老的解决方案来解决计算机系统中的所有问题——重新启动计算机。

解决方案:时钟中断

时钟设备可以编程为每隔几毫秒产生一次中断。产生中断时,当前正在运行的进程停止,操作系统中预先配置的中断处理程序(interrupt handler)会运行。此时,操作系统重新获得CPU 的控制权,因此可以做它想做的事:停止当前进程,并启动另一个进程。

保存和恢复上下文

上下文切换在概念上很简单:操作系统要做的就是为当前正在执行的进程保存一些寄存器的值(例如,到它的内核栈),并为即将执行的进程恢复一些寄存器的值(从它的内核栈)。这样一来,操作系统就可以确保最后执行从陷阱返回指令时,不是返回到之前运行的进程,而是继续执行另一个进程。

为了保存当前正在运行的进程的上下文,操作系统会执行一些底层汇编代码,来保存通用寄存器、程序计数器,以及当前正在运行的进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。通过切换栈,内核在进入切换代码调用时,是一个进程(被中断的进程)的上下文,在返回时,是另一进程(即将执行的进程)的上下文。当操作系统最终执行从陷阱返回指令时,即将执行的进程变成了当前运行的进程。至此上下文切换完成。