操作系统开发系列—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

运行结果如下:

一个码农的日常 

源码

原文地址:https://www.cnblogs.com/joey-hua/p/5418380.html