|
批处理
|
|
特权级
|
|
user/bin
|
有几个应用
hello_world:
|
bad_address:
访问一个非法的物理地址,测试批处理系统是否会被该错误影响
|
|
power:
|
|
应用中
|
#[macro_use]
|
extern crate user_lib;
|
|
|
|
user/lib.rs
|
定义_start
|
3#[linkage = "weak"]
|
main是weak
|
因而bin和这里虽然都有main
|
但bin的会覆盖掉这个
|
|
封装了一下syscall
|
装成标准库
|
|
|
user/syscall
|
ABI 或者系统调用
应用运行用户态U
|
ecall
|
调用批处理系统提供的接口
|
触发Environment call from U-mode 的异常
|
|
|
Trap 进入 S 模式
|
执行批处理系统针对这个exception提供的服务程序
|
|
|
用俩
|
|
syscall
rust内联汇编
|
符合riscv要求
|
以寄存器 a0~a2/x10-x12 来保存系统调用的参数
|
寄存器 a7/x17 保存 syscall ID
|
返回值通过寄存器 a0/x10 传递给局部变量 ret
|
|
|
|
|
os/src/link_app.S
|
cargo build时,os/build.rs生成
|
|
|
第一个.section .data是一个64bit整数数组
|
align 3 8bytes/64bit对齐
|
quad表示8 bytes/64bit
|
写入了数组长度3
|
和3个app的起始地址
|
|
第二个.section .data插入app们的bin
|
|
|
os/src/batch.rs
|
AppManager
|
app管理
|
lazy_static定义延迟初始化static
|
UPSafeCell,只允许一个内部可变引用
|
|
找到link_app.S中提供的符号_num_app
|
解析app数量,和开头address
|
|
|
load_app
|
fence.i
|
清理i-cache
|
d-cache数据缓存
|
cpu 访存
|
i-cache指令缓存
|
cpu 取指令
|
|
这里修改了cpu取指的内存区域,需要刷新i-cache
|
|
清空app程序加载区
|
from_raw_parts_mut
|
|
将APP_BASE_ADDRESS转为&mut [u8]
|
长度APP_SIZE_LIMIT
|
填零
|
|
|
获取app的bin
|
from_raw_parts
|
从start拿到end
|
|
拷贝app.bin到target地址
|
dst是
|
APP_BASE_ADDRESS开始
|
长度app_src.len()
|
|
|
|
特权级切换
|
|
S特权级
|
Supervisor Mode
|
os使用
|
|
|
CSR
|
Control and Status Register,控制与状态寄存器
|
U-Mode下大部分CSR不可直接操作
|
Trap下CSR使用
CSR 名
|
该 CSR 与 Trap 相关的功能
|
sstatus
|
SPP 等字段给出 Trap 发生之前 CPU 处在哪个特权级(S/U)等信息
|
sepc
|
当 Trap 是一个异常的时候,记录 Trap 发生之前执行的最后一条指令的地址
|
scause
|
描述 Trap 的原因
|
stval
|
给出 Trap 附加信息
|
stvec
|
控制 Trap 处理代码的入口地址
64 位的 CSR,在中断使能interrupt enable的情况下,保存了中断处理的入口地址
|
MODE 位于 [1:0],长度为 2 bits;
|
BASE 位于 [63:2],长度为 62 bits。
|
|
MODE = 0
|
stvec Direct 模式,
|
进入 S 模式的 Trap 无论原因如何,
|
处理 Trap 的入口地址都是 BASE<<2 ,
|
CPU 会跳转到这个地方进行异常处理。
|
|
|
|
sret
|
CPU 按照 sstatus 的 SPP 字段设置特权级 U 或者 S ;
|
CPU 跳转到 sepc 指向的那条instruction,然后继续执行。
|
|
|
|
|
|
|
app启动,初始化用户态context,切换到用户态执行app
|
|
app发起syscall,切换到os中处理
|
|
app出错,os杀死app并加载next app
|
|
app执行结束,os加载下一个app
|
|
|
trap
|
从U进S的过程
|
|
|
进入trap处理之前
|
通过os的专用stack。保存原控制流reg状态
|
全局变量形式在.bss段里
|
.bss应该负责存储全0初始化
|
objdump看了一下在.rodata里
|
ds说是llvm干的,认为const不可变,所以.rodata
|
而且.rodata可以处理对齐要求,但.bss不能
|
|
Q
|
|
KernelStack
|
UserStack
|
获得stack top的address
|
stack向下增长(这个下有歧义,看图其实是向上,因为高地址在下面,向上就是向低地址增长)
|
有图three-piece,所以是x86的情况
|
|
|
所以stack top是data开始地址+USER_STACK_SIZE
|
|
每次sp-8
|
|
self.data.as_ptr() as usize + USER_STACK_SIZE
|
|
|
换stack
|
|
|
|
|
|
硬件会
|
sstatus
|
SPP 字段会被修改为 CPU 当前的特权级(U/S)。
|
|
sepc
|
被修改为 Trap 处理完成后默认会执行的下一条instruction的address。
|
|
scause/stval
|
分别会被修改成这次 Trap 的原因以及相关的附加信息。
|
|
stvec
|
CPU 会跳转到stvec所设置的 Trap 处理entry address,
|
|
将当前特权级设置为 S ,
|
然后从Trap 处理入口地址处开始执行。
|
|
完成trap,sret
CPU 按照 sstatus 的 SPP 字段设置特权级 U 或者 S ;
|
CPU 跳转到 sepc 指向的那条instruction,然后继续执行。
|
|
|
|
|
trap上下文结构体
|
包含所有reg,x0~x31
|
+sstatus, spec
|
|
x0
|
硬编码0,不会变化
|
x4/tp
|
除非手动出于特殊原因使用,一般不会用,无需保存
|
|
csr部分
|
进入trap立即覆盖掉 scause/stval/sstatus/sepc 全部或者部分
|
|
scause/stval 在 Trap 处理的第一时间就被使用或者是在其他地方保存下来了,因此它没有被修改并造成不良影响的风险。
|
|
sstatus/sepc 在 Trap 处理的全程有意义(在 Trap 控制流最后 sret 的时候还用到了它们),而且确实会出现 Trap 嵌套的情况使得它们的值被覆盖掉。
|
|
|
|
trap保存和恢复过程
|
stvec::write
|
设置Direct mode
|
|
指向__alltraps的address
|
|
|
src/trop/trap.S
|
__alltraps
|
将trap context保存到os stack上
|
跳转到使用rust编写的trap_handler
|
|
align 2^2 4bytes对齐,risc-v特权级规范要求
|
交换sscratch和sp
现在sp指向os stack
|
sscratch指向user stack
|
|
addi sp, sp, -34*8
|
预分配-38*8字节stack frame
|
|
使用sd保存x1,x3,x5-x27
|
使用了类似循环宏SAVE_GP来保存大头
|
在 trap.S 开头加上 .altmacro 才能正常使用 .rept 命令
|
|
跳过x0,x4/tp,x2/sp
|
|
因为sp现在指向os stack
|
|
保存sp对于时候恢复到U-Mode没有意义
|
|
|
sstatus给t0/x5
|
sepc给t1/x6
|
继续保存到 os stack
|
|
sscratch保存到t2/x7
|
这玩意儿现在是U-Mode的sp
|
继续保存
|
|
|
sp/x2给a0/x10
|
根据riscv调用规范,trap_handler的第一个参数是a0/x10
|
|
|
trap_handler需要应用程序传来的syscall ID和对应参数
|
|
|
由于这些reg的value可能被修改
|
|
|
所以不能让trap_handler直接reg value
|
|
|
因而使用stack保存的值
|
|
|
====指令记录====
|
addi
|
rd, rs1, imm
|
|
rs1+立即数imm保存进rd
|
|
例子
addi sp, sp, -34*8
|
sp分配38*8字节,因为向下增长,所以-
|
|
|
sd
|
rs2, offset(rs1)
|
|
将rs2 reg数据存储到指定内存地址offset(rs1)
|
|
就是rs1+offset的内存地址
|
|
例子
sd x1, 1*8(sp)
|
x1存入sp + 8的内存地址
|
|
|
|
mv
|
rd, rs
|
|
伪指令,实际是addi实现
|
|
将rs的值复制给rd
|
|
例子
mv a0, sp
|
将sp/x2的值复制给a0/x10
|
|
|
|
TODO
|
riscv
|
已记录
|
csrr
|
csrrw
|
|
__restore
|
trap_handler返回后调用
|
保存在os stack上的trap context 恢复reg
|
通过sret回到U继续执行保存在sepc里的下一条instruction
|
|
sp在__restore作用不同的时候,有可能会改变
|
所以先从a0读入sp
|
sp目前指向os stack
|
ld从stack top的trap context恢复csr和reg注意顺序
|
回收trap context占用stack内存
|
addi sp, sp, 34*8
|
|
交换sscratch和sp
|
sp指向U-Mode stack
|
sscratch指向S-Mode stack
|
|
sret回到U-Mode继续运行sepc里的下一条instruction
|
|
|
|
|
|
trap/mod.rs
|
syscall/mod.rs
|
syscall/fs.rs
|
|
处理trap_handler
|
cx原样返回,因而a0的值一直是os stack top
|
传入的sp的值
|
|
根据scause保存的trap原因进行处理
发现trap来自U 特权级的 Environment Call
|
就是syscall
|
|
|
因为这个是通过ecall instruction触发的syscall
|
ecall调用后sepc会设置为ecall instruction所在的address
|
为了从ecall的下一条instruction开始执行
|
+4
|
__restore在sret后会从+4的sepc开始执行
|
|
|
cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
|
a0会变化,这里的a0是cx上下文中,也就是os stack里保存的a0的值,并不是当前reg a0会发生变化
|
从trap context取出
|
syscall ID,a7/x17
|
syscall三个参数,a0~a2/x10-x12
|
传给syscall
|
|
|
处理其他错误--
|
|
|
调用sys_call
|
只调用
|
|
sys_write/sys_exit
|
比较直观
|
|
|
next_app
|
|
应用结束或出错,进入下一个
|
load下一个app, copy到0x80400000
|
load_app
|
|
跳转到应用程序入口点 0x80400000;
|
设置sepc
|
将stack切换到User-Mode
|
走__restore流程
|
恢复sscratch指向os stack
|
|
从S-Mode切换到U-Mode
|
设置sstatus
|
|
os stack上压入一个为启动app特殊构造的trap context
TrapContext
|
sstatus的SPP设置为User
|
sepc修改为app入口点entry
|
sp寄存器为设定的一个stack pointer
|
|
|
再复用__restore
17 __restore(KERNEL_STACK.push_context(TrapContext::app_init_context(
|
18 APP_BASE_ADDRESS,
|
19 USER_STACK.get_sp(),
|
20 )) as *const _ as usize);
|
|
使用push_context在os上压入一个trap context,
|
作为__restore的参数
|
因为sp <- a0将参数给到sp,在__restore里sp保存的是os stack top
|
|
|
|
|
|
|
|
Generated
2025-05-05 04:17:35 +0000
25bcfca-dirty
2025-05-04 14:47:56 +0000