lab1

本实验目标是实现中断系统。
lab1实验指导书
实验完成后目录结构如下:

Project
│  rust-toolchain
│
└─os│  .gitignore│  Cargo.lock│  Cargo.toml│  Makefile│├─.cargo│      config│└─src│  console.rs│  entry.asm│  linker.ld│  main.rs│  panic.rs│  sbi.rs│└─interruptcontext.rshandler.rsinterrupt.asmmod.rstimer.rs

一,中断原理简介

1.1 中断简介

中断是操作系统所有功能的基础,其决定了操作系统的模式切换以及各种资源的调度实现。

中断主要分为三种

  • 异常(Exception) : 执行指令时产生的,通常无法预料的错误。例如:访问无效内存地址、执行非法指令(除以零)等。
  • 陷阱(Trap): 陷阱是一系列强行导致中断的指令,例如:系统调用(Syscall)等。
  • 硬件中断(Hardware Interrupt): 硬件中断是由 CPU 之外的硬件产生的异步中断,例如:时钟中断、外设发来数据等。

1.2 RISC-V 与中断相关的寄存器和指令

在次只列举部分实验常用的寄存器与指令,更多信息请查阅官方文档。

寄存器

线程相关寄存器

  1. sscratch : 在用户态,sscratch 保存内核栈的地址;在内核态,sscratch 的值为 0。

发生中断时,硬件自动填写的寄存器

  1. sepc : 即 Exception Program Counter,用来记录触发中断的指令的地址。
  2. scause: 记录中断是否是硬件中断,以及具体的中断原因。
  3. stval :scause 不足以存下中断所有的必须信息。例如缺页异常,就会将 stval 设置成需要访问但是不在内存中的地址,以便于操作系统将这个地址所在的页面加载进来。

指导硬件处理中断的寄存器

  1. stvec: 设置内核态中断处理流程的入口地址。存储了一个基址 BASE 和模式 MODE:
    * MODE 为 0 表示 Direct 模式,即遇到中断便跳转至 BASE 进行执行。
    * MODE 为 1 表示 Vectored 模式,此时 BASE 应当指向一个向量,存有不同处理流程的地址,遇到中断会跳转至 BASE + 4 * cause 进行处理流程。
  2. sstatus: 具有许多状态位,控制全局中断使能等。
  3. sie :即 Supervisor Interrupt Enable,用来控制具体类型中断的使能,例如其中的 STIE 控制时钟中断使能。
  4. sip :即 Supervisor Interrupt Pending,和 sie 相对应,记录每种中断是否被触发。仅当 sie 和 sip 的对应位都为 1 时,意味着开中断且已发生中断,这时中断最终触发。

1.3 与中断相关的指令

进入和退出中断

  1. ecall:触发中断,进入更高一层的中断处理流程之中。用户态进行系统调用进入内核态中断处理流程,内核态进行 SBI 调用进入机器态中断处理流程,使用的都是这条指令。
  2. sret:从内核态返回用户态,同时将 pc 的值设置为 sepc。(如果需要返回到 sepc 后一条指令,就需要在 sret 之前修改 sepc 的值)
  3. ebreak:触发一个断点。
  4. mret 从机器态返回内核态,同时将 pc 的值设置为 mepc。

操作 CSR(读写重置)

  1. csrrw dst, csr, src(CSR Read Write):同时读写的原子操作,将指定 CSR 的值写入 dst,同时将 src 的值写入 CSR。
  2. csrr dst, csr(CSR Read):仅读取一个 CSR 寄存器。
  3. csrw csr, src(CSR Write):仅写入一个 CSR 寄存器。
  4. csrc(i) csr, rs1(CSR Clear):将 CSR 寄存器中指定的位清零,csrc 使用通用寄存器作为 mask,csrci 则使用立即数。
  5. csrs(i) csr, rs1(CSR Set):将 CSR 寄存器中指定的位置 1,csrc 使用通用寄存器作为 mask,csrci 则使用立即数。

二,程序运行状态

3.1 上下文设计

在程序运行中,各种寄存器存储着当前程序的运行时信息,包括PC,返回值等,这些信息被统称为程序的上下文。当中断发生时,操作系统必须将当前程序的上下文保存,以便于中断完成后会恢复现场;接着会将中断程序的上下文赋值到寄存器中。
本节的目的在于设计上下文信息。
第 1 步 设计 Context类。在os/src/interrupt/context.rs内添加如下代码:

use riscv::register::{sstatus::Sstatus, scause::Scause};#[repr(C)]
pub struct Context {pub x: [usize; 32],     // 32 个通用寄存器pub sstatus: Sstatus,  //状态位,控制全局中断使能pub sepc: usize       //记录触发中断的指令的地址
}

第 2 步 添加依赖
为了使用riscv的寄存器,必须在os/Cargo.toml 中添加依赖,将依赖修改如下:

[dependencies]
riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] }

3.2 状态的保存与恢复

状态保存:先用栈上的一小段空间来把需要保存的全部通用寄存器和 CSR 寄存器保存在栈上,保存完之后在跳转到 Rust 编写的中断处理函数。
状态恢复:直接把备份在栈上的内容写回寄存器。由于涉及到了寄存器级别的操作,我们需要用汇编来实现。

第 1 步 编写汇编代码实现保存与恢复
建立os/src/interrupt/interrupt.asm文件,编写以下内容:
(本版本中保存运用循环的方式保存和恢复#3-31号通用寄存器,旧版的采用一次列出所有寄存器的方式。)

# 我们将会用一个宏来用循环保存寄存器。这是必要的设置
.altmacro
# 寄存器宽度对应的字节数
.set    REG_SIZE, 8
# Context 的大小
.set    CONTEXT_SIZE, 34# 宏:将寄存器存到栈上
.macro SAVE reg, offsetsd  \reg, \offset*8(sp)
.endm.macro SAVE_N nSAVE  x\n, \n
.endm# 宏:将寄存器从栈中取出
.macro LOAD reg, offsetld  \reg, \offset*8(sp)
.endm.macro LOAD_N nLOAD  x\n, \n
.endm.section .text.globl __interrupt
# 进入中断
# 保存 Context 并且进入 Rust 中的中断处理函数 interrupt::handler::handle_interrupt()
__interrupt:# 在栈上开辟 Context 所需的空间addi    sp, sp, -34*8# 保存通用寄存器,除了 x0(固定为 0)SAVE    x1, 1# 将原来的 sp(sp 又名 x2)写入 2 位置addi    x1, sp, 34*8SAVE    x1, 2# 保存 x3 至 x31.set    n, 3.rept   29SAVE_N  %n.set    n, n + 1.endr# 取出 CSR 并保存csrr    s1, sstatuscsrr    s2, sepcSAVE    s1, 32SAVE    s2, 33# 调用 handle_interrupt,传入参数# context: &mut Contextmv      a0, sp# scause: Scausecsrr    a1, scause# stval: usizecsrr    a2, stvaljal  handle_interrupt.globl __restore
# 离开中断
# 从 Context 中恢复所有寄存器,并跳转至 Context 中 sepc 的位置
__restore:# 恢复 CSRLOAD    s1, 32LOAD    s2, 33csrw    sstatus, s1csrw    sepc, s2# 恢复通用寄存器LOAD    x1, 1# 恢复 x3 至 x31.set    n, 3.rept   29LOAD_N  %n.set    n, n + 1.endr# 恢复 sp(又名 x2)这里最后恢复是为了上面可以正常使用 LOAD 宏LOAD    x2, 2sret

三,中断处理

第 1 步 开启和处理中断
新建os/src/interrupt/handler.rs文件,在其中编写以下内容开启和处理中断:

use super::context::Context;
use riscv::register::stvec;global_asm!(include_str!("./interrupt.asm"));/// 初始化中断处理
///
/// 把中断入口 `__interrupt` 写入 `stvec` 中,并且开启中断使能
pub fn init() {unsafe {extern "C" {/// `interrupt.asm` 中的中断入口fn __interrupt();}// 使用 Direct 模式,将中断入口设置为 `__interrupt`stvec::write(__interrupt as usize, stvec::TrapMode::Direct);}
}
/// 中断的处理入口
///
/// `interrupt.asm` 首先保存寄存器至 Context,其作为参数和 scause 以及 stval 一并传入此函数
/// 具体的中断类型需要根据 scause 来推断,然后分别处理
#[no_mangle]
pub fn handle_interrupt(context: &mut Context, scause: Scause, stval: usize) {panic!("Interrupted: {:?}", scause.cause());
}

第 2 步 模块初始化
基于Rust的语法,新建os/src/interrupt/mod.rs文件添加以下代码初始化interrupt模块:

//! 中断模块
//!
//! mod handler;
mod context;/// 初始化中断相关的子模块
///
/// - [`handler::init`]
/// - [`timer::init`]
pub fn init() {handler::init();println!("mod interrupt initialized");
}

第 3 步 触发中断
在os/src/main.rs中添加 mod interrupt; 并使用ebreak来触发中断。修改代码如下:

...
mod interrupt;
.../// Rust 的入口函数
///
/// 在 `_start` 为我们进行了一系列准备之后,这是第一个被调用的 Rust 函数
pub extern "C" fn rust_main() -> ! {// 初始化各种模块interrupt::init();unsafe {llvm_asm!("ebreak"::::"volatile");};unreachable!();
}

四,时钟中断

时钟中断是操作系统能够进行线程调度的基础,操作系统会在每次时钟中断时被唤醒,暂停正在执行的线程,并根据调度算法选择下一个应当运行的线程。本节目标在于实现时钟中断。

第 1 步 开启与设置时钟中断
新建os/src/interrupt/timer.rs文件,编辑如下代码:

//! 预约和处理时钟中断use crate::sbi::set_timer;
use riscv::register::{time, sie, sstatus};/// 初始化时钟中断
///
/// 开启时钟中断使能,并且预约第一次时钟中断
pub fn init() {unsafe {// 开启 STIE,允许时钟中断sie::set_stimer(); // 开启 SIE(不是 sie 寄存器),允许内核态被中断打断sstatus::set_sie();}// 设置下一次时钟中断set_next_timeout();
}/// 时钟中断的间隔,单位是 CPU 指令
static INTERVAL: usize = 100000;/// 设置下一次时钟中断
///
/// 获取当前时间,加上中断间隔,通过 SBI 调用预约下一次中断
fn set_next_timeout() {set_timer(time::read() + INTERVAL);
}/// 触发时钟中断计数
pub static mut TICKS: usize = 0;/// 每一次时钟中断时调用
///
/// 设置下一次时钟中断,同时计数 +1
pub fn tick() {set_next_timeout();unsafe {TICKS += 1;if TICKS % 100 == 0 {println!("{} tick", TICKS);}}
}

第 2 步 修改sbi
为简化操作系统实现,操作系统可请求(sbi_call调用ecall指令)SBI服务来完成时钟中断的设置。
在os/src/sbi.rs文件添加如下代码。

/// 设置下一次时钟中断的时间
pub fn set_timer(time: usize) {sbi_call(SBI_SET_TIMER, time, 0, 0);
}

第 3 步 实现时钟中断的处理流程
修改os/src/interrupt/handler.rs文件中的handle_interrupt()函数。

/// 中断的处理入口
///
/// `interrupt.asm` 首先保存寄存器至 Context,其作为参数和 scause 以及 stval 一并传入此函数
/// 具体的中断类型需要根据 scause 来推断,然后分别处理
#[no_mangle]
pub fn handle_interrupt(context: &mut Context, scause: Scause, stval: usize) {// 可以通过 Debug 来查看发生了什么中断// println!("{:x?}", context.scause.cause());match scause.cause() {// 断点中断(ebreak)Trap::Exception(Exception::Breakpoint) => breakpoint(context),// 时钟中断Trap::Interrupt(Interrupt::SupervisorTimer) => supervisor_timer(context),// 其他情况,终止当前线程_ => fault(context, scause, stval),}
}/// 处理 ebreak 断点
///
/// 继续执行,其中 `sepc` 增加 2 字节,以跳过当前这条 `ebreak` 指令
fn breakpoint(context: &mut Context) {println!("Breakpoint at 0x{:x}", context.sepc);context.sepc += 2;
}/// 处理时钟中断
///
/// 目前只会在 [`timer`] 模块中进行计数
fn supervisor_timer(_: &Context) {timer::tick();
}/// 出现未能解决的异常
fn fault(context: &mut Context, scause: Scause, stval: usize) {panic!("Unresolved interrupt: {:?}\n{:x?}\nstval: {:x}",scause.cause(),context,stval);
}

补充内容

1.修改mod.rs
因为加入了timer.rs,使用需要加入相应的初始化操作。修改os/src/interrupt/mod.rs如下。

//! 中断模块
//!
//! mod handler;
mod context;
mod timer;
pub use context::Context;
/// 初始化中断相关的子模块
///
/// - [`handler::init`]
/// - [`timer::init`]
pub fn init() {handler::init();timer::init();println!("mod interrupt initialized");
}

2. 修改Context
因为要输出,所以要实现相应的trait。
修改os/src/interrupt/context.rs,内容如下:

use core::fmt;
use core::mem::zeroed;
use riscv::register::{sstatus::Sstatus, scause::Scause};#[repr(C)]
#[derive(Clone, Copy)]
pub struct Context {pub x: [usize; 32],     // 32 个通用寄存器pub sstatus: Sstatus,  //状态位,控制全局中断使能pub sepc: usize       //记录触发中断的指令的地址
}impl Default for Context {fn default() -> Self {unsafe { zeroed() }}
}/// 格式化输出
///
/// # Example
///
/// ```rust
/// println!("{:x?}", Context);   // {:x?} 表示用十六进制打印其中的数值
/// ```
impl fmt::Debug for Context {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {f.debug_struct("Context").field("registers", &self.x).field("sstatus", &self.sstatus).field("sepc", &self.sepc).finish()}
}

3.修改 handle
因为调整了包的结构,以及修改了中断处理函数,所以需要修改包含的"头文件"。
os/src/interrupt/handler.rs“头文件”修改如下:

use super::context::Context;
use super::timer;
use riscv::register::{scause::{Exception, Interrupt, Scause, Trap},sie, stvec,
};

4.修改主函数
为了方便测试,需要修改main函数内容,加入无限循环。
os/src/main.rs中main函数修改如下:
#[no_mangle]
pub extern “C” fn rust_main() -> ! {
interrupt::init();

unsafe {llvm_asm!("ebreak"::::"volatile");
};
loop{};
unreachable!();

}

至此,lab1实验使用完成。源代码于实验题代码均在github中。

rust 实现 rCore lab1相关推荐

  1. rcore lab1

    中断 中断是我们在操作系统上首先实现的功能,因为它是操作系统所有功能的基础.假如没有中断,操作系统在唤起一个用户程序之后,就只能等到用户程序执行完成之后才能继续执行,那操作系统完全无法进行资源调度. ...

  2. THU-OS rCore学习总结 基于Rust + RISC-V

    文章目录 之后在知乎更新了: **[操作系统学习之路](https://www.zhihu.com/column/c_1368645188836106240)** --- ^^ --- 资料汇总 实验 ...

  3. 用 Rust 开发 Linux,可行吗?

    作者 | 马超 出品 | CSDN(ID:CSDNnews) 继Python之后,Rust最近也火爆得出了圈,目前Rust在Serverless等很多云原生领域已经稳定占据了C位,那么让Rust更进一 ...

  4. 三万字 | 2021 年 Rust 行业调研报告

    作者 | 张汉东       责编 | 欧阳姝黎 文前 Rust 语言是一门通用系统级编程语言,无GC且能保证内存安全.并发安全和高性能而著称.自2008年开始由 Graydon Hoare 私人研发 ...

  5. Rust 学习总结(2)—— 2021 年 Rust 行业调研报告

    前言 Rust 语言是一门通用系统级编程语言,无GC且能保证内存安全.并发安全和高性能而著称.自2008年开始由 Graydon Hoare 私人研发,2009年得到 Mozilla 赞助,2010年 ...

  6. Rust China Conf 2021 首批议题确定

    10 月16日-17日,Rust China Conf 2021 将在上海举办,本次大会主题为"Rust the World".在这一主题下,大会广泛接受海内外 Rust 开发者高 ...

  7. 从零开始的 Rust 语言 blas 库之预备篇(1)—— blas 基础介绍

    从零开始的 Rust 语言 blas 库之预备篇(1)-- blas 基础介绍 下一篇:从零开始的 Rust 语言 blas 库之预备篇(2)-- blas 矩阵格式详解 文章部分参考:https:/ ...

  8. 【Rust日报】 2019-03-31

    Rust日报小组成立 从下周开始将由ChaosBot.Mike和Damody三位轮流发布日报,其中Damody为台湾同胞,如果大家看到中文繁体日报,就是他发布的. 发日报有什么好处?我来给大家透露一下 ...

  9. Rust和C / C ++的跨语言链接时间优化LTO

    Rust和C / C ++的跨语言链接时间优化LTO 链接时间优化(LTO)是LLVM实施整个程序优化的方法.跨语言LTO是Rust编译器中的一项新功能,使LLVM的链接时间优化可以在混合的C / C ...

最新文章

  1. Microbiome:扩增子检测环境样本单细胞真核生物和寄生虫的新方法
  2. php把时间变成整数,php怎么将字符串转为整数
  3. python日历函数_python 怎么定义一个函数,输出日历
  4. step4 . day5 进程与进程的创建
  5. OPT和LRU页面置换算法C语言代码,页面置换算法模拟——OPT、FIFO和LRU算法.doc
  6. 谈谈入职新公司1月的体会
  7. Syzmlw 让子弹飞迅雷下载
  8. 随想录(scons编译)
  9. java多继承_为什么 Java 不支持类多重继承?
  10. 阶段5 3.微服务项目【学成在线】_day03 CMS页面管理开发_07-新增页面-前端-页面完善...
  11. Opencv图像预处理
  12. 存储和多屏互动,蜂鸟网的NAS应用解析
  13. 企业邮箱收发信息服务器怎么设置,企业邮箱服务器如何配置POP3和SMTP?
  14. 上线啦,可以定时周期性提醒群成员的机器人
  15. WinScp 安装连接使用
  16. Signal Processing Toolbox
  17. 华为鸿蒙手机充电期间系统将进行深度优化
  18. [OT] 线性规划标准形式互补松弛定理对偶问题
  19. 程序员哑巴英语修炼指南
  20. 继续中文,晒代码,真的没什么新意,好在得意自满!

热门文章

  1. Revit 2014 SDK中例子整理 (二)
  2. 数字通信第四章——波形解调与最佳检测接收
  3. 管道命令( pipe )
  4. 突破360防黑加固添加用户
  5. 【综合案例】信用评分模型开发
  6. verilog学习笔记- 10)按键控制 LED 灯实验
  7. 《丑陋的中国人》书写现代婚礼
  8. DS90UB960-Q1调试
  9. Reeds-Shepp曲线学习笔记及相关思考
  10. 鞍钢的机会 攀钢的利好