操作系统开发系列—12.f.在内核中添加中断处理 ●
因为CPU只有一个,同一时刻要么是客户进程在运行,要么是操作系统在运行,如果实现进程,需要一种控制权转换机制,这种机制便是中断。
要做的工作有两项:设置8259A和建立IDT。
/*======================================================================* init_8259A *======================================================================*/ PUBLIC void init_8259A() { /* Master 8259, ICW1. */ out_byte(INT_M_CTL, 0x11); /* Slave 8259, ICW1. */ out_byte(INT_S_CTL, 0x11); /* Master 8259, ICW2. 设置 '主8259' 的中断入口地址为 0x20. */ out_byte(INT_M_CTLMASK, INT_VECTOR_IRQ0); /* Slave 8259, ICW2. 设置 '从8259' 的中断入口地址为 0x28 */ out_byte(INT_S_CTLMASK, INT_VECTOR_IRQ8); /* Master 8259, ICW3. IR2 对应 '从8259'. */ out_byte(INT_M_CTLMASK, 0x4); /* Slave 8259, ICW3. 对应 '主8259' 的 IR2. */ out_byte(INT_S_CTLMASK, 0x2); /* Master 8259, ICW4. */ out_byte(INT_M_CTLMASK, 0x1); /* Slave 8259, ICW4. */ out_byte(INT_S_CTLMASK, 0x1); /* Master 8259, OCW1. */ out_byte(INT_M_CTLMASK, 0xFF); /* Slave 8259, OCW1. */ out_byte(INT_S_CTLMASK, 0xFF); }
out_byte的函数体位于kliba.asm中
global out_byte global in_byte ; ======================================================================== ; void out_byte(u16 port, u8 value); ; ======================================================================== out_byte: mov edx, [esp + 4] ; port mov al, [esp + 4 + 4] ; value out dx, al nop ; 一点延迟 nop ret ; ======================================================================== ; u8 in_byte(u16 port); ; ======================================================================== in_byte: mov edx, [esp + 4] ; port xor eax, eax in al, dx nop ; 一点延迟 nop ret
现在,该是把这些中断和异常的处理程序统统添加上的时候了。
global divide_error global single_step_exception global nmi global breakpoint_exception global overflow global bounds_check ... lidt [idt_ptr] ... ; 中断和异常 -- 异常 divide_error: push 0xFFFFFFFF ; no err code push 0 ; vector_no = 0 jmp exception single_step_exception: push 0xFFFFFFFF ; no err code push 1 ; vector_no = 1 jmp exception nmi: push 0xFFFFFFFF ; no err code push 2 ; vector_no = 2 jmp exception breakpoint_exception: push 0xFFFFFFFF ; no err code push 3 ; vector_no = 3 jmp exception overflow: push 0xFFFFFFFF ; no err code push 4 ; vector_no = 4 jmp exception ... exception: call exception_handler add esp, 4*2 ; 让栈顶指向 EIP,堆栈中从顶向下依次是:EIP、CS、EFLAGS hlt
异常发生时堆栈的变化情况是,中断或异常发生时eflags、cs、eip已经被压栈,如果有错误码的话,错误码也已经被压栈。
所以我们对异常处理的总体思想是,如果有错误码,则直接把向量号压栈,然后执行一个函数exception_handler;如果没有错误码,则先在栈中压入一个0xFFFFFFFF,再把向量号压栈并随后执行exception_handler。
函数exception_hanlder()的原型是这样的:
void exception_handler(int vec_no,int err_code,int eip,int cs,int eflags);
由于C调用约定是调用者恢复堆栈,所以不用担心exception_handler会破坏堆栈中的eip、cs以及eflags。
现在我们已经有了异常处理函数,该是设置IDT的时候了。设置IDT的代码放进函数init_prot()中,它也位于protect.c中。protect.c通篇几乎只调用一个函数,就是init_idt_desc(),它用来初始化一个门描述符。其中用到的函数指针类型是这样定义的:
typedef void (*int_handler) ();
在init_prot()中,所有描述符都被初始化成中断门。DA_386IGate表示中断门。
Intel为我们准备了一个指令叫做ud2,能够产生一个#UD异常。
编译:
make image
运行结果如下:
【源码】