x86中断基础

x86中断基础

原文:Basic x86 interrupts 

作者:Alex Dzyoba

原文发表日期:2016年4月2日

在我的关于多重引导内核的文章中,我们看到了如何加载内核、打印文本,然后停止。然而要让操作系统可用,需要支持键盘输入,将敲击的按键打印在屏幕上。

所需的工作会超出你最初的估计,因为需要初始化x86中断。x86中断是一种古怪而繁琐的机制,拥有超过40年的历史。

1. x86中断

中断是设备发送给CPU的事件,用以通知CPU设备有新的消息:比如用户在键盘上输入,或者网络报文到达。如果没有中断,系统需要轮询所有外部设备,这会浪费CPU时间,增加延迟。

中断按照来源可以分为三种类型:

  1. 硬件中断。来自硬件设备,比如键盘或网卡。
  2. 软件中断。由软件通过int指令产生。在引入sysenter和sysexit指令之前,Linux的系统调用就是通过中断0x80实现的。
  3. 异常。在遇到诸如“除零”或“页错误”的异常时,由CPU产生。

x86中断系统由三个部分共同构成,初始化操作也分为三步:

  1. 配置可编程中断控制器(PIC)从设备接收中断请求(IRQs),发送给CPU。
  2. 配置CPU从PIC接收中断请求,通过中断描述符表(IDT)中定义的调用门(gate),调用相应的中断处理程序。
  3. 操作系统需要提供中断服务程序(ISRs)来处理中断。操作系统要支持被中断抢占,并配置PIC和CPU启用中断。

请在阅读文章时回顾下面的参考图。

x86中断基础_第1张图片

 

在配置中断之前,我们需要像之前那样设置好GDT。

2. 可编程中断控制器(PIC)

PIC是替代CPU连接各种外部设备的硬件,本质上它是一个多路复用器或代理。PIC可以减少CPU引脚,提供多项功能:

  • 通过级联支持更多的中断线。2个级联的PIC可以支持15个中断线。
  • 可以屏蔽特定中断,无需屏蔽全部中断(cli)。
  • 中断排队,即对传递给CPU的中断排序。当某些中断被屏蔽时,PIC将中断加入队列延迟发送,不会直接丢弃中断。

最初的IBM PC有独立的8259 PIC芯片。后来它被集成为南桥/ICH/PCH的一部分。现代PC系统使用高级可编程中断控制器(APIC)来解决多处理器机器上的中断路由问题。但为了向后兼容,APIC可以模拟8259 PIC。只要不使用旧硬件,你实际上使用的是手动配置或由BIOS自动配置的APIC。在本文中,我将使用BIOS自动配置的APIC,不会手动配置PIC。原因有两个:首先PIC有很多常人难以理解的怪异之处。其次,后续我们将为SMP配置APIC。BIOS自动按照IBM PC AT机器的模式配置APIC,即2个级联的PCI和15个中断线。

除了连接引发CPU中断的中断线,PIC还连接CPU数据总线。数据总线用于将IRQ号从PIC发送到CPU,并将配置命令从CPU发送到PIC。配置命令包括PIC初始化、IRQ屏蔽、中断结束(EOI)命令等。

3. 中断描述符表(IDT)

中断描述符表(IDT)是一个系统表,保存了中断服务程序(ISR)或简单中断处理程序的描述符。

实模式下有一个中断向量表(IVT)。中断向量表固定在0x0位置,包含以CS和IP描述的中断处理函数指针。这确实不是一种灵活的方式,并且依赖于分段内存管理。自从80286以来,保护模式使用中断描述符表(IDT)。

IDT是内存中的表,由操作系统建立和维护。IDT地址保存在idtr寄存器中。使用lidt指令可以将IDT地址加载到idtr寄存器。IDT只能在保护模式下使用。IDT条目包含门描述符,其中有32位的中断处理程序(ISR)地址、标志位和特权等级。IDT条目是中断门的描述符,类似于GDT中的段描述符。

x86中断基础_第2张图片

描述符的主要部分是偏移量。偏移量实质上是由段选择子指定的代码段中ISR的指针。段选择子包含GDT表索引、表指示器(指定GDT或LDT)和特权级别(RPL)。对于中断门,选择子指定的是GDT中的内核代码段。

调用门描述符中的类型(type)域定义了调用门类型:任务、陷阱或中断。对于中断处理函数,需要使用中断调用门。调用中断门时,CPU会清除IF标志位。基本上在设置IDT时,各描述符只有偏移量,即ISR函数地址是不同的。

4. 中断服务程序(ISR)

IDT的主要目的是存储指向ISR的指针,CPU在收到中断后自动调用ISR。你不能手动调用中断处理程序。一旦配置好了IDT并打开中断(sti),在发生中断时,在完成一些幕后工作后,CPU将控制传递给中断处理函数。了解这些幕后工作的内容是很重要的。

如果中断发生在用户空间(即中断发生在不同特权级别的代码段),CPU执行下列操作:

  1. 在内部临时保存SS、ESP、EFLAGS、CS和EIP的值。
  2. 从TSS为新栈(即中断处理函数栈)加载段选择子和栈指针到SS和ESP,切换到新栈。
  3. 将临时保存的SS、ESP、EFLAGS、CS和EIP压栈。
  4. 如果有错误代码,将错误码压栈。
  5. 从中断门或陷阱门加载新代码段的段寄存器和指令指针到CS和EIP寄存器。
  6. 如果调用通过中断门进行,清除EFLAGS寄存器的IF标志位。
  7. 以新的特权等级开始执行中断处理函数。

如果中断发生在内核空间,CPU不会切换堆栈,这意味着内核空间的中断没有自己的堆栈,而是使用被中断函数的堆栈。在x64系统上,由于red zone的存在,内核中断可能会导致堆栈损坏。这就是内核代码需要使用参数-mno-red-zone编译的原因。关于这点我有一个有趣的故事。

当内核模式下发生中断时,CPU将:

  1. 将EFLAGS、CS和EIP的值压栈。
  2. 如果有错误码,将其压栈。
  3. 从中断门或陷阱门中加载新代码段的段选择子和指令指针到CS和EIP寄存器。
  4. 如果调用的是中断门,清除EFLAGS寄存器的IF标志位。
  5. 开始执行中断处理程序。

请注意这两种情况的区别在于压栈数据不同。EFLAGS、CS和EIP一定会压栈。当用户空间中断时,还会将SS和ESP压栈。

这意味着当中断处理程序开始执行时,它拥有下面的堆栈:

x86中断基础_第3张图片

 

那么,当控制传递给中断处理程序时,它应该做什么呢?

记住,中断发生在用户空间或内核空间某些代码执行过程的中间,因此首先要做的是在处理中断之前保存被中断过程的状态。过程状态由寄存器的值定义。指令pusha可以将通用寄存器的值保存到栈中。

下一步是修改段寄存器,完全切换到中断处理程序的运行环境。CPU会自动切换CS,因此中断处理程序需要加载4个段寄存器DS、FS、ES和GS。不要忘记保存这些寄存器的原值,并在将来恢复。

当状态已保存、执行环境就绪后,中断处理程序可以开始工作。不过首要的是向PIC发送命令EOI来确认中断。

最后,在完成所有工作之后,中断处理程序要干净的退出。恢复被中断过程的状态(恢复数据段寄存器,执行popa),开启在CPU进入ISR时关闭的中断(sti),并调用iret将控制权返回被中断过程。

下面是基本的ISR处理过程:

  1. 保存被中断过程的状态。
  2. 保存数据段寄存器。
  3. 使用内核数据描述符加载数据段寄存器。
  4. 向PIC发送EOI命令确认中断。
  5. 处理中断。
  6. 恢复数据段寄存器。
  7. 恢复被中断过程的状态。
  8. 启用中断。
  9. 调用iret退出中断处理程序。

5. 汇总

现在可以完成全景图,让我们看看键盘事件是如何被处理的:

  1. 设置中断:
    1. 创建IDT表。
    2. 设置编号为9的IDT条目,将中断门指向键盘ISR。
    3. 使用lidt加载IDT地址。
    4. 发送中断屏蔽码0xfd(11111101)到主PIC,恢复IRQ1。
    5. 使用sti启用中断。
  2. 用户点击键盘按钮。
  3. 键盘控制器在主PIC中引发中断IRQ1。
  4. PIC确认中断IRQ1没有被屏蔽,向CPU发送中断号9。
  5. CPU检查EFLAGS的IF标志位,确认中断未被屏蔽。
  6. (假设当前正在内核模式下运行)
  7. CPU将EFLAGS、CS和EIP压栈。
  8. 如果PIC有错误码,将其压栈。
  9. 查询idtr寄存器指向的IDT,从描述符9中获得段选择子。
  10. 检查特权级别,将段选择器和ISR地址加载到CS:EIP。
  11. 因为IDT条目是中断门,CPU清除IF标志。
  12. 将控制权传递给ISR。
  13. ISR接收中断:
    1. 使用cli屏蔽中断。
    2. 使用pusha保存被中断过程的状态。
    3. 将DS压栈。
    4. 从内核数据段加载DS、ES、FS、GS。
  14. 发送命令EOI(0x20)到主PIC(I/O端口0x20),确认中断。
  15. 从键盘控制器(I/O端口0x64)读取键盘状态。
  16. 如果状态是1,从键盘控制器(I/O端口0x60)读取键码。
  17. 最后,写VGA缓冲区或发送字符到TTY。
  18. 从中断处理程序返回:
    1. 从堆栈中弹出并恢复DS。
    2. 使用popa恢复被中断过程状态。
    3. 使用sti启用中断。
    4. 调用iret退出中断处理程序。

请注意每次敲击按键时,上述步骤都会发生。也别忘了有几十个其他类型的中断,如时钟、网络数据包等。有些中断是悄悄处理的,你可能都没有注意到。你能想象硬件有多快吗?你能想象你使用的操作开发得有多好吗?现在考虑一下,给操作系统作者和硬件设计师一个大大的表扬。

你可能感兴趣的:(网络,linux,运维)