> For the complete documentation index, see [llms.txt](https://xy-plus.gitbook.io/rcore-step-by-step/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xy-plus.gitbook.io/rcore-step-by-step/shi-xian-zhong-duan/shi-zhong-zhong-duan.md).

# 4.2. 时钟中断

> 本章代码对应 commit ：30d346bfa2638932118b91bf6a5cc4ad10675a2a

## 概要

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

1. 简化 **main.rs** ，调整代码结构。
2. 实现时钟中断。
3. 在 **rust\_trap** 中区分中断类型，根据中断类型进行不同的处理。

## 简化 main.rs

前面四章的内容使得我们的 main.rs 变得异常冗长，代码结构也并不清晰。磨刀不误砍柴工，所以在继续编写 os 之前，我们先来对已有文件进行一些整理：

创建 **lang\_items.rs** ：

```rust
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** ：

```rust
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** ：

```rust
global_asm!(include_str!("trap/trap.asm"));
```

创建 **lib.rs** ，用于声明属性和库文件：

```rust
#![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** 变得十分整洁：

```rust
#![no_std]
#![no_main]

#[allow(unused_imports)]
use os;
```

现在，让我们来实现时钟中断吧！

## 实现时钟中断响应

### step1:  初始化时钟

首先，创建 **clock.rs** ，并在 **lib.rs** 中加入 `mod clock` 。

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

```rust
pub static mut TICK: usize = 0;
```

然后对时钟进行初始化：

```rust
use riscv::register::sie;
pub fn init() {
    unsafe{
        TICK = 0;
        sie::set_stimer();
    }
    clock_set_next_event();
    println!("++++setup timer !++++");
}
```

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

```rust
static timebase: u64 = 100000;
use bbl::sbi::set_timer;

pub fn clock_set_next_event() {
    set_timer(get_cycle() + timebase);
}
```

**timebase** 的数值约为 cpu 频率的 1% ，防止时钟中断占用过多的 cpu 资源。`get_cycle` 可以获取当前时间，当前时间加上 **timebase（两次中断的时间差）** 为下一次中断产生的时间，通过 `set_timer` 设置。

cpu 中有一个专门用于储存时间的 64 位寄存器。由于 **system call** 的返回值存放于 32 位的 **x10** 通用寄存器，所以需要分别读取时间的前 32 位和后 32 位：

```rust
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** 寄存器进行初始化：

```rust
// 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);
    }
}
```

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

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

### step3: 响应时钟中断

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

```rust
// in interrupt.rs

use riscv::register::scause::Trap;
use riscv::register::scause::Exception;
use riscv::register::scause::Interrupt;
use crate::clock::{ TICK, clock_set_next_event };

#[no_mangle]
pub extern "C" 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() {
    // 响应当前时钟中断的同时，手动设置下一个时钟中断。这一函数调用同时清除了 MTIP ，使得 CPU 知道当前这个中断被正确处理。
    clock_set_next_event();
    unsafe{
        TICK = TICK + 1;
        if TICK % 100 == 0 {
            println!("100 ticks!");
        }
    }
}
```

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

最后，在 **rust\_main** 中使用 **use crate::clock::init as clock\_init** 引入时钟，修改 **rust\_main** 为：

```rust
// in init.rs

use crate::clock::init as clock_init;

#[no_mangle]
pub extern "C" fn rust_main() -> ! {
    interrupt_init();
    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:66:5
```

## 预告

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