前面的 Voluntary context switch 可能會產生 starvation 的問題,那有什麼方式可以強迫 process 到一定的時間就進行 context switch?
圖片來自 Lab 4 Spec
透過每次 Timer Interrupt 的 IRQ 進到 kernel space 檢查目前 process 的使用時間,如果超過就將 task structure 的 reschedule flag 設起來,並在回到 user space 時檢查是否需要進行 reschedule。
在 process 0 初始化完 scheduler 前,把 core timer 打開。
1
2
3
4
5
6
| void schedule_init() {
...
arm_core_timer_enable();
schedule();
}
|
將原本的 irq_exc_router
再包一層 irq_stk_switcher
。
1
2
3
4
5
| irq_exc_handler:
kernel_entry
bl irq_stk_switcher
bl irq_return
kernel_exit
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| void irq_stk_switcher() {
// Switch to interrupt stack if entry_sp in kernel stack
register char* entry_sp;
asm volatile("mov %0, sp": "=r"(entry_sp));
if (!(entry_sp <= &intr_stack[4095] && entry_sp >= &intr_stack[0])) {
asm volatile("mov sp, %0" : : "r"(&intr_stack[INTR_STK_TOP_IDX]));
}
irq_exc_router();
// Restore to kernel stack if entry_sp in kernel stack
if (!(entry_sp <= &intr_stack[4095] && entry_sp >= &intr_stack[0])) {
asm volatile("mov sp, %0" : : "r"(entry_sp));
}
}
|
在離開 interrupt handler 前檢查 task structure 的 reschedule flag,如果有設定 reschedule flag 就進行 reschedule。
1
2
3
4
5
6
7
8
9
| void irq_return() {
// check reschedule flag
struct task_t *current = get_current_task();
if (current->need_resched) {
current->counter = TASK_EPOCH;
current->need_resched = 0;
schedule();
}
}
|
Core timer interrupt handler
若 task structure 的 counter
<= 0 時,設定 reschedule flag。
在所有的 critical region 結束後,將 interrupt 再次打開 (for nested interrupt)。或是你也可以讓 EL1 完全不能被 interrupt,但這樣可能 responsive 會不好就是了。
1
2
3
4
5
6
7
8
9
10
11
12
| void arm_core_timer_intr_handler() {
register unsigned int expire_period = CORE_TIMER_EXPRIED_PERIOD;
asm volatile("msr cntp_tval_el0, %0" : : "r"(expire_period));
// check current task running time
struct task_t *current = get_current_task();
if (--current->counter <= 0) {
current->counter = 0;
current->need_resched = 1;
}
irq_enable();
}
|
請盡量保持 interrupt handler 的簡潔與效能,最好不要在 interrupt context 中進行 reschedule