> 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
```

## 预告

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xy-plus.gitbook.io/rcore-step-by-step/shi-xian-zhong-duan/shi-zhong-zhong-duan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
