内核2 中断与异常处理
关于中断和异常,相信学习过计算机组成原理(计算机体系结构)、操作系统原理的同学们都有一定的了解。这里就不再赘述它们的定义了。接下来我们来看一下中断和异常处理的具体实现。
异常处理
让我们先来看一下intel给出的cpu可以处理的异常类型:

那么cpu如何捕捉到这些异常呢?让我们以除零异常为例。假如内核代码中有一条代码为
1 | int a = 1 / 0; |
那么cpu在执行这条指令时,会检测到除零异常,现在我们要将异常处理函数配置给cpu。进入保护模式后,bios提供的中断向量表就不能用了,我们需要使用中断描述符表(IDT)来完成这些功能。
设置idt并加载异常处理函数
idt表项有多种类型,如任务门、中断门、陷阱门等。在这里我们只用到了中断门,其结构如下:
如何设置表项?cpu检测到异常后,根据向量号在idt中取出表项,再根据选择子取出gdt中的表项,组合起来就是异常处理函数的入口。由于我们采用的平坦模型,所以段选择子我们可以使用代码段选择子,偏移量设置为异常处理函数的入口,这样在发生异常的时候就可以跳转到相应的处理函数中了。
和gdt表类似,idt表也是由表项组成的数组,我们按照上图定义好的异常的顺序,在数组的每一项中设置好段选择子、偏移量、属性值(参考手册),再使用lidt指令将其加载到idtr寄存器中就可以了。不过这其中有一个问题,从异常(中断)返回时要用到iret指令,但是一般的函数返回是ret指令,所以我们要在汇编代码中手动来写iret指令,完成中断返回的功能。在iret之前,我们还有一项重要的工作要做,即保存上下文。相信学习过计算机组成原理(计算机体系结构)、操作系统原理的同学们都听说过保护现场这个词,关于它的作用和重要性也不必多说。我们现在要做的就是这个事。那么如何进行现场保护及恢复呢?最简单的方法就是在调用C异常处理函数之前push所有寄存器,在C异常处理函数处理完毕之后再按顺序pop所有的寄存器,然后再iret就可以了。我们在汇编代码中可以通过call指令调用C函数作为真正的异常处理函数,这样我们就可以在异常发生的时候输出一些信息和进行一些操作,目前我们采取的措施是停机并输出寄存器信息,通过输出的eip值,再查看反汇编就可以定位到错误的代码处。以后可能会加输出错误代码是哪一条的功能。
异常处理过程
现在再梳理一下异常处理的过程。cpu初始化时会加载irq初始化函数,在这里设置好中断描述符表的每一项,并加载到idtr中。cpu检测到异常时,会从idt中取表项并执行对应的汇编处理函数。在汇编处理函数中先保护现场,再调用C处理函数,然后恢复现场并iret。
中断处理
中断的设置、打开与关闭
中断处理大多来源于io设备,在早期x86处理器使用了8259控制器来管理中断,最多可支持15种中断。中断信号发出后首先由8259控制器进行控制,然后再由8259给cpu发送中断请求。如果我们要使用8259芯片,则需要做一些特殊的初始化,即对ICW1~ICW4,IMR寄存器的某些位进行设置,详细设置可以参考8259芯片手册。
中断的打开与关闭受制于两个因素,一个是8259芯片中的IMR寄存器,另一个是eflags寄存器中的if标志位。if位是全局标志位,IMR用于打开特定的中断。全局中断打开用sti指令,关闭用cli指令。在IMR中打开某一个序号的特定中断只需要将序号的位设置为0,关闭则为置1。
设置第一个中断:定时器中断
时钟在计算机内部十分重要,我们后续一些功能的编写也要用到时钟功能,所以要首先初始化定时器中断。
在早期计算机中,定时器的实现是用8253芯片。在这个系统中主要用到了8253中的定时器0和命令端口,对于这两个端口的设置请参考手册。在8253的配置中,有一个供给芯片内部使用的计数值,含义是达到中断毫秒数所需要的时间频率,当这个计数值减为零,就达到了设定的中断时间,cpu发生一次定时中断,计数值重设为初始值继续中断过程。
1 | static void init_pit(void) |
计时器中断处理时可以使ticks++来判断中断了多少时间。在完成一次中断后,还要告诉8259芯片中断已完成,这样它才能进行后面的中断。告诉8259芯片的方法为向OCW2寄存器中写入1<<5,这样就可以一直产生中断了。
Attention:
①中断描述符表的0-31号被cpu内部所占用,所以用户自定义中断不能放在0-31处,cpu同样通过偏移量和选择子来确定中断处理程序的入口地址。
②IF位在进入中断的时候会自动清零。(这个知识点好像在汇编语言学过?)
③CPU如何找到表项?具体来说,当发生除零异常/定时器中断时,cpu如何知道对应的处理函数在哪个表项中?
我的理解如下:对应异常,只能按照手册中规定好的顺序进行配置,例如将除零异常和debug异常的表项对调一下,发生除零异常的时候就会跳转到debug异常了。对于中断我也不是很明白,猜测应该是8259芯片在发送中断信号的时候会将对应的表项信息传递给cpu。