前面四章的内容使得我们的 main.rs 变得异常冗长,代码结构也并不清晰。磨刀不误砍柴工,所以在继续编写 os 之前,我们先来对已有文件进行一些整理:
创建 lang_items.rs :
use core::panic::PanicInfo;
// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
println!("{}", _info);
loop {}
}
#[no_mangle]
pub extern fn abort() {
panic!("abort!");
}
创建 init.rs :
use crate::interrupt::init as interrupt_init;
global_asm!(include_str!("boot/entry.asm"));
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
interrupt_init();
println!("Hello World");
unsafe{
asm!("ebreak\n"::::);
}
panic!("End of rust_main");
}
把中断相关的代码放入 interrupt.rs :
global_asm!(include_str!("trap/trap.asm"));
创建 lib.rs ,用于声明属性和库文件:
#![feature(lang_items)]
#![feature(asm)]
#![feature(panic_info_message)]
#![feature(global_asm)]
#![no_std]
#[macro_use]
pub mod io;
mod lang_items;
mod context;
mod interrupt;
mod init;
经过以上的精简,我们的 main.rs 变得十分整洁:
#![no_std]
#![no_main]
#[allow(unused_imports)]
use os;
timebase 的数值约为 cpu 频率的 1% ,防止时钟中断占用过多的 cpu 资源。get_cycle 可以获取当前时间,当前时间加上 timebase(两次中断的时间差) 为下一次中断产生的时间,通过 set_timer 设置。
cpu 中有一个专门用于储存时间的 64 位寄存器。由于 system call 的返回值存放于 32 位的 x10 通用寄存器,所以需要分别读取时间的前 32 位和后 32 位:
use riscv::register::{ time, timeh};
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);
}
}
}
hi 是时间的高 32 位,lo 是时间的低 32 位。注意到这里并没有之间拼接 hi 和 lo 然后将其返回,而是多了一步 if (hi == tmp) 判断。这是由于在执行完 let lo = time::read() 后,当前时间会改变。尽管时间的前 32 位改变的概率很小,但是仍然需要进行一次判断。
step2: 外设中断初始化
前面看到,时钟中断会用到 sie 寄存器。为了能够正确的产生时钟中断,我们需要先对 sie 寄存器进行初始化:
// in interrupt.rs
use riscv::register::{stvec, sstatus};
#[no_mangle]
pub fn init() {
extern {
fn __alltraps();
}
unsafe {
sstatus::set_sie();
stvec::write(__alltraps as usize, stvec::TrapMode::Direct);
}
}