
OS 简单教程十一
__switch
.altmacro
.macro SAVE_SN n
sd s\n, (\n+1)*8(sp)
.endm
.macro LOAD_SN n
ld s\n, (\n+1)*8(sp)
.endm
.section .text
.globl __switch
__switch:
# __switch(
# current_task_cx_ptr2: &*const TaskContext,
# next_task_cx_ptr2: &*const TaskContext
# )
# push TaskContext to current sp and save its address to where a0 points to
addi sp, sp, -13*8
# sd 是寄存器到内存
sd sp, 0(a0)
# fill TaskContext with ra & s0-s11
sd ra, 0(sp)
.set n, 0
.rept 12
SAVE_SN %n
.set n, n + 1
.endr
# ready for loading TaskContext a1 points to
# a1 -> sp
ld sp, 0(a1)
# load registers in the TaskContext
ld ra, 0(sp)
.set n, 0
.rept 12
LOAD_SN %n
.set n, n + 1
.endr
# pop TaskContext
addi sp, sp, 13*8
ret
__switch 函数主要保存被调用者的寄存器到内存中的 TaskContext 中,而后改变 sp 和 ra 寄存器。
- sp 寄存器指定了新任务的堆栈位置
- ra 寄存器其实是 return address ,函数返回的地址,指定了新任务下一步要执行的指令 切换了它们两个寄存器,就切换了任务。
trap_return
#[no_mangle]
pub fn trap_return() -> ! {
set_user_trap_entry();
let trap_cx_ptr = TRAP_CONTEXT;
let user_satp = current_user_token();
extern "C" {
fn __alltraps();
fn __restore();
}
let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE;
unsafe {
asm!(
"fence.i",
"jr {restore_va}",
restore_va = in(reg) restore_va,
in("a0") trap_cx_ptr,
in("a1") user_satp,
options(noreturn)
);
}
// panic!("Unreachable in back_to_user!");
}
为什么不直接调用 __restore 呢? 因为 TrapContext 在应用的虚拟内存里,要拿到应用的 token 和 TrapContext 的虚拟地址,__restore 先通过 token 切换地址空间再访问 TrapContext 。
抢占式调度
为什么使用抢占式调度?协作就像靠自觉,抢占就像排队,排队整体效率提高了,虽然牺牲了个人利益。 抢占式调度采用时钟中断实现,时钟中断就是常说的时间片,时钟中断其实是 SBI 提供的。到了固定的时间触发时钟中断,进而进入中断处理程序,中断处理程序发现是时钟中断,切换到下一个任务来完成抢占式调度。
pub fn get_time() -> usize {
time::read()
}
pub fn get_time_ms() -> usize {
time::read() / (CLOCK_FREQ / MSEC_PER_SEC)
}
pub fn set_next_trigger() {
set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC);
}