第三节 时钟中断
内核在中断处理方面,与异常处理有着许多重合的部分,这主要体现在向量表机制上。这一节首先来扩展异常中断向量表,让内核具备处理中断的能力,然后实现对全局和具体中断的启用和关闭。让我们先从时钟中断入手。
扩展异常中断向量表的具体处理逻辑:
// axhal/src/riscv64/trap.rs
#[no_mangle]
fn riscv_trap_handler(tf: &mut TrapFrame) {
let scause = scause::read();
match scause.cause() {
... ...
Trap::Interrupt(_) => handle_irq_extern(scause.bits()),
... ...
}
}
/// Call the external IRQ handler.
#[allow(dead_code)]
pub(crate) fn handle_irq_extern(irq_num: usize) {
call_interface!(TrapHandler::handle_irq, irq_num);
}
这里用到了 crate_interface,具体的 TrapHandler::handle_irq
实现在 axruntime 中:
// axruntime/src/trap.rs
#[cfg(all(target_os = "none", not(test)))]
struct TrapHandlerImpl;
#[cfg(all(target_os = "none", not(test)))]
#[crate_interface::impl_interface]
impl axhal::trap::TrapHandler for TrapHandlerImpl {
fn handle_irq(irq_num: usize) {
axhal::irq::dispatch_irq(irq_num);
}
}
// axhal/src/riscv64/irq.rs
static TIMER_HANDLER: BootOnceCell<IrqHandler> = unsafe {
BootOnceCell::new()
};
pub fn dispatch_irq(scause: usize) {
match scause {
S_TIMER => {
log::trace!("IRQ: timer");
TIMER_HANDLER.get()();
},
_ => panic!("invalid trap cause: {:#x}", scause),
}
}
pub fn register_handler(scause: usize, handler: IrqHandler) -> bool {
match scause {
S_TIMER => {
if !TIMER_HANDLER.is_init() {
TIMER_HANDLER.init(handler);
true
} else {
false
}
},
_ => panic!("invalid trap cause: {:#x}", scause),
}
}
中断处理同样基于中断的编号 scause 来确定具体的例程,就是上面的 dispatch_irq(scause),看到它会再调用TIMER_HANDLER 这个代表时钟处理例程的句柄。
内核在 axruntime 启动过程中会初始化中断 irq 的设施,并注册时钟处理的例程:
#[no_mangle]
#[cfg(all(target_os = "none", not(test)))]
pub extern "C" fn rust_main(hartid: usize, dtb: usize) -> ! {
... ...
axtask::init_scheduler();
info!("Initialize interrupt handlers...");
#[cfg(all(target_os = "none", not(test)))]
init_interrupt();
#[cfg(not(test))]
unsafe {
main();
}
... ...
}
#[cfg(all(target_os = "none", not(test)))]
fn init_interrupt() {
use axhal::irq::TIMER_IRQ_NUM;
// Setup timer interrupt handler
const PERIODIC_INTERVAL_NANOS: u64 =
axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64;
static mut NEXT_DEADLINE: u64 = 0;
fn update_timer() {
let now_ns = axhal::time::current_time_nanos();
// Safety: we have disabled preemption in IRQ handler.
let mut deadline = unsafe { NEXT_DEADLINE };
if now_ns >= deadline {
deadline = now_ns + PERIODIC_INTERVAL_NANOS;
}
unsafe { NEXT_DEADLINE = deadline + PERIODIC_INTERVAL_NANOS };
trace!("now {} deadline {}", now_ns, deadline);
axhal::time::set_oneshot_timer(deadline);
}
axhal::irq::register_handler(TIMER_IRQ_NUM, || {
update_timer();
debug!("On timer tick!");
});
// Enable IRQs before starting app
axhal::irq::enable_irqs();
}
注册了一个时钟中断的例程:设置一个 1 秒后触发的定时器,每次触发都会重置定时器,让其下个 1 秒后再次触发,由此形成一个周期定时器。其中,设置定时器通过 axhal::time 模块实现:
// axhal/src/riscv64/time.rs
const TIMER_FREQUENCY: u64 = 10_000_000; // 10MHz
pub const NANOS_PER_SEC: u64 = 1_000_000_000;
const NANOS_PER_TICK: u64 = NANOS_PER_SEC / TIMER_FREQUENCY;
#[inline]
pub fn current_ticks() -> u64 {
time::read() as u64
}
#[inline]
pub const fn ticks_to_nanos(ticks: u64) -> u64 {
ticks * NANOS_PER_TICK
}
#[inline]
pub fn current_time_nanos() -> u64 {
ticks_to_nanos(current_ticks())
}
pub fn set_oneshot_timer(deadline_ns: u64) {
sbi_rt::set_timer(nanos_to_ticks(deadline_ns));
}
OpenSBI 本身已经封装了对定时器处理的功能,所以函数 set_oneshot_timer 只需要调用 SBI 的 ecall 即可设置定时器。
最后需要注意:在 init_interrupt
的最后,需要开启全局中断。
// axhal/src/riscv64/irq.rs
#[inline]
pub fn enable_irqs() {
unsafe { sstatus::set_sie() }
}
测试一下 make run LOG=trace
,从跟踪日志中可以看到,定时器以 1 秒为周期进行调度,测试通过!