时钟中断

本章代码对应分支:clock

概要

  1. 实现时钟中断。
  2. 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 位。注意到这里并没有之间拼接 hilo 然后将其返回,而是多了一步 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 !++++");
}

现在我们有了两个产生中断的方式:

  1. 通过内联汇编使用 ebreak, ecall 。
  2. 时钟中断。

响应时钟中断

但是我们的 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

预告

本章我们实现了时钟中断,并且能够进行一些简单的中断处理。下一章我们将实现物理内存的分配和释放。

results matching ""

    No results matching ""

    results matching ""

      No results matching ""