Skip to content

L08 进程与异常控制流

一个程序的正常执行流程为顺序加跳转,CPU 所执行的指令地址序列称为「CPU 控制流」,按正常执行流程得到的控制流称为「正常控制流」。

由特殊事件引起用户程序正常执行被打断所形成的意外控制流称为「异常控制流」(ECF, exceptional control of flow),显然,计算机系统必须提供一种机制使自身能够实现异常控制流。

计算机系统各层都有实现异常控制流的机制:

  • 硬件层:CPU 可检测异常和中断事件并将控制转移到操作系统内核执行。
  • 操作系统层:内核通过进程的上下文切换将控制流从一个进程切换到另一个进程。
  • 应用软件层:一个进程可直接发送信号到另一个进程,接收信号的进程将控制转移到它注册的「信号处理程序」。

进程与进程的上下文切换

程序和进程的概念

对计算机来说,「程序」(program)就是代码和数据的集合,因而程序的概念是静态的。

「进程」(process)则是程序的一次运行过程,是操作系统对程序运行过程的一种抽象,是动态的概念,它有自己的生命周期。

计算机系统中的「任务」通常是指「进程」。Linux 内核中把进程称为任务,每个进程主要通过一个称为「进程控制块」或「进程描述符」(process descriptor)的结构来描述,其结构类型定义为 task_struct,所有进程通过一个双向循环链表来实现的「任务列表」(task list)来描述,其元素为一个进程描述符。IA32 中「任务状态段」(TSS)、「任务门」(task gate)等概念中的任务,其实页式进程。

进程的引入为应用程序提供了两方面的抽象:一个独立的逻辑控制流和一个私有的虚拟地址空间。

为了实现上述两方面的抽象,操作系统必须提供一整套管理的机制,包括「处理器调度」、「进程上下文切换」、「虚拟存储管理」。

进程的逻辑控制流

对于一个仅有单处理器核的系统,若一段时间内有多个进程在该系统上运行,则它们会轮流使用处理器,即处理器的「物理控制流」由多个逻辑控制流交织组成。

不同进程的逻辑控制流在时间上交错,这种情况称为「并发执行」,并发执行概念和处理器核数无关。

注意:两个逻辑控制流在时间上交错或重叠都称为「并发」(concurrency),但在时间上重叠称为「并行」(parallelism),并行是并发的一种特殊情形。

进程的上下文切换

连续执行同一个进程的时间段称为「时间片」(time slice)。

一个进程的逻辑控制流不会因为中间被其他进程打断而改变,被打断后还能回到被打断的「断点」处继续执行,这种实现不同进程中指令交替执行的机制称为进程的「上下文切换」(context switching)。

操作系统通过进程的上下文切换,换一个新的进程到处理器上执行,并开始一个新的时间片,这个过程称为「时间片轮转处理器调度」。

进程的代码、数据和支撑进程运行的环境合称为「进程的上下文」。由用户进程的代码、用户进程数据、运行时的堆和用户栈(通称为「用户堆栈」)等组成的「用户空间信息」称为「用户级上下文」。由进程标识信息、进程现场信息、进程控制信息和系统内核栈等组成的「内核空间信息」称为「系统级上下文」。

进程的上下文包括用户级上下文和系统级上下文。其中用户级上下文和系统级上下文一起构成了进程的整个存储器映像,即进程的虚拟地址空间,见下图:

【这里有一张图,图 8.2】

「进程控制信息」包含各种内核数据结构,例如「进程表」(process table)、「页表」、「打开文件列表」等。

处理器中各个寄存器的内容称为「寄存器上下文」(也称为「硬件上下文」)。操作系统需要通过上下文切换调度一个新进程到处理器上运行,具体过程如下:

  • 将当前寄存器上下文保存到当前进程系统级上下文的现场信息中。
  • 根据新进程系统级上下文中的现场信息恢复寄存器上下文。
  • 将控制转移到新进程执行。

这里,一个重要的上下文信息是 PC 值,其作为上下文信息的一部分保存在进程现场信息中。

【这里有一个例子】

【这里有一张图,图 8.3】

为了准确统计每个进程运行的事件,操作系统将进程在用户态运行的事件称为其「用户时间」(user time),将进程在内核态运行的事件称为其「系统时间」(system time),两者之和为其「实际时间」(real time)或「挂钟时间」(wall clock time)。

系统中由多个进程并发执行时,操作系统内核通常通过某种算法策略决定在哪个时间点进行进程的换上换下操作,这称作「处理器调度」(scheduling),由内核中的「调度程序」(schedular)进行处理。

异常和中断

除了前文提到的时间片轮转处理器调度外,进程还可能被别的特殊事件打断,其统称为「异常」(exception)或「中断」(interrupt)。

当发生异常或中断时,CPU 专区执行具体的内核程序来处理这些特殊事件。

异常和中断两个概念定义各家有各家的说法。本书使用 Intel 体系结构规定的两者概念。

早期 Intel 8086/8088 微处理器并不区分二者而统称中断,CPU 内部产生的意外事件称为「内中断」,从 CPU 外部通过中断请求引脚 INTR 和 NMI 向 CPU 发出的中断请求为「外中断」。

但 80286 开始,Intel 统一把内中断称为异常,外中断称为中断。

IA32 架构说明文档中,Intel 作出如下说明:

  • 中断是一种由 I/O 设备触发的与当前正在执行的指令无关的典型「异步事件」。
  • 异常是处理器执行一条指令时,由处理器在其内部检测到的、与正在执行的指令相关的「同步事件」。

有时为强调,称异常为「内部异常」,中断为「外部中断」。

内部异常

「内部异常 」是由 CPU 内部的异常引起的意外事件。

根据其发生的原因又分为「硬故障中断」和「程序性异常」。

  • 「硬故障中断」是由于硬连线路出现异常而引起的,如主存校验线路错等。
  • 「程序性异常」由 CPU 执行某指令而引起的发生在 CPU 内部的异常事件。

程序性异常包括「除数为 0」、「结果溢出」、「寻址错」、「访问超时」、「非法操作码」、「栈溢出」、「缺页」、「地址越界(段错误)」等。

此外还有一种异常称为「陷阱」,其和其他异常事件不同,是预先安排的一种异常事件,如「系统调用」、「单步跟踪调试」、「调试断点设置」等都可以通过陷阱机制实现。

外部中断

程序执行过程中,若外设完成任务或发生某些特殊事件,例如:「打印机缺纸」、「定时采样计数时间到」、「键盘缓冲区已满」、「从网络中接收到一个信息包」、「从磁盘读入一块数据」等。

【这里有一张图,图 8.4】

上图展示了中断和异常处理过程。

异常的分类

通常将内部分为三类:「故障」(fault)、「陷阱」(trap)和「终止」(abort)。

故障

「故障」是 CPU 在执行指令过程中检测到的一类与指令执行相关的意外事件,有些可以恢复,有些不可以恢复。例如「非法操作码」、「除数为零」、「页故障」:

  • 非法操作码:一般通过信号机制在屏幕上说明,并调用 abort 例程终止当前进程。
  • 除数为零:浮点数返回非规格数,整数除以零一般调用 abort 例程终止当前进程。
  • 页故障:地址越界或访问越权,不可恢复,终止;若是缺页,从硬盘读入页以恢复。在 Linux 中不可恢复的访存故障称为「段故障」。

陷阱

「陷阱」也称「自陷」或「陷入」,是预先安排的异常。

「系统调用号」、「单步跟踪状态」:asd

终止

执行指令过程中发生了严重错误,则只能终止当前程序。

中断的分类

「中断请求」是有 CPU 外部的 I/O 设备需要 CPU 进行某种处理时发出的一种请求信号。

通常,在「中断响应周期」中,CPU 将当前 PC 值(称为「断点」)和当前的及其状态保存到栈或特定寄存器中,并切换至「关中断」状态,然后跳转到统一的中断服务程序执行。

中断响应过程由硬件完成,具体的中断处理工作由 CPU 执行统一的中断服务程序完成,包括读取「中断类型号」,并根据中断类型号跳转到具体的服务执行。中断处理完成后,再回到被打断程序的断点处继续执行。

Intel 将中断分为「可屏蔽中断」(maskable interrupt)和「不可屏蔽中断」(non-maskable interrupt)。

可屏蔽中断

「可屏蔽中断」是指通过「可屏蔽中断请求线」INTR 向 CPU 请求的中断,主要来自 I/O 设备的中断请求。

不可屏蔽中断

「不可屏蔽中断」通常由非常紧急的硬件故障引起,通过专门的「不可屏蔽中断请求线」NMI 向 CPU 发出中断请求。

异常和中断的响应

CPU 对异常和中断的响应过程可分为三个步骤:「保护断点和程序状态」、「关中断」、「识别异常和中断事件并转到相应的处理程序」。

保护断点和程序状态

为了支持异常/中断的嵌套处理,CISC 处理器将断点保存在栈中。

异常/中断处理后可能恢复执行,因此必须保存并恢复被中断时源程序的状态(如产生的各种信息、允许中断标志等)。

每个正在运行程序的状态信息称为「程序状态字」(PSW, program status word),通常存放在「程序状态字寄存器」(PSWR, program status word register),如在 IA32 中 PSWR 就是 EFLAGS.

显然 PSW 也要保存到栈或特定寄存器中。

关中断

如果中断在保存现场过程中又发生了新的中断,那么就会因为要处理新中断而破坏原现场,这种新的中断不应被响应。

因此引入了「中断使能位」,置一称为「开中断」,置零称为「关中断」,IA32 中中断使能位就是 EFLAGS 寄存器中的中断标志位 IF.

识别异常和中断事件并转到相应的处理程序

在转到相应的处理程序前,我们应得知异常/中断请求源。

内部异常事件的识别很简单,CPU 会给出异常事件相关的信息。

外部中断源的识别比较复杂,通常是由「中断控制器」根据 I/O 设备的中断请求和中断屏蔽情况,集合中断响应优先级来识别当前请求的中断类型号,并通过数据总线送 CPU. 详细内容参见 Ch09.4.5

IA-32/x86-64 + Linux 的异常和中断机制*

中断向量表和中断描述符表

异常和中断的处理

IDT 的初始化

对异常的处理

对中断的处理

系统调用机制

通过软中断指令进入和推出系统调用

通过快速系统调用指令进入和推出系统调用

Linux 中的进程控制*

Linux 中的信号与非本地跳转*

章节小结