4.2. 时钟中断

本章代码对应 commit :30d346bfa2638932118b91bf6a5cc4ad10675a2a

概要

目前我们已经实现了简易的中断机制,同时把 main.rs 变得乱七八糟的。本章我们将:

  1. 简化 main.rs ,调整代码结构。

  2. 实现时钟中断。

  3. rust_trap 中区分中断类型,根据中断类型进行不同的处理。

简化 main.rs

前面四章的内容使得我们的 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

把中断相关的代码放入 interrupt.rs

创建 lib.rs ,用于声明属性和库文件:

经过以上的精简,我们的 main.rs 变得十分整洁:

现在,让我们来实现时钟中断吧!

实现时钟中断响应

step1: 初始化时钟

首先,创建 clock.rs ,并在 lib.rs 中加入 mod clock

时钟中断,最重要的就是时钟。在 clock.rs 的第一行加入计时器:

然后对时钟进行初始化:

sie::set_stimer 开启了时钟中断。 clock_set_next_event 的作用是设置下一次时钟中断触发的时间,现在我们来实现他:

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 位改变的概率很小,但是仍然需要进行一次判断。

step2: 外设中断初始化

前面看到,时钟中断会用到 sie 寄存器。为了能够正确的产生时钟中断,我们需要先对 sie 寄存器进行初始化:

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

  1. 通过内联汇编使用 ebreak 。

  2. 时钟中断。

step3: 响应时钟中断

但是我们的 rust_trap 目前除了会打印 trap! 之外什么都不会。让我们来教他怎么区分中断类型吧:

tf.scause.cause 表示触发中断的中断类型,这里我们只对两种中断类型进行处理,除此之外的中断则调用 panic! 宏。在触发时钟中断时,我们要做的第一件事就是通过 clock_set_next_event 设置下一次中断时间并告知 cpu ,当前时钟中断已经被正确处理。其余的事情十分简单,只需要把从 clock.rs 中引入的 TICK 加一,表示经过了一个时钟周期。每经过 100 个时钟周期,就在屏幕上打印一些字符。

最后,在 rust_main 中使用 use crate::clock::init as clock_init 引入时钟,修改 rust_main 为:

这样,我们就成功的设置好了时钟中断。编译运行:

loop {} 之前加上: unsafe { asm!("ebreak"::::"volatile"); } ,编译运行:

预告

本章我们实现了时钟中断,并且能够进行一些简单的中断处理。下一章,我们将学习操作系统中最常见的内存管理方式:分页。为此我们会介绍虚拟内存和物理内存的关系,理解页表的功能以及实现方式。

Last updated

Was this helpful?