因为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_byteglobal 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_errorglobal single_step_exceptionglobal nmiglobal breakpoint_exceptionglobal overflowglobal bounds_check... lidt [idt_ptr]...; 中断和异常 -- 异常divide_error: push 0xFFFFFFFF ; no err code push 0 ; vector_no = 0 jmp exceptionsingle_step_exception: push 0xFFFFFFFF ; no err code push 1 ; vector_no = 1 jmp exceptionnmi: push 0xFFFFFFFF ; no err code push 2 ; vector_no = 2 jmp exceptionbreakpoint_exception: push 0xFFFFFFFF ; no err code push 3 ; vector_no = 3 jmp exceptionoverflow: 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
运行结果如下:
【】