8.3 进程与上下文
Processes(进程)
进程就是一个正在执行的程序实例,当我们在shell中输入一个可执行程序的名字后,然后按下回车键,此时shell就会创建一个新的进程,然后处理器运行这个可执行程序。我们现在主要关心的是进程提供给应用程序的抽象。在现代系统上运行一个程序时,我们会得到一个假象。好像是我们的程序独占了整个处理器,处理器在不间断的执行程序指令。还有一个假象,就是我们程序中的代码和数据好像是系统内存中唯一的对象。这些假象都是通过进程的方式提供给程序的。
- program has exclusive use of processor(程序独占的使用处理器)
- program has exclusive use of the memory system (程序独占的使用内存系统)
Logical Control Flow(逻辑控制流)
接下来,我们先来解释一个概念--逻辑控制流。假如我们用调试器来控制程序单步执行,我们会看到一系列的程序计数器的数值。这些数值与可执行程序中的指令是一一对应的。我们把这个PC值的蓄力叫做逻辑控制流,简称逻辑流。假设我们的系统中运行着三个进程,分别是进程A,进程B以及进程C。对于一个单核处理器,他在某一个时间段内只能执行一个进程。因此,处理器的物理控制流被分成了三个逻辑流,每个进程一个。每个竖直的线表示一个进程的逻辑控制流的一部分,在这个例子中,三个进程的执行是交错进行的,进程A执行一会,然后进程B开始执行,一直到结束。接下来进程C开始执行,运行一段时间后,进程A开始执行,一直到结束。最后进程C运行到结束。下面的图描述了不同进程之间轮流的使用处理器的情况。每个进程执行逻辑流的一部分,我们把一个逻辑流的执行在时间上与另一个流重叠的情况,称为并发流。这两个流的执行被称为并发运行。例如,进程A和进程B是并发运行的,同样A和C也是。不过进程B和进程C并不是并发运行的。这是因为进程C开始运行之前进程B执行结束了,所以二者不能称为并发。
并行是两个进程在不同的处理器核上同时运行。因此,对于二者的区别是:并发是交替运行的,而不是同时运行的。除此之外,进程也为每个程序提供了另外一种假象,好像是程序本身独占整个系统的地址空间,下图中展示了一个x86-64 Linux进程的地址空间分布,地址空间的低地址部分是预留给应用程序的,包括代码段、数据段、堆和栈。
代码段总是从地址 0x400000 处开始,地址空间的高地址是预留给操作系统内核的,属于用户代码不可见的内存区域。在Linux系统的根目录下有一个 /proc 的文件夹,这个文件夹下的文件是用户程序可以读取的,这些文件记录的是内核相关的数据结构,例如我们可以使用 cat cpuinfo 这条命令查看CPU的信息。因此,通过这种方式用户模式下的进程也能访问内核数据结构的内容。
以上就是地址空间的相关内容。
User and Kernel Modes
为了限制应用程序执行某些特殊的指令以及限制可以访问的地址空间范围,通常处理器通过控制寄存器的模式位来实现这些限制功能,该寄存器描述了进程当前的权限,也就是说当设置了控制寄存器的模式位后,进程就运行在内核模式,有的地方把内核模式也叫做超级用户模式。对于一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中任意的内存位置,如果没有设置模式位,进程就运行在用户模式,处于用户模式的进程不允许执行特权指令。特权指令可以停止处理器、改变模式位以及发起一个 I/O 操作等。除此之外,用户模式的进程也不能直接引用内核区域的代码和数据。如果用户程序试图访问内核区域,就会导致保护故障,然后该用户程序将终止运行。不过用户程序可以通过系统调用间接的来访问内核的代码和数据。通常情况下,应用程序在一开始是运行在用户模式下,进程用用户模式切换到内核模式需要通过中断、故障或者系统调用的方式。当这类异常发生时,执行异常处理程序处理异常,处理器的模式就会从用户模式变为内核模式。当返回到应用程序继续执行时,会从内核模式该回到用户模式。
以上就是用户模式以及内核模式的相关内容。
Context(上下文)
内核为每一个进程维持了一个上下文,上下文就是内容重新启动一个被抢占的进程所需的状态,踏实一些对象的值组成。这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。其中内核数据结构包括描述地址空间的页表,包含有关当前进程信息的进程表以及包含进程已经打开文件的信息表。在进程执行的某些时刻,内核可以决定抢占当前进程,然后重新开始执行先前被抢占的进程。
- general-purpose registers floating-point registers
- program counter user's stack status register
- kernel's stack various kernel data structures
这种决策称为进程调度,是由内核中的调度器来执行的。当内核选择一个新的进程运行时,我们就说内核调度了这个进程,当内核调度了一个新的进程运行后,他就抢占当前进程,并使用一种称为上下文切换的机制来控制转到新的进程。
Context Switch
上下文切换主要分为三步:第一,保存当前进程的上下文;第二,恢复某个先前被强占的进程的上下文;第三,将控制传递给这个新恢复的进程。当内核代表用户程序执行系统调用时,可能会发生上下文的切换。
- Saves the context of the current process(保存当前进程的上下文)
- Restores the saved context of some previously preempted process(恢复某个先前被抢占进程的上下文)
- Passes control to this newly restored process(将控制传递给这个新恢复的进程)
当内核代表用户程序执行系统调用时,可能会发生上下文的切换。
Process Context Switching
中断也可能引发上下文的切换,下图展示了进程A和B之间上下文切换的示例。最开始进程A运行在用户模式下,当进程A需要从磁盘中读取数据时,它会执行系统调用函数 read 陷入到内核中,然后陷阱处理程序向磁盘控制器发起一个 DMA 的请求。并且安排磁盘控制器完成从磁盘到内存的数据传输之后,磁盘需要向处理器发送中断,磁盘读取数据需要一段较长的事件,一般为几十毫秒。对于处理器来说,几十毫秒的时间是非常长的,所以内核执行从进程A到进程B的上下文切换,而不是等着什么都不做。随后,进程B在用户模式下运行,直到磁盘发出一个中断的信号,表示数据已经从磁盘传送到了内存,内核判定进程B已经运行了足够长的时间,就执行一个从进程B到进程A的上下文切换。将控制返回给进程A中紧跟在系统调用 read 之后的那条指令。然后进程A继续执行,直到下一次异常发生。
以上就是进程A和进程B进行上下文切换的具体过程。

浙公网安备 33010602011771号