BatchOS
BatchOS
- 实现批处理程序功能的OS
- APP与OS隔离
- 自动加载并运行多个程序
特权级机制
ecall
具有用户态到内核态的执行环境切换能力的函数调用指令;sret
:具有内核态到用户态的执行环境切换能力的函数返回指令。- 首先,操作系统需要提供相应的功能代码,能在执行
sret
前准备和恢复用户态执行应用程序的上下文。其次,在应用程序调用ecall
指令后,能够检查应用程序的系统调用参数,确保参数不会破坏操作系统。
RISC-V异常
RISC-V S模式特权指令
- sert:从S模式返回U模式
- wfi:处理器在空闲时进入低功耗状态等待终端
- sfence.vma:刷新TLB缓存
- 访问S模式CSR指令:改变系统状态
控制状态寄存器
- sstatus:SPP字段给出Trap发生前CPU的特权级
- sepc:记录异常发生前执行的最后一条指令的地址
- scause:描述Trap的原因
- stval:给出Trap的附加信息
- stvec:控制Trap处理代码的入口地址
硬件切换的硬件控制机制
ecall
sstatus
中的SPP
字段切换到CPU当前特权级sepc
修改为Trap处理完成后默认执行的下一条指令的地址scause\stval
修改成Trap原因和Trap额外信息- CPU跳转到
stvec
设置的Trap处理入口函数,设置特权级为S
sret
- CPU按照
sstatus
设置特权级 - CPU跳转到
sepc
指向的地址,然后继续执行
用户库
- 使用 Rust 的宏将其函数符号
main
标志为弱链接。这样在最后链接的时候,虽然在lib.rs
和bin
目录下的某个应用程序都有main
符号,但由于lib.rs
中的main
符号是弱链接,链接器会使用bin
目录下的应用主逻辑作为main
- 这里我们主要是进行某种程度上的保护,如果在
bin
目录下找不到任何main
,那么编译也能够通过,但会在运行时报错。
|
#![feature(linkage)]
支持链接操作
系统调用
&[u8]
切片类型来描述缓冲区,这是一个 胖指针 (Fat Pointer),里面既包含缓冲区的起始地址,还 包含缓冲区的长度。
// user/src/syscall.rs |
加载不同的APP
AppManager
- 在
RefCell
的基础上再封装一个UPSafeCell
,它名字的含义是:允许我们在 单核 上安全使用可变全局变量。 - 当我们要访问数据时(无论读还是写),需要首先调用
exclusive_access
获得数据的可变借用标记,通过它可以完成数据的读写,在操作完成之后我们需要销毁这个标记,此后才能开始对该数据的下一次访问
// os/src/sync/up.rs |
New
- 使用
core::slice::from_raw_parts
将指针解释为&[usize]
切片; - 使用
copy_from_slice
将切片上的元素复制到app_start
上
static ref APP_MANAGER: UPSafeCell<AppManager> = unsafe { |
load_app
CPU用存在指令缓存,使用load_app加载新的程序,需要让OS知道取指内存的变化
OS 在这里必须使用取指屏障指令
fence.i
,它的功能是保证 在它之后的取指过程必须能够看到在它之前的所有对于取指内存区域的修改
unsafe fn load_app(&self, app_id: usize) { |
run_next_app
- 先将一个上下文压入内核栈中;在
__restore
中更新sscrath
指针指向内核栈栈顶 - 如果发生系统调用,从
__alltraps
开始执行
/// run next app |
Trap管理
TrapContext
- Trap 发生时需要保存的物理资源内容,包括32个通用寄存器、sstatus以及sepc
- 对于 CSR 而言,我们知道进入 Trap 的时候,硬件会立即覆盖掉
scause/stval/sstatus/sepc
的全部或是其中一部分。scause/stval
的情况是:它总是在 Trap 处理的第一时间就被使用或者是在其他地方保存下来了,因此它没有被修改并造成不良影响的风险。- 而对于
sstatus/sepc
而言,它们会在 Trap 处理的全程有意义(在 Trap 控制流最后sret
的时候还用到了它们),而且确实会出现 Trap 嵌套的情况使得它们的值被覆盖掉。所以我们需要将它们也一起保存下来,并在sret
之前恢复原样。
// os/src/trap/context.rs |
TrapContext的保存与恢复
- 首先通过
__alltraps
将 Trap 上下文保存在内核栈上,然后跳转到使用 Rust 编写的trap_handler
函数完成 Trap 分发及处理。当trap_handler
返回之后,使用__restore
从保存在内核栈上的 Trap 上下文恢复寄存器。最后通过一条sret
指令回到应用程序执行。
// os/src/trap/mod.rs |
__alltraps
sscratch
在__restore
中设置为内核栈栈顶,run_next_app
从__restore
先执行
# os/src/trap/trap.S |
__restore
- 先恢复CSR寄存器再恢复通用寄存器
csrrw sp, sscratch, sp
此时sscratch设置为内核栈栈顶(由应用程序在执行前压入内核栈)
# os/src/trap/trap.S |
trap_handler
// os/src/trap/mod.rs |
Practice
扩展内核,能够统计多个应用的执行过程中系统调用编号和访问此系统调用的次数
- 增加一个SyscallNum的结构体记录发生系统调用的次数
// batch.rs |
// syscall/mod.rs |
扩展内核,能够统计每个应用执行后的完成时间
- 在
AppManager
中增加应用执行时长字段;在run_next_app
中获取系统时钟,记录应用时间
// batch.rs |
sys_write 仅能输出位于程序本身内存空间内的数据,否则报错
- 构建ReliableAddr结构体记录应用程序起始和终止地址
// batch.rs |
// syscall/mod.rs |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LZY的Code生活!