主要的代码有三部分:(在chapter6\k\kernel中)
(1)kernel.asm中时钟中断处理的部分:
; 中断和异常 -- 硬件中断
; ---------------------------------
%macro hwint_master 1
call save
in al, INT_M_CTLMASK ; `.
or al, (1 << %1) ; | 屏蔽当前中断
out INT_M_CTLMASK, al ; /
mov al, EOI ; `. 置EOI位
out INT_M_CTL, al ; /
sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断
push %1 ; `.
call [irq_table + 4 * %1] ; | 中断处理程序
pop ecx ; /
cli
in al, INT_M_CTLMASK ; `.
and al, ~(1 << %1) ; | 恢复接受当前中断
out INT_M_CTLMASK, al ; /
ret
%endmacro
ALIGN 16
hwint00: ; Interrupt routine for irq 0 (the clock).
hwint_master
其中的save是
; ====================================================================================
; save
; ====================================================================================
save:
pushad ; `.
push ds ; |
push es ; | 保存原寄存器值
push fs ; |
push gs ; /
mov dx, ss
mov ds, dx
mov es, dx
mov eax, esp ;eax = 进程表起始地址
inc dword [k_reenter] ;k_reenter++;
cmp dword [k_reenter], 0 ;if(k_reenter ==0)
jne .1 ;{
mov esp, StackTop ; mov esp, StackTop <--切换到内核栈
push restart ; push restart
jmp [eax + RETADR - P_STACKBASE]; return;
.1: ;} else { 已经在内核栈,不需要再切换
push restart_reenter ; push restart_reenter
jmp [eax + RETADR - P_STACKBASE]; return;
;}
最后ret跳到的地方是
; ====================================================================================
; restart
; ====================================================================================
restart:
mov esp, [p_proc_ready]
lldt [esp + P_LDT_SEL]
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
restart_reenter:
dec dword [k_reenter]
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
iret
(2)clock.c中时钟中断中调用的进程调度函数:
/*======================================================================*
clock_handler
*======================================================================*/
PUBLIC void clock_handler(int irq)
{
disp_str("#");
if (k_reenter != 0) {
disp_str("!");
return;
}
p_proc_ready++;
if (p_proc_ready >= proc_table + NR_TASKS) {
p_proc_ready = proc_table;
}
(3)main.c中三个进程的执行函数:
/*======================================================================*
TestA
*======================================================================*/
void TestA()
{
int i = 0;
while(1){
disp_str("A");
disp_int(i++);
disp_str(".");
delay(1);
}
}
/*======================================================================*
TestB
*======================================================================*/
void TestB()
{
int i = 0x1000;
while(1){
disp_str("B");
disp_int(i++);
disp_str(".");
delay(1);
}
}
/*======================================================================*
TestB
*======================================================================*/
void TestC()
{
int i = 0x2000;
while(1){
disp_str("C");
disp_int(i++);
disp_str(".");
delay(1);
}
}
最后执行的结果是
可以看到,执行结果中出现了连续的#,但是从来没有出现过!(!会在中断重入的时候显示),原因是:
从call save 到 ret 的这段时间内,时钟中断一直是关闭的(save执行完之后通过写ocw1屏蔽了时钟中断),置eoi并执行sti后,开启的是对其它中断的响应,对当前中断的响应是在ret之前才开启的。
因此这段时间里如果发生了时钟中断是不会重入的,其它的中断可以重入,这期间如果发生时钟中断会在本次中断完成后iretd后立即处理(iretd会置if位,iretd之前恢复了对本中断即时钟中断的屏蔽,前面也写过了eoi,此三个处理缺少任意一个都不能使时钟中断开启),因此在这种情况下会出现##;而如果在进程执行过程中出现中断(delay过程中很容易出现),这时会出现如A0x3.#。
这里涉及到了关闭中断过程中的中断如何处理,在关闭中断期间有新的中断到达,新中断是会在当前中断结束后立即响应的,我所查的资料的原文是这样的:
Disabled interrupts are not lost; the PIC sends them to the CPU as soon as they are
enabled again.
那么我们如何才能使时钟中断本身可以发生嵌套呢,我们可以将kernel.asm中屏蔽当前中断的那段代码注掉,即:
; 中断和异常 -- 硬件中断
; ---------------------------------
%macro hwint_master 1
call save
;in al, INT_M_CTLMASK ; `.
;or al, (1 << %1) ; | 屏蔽当前中断
;out INT_M_CTLMASK, al ; /
mov al, EOI ; `. 置EOI位
out INT_M_CTL, al ; /
sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断
push %1 ; `.
call [irq_table + 4 * %1] ; | 中断处理程序
pop ecx ; /
cli
;in al, INT_M_CTLMASK ; `.
;and al, ~(1 << %1) ; | 恢复接受当前中断
;out INT_M_CTLMASK, al ; /
ret
%endmacro
此时,在置eoi位并执行sti置if位后,在中断处理程序中便能嵌套时钟中断了,为了保证在处理程序中可以发生时钟中断,我们在clock.c的处理过程中加一个delay(1)使其执行足够长的时间,即
/*======================================================================*
clock_handler
*======================================================================*/
PUBLIC void clock_handler(int irq)
{
disp_str("#");
if (k_reenter != 0) {
disp_str("!");
return;
}
delay(1);
p_proc_ready++;
if (p_proc_ready >= proc_table + NR_TASKS) {
p_proc_ready = proc_table;
}
下面是执行的结果:
可以看到,!即中断重入出现了。