操作系统源码学习笔记(四) trap_init/挂接IDT,建立中断服务体系

/ 默认分类 / 0 条评论 / 479浏览
void trap_init(void)
{
	int i;

    // 设置除操作出错的中断向量值。
	set_trap_gate(0,&divide_error);
	set_trap_gate(1,&debug);
	set_trap_gate(2,&nmi);
	set_system_gate(3,&int3);	/* int3-5 can be called from all */
	set_system_gate(4,&overflow);
	set_system_gate(5,&bounds);
	set_trap_gate(6,&invalid_op);
	set_trap_gate(7,&device_not_available);
	set_trap_gate(8,&double_fault);
	set_trap_gate(9,&coprocessor_segment_overrun);
	set_trap_gate(10,&invalid_TSS);
	set_trap_gate(11,&segment_not_present);
	set_trap_gate(12,&stack_segment);
	set_trap_gate(13,&general_protection);
	set_trap_gate(14,&page_fault);
	set_trap_gate(15,&reserved);
	set_trap_gate(16,&coprocessor_error);
    // 下面把int17-47的陷阱门先均设置为reserved,以后各硬件初始化时会重新设置自己的陷阱门。
	for (i=17;i<48;i++)
		set_trap_gate(i,&reserved);
    // 设置协处理器中断0x2d(45)陷阱门描述符,并允许其产生中断请求。设置并行口中断描述符。
	set_trap_gate(45,&irq13);
	outb_p(inb_p(0x21)&0xfb,0x21);  // 允许8259A主芯片的IRQ2中断请求。
	outb(inb_p(0xA1)&0xdf,0xA1);    // 允许8259A从芯片的IRQ3中断请求。
	set_trap_gate(39,&parallel_interrupt); // 设置并行口1的中断0x27陷阱门的描述符。
}

再来看下set_trap_gate的调用链路:

#define set_trap_gate(n,addr) 
	_set_gate(&idt[n],15,0,addr)
#define _set_gate(gate_addr,type,dpl,addr) 
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))

先来整体介绍下trap_init()函数的作用。 CPU在参与运算过程中,可能会遇到除零错误、溢出错误、边界检查错误、缺页错误……免不了需要“异常处理”。 中断技术也是广泛使用的,系统调用就是利用中断技术实现的。 这些中断、异常都需要具体的服务程序来执行。 前面我们说了IDT,所谓IDT指的是 "中断描述符表"(Interrupt Descriptor Table)。IDT是在x86架构中用于管理中断处理程序的数据结构。 当系统中发生中断或异常时,CPU会根据中断号或异常号在IDT中查找相应的中断门(Interrupt Gate)或陷阱门(Trap Gate)。中断门和陷阱门包含了指向相应中断处理程序的地址,CPU会跳转到这些地址执行相应的处理代码。也就是说,可以将IDT理解为一个键值映射表,cpu可以根据收到的中断信号在表中查出对应的需要处理的中断程序的地址。 在操作系统内核中,通过修改IDT中的中断门和陷阱门的地址,可以重建中断服务体系,实现对中断和异常的处理。这些修改通常在操作系统初始化过程中完成,以确保系统能够正确响应各种中断和异常事件。 trap_init()函数将中断、异常处理的服务程序与IDT进行挂接,逐步重建中断服务体系,支持内核、进程在主机中的运算。 所以,中断描述符表中的每一条数据存储的就是一个个中断信号和对应的处理程序。实际上,每一个中断描述符占用64位,也就是8字节,下面的图就是一个中断描述符大致的数据结构: image.png 对于其中详细的每个字节范围内的数据是干嘛的,可以参考一些书籍的介绍,对于我们,需要知道的是,所谓的IDT其实就是保护模式下的中断向量表(中断向量表就是之前实模式的时候cpu用于处理中断的数据表),中断描述符表中的每个64位的数据就是为了告诉CPU在发生某个具体的中断时应该执行哪段代码,以及在何种特权级别下执行。

中断描述符为64位,包含了其对应中断服务程序的段内偏移地址(OFFSET)、所在段选择符(SELECTOR)、描述符特权级(DPL)、段存在标志(P)、段描述符类型(TYPE)等信息,供CPU在程序中需要进行中断服务时找到相应的中断服务程序。其中,第0~15位和第48~63位组合成32位的中断服务程序的段内偏移地址(OFFSET);第16~31位为段选择符(SELECTOR),定位中断服务程序所在段;第47位为段存在标志(P),用于标识此段是否存在于内存中,为虚拟存储提供支持;第45~46位为特权级标志(DPL),特权级范围为0~3;第40~43位为段描述符类型标志(TPYE), 中断描述符对应的类型标志为0111(0xE),即将此段描述符标记为“386中断门”。

下面我画了一个简单的IDT的基本结构,其实就可以是一个64位长整型的数组,只是数组中的元素是64位的字节形态。 image.png 我们来看下,详细的代码逻辑:

	set_trap_gate(0,&divide_error);
	set_trap_gate(1,&debug);
	set_trap_gate(2,&nmi);
	set_system_gate(3,&int3);	/* int3-5 can be called from all */
	set_system_gate(4,&overflow);

这里我们只贴了一部分,再来看下调用进去的逻辑:

#define set_trap_gate(n,addr) 
	_set_gate(&idt[n],15,0,addr)

#define set_system_gate(n,addr) 
    _set_gate(&idt[n],15,3,addr)
#define _set_gate(gate_addr,type,dpl,addr) 
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))

这里先解释下_set_gate函数的形参的含义:

  1. gate_addr:
    • 这是一个指针,指向中断门的地址。中断门也就是中断描述符表(IDT)中的一个条目,用于存储中断处理程序的位置以及其他相关信息。
  2. type:
    • 这是一个整数,表示中断门的类型。在 x86 架构中,中断门的类型包括中断门(Interrupt Gate)和陷阱门(Trap Gate),用于决定中断处理的行为。type参数通常用数字表示,例如,0x8 表示中断门,0xF 表示陷阱门。
  3. dpl:
    • 这是一个整数,表示中断门的特权级别(Descriptor Privilege Level)。特权级别定义了中断处理程序的权限,以及哪些代码可以调用它。通常,0 表示最高特权级别,3 表示最低特权级别。
  4. addr:
    • 这是一个指针,指向中断处理程序的地址。当中断发生时,CPU将跳转到这个地址执行中断处理程序。

小知识:关于中断门和陷阱门的区别 在x86架构中,陷阱门(Trap Gate)和中断门(Interrupt Gate)是两种不同类型的门,用于处理中断或异常事件。它们之间的主要区别在于如何处理被触发的事件。

  1. 中断门(Interrupt Gate)
    • 中断门用于处理外部中断(例如硬件中断或来自外部设备的信号)。当CPU遇到中断门时,会暂停当前执行的程序,并在特权级别允许的情况下转移到中断处理程序执行。中断处理程序执行完毕后,CPU会返回到原来的程序继续执行。中断门通常用于异步事件,例如外部设备请求CPU处理数据或通知CPU某种状态的变化。
  2. 陷阱门(Trap Gate)
    • 陷阱门用于处理同步事件或软件中断,例如系统调用或程序中的异常情况。当CPU遇到陷阱门时,它会立即转移到陷阱处理程序,并在当前特权级别下执行。执行完陷阱处理程序后,CPU会继续执行原来的程序,而不会返回到引起陷阱的指令处。因此,陷阱门用于同步事件或需要立即响应的软件中断。

总的来说,中断门主要用于处理异步事件,而陷阱门主要用于处理同步事件或需要立即响应的软件中断。在操作系统中,系统调用通常是通过陷阱门实现的,而硬件中断通常是通过中断门实现的。

最终的_set_gate函数的一顿汇编操作,其实就是为了组装一条规定的64位的中断描述符,这里就不深入了解了。