深入理解 Linux 内核---中断和异常

中断或异常会改变处理器执行的指令顺序。

异常:

  • 来源:CPU 控制单元,
  • 时机:只有在一条指令终止执行后 CPU 才会发出中断。
  • 原因:程序产生错误,或内核必须处理的异常条件。

中断:

  • 来源:间隔定时器或 I/O 设备。
  • 时机:随机产生。
  • 原因:依照 CPU 时钟信号。

中断信号的作用

为什么要引入中断信号?因为中断信可使得处理器转而去运行正常控制流之外的代码。

当中断信号到来时,CPU 需进行切换。在内核态堆栈保存程序计数器的当前值(eip、cs),并把中断类型相关的地址放入程序计数器(eip、cs)。

中断处理与进程切换的明显差异:中断或异常处理程序执行的代码不是进程,而是一个内核控制路径,比进程“轻”(中断的上下文很少,建立或终止中断处理所需的时间很少)。

中断处理满足如下约束:

  • 为了尽快处理完中断,需尽量推迟更多的处理。
  • 允许中断嵌套。
  • 在内核代码的某些临界区,中断处理程序以关中断方式运行,但这种情况尽量少。

中断和异常

中断类型:

  • 可屏蔽中断
  • 非屏蔽中断

异常类型:

  • 处理器探测异常
  • 故障,eip 保存引起故障的指令地址。
  • 陷阱,eip 保存随后要执行的指令的地址。
  • 终止,eip 不保存值。
  • 编程异常,软中断,2种用途:执行系统调用;向调试程序通报一个特定事件。

每个中断和异常由 0~255 之间的数标识。Intel 将该 8 位无符号整数称为向量。非屏蔽中断和异常的向量固定,可屏蔽中断的向量可通过对中断控制器的编程改变。

IRQ 和中断

每个能发出中断请求的硬件设备都有一条 IRQ(Interrupt ReQuest) 输出线,IRQ 线与 PIC(Programmable Interrpt Controuer, 可编程中断控制器)的输入引脚相连。PIC 执行下列动作:

  • 监视 IRQ 线是否产生信号。如有多条 IRQ 线产生信号,选择引脚编号较小的。
  • 如果监视到信号:
    • 将信号转换为对应的向量。
    • 将向量放入 PIC 的一个 I/O 端口,CPU 便可通过数据总线读该向量。
    • 将信号送到处理器的 INTR 引脚,引发中断。
    • 等待,直到 CPU 确认该信号(通过将其写入 PIC 的 I/O 端口);清除 INTR 线。
  • 返回第 1 步。

第一条 IRQ 通常为 IRQ0,与 IRQn 关联的 Intel 的缺省向量是 n+32,PIC 可修改 IRQ 和向量之间的映射。

可对 PIC 编程从而禁止 IRQ,禁止的中断不会丢失,一旦被**,PCI 又把它们发送到 CPU,这允许中断处理程序逐次处理同一类型的 IRQ。

eflags 寄存器的 IF 标志被清 0 时,PCI 发布的可屏蔽中断会被 CPU 暂时忽略。cli 和 sti 分别清除、设置 IF 标志。

高级 PIC(Advanced PIC,APIC)

当系统包含多个 CPU 时,需要能把中断传递给每个 CPU。因此 APIC 取代了 PIC/

每个 CPU 都有一个本地 APIC,每个本地 APIC 有 32 位的寄存器、一个内部时钟、一个本地定时设备、额外的两条 IRQ 线 LINT0 和 LINT1(为本地 APIC 中断保留的)。

本地 APIC 连接到一个外部 IO APIC,形成一个多 APIC 系统。

深入理解 Linux 内核---中断和异常

I/O APIC 与 IRQ 引脚不同,中断优先级不与引脚关联,中断重定向表的每一项可被单独指明中断向量和优先级。

来自外部硬件的中断请求在可用 CPU 之间的分发方式:

  • 静态分发。中断会传递给一个特定的 CPU,或一组 CPU,或所有 CPU。
  • 动态分发。中断会传递给当前运行最低优先级进程的 CPU,如果有多个 CPU 满足,则使用仲裁技术分配。

多 APIC 系统还允许 CPU 产生处理器间中断(InterProcessor Interrupt,PIP)。

异常

内核必须为每种异常提供一个专门的异常处理程序。对于某些异常,CPU 控制单元在执行异常处理程序前会产生一个硬件出错码,并压入内核堆栈。

中断描述符表(Interrupt Descriptor Table,IDT)

每一项对应一个中断或异常向量,每个向量由 8 个字节组成。

IDT 中有中断或异常处理程序的入口地址。

idtr CPU 寄存器指定 IDT 的线性基址及最大长度。lidt 汇编指令初始化 idtr。

IDT 包含三种类型的描述符:

  • 任务门。中断信号发生时,存放新进程的 TSS 选择符。
  • 中断门。处理中断。处理器会清 IF 标志,从而关闭可能会发生的可屏蔽中断。
  • 陷阱门。处理异常。不修改 IF 标志。

中断和异常的硬件处理

假设内核已被初始化,CPU 在保护模式下运行。

在处理下一条指令时,控制单元会检查在运行前一条指令时是否发生了一个中断或异常,如果发生,控制单元执行下列操作:

  • 确定中断或异常关联的向量 i(0~255)。
  • 读 idtr 寄存器指向的 IDT 表的第 i 项(假定包含一个中断门或陷阱门)。
  • 从 gdtr 获得 GDT 的基地址,在 GDT 中查找 IDT 表第 i 项中选择符标识的段描述符。该描述符指定中断或异常处理程序所在段的基地址。
  • 确信中断是由授权的中断发生源发出的。即 CPL(cd 寄存器的低两位)与段描述符(GDT 中)的描述符特权级 DPL 比较,大于则产生异常,因为说明引起中断的处理程序的特权>中断处理程序的特权 。对于编程异常,还需比较 CPL 与 IDT 中的门描述符 DPL,大于则产生异常,可避免用户程序访问特殊的陷阱门或中断门。
  • 检查是否发生特权级的变化,即 CPU 不等于当前段描述符的 DPL。如果是,控制单元必须使用与新特权级相关的栈。
  • 如果产生故障,用一起异常的指令的地址装载 cs 和 eip 寄存器。
  • 将 eflags、cs 及 eip 的内容保存到栈中。
  • 如果异常产生了一个硬件出错码,保存到栈中。
  • 用 IDT 表中第 i 项门描述符的段选择符和偏移量字段装载 cs 和 eip 寄存器,为中断或异常处理程序的第一条指令的逻辑地址。

未完待续…