--------------------------------------------------------
调度初探
--------------------------------------------------------
上一节我们实现了从内核跳转到用户任务执行,这节我们实现时钟的调度...
一,测试时钟中断是否正常
运行:
——OK,时钟中断运行正常(0,0位置的字符在变幻)!
二,试图揭露调度的关键
在试图探索调度的关键之前,我们先要清楚一个问题:三个堆栈!第一个堆栈就是任务在运行时使用的低特权级堆栈,第二个堆栈是指向进程的进程表的堆栈,这个堆栈在用户进程进入时钟中断之后就设置了,我们可以称其为高特权级堆栈,第三个堆栈是内核堆栈,在进入调度程序之后,因为 sp0 指向进程的进程表,所以不能作为调度程序内使用的堆栈,此时使用调度程序重定向的堆栈!
ALIGN 16
hwint00:
; ----
; so there is important!
; we save the old task's status!
; ----
sub esp, 4
pushad
push ds
push es
push fs
push gs
mov dx, ss
mov ds, dx
mov es, dx
; ----
; important!
; change to kernel stack!
; ----
mov esp, StackTop
push clock_int_msg
call disp_str
add esp, 4
mov al, EOI
out INT_M_CTL, al
; ----
; imortant!
; back to process table stack!
; ----
mov esp, [p_proc_ready]
; ----
; important!
; set sp0 to new task!
; ----
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
; ----
; important!
; load new task's status!
; ----
pop gs ; ┓
pop fs ; ┃
pop es ; ┣ 恢复原寄存器值
pop ds ; ┃
popad ; ┛
add esp, 4
; do switch!
iretd
——这段节选代码做了 6 件事:
1,保存旧的(要被换下)进程的状态
2,切换内核态堆栈
3,调度程序内的动作(打印字符)
4,从内核堆栈恢复
5,设置 sp0 到新进程(要被换上)的进程表
6,iretd 指令调度新进程执行
——于是调度的关键就在于 5,6 ——5 来保存, 6 来调度!在调度一个进程执行之后,这个进程就要准备着在下一次调度的时候被换下,那时需要保存其状态!
运行:
因为字符的打印是有延时的,所以在延时的途中中断了很多次!
三,探讨中断重入
默认当进入中断后会关中断,因此默认情况下中断内部是不会发生中断的,但是我们要求中断内部可以发生中断,叫做中断的嵌套,遇到的问题就是上次中断没有结束下次中断又开始了,对于时钟中断而言,这将递归嵌套到死机,我们的解决办法是只有一个有效的时钟中断占用长时间的中断例程,其余嵌套的时钟中断立马结束!
ALIGN 16
hwint00:
; ----
; so there is important!
; we save the old task's status!
; ----
sub esp, 4
pushad
push ds
push es
push fs
push gs
mov dx, ss
mov ds, dx
mov es, dx
mov al, EOI
out INT_M_CTL, al
; if interrupt reenter?
inc dword [k_reenter]
cmp dword [k_reenter], 0
jne .re_enter
; ----
; important!
; change to kernel stack!
; ----
mov esp, StackTop
sti
push clock_int_msg
call disp_str
add esp, 4
push 1
call delay
add esp, 4
; ----
; imortant!
; back to process table stack!
; ----
mov esp, [p_proc_ready]
; ----
; important!
; set sp0 to new task!
; ----
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
.re_enter: ; 如果(k_reenter != 0),会跳转到这里
dec dword [k_reenter] ; k_reenter--;
; ----
; important!
; load new task's status!
; ----
pop gs ; ┓
pop fs ; ┃
pop es ; ┣ 恢复原寄存器值
pop ds ; ┃
popad ; ┛
add esp, 4
; do switch!
iretd
这种解决嵌套的本质:
ALIGN 16
hwint00:
; ----
; so there is important!
; we save the old task's status!
; ----
sub esp, 4
pushad
push ds
push es
push fs
push gs
mov dx, ss
mov ds, dx
mov es, dx
mov al, EOI
out INT_M_CTL, al
; if interrupt reenter?
inc dword [k_reenter]
cmp dword [k_reenter], 0
jne .re_enter
; ----
; important!
; change to kernel stack!
; ----
mov esp, StackTop
sti
push clock_int_msg
call disp_str
add esp, 4
push 1
call delay
add esp, 4
; ----
; imortant!
; back to process table stack!
; ----
mov esp, [p_proc_ready]
; ----
; important!
; set sp0 to new task!
; ----
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
.re_enter: ; 如果(k_reenter != 0),会跳转到这里
dec dword [k_reenter] ; k_reenter--;
push 1
call delay
add esp, 4
; ----
; important!
; load new task's status!
; ----
pop gs ; ┓
pop fs ; ┃
pop es ; ┣ 恢复原寄存器值
pop ds ; ┃
popad ; ┛
add esp, 4
; do switch!
iretd
我们修改一下上面的片段,发现不奏效了,因此关键在于如果是嵌套中断,要让他“快速”地结束,多快呢?在下一次嵌套来临之前要结束!
OK!我们已经认识了调度,知道了调度的关键点,下一节我们就引入多任务!