内核在运行的时候总是会不知何时卡死,底层原因是持续触发时钟中断

// 时间片到了
Trap::Interrupt(Interrupt::SupervisorTimer) => {
    set_next_trigger(); // 主要是这里
    suspend_current_and_run_next();
}

初步解决

我们将设置下一个时钟中断放置在了 suspend_current_and_run_next 之前,导致可能因为后者执行 时间过长而使用户态一直处于时钟中断触发状态,至于为什么会在 RV64 上一直触发中断,可以参阅 RV 的特权级手册:

Platforms provide a real-time counter, exposed as a memory-mapped machine-mode read-write register, mtime. mtime must increment at constant frequency, and the platform must provide a mechanism for determining the period of an mtime tick. The mtime register will wrap around if the count overflows.

The mtime register has a 64-bit precision on all RV32 and RV64 systems. Platforms provide a 64- bit memory-mapped machine-mode timer compare register (mtimecmp). A machine timer interrupt becomes pending whenever mtime contains a value greater than or equal to mtimecmp, treating the values as unsigned integers. The interrupt remains posted until mtimecmp becomes greater than mtime (typically as a result of writing mtimecmp). The interrupt will only be taken if interrupts are enabled and the MTIE bit is set in the mie register.

由于 suspend_current_and_run_next 执行的时间超过了一个时间片的长度,导致其返回用户态进程时, mtime 的值已经大于了 set_next_trigger 设置的时间点,由上文可得,如果 mtime 大于等于 mtimecmp(即 set_next_trigger 设置的值),并且 mie 为使能状态,那么时钟中断会一直处于触发状态.

而我们的内核 mie 一直处于使能状态,所以 S 态的时钟中断会持续在用户态发生(S 态中断不会打断同级与 更高特权级代码的执行),导致用户态毫无进展,而我们内核的引导程序 initproc 会一直等待卡死用户进程 变为僵尸态,所以造成了内核执行流的卡死.

解决办法:

简单调整下位置

Trap::Interrupt(Interrupt::SupervisorTimer) => {
    suspend_current_and_run_next();
    set_next_trigger();
}

但这样真的对吗?

不对,因为会导致用户态程序卡死整个内核的执行流

一个致命的缺点是,用户态的程序需要第一次运行后才能正确的获取时钟中断,不然只能等轮回一边后才可能正确让出

当前的逻辑是:

RustSBI 完成初始化后,在 meow(没错,这是我们 Rust 代码的 ENTRYPOINT),中初步设定一个时钟中断

#[cfg(not(feature = "multi_harts"))]
#[no_mangle]
pub fn meow() -> ! {
    if hartid!() == 0 {
        init_bss();
        unsafe { set_fs(FS::Dirty) }
        lang_items::setup();
        logging::init();
        mm::init();
        trap::init();
        trap::enable_stimer_interrupt();
        trap::set_next_trigger();
        fs::init();
        task::add_initproc();
        task::run_tasks();
    } else {
        loop {}
    }

    unreachable!("main.rs/meow: you should not be here!");
}

这是第一个问题,我们原本想的是,这个时钟中断会在第一用户态程序运行时发生,但是有可能它在 fs::init() 或者 task::add_initproc() 中已经发生了,这会导致一进入用户态程序就发生中断,这和我们 预期的不一样.

而且,陷入中断后,除非使失能 mie,或者再次 set_next_trigger()(又或者 mtime 发生回环), 否则将一直处于中断触发的状态

而这之后切换的用户进程都会遇到中断而直接返回,直到运行到第一个用户进程(其实应该是引导程序 initproc), 在下面 suspend_current_and_run_next 真正意义上的返回后,重新设置下一个中断时间点,这才能让 OS 内核 所有的用户进程进入正常的运行流.

Trap::Interrupt(Interrupt::SupervisorTimer) => {
    suspend_current_and_run_next();
    set_next_trigger();
}

而这种时间上的开销显然是没必要的,所以我们根据所有用户进程都会通过 trap_return 返回用户态这一点, 将 set_next_trigger 设置在了 trap_return 中,同时判断当前进程是否是因为时间片耗尽而导致的 trap 返回:

Trap::Interrupt(Interrupt::SupervisorTimer) => {
    suspend_current_and_run_next();
}

// ...
#[no_mangle]
pub fn trap_return() -> ! {
    // ...

    if is_time_intr_trap() {
        set_next_trigger();
    }
    
    // ...
}

/// 是否是由于时间片耗尽导致的 trap
fn is_time_intr_trap() -> bool {
    let scause = scause::read();
    scause.cause() == Trap::Interrupt(scause::Interrupt::SupervisorTimer)
}

最终结果

  • 我们消除了一个严重的 bug: 内核在执行用户程序时随机卡死
  • 删去了 meow 中不利于系统鲁棒性的代码