Involuntary Context Switch

前面的 Voluntary context switch 可能會產生 starvation 的問題,那有什麼方式可以強迫 process 到一定的時間就進行 context switch?

思路

圖片來自 Lab 4 Spec

透過每次 Timer Interrupt 的 IRQ 進到 kernel space 檢查目前 process 的使用時間,如果超過就將 task structure 的 reschedule flag 設起來,並在回到 user space 時檢查是否需要進行 reschedule。

開啟 Core timer

schedule.c

在 process 0 初始化完 scheduler 前,把 core timer 打開。

1
2
3
4
5
6
void schedule_init() {
    ...
    
    arm_core_timer_enable();
    schedule();
}

從 Kernel stack 切換至 Interrupt Stack

將原本的 irq_exc_router 再包一層 irq_stk_switcher

exception_table.S

1
2
3
4
5
irq_exc_handler:
    kernel_entry
    bl  irq_stk_switcher
    bl  irq_return
    kernel_exit

exception.c

 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));
    }
}

Reschedule

在離開 interrupt handler 前檢查 task structure 的 reschedule flag,如果有設定 reschedule flag 就進行 reschedule。

exception.c

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。

exception.c

在所有的 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

comments powered by Disqus