计算机系统中的异常 & 中断

转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/705

中断和异常可以归结为一种事件处理机制,通过中断或异常发出一个信号,然后操作系统会打断当前的操作,然后根据信号找到对应的处理程序处理这个中断或异常,处理完毕之后再根据处理结果是否要返回到原程序接着往下执行。

exception2

对于异常和中断不同的书看起来都有不同的定义,但实际上讲的都是同一回事,我这里用 《intel architectures software developer’s manual》这本书里面的定义:

  • An interrupt is an asynchronous event that is typically triggered by an I/O device.
  • An exception is a synchronous event that is generated when the processor detects one or more predefined conditions while executing an instruction. The IA-32 architecture specifies three classes of exceptions: faults,traps, and aborts.

我们可以大致上把中断理解为是一个被 I/O 设备触发的异步事件,例如用户的键盘输入。它是一种电信号,由硬件设备生成,然后通过中断控制器传递给 CPU,CPU 有两个特殊的引脚 NMI 和 INTR 负责接收中断信号。

异常是一个同步的事件,通常由程序的错误产生的,或是由内核必须处理的异常条件产生的,如缺页异常或 syscall 等。异常可以分为错误、陷阱、终止。

上面所说的同步是因为只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间,比如系统调用;而异步意味着中断能够在指令之间发生。

当一个异常或中断发生时,处理器会停止执行当前程序或任务转而去执行专门用于处理中断或异常的程序。处理器会从中断描述符表(IDT)中获取到对应的处理程序,当异常或中断执行完毕之后,会继续回到被中断的程序或任务继续执行。

exception1

在 Linux 中,中断处理程序就是一个 C 函数,只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递处理程序的信息。中断处理程序运行于中断上下文(interrput context)中,该上下文中的执行代码不可阻塞。

在 x86 系统中,提前在 IDT 里定好了 256 种异常(interrupt vector),0~31 的号码对应的是由 Intel 规定的异常,32~255 的号码对应的是操作系统定义的异常:

Vector Mnemonic Description Source
0 #DE Divide Error DIV and IDIV instructions.
13 #GP General Protection Any memory reference and other protection checks.
14 #PF Page Fault Any memory reference.
18 #MC Machine Check Error codes (if any) and source are model dependent.
32-255 Maskable Interrupts External interrupt from INTR pin or INT n instruction.

上面这张表可以在 《intel architectures software developer’s manual》里找到,我上面列举了一些常见的异常,如除零、一般保护异常、缺页、机器检查等。

下面再来看几种高层的异常和中断形式:系统调用、上下文切换、信号等。

系统调用 system call

对于操作系统来说,设定了4个优先等级:

0 提供对所有硬件资源的直接访问。限于最低级别的操作系统功能,如BIOS、内存管理;

1 对硬件资源的访问受到一些限制。可能会被库程序和控制I/O设备的软件使用;

2 对硬件资源的访问更受限制。可能会被控制I/O设备的库程序和软件使用;

3 没有对硬件资源的直接访问。应用程序在这个级别运行;

image-20221001165748907

由于系统的安全性和稳定性,用户空间是无法直接执行系统调用的,需要切换到内核态执行。所以应用程序会通过int $n指令触发一个异常 n 表示上面提到的 IDT 表中的异常序号,老版本的 Linux 内核用 int $0x80指令,触发异常后会导致系统切换到内核态并执行对应的异常处理程序。

在 Linux 上,每个系统调用被赋予了一个系统调用号,内核记录了系统调用表中的所有已注册过的系统调用的列表,存储在 sys_call_table 中。因为所有系统调用陷入内核方式都一样,所以系统调用号会通过 eax 寄存器传递给内核。在陷入内核之前,用户空间就把相应的系统调用所对应的号放入 eax 中。

exception3

所以系统调用执行流程如下:

  1. 应用程序代码调用系统调用( read ),该函数是一个包装系统调用的 库函数

  2. 库函数 ( read )负责准备向内核传递的参数,并触发 异常中断 以切换到内核;

  3. CPU 被 中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call );

  4. 系统调用处理函数 调用 系统调用服务例程 ( sys_read ),真正开始处理该系统调用;

上下文切换 context switch

上下文切换是一种较高层形式的异常控制流,操作系统通过它来实现多任务。也就是说上下文切换实际上是建立在较低的异常机制之上的。

内核通过调度器(scheduler)来控制当前进程是否可以被抢占,如果被抢占那么内核会选择一个新的进程运行,将旧进程的上下文保存起来,并恢复新进程的上下文,然后将控制转交给新进程,这就是上下文切换。

当执行系统调用的时候可能发生上下文切换,例如有个进程因为 read 系统调用访问磁盘发生阻塞,那么可以让当前进程休眠切换到另一个进程;中断也可能发生上下文切换,比如系统内部的周期性的定时器发生中断时,内核觉得当前进程已经运行足够长时间了,并切换到一个新的进程。

进程切换只发生在内核态,在执行进程切换之前,用户态进程使用的所有寄存器内容都已保存在内核态堆栈上。

image-20220918163130135

上图展示的是 A 和 B 进程之间切换的示例。进程 A 初始在用户模式中,直到它通过执行系统调用 read 进入到内核,因为磁盘读取数据需要一定时间,所以内核执行进程 A 到 B 的切换。随后磁盘数据 ready 之后磁盘控制器会发出一个中断信号,表示数据已经从磁盘传送到了内存,那么内核会从内核 B 切换到 A ,并接着执行进程 A 中紧随在系统调用 read 之后的那条指令。

信号

信号是一种软件形式的异常,它允许进程和内核中断其他进程,可以通知进程系统中发生了一个某种类型的事件。每种信号类型都对应某种系统事件。底层的硬件异常是由内核异常处理程序处理的,正常情况下对用户进程是不可见。但是信号可以通知用户进程发生了某个异常,如进程试图除 0 ,那么内核就会发送给它一个 SIGFPE 信号;进程执行非法指令,内核就会发送一个 SIGILL 信号等等。

信号由两个步骤组成,发送信号和接收信号。

exception4

一个发出而没有被接受的信号叫做待处理信号(pending signal)。一个类型至多只会由一个待处理信号,如果一个进程有一个类型为 k 的待处理信号,那么接下来发送到这个进程的类型为 k 的信号都只会被丢弃。

一个进程还可以阻塞接收某种信号。当一个信号被阻塞时,它仍可以被发送,但是不会被接收,知道进程取消对这种信号的阻塞。

内核为每个进程在 pending 位向量中维护着待处理信号集合,在 blocked 位向量中维护被阻塞的信号集合。

进程从内核模式切换到用户模式时,会检查进程中未被阻塞的待处理信号的集合(pending & ~blocked),如果这个集合为空,那么内核将控制传递到进程的下一条指令(I_next);如果是非空,那么内核选择集合中某个信号 k (通常是最小的 k)强制进程接收,然后进程会根据信号触发某种行为,完成之后会回到控制流中执行下一个指令(I_next)。

exception5

如上图,信号处理程序可以被其他信号处理程序中断。

总结

本篇内容主要介绍了计算机系统中是如何通过异常和中断来控制程序的执行。异常控制流发生在计算机系统的各个层次,是计算机系统中提供并发的基本机制。在低层次的硬件层,异常是通过异常控制器通过处理器给发送信号实现异常处理,这样得以形快速的响应 I/O 事件;在操作系统层次上通过异常来使程序陷入内核态,然后进行系统调用;通过中断还可以打断当前的进程执行过程,从而实现进程之间的切换,这也是计算机系统中提供并发的基本机制;通过信号,还可以让进程和内核中断其他进程,从而实现进程间的控制。

Reference

《深入理解 Linux 内核》

《深入理解计算机原理》

https://bob.cs.sonoma.edu/IntroCompOrg-x64/bookch15.html

https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html

扫码_搜索联合传播样式-白色版 1