原文:Basic x86 interrupts
作者:Alex Dzyoba
原文发表日期:2016年4月2日
在我的关于多重引导内核的文章中,我们看到了如何加载内核、打印文本,然后停止。然而要让操作系统可用,需要支持键盘输入,将敲击的按键打印在屏幕上。
所需的工作会超出你最初的估计,因为需要初始化x86中断。x86中断是一种古怪而繁琐的机制,拥有超过40年的历史。
中断是设备发送给CPU的事件,用以通知CPU设备有新的消息:比如用户在键盘上输入,或者网络报文到达。如果没有中断,系统需要轮询所有外部设备,这会浪费CPU时间,增加延迟。
中断按照来源可以分为三种类型:
x86中断系统由三个部分共同构成,初始化操作也分为三步:
请在阅读文章时回顾下面的参考图。
在配置中断之前,我们需要像之前那样设置好GDT。
PIC是替代CPU连接各种外部设备的硬件,本质上它是一个多路复用器或代理。PIC可以减少CPU引脚,提供多项功能:
最初的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)命令等。
中断描述符表(IDT)是一个系统表,保存了中断服务程序(ISR)或简单中断处理程序的描述符。
实模式下有一个中断向量表(IVT)。中断向量表固定在0x0位置,包含以CS和IP描述的中断处理函数指针。这确实不是一种灵活的方式,并且依赖于分段内存管理。自从80286以来,保护模式使用中断描述符表(IDT)。
IDT是内存中的表,由操作系统建立和维护。IDT地址保存在idtr寄存器中。使用lidt指令可以将IDT地址加载到idtr寄存器。IDT只能在保护模式下使用。IDT条目包含门描述符,其中有32位的中断处理程序(ISR)地址、标志位和特权等级。IDT条目是中断门的描述符,类似于GDT中的段描述符。
描述符的主要部分是偏移量。偏移量实质上是由段选择子指定的代码段中ISR的指针。段选择子包含GDT表索引、表指示器(指定GDT或LDT)和特权级别(RPL)。对于中断门,选择子指定的是GDT中的内核代码段。
调用门描述符中的类型(type)域定义了调用门类型:任务、陷阱或中断。对于中断处理函数,需要使用中断调用门。调用中断门时,CPU会清除IF标志位。基本上在设置IDT时,各描述符只有偏移量,即ISR函数地址是不同的。
IDT的主要目的是存储指向ISR的指针,CPU在收到中断后自动调用ISR。你不能手动调用中断处理程序。一旦配置好了IDT并打开中断(sti),在发生中断时,在完成一些幕后工作后,CPU将控制传递给中断处理函数。了解这些幕后工作的内容是很重要的。
如果中断发生在用户空间(即中断发生在不同特权级别的代码段),CPU执行下列操作:
如果中断发生在内核空间,CPU不会切换堆栈,这意味着内核空间的中断没有自己的堆栈,而是使用被中断函数的堆栈。在x64系统上,由于red zone的存在,内核中断可能会导致堆栈损坏。这就是内核代码需要使用参数-mno-red-zone编译的原因。关于这点我有一个有趣的故事。
当内核模式下发生中断时,CPU将:
请注意这两种情况的区别在于压栈数据不同。EFLAGS、CS和EIP一定会压栈。当用户空间中断时,还会将SS和ESP压栈。
这意味着当中断处理程序开始执行时,它拥有下面的堆栈:
那么,当控制传递给中断处理程序时,它应该做什么呢?
记住,中断发生在用户空间或内核空间某些代码执行过程的中间,因此首先要做的是在处理中断之前保存被中断过程的状态。过程状态由寄存器的值定义。指令pusha可以将通用寄存器的值保存到栈中。
下一步是修改段寄存器,完全切换到中断处理程序的运行环境。CPU会自动切换CS,因此中断处理程序需要加载4个段寄存器DS、FS、ES和GS。不要忘记保存这些寄存器的原值,并在将来恢复。
当状态已保存、执行环境就绪后,中断处理程序可以开始工作。不过首要的是向PIC发送命令EOI来确认中断。
最后,在完成所有工作之后,中断处理程序要干净的退出。恢复被中断过程的状态(恢复数据段寄存器,执行popa),开启在CPU进入ISR时关闭的中断(sti),并调用iret将控制权返回被中断过程。
下面是基本的ISR处理过程:
现在可以完成全景图,让我们看看键盘事件是如何被处理的:
请注意每次敲击按键时,上述步骤都会发生。也别忘了有几十个其他类型的中断,如时钟、网络数据包等。有些中断是悄悄处理的,你可能都没有注意到。你能想象硬件有多快吗?你能想象你使用的操作开发得有多好吗?现在考虑一下,给操作系统作者和硬件设计师一个大大的表扬。