操作系统开发系列—13.b.进程之丰富中断处理程序

首先打开时钟中断:

	out_byte(INT_M_CTLMASK,	0xFE);	// Master 8259, OCW1.
	out_byte(INT_S_CTLMASK,	0xFF);	// Slave  8259, OCW1.

为了让时钟中断可以不停地发生而不是只发生一次,还需要设置EOI:

hwint00:		; Interrupt routine for irq 0 (the clock).
	mov	al, EOI		; `. reenable
	out	INT_M_CTL, al	; / master 8259
	iretd

运行后发现结果和原来没有任何区别,因为我们只是可以继续接受中断而已,其余并没有做什么。

中断现在已经被打开,于是就存在ring0和ring1之间频繁的切换。两个层级之间的切换包含两方面,一是代码的跳转,还有一个不容忽视的方面,就是堆栈也在切换。

代码如下:

hwint00:		; Interrupt routine for irq 0 (the clock).
	sub	esp,4
	pushad		; `.
	push	ds	;  |
	push	es	;  | 保存原寄存器值
	push	fs	;  |
	push	gs	; /
	mov	dx, ss
	mov	ds, dx
	mov	es, dx

	inc	byte [gs:0]	; 改变屏幕第 0 行, 第 0 列的字符

	mov	al, EOI		; `. reenable
	out	INT_M_CTL, al	; /  master 8259

	lea	eax, [esp + P_STACKTOP]
	mov	dword [tss + TSS3_S_SP0], eax

	pop	gs	; `.
	pop	fs	;  |
	pop	es	;  | 恢复原寄存器值
	pop	ds	;  |
	popad		; /
	add	esp,4

	iretd

你可以看到,sub/add esp这两句代码实际上是跳过了4字节,结合进程表的定义知道,被跳过的这4字节实际上就是那个retaddr,我们还是先不管这个值。

我们曾经提到过内核栈的问题,如今这个问题真的出现了。现在esp指向的是进程表,如果此时我们要执行复杂的进程调度程序呢?最简单的例子是如果我们想调用一个函数,这时一定会用到堆栈操作,那么我们的进程表立刻会被破坏掉。所以我们需要切换堆栈,将esp指向另外的位置。

hwint00:		; Interrupt routine for irq 0 (the clock).
	sub	esp, 4
	pushad		; `.
	push	ds	;  |
	push	es	;  | 保存原寄存器值
	push	fs	;  |
	push	gs	; /
	mov	dx, ss
	mov	ds, dx
	mov	es, dx
	
	mov	esp, StackTop		; 切到内核栈

	inc	byte [gs:0]		; 改变屏幕第 0 行, 第 0 列的字符

	mov	al, EOI			; `. reenable
	out	INT_M_CTL, al		; /  master 8259
	
	mov	esp, [p_proc_ready]	; 离开内核栈

	lea	eax, [esp + P_STACKTOP]
	mov	dword [tss + TSS3_S_SP0], eax

	pop	gs	; `.
	pop	fs	;  |
	pop	es	;  | 恢复原寄存器值
	pop	ds	;  |
	popad		; /
	add	esp, 4

	iretd

切到内核栈和重新将esp切到进程表都很简单,一个mov语句就够了,但是它却非常关键。如果没有这个简单的mov,随着中断例程越来越大,出错的时候,你可能都不知道错在哪里。

在这里我们尽可能地把代码放在使用内核栈的过程中来执行,只留下跳回进程所必需的代码。我们在这里试一下,把这段打印字符的代码替换成使用DispStr这个函数:

extern	disp_str

[SECTION .data]
clock_int_msg		db	"^", 0

hwint00:		; Interrupt routine for irq 0 (the clock).
	sub	esp, 4
	pushad		; `.
	push	ds	;  |
	push	es	;  | 保存原寄存器值
	push	fs	;  |
	push	gs	; /
	mov	dx, ss
	mov	ds, dx
	mov	es, dx
	
	mov	esp, StackTop		; 切到内核栈

	inc	byte [gs:0]		; 改变屏幕第 0 行, 第 0 列的字符

	mov	al, EOI			; `. reenable
	out	INT_M_CTL, al		; /  master 8259
	
	push	clock_int_msg
	call	disp_str
	add	esp, 4
	
	mov	esp, [p_proc_ready]	; 离开内核栈

	lea	eax, [esp + P_STACKTOP]
	mov	dword [tss + TSS3_S_SP0], eax

	pop	gs	; `.
	pop	fs	;  |
	pop	es	;  | 恢复原寄存器值
	pop	ds	;  |
	popad		; /
	add	esp, 4

	iretd

运行结果如下,我们看到不断出现的字符“^”,说明函数disp_str运行正常,而且没有影响到中断处理的其他部分以及进程A,之所以在两次字符A的打印中间有多个“^”,是因为我们的进程执行体中加入了delay()函数,在此函数的执行过程中发生了多次中断:

 

源码

posted @ 2016-05-08 12:49  是非猫  阅读(479)  评论(0编辑  收藏  举报