图8-1是IA32体系架构定义的中断描述符(Interrupt Descriptor)的结构。
图8-1 中断描述符的结构
其中,各个字段定义如下。
●Segment Selector:段选择符,指明了中断处理程序所在的代码段。
●Offset:中断处理程序在段内的偏移量。
●DPL:Descriptor Privilege Level,即可以调用该中断描述符的当前处理级。
●P:Present flags,如果段描述符在内存内,则该标志置为1,如果当前描述符不存在,则置为0。
●D:门尺寸,如果为0,则是一个16bit的段描述符,否则是32bit描述符。
需要注意的是,Offset字段长32bit被分成了两部分。
在Hello China的当前实现中,所有中断和异常处理程序都位于代码段内,即段描述符索引为0x08(详细信息参考Hello China的初始化部分),而当前Hello China的实现中,没有使用IA32提供的优先级保护,因此所有涉及DPL字段的地方,都设置为0,D字段设置为1(32位操作系统),P字段也设置为1,因为当前版本的实现中,所有中断描述符都是合法的(存在于内存中的)。
剩下的唯一需要确定的字段就是Offset字段,即中断处理程序在代码段中的位置。为方便起见,在当前版本的实现中为中断描述符中前48个中断描述符项定义了48个处理程序。按照IA32的定义,目前中断描述符表中,前32个(0~31)为异常处理描述符,后面224个(32~255)为用户定义的中断描述符,因此,在当前版本的实现中只定义了48个中断和异常处理程序,因为一般的PC上只有16个外部中断,分别对应中断描述符表中的第32~47个中断描述符。目前,这些中断或异常处理程序完成的功能非常有限,主要完成下列功能:
(1)保存所有的通用寄存器。
(3)把当前堆栈指针(ESP)压入堆栈。
(4)调用同一个中断或异常处理程序(采用C语言实现这个处理程序)。
(5)调用通用处理程序返回后,恢复保存的通用寄存器。
(6)如果是中断处理程序,则通知中断控制器(8259芯片)中断处理完毕。
(7)从中断中返回。
下面是一个典型的中断处理程序:
这是一段汇编代码,采用NASM进行编译。在程序的开始首先压入EAX寄存器,然后判断gl_general_int_handler是否为0,如果为0,则直接跳转到.ll_continue(这是一个局部标号)处。(www.xing528.com)
gl_general_int_hander是在[kernel/arch/sysinit/MINIKER.ASM]文件中定义的一个32比特的全局变量,用来保存通用的中断和异常处理程序。这个通用的中断和异常处理程序采用C语言编写,在操作系统初始化的时候,使用通用处理程序的地址初始化gl_general_int_handler。缺省情况下(未初始化的情况下),gl_general_int_handler的值是0,这样上述汇编代码就很容易理解了,首先判断是否对gl_general_int_handler进行了初始化,如果没有,则直接跳转到.ll_continue处直接解除中断。如果进行了初始化,即gl_general_int_handler的值不为0,则进行正常的处理操作,包括保存通用寄存器、压入中断向量号(黑体标出的汇编语句),然后调用通用的中断和异常处理程序gl_general_int_handler。从gl_general_int_handler返回后,再执行反向的恢复寄存器操作,然后解除中断(通过向中断控制芯片8259发送解除信号),并从中断中返回。需要注意的是,所有的中断处理程序(32~47)都是类似的,唯一不同的是,不同的中断处理程序压入堆栈的中断向量号不同(代码中黑色部分)。
一个外部中断发生后,CPU依次把EFLAGS、CS、EIP压入堆栈,然后根据中断向量号,从中断描述符表中找到中断描述符,从中提取出中断处理程序的代码段选择符(Segment Selector)和中断处理程序,然后跳转到中断处理程序,也就是上述汇编语言编写的程序继续执行。因此,在中断发生后,上述处理程序还未执行前,中断发生后的堆栈框架如图8-2所示。
一旦中断处理程序(上面描述的汇编语言程序)被执行,在实际调用gl_general_int_handler之前形成如图8-3所示的堆栈框架。
图8-2 中断发生后的堆栈框架
图8-3 执行中断处理程序前建立的堆栈框架
其中,在上述堆栈框架中保存的ESP的值,是压入EBP之后的ESP的值(图8-3中的折线示意位置)。之所以保存ESP的值,是要把ESP作为通用中断和异常处理程序的一个参数传递给通用中断和异常处理程序,这样通用的中断和异常处理程序就可以访问堆栈框架了。下面是通用异常和中断处理程序的原型:
这样一切就明了了,汇编语言编写的中断处理程序只是建立起一个堆栈框架(保存了通用寄存器的值),然后把ESP寄存器的值和当前发生中断的中断向量号压入堆栈,作为GeneralIntHandler的参数,最后调用GeneralIntHandler(gl_general_int_handler)函数。GeneralIntHandler函数就可以通过dwVector和lpEsp直接访问中断向量号和堆栈框架了。
对异常的处理与中断处理类似,唯一不同的是,对异常的处理在异常处理程序的最后不用向中断控制器芯片发命令来解除中断。下面是一个异常处理程序:
需要进一步说明的是,对异常的处理,IA32 CPU会根据异常类型的不同有选择地向堆栈中压入一个异常错误号,这样就导致异常发生后的堆栈框架与中断发生后的堆栈框架不一致(因为异常发生后,堆栈中比中断发生后多了一个错误号),因此需要特殊的处理。但在当前Hello China的实现中没有考虑这种情况,因为一旦异常发生,按照当前版本的实现,只是打印出引发异常的上下文信息,然后停机(HLT指令)。后续版本的实现中,需要充分考虑这种区别。
各个中断和异常处理程序编写完成之后,只需把每个中断和异常的处理程序的地址(偏移),填写在相应的中断描述符的Offset处即可。这项任务十分简单,在当前版本的实现中,是通过一个汇编语言例程(np_fill_idt)来实现的,不再赘述。
最后说明一下gl_general_int_handler和GeneralIntHandler之间的关系。gl_general_int_handler是在MINIKER.ASM文件中声明的一个全局变量,并被初始化为0,与其他全局变量不同的是,gl_general_int_handler被声明在固定的位置,即MINIKER.BIN文件的末尾处。而当前版本下,MINIKER.BIN文件(MINIKER.ASM编译后形成的二进制文件)大小固定为48KB,因此,gl_general_int_handler相对于MINIKER.BIN文件的偏移就是48K−4位置处。
而GeneralIntHandler则是一个C语言函数,被编译在MASTER.BIN文件中。在操作系统初始化的时候,MINIKER.BIN被加载到内存地址1MB开始的地方,而MASTER.BIN则被加载到物理内存0x00110000位置处(1MB+64KB),如图8-4所示。
图8-4 Hello China相关模块在内存中的位置
其中阴影部分是在MINIKER.ASM中声明的gl_general_int_handler变量的位置。MASTER.BIN初始化时,直接把GeneralIntHandler的值(其实是一个函数指针值)填写在gl_general_int_handler处,这样就完成了gl_general_int_handler的初始化。在Hello China的当前实现中,为了链接MINIKER.BIN和MASTER.BIN两个模块,大量地采用了这种方式。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。