参考Minix,时钟中断处理程序是经过很长时间优化出来的,经过本过程理解代码。
ALIGN 16
hwint00: ; Interrupt routine for irq 0 (the clock).
call save //保存进程表一个进程信息,并且call指令会在进程表占用一个表项
in al, INT_M_CTLMASK ; `.
or al, 1 ; | 不允许再发生时钟中断
out INT_M_CTLMASK, al ; /
mov al, EOI ; `. reenable
out INT_M_CTL, al ; / master 8259
sti
push 0
call clock_handler
add esp, 4
cli
in al, INT_M_CTLMASK ; `.
and al, 0xFE ; | 又允许时钟中断发生
out INT_M_CTLMASK, al ; /
ret //对应 jmp [eax + RETADR - P_STACKBASE] 前面一句的push 的地址
现在来考虑一下,为什么这个save与我们以前的函数看起来是如此的不同?一般的函数最后都是以ret指令结尾,跳回调用处
继续执行,那是因为函数所使用的堆栈最后都被释放了,调用时call指令的下一条指令地址被压栈,最后ret指令将这条指令从堆
栈中弹出,函数调用前后esp的值是一样的。可是我们这里的save函数则不同,调用前后esp的值是完全不同的,甚至是否发生中断重入也影响着esp的值。所以我们必须事先将返回地址保存起来,最后用jmp指令跳转回去。
; =====================================================
; 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;
;}
; =====================================================
; 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
iretd //最后中断处理完毕,返回中断的进程