时钟中断
本章代码对应分支:clock
概要
- 实现时钟中断。
- 在
rust_trap
中区分中断类型,根据中断类型进行不同的处理。
初始化时钟
首先,创建 clock.rs
,并在 lib.rs
中加入 mod clock
。
时钟中断,最重要的就是时钟。首先加入计时器,然后对时钟进行初始化:
clock.rs
use crate::sbi::set_timer;
use riscv::register::sie;
use riscv::register::{ time, timeh };
pub static mut TICK: usize = 0;
static TIMEBASE: u64 = 100000;
pub fn init() {
unsafe {
TICK = 0;
sie::set_stimer();
}
clock_set_next_event();
println!("++++setup timer !++++");
}
pub fn clock_set_next_event() {
set_timer(get_cycle() + TIMEBASE);
}
fn get_cycle() -> u64 {
loop {
let hi = timeh::read();
let lo = time::read();
let tmp = timeh::read();
if hi == tmp {
return ((hi as u64) << 32) | (lo as u64);
}
}
}
sie::set_stimer
通过将 mie
寄存器的 STIE 位(第 5 位)设为 1 开启了内核态的时钟中断。 clock_set_next_event
的作用是设置下一次时钟中断触发的时间。riscv 不支持直接设置时钟中断的间隔,只能在每次触发时钟中断的时候,设置下一次时钟中断的时间。TIMEBASE
便是时间间隔,其数值一般约为 cpu 频率的 1% ,防止时钟中断占用过多的 cpu 资源。get_cycle
用于获取当前时间,当前时间加上 TIMEBASE
为下一次中断产生的时间,通过 set_timer
设置。
cpu 中有一个专门用于储存时间的 64 位寄存器。由于 system call 的返回值存放于 32 位的 x10
通用寄存器,所以需要分别读取时间的前 32 位和后 32 位:
hi
是时间的高 32 位,lo
是时间的低 32 位。注意到这里并没有之间拼接 hi
和 lo
然后将其返回,而是多了一步 if hi == tmp
判断。这是由于在执行完 let lo = time::read()
后,当前时间会改变。尽管时间的前 32 位改变的概率很小,但是仍然需要进行一次判断。
开启内核态中断
sie
寄存器控制了所有内核态的中断。需要将其 SSIE 位(第 2 位)设为 1 ,内核态才能接受软件中断。
为了能够正确响应内核态的时钟中断,需要将 sie
寄存器进行设置:
interrupt.rs
use riscv::register::{ stvec, sscratch, sstatus };
#[no_mangle]
pub fn init() {
extern {
fn __alltraps();
}
unsafe {
sscratch::write(0); // 给中断 asm 初始化
sstatus::set_sie();
stvec::write(__alltraps as usize, stvec::TrapMode::Direct);
}
println!("++++setup interrupt !++++");
}
现在我们有了两个产生中断的方式:
- 通过内联汇编使用 ebreak, ecall 。
- 时钟中断。
响应时钟中断
但是我们的 rust_trap 目前除了会打印 trap! 之外什么都不会。让我们来教他怎么区分中断类型吧:
interrupt.rs
use riscv::register::scause::{ Trap, Exception, Interrupt };
use crate::clock::{ TICK, clock_set_next_event };
#[no_mangle]
pub fn rust_trap(tf: &mut TrapFrame) {
match tf.scause.cause() {
Trap::Exception(Exception::Breakpoint) => breakpoint(),
Trap::Interrupt(Interrupt::SupervisorTimer) => super_timer(),
_ => panic!("unexpected trap"),
}
}
fn breakpoint() {
panic!("a breakpoint set by kernel");
}
fn super_timer() {
// 响应当前时钟中断的同时,手动设置下一个时钟中断
clock_set_next_event();
unsafe{
TICK = TICK + 1;
if TICK % 100 == 0 {
println!("100 ticks!");
}
}
}
tf.scause.cause
表示触发中断的中断类型,这里我们只对两种中断类型进行处理,除此之外的中断则直接 panic。在触发时钟中断时,我们要做的第一件事就是通过 clock_set_next_event
设置下一次中断时间。其余的事情十分简单,只需要把从 clock.rs
中引入的 TICK
加一,表示经过了一个时钟周期。每经过 100 个时钟周期,就在屏幕上打印一些字符。
最后,在 rust_main
中对时钟进行初始化。为了能够看到后面持续产生的时钟中断,需要删掉其原先的 panic :
init.rs
#[no_mangle]
pub fn rust_main() -> ! {
crate::interrupt::init();
crate::clock::init();
loop {}
}
这样,我们就成功的设置好了时钟中断。编译运行:
++++setup interrupt !++++
++++setup timer !++++
100 ticks!
100 ticks!
...
在 loop {}
之前加上: unsafe { asm!("ebreak"::::"volatile"); }
,编译运行:
++++setup interrupt !++++
++++setup timer !++++
panicked at 'a breakpoint set by kernel', src/interrupt.rs:31:5
预告
本章我们实现了时钟中断,并且能够进行一些简单的中断处理。下一章我们将实现物理内存的分配和释放。