rust 实现 rCore lab1
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 与中断相关的寄存器和指令
在次只列举部分实验常用的寄存器与指令,更多信息请查阅官方文档。
寄存器
线程相关寄存器
- sscratch : 在用户态,sscratch 保存内核栈的地址;在内核态,sscratch 的值为 0。
发生中断时,硬件自动填写的寄存器
- sepc : 即 Exception Program Counter,用来记录触发中断的指令的地址。
- scause: 记录中断是否是硬件中断,以及具体的中断原因。
- stval :scause 不足以存下中断所有的必须信息。例如缺页异常,就会将 stval 设置成需要访问但是不在内存中的地址,以便于操作系统将这个地址所在的页面加载进来。
指导硬件处理中断的寄存器
- stvec: 设置内核态中断处理流程的入口地址。存储了一个基址 BASE 和模式 MODE:
* MODE 为 0 表示 Direct 模式,即遇到中断便跳转至 BASE 进行执行。
* MODE 为 1 表示 Vectored 模式,此时 BASE 应当指向一个向量,存有不同处理流程的地址,遇到中断会跳转至 BASE + 4 * cause 进行处理流程。 - sstatus: 具有许多状态位,控制全局中断使能等。
- sie :即 Supervisor Interrupt Enable,用来控制具体类型中断的使能,例如其中的 STIE 控制时钟中断使能。
- sip :即 Supervisor Interrupt Pending,和 sie 相对应,记录每种中断是否被触发。仅当 sie 和 sip 的对应位都为 1 时,意味着开中断且已发生中断,这时中断最终触发。
1.3 与中断相关的指令
进入和退出中断
- ecall:触发中断,进入更高一层的中断处理流程之中。用户态进行系统调用进入内核态中断处理流程,内核态进行 SBI 调用进入机器态中断处理流程,使用的都是这条指令。
- sret:从内核态返回用户态,同时将 pc 的值设置为 sepc。(如果需要返回到 sepc 后一条指令,就需要在 sret 之前修改 sepc 的值)
- ebreak:触发一个断点。
- mret 从机器态返回内核态,同时将 pc 的值设置为 mepc。
操作 CSR(读写重置)
- csrrw dst, csr, src(CSR Read Write):同时读写的原子操作,将指定 CSR 的值写入 dst,同时将 src 的值写入 CSR。
- csrr dst, csr(CSR Read):仅读取一个 CSR 寄存器。
- csrw csr, src(CSR Write):仅写入一个 CSR 寄存器。
- csrc(i) csr, rs1(CSR Clear):将 CSR 寄存器中指定的位清零,csrc 使用通用寄存器作为 mask,csrci 则使用立即数。
- 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相关推荐
- rcore lab1
中断 中断是我们在操作系统上首先实现的功能,因为它是操作系统所有功能的基础.假如没有中断,操作系统在唤起一个用户程序之后,就只能等到用户程序执行完成之后才能继续执行,那操作系统完全无法进行资源调度. ...
- THU-OS rCore学习总结 基于Rust + RISC-V
文章目录 之后在知乎更新了: **[操作系统学习之路](https://www.zhihu.com/column/c_1368645188836106240)** --- ^^ --- 资料汇总 实验 ...
- 用 Rust 开发 Linux,可行吗?
作者 | 马超 出品 | CSDN(ID:CSDNnews) 继Python之后,Rust最近也火爆得出了圈,目前Rust在Serverless等很多云原生领域已经稳定占据了C位,那么让Rust更进一 ...
- 三万字 | 2021 年 Rust 行业调研报告
作者 | 张汉东 责编 | 欧阳姝黎 文前 Rust 语言是一门通用系统级编程语言,无GC且能保证内存安全.并发安全和高性能而著称.自2008年开始由 Graydon Hoare 私人研发 ...
- Rust 学习总结(2)—— 2021 年 Rust 行业调研报告
前言 Rust 语言是一门通用系统级编程语言,无GC且能保证内存安全.并发安全和高性能而著称.自2008年开始由 Graydon Hoare 私人研发,2009年得到 Mozilla 赞助,2010年 ...
- Rust China Conf 2021 首批议题确定
10 月16日-17日,Rust China Conf 2021 将在上海举办,本次大会主题为"Rust the World".在这一主题下,大会广泛接受海内外 Rust 开发者高 ...
- 从零开始的 Rust 语言 blas 库之预备篇(1)—— blas 基础介绍
从零开始的 Rust 语言 blas 库之预备篇(1)-- blas 基础介绍 下一篇:从零开始的 Rust 语言 blas 库之预备篇(2)-- blas 矩阵格式详解 文章部分参考:https:/ ...
- 【Rust日报】 2019-03-31
Rust日报小组成立 从下周开始将由ChaosBot.Mike和Damody三位轮流发布日报,其中Damody为台湾同胞,如果大家看到中文繁体日报,就是他发布的. 发日报有什么好处?我来给大家透露一下 ...
- Rust和C / C ++的跨语言链接时间优化LTO
Rust和C / C ++的跨语言链接时间优化LTO 链接时间优化(LTO)是LLVM实施整个程序优化的方法.跨语言LTO是Rust编译器中的一项新功能,使LLVM的链接时间优化可以在混合的C / C ...
最新文章
- Microbiome:扩增子检测环境样本单细胞真核生物和寄生虫的新方法
- php把时间变成整数,php怎么将字符串转为整数
- python日历函数_python 怎么定义一个函数,输出日历
- step4 . day5 进程与进程的创建
- OPT和LRU页面置换算法C语言代码,页面置换算法模拟——OPT、FIFO和LRU算法.doc
- 谈谈入职新公司1月的体会
- Syzmlw 让子弹飞迅雷下载
- 随想录(scons编译)
- java多继承_为什么 Java 不支持类多重继承?
- 阶段5 3.微服务项目【学成在线】_day03 CMS页面管理开发_07-新增页面-前端-页面完善...
- Opencv图像预处理
- 存储和多屏互动,蜂鸟网的NAS应用解析
- 企业邮箱收发信息服务器怎么设置,企业邮箱服务器如何配置POP3和SMTP?
- 上线啦,可以定时周期性提醒群成员的机器人
- WinScp 安装连接使用
- Signal Processing Toolbox
- 华为鸿蒙手机充电期间系统将进行深度优化
- [OT] 线性规划标准形式互补松弛定理对偶问题
- 程序员哑巴英语修炼指南
- 继续中文,晒代码,真的没什么新意,好在得意自满!