因为我是一边跟着教程看一遍领悟一边魔改的,如有错误敬请指正
trap.S
# 前7行是我们定义了两个宏,因为我们存储上下文的时候有32个寄存器状态要保存,不可能写32行保存的代码
# 于是用宏简化我们的多行代码编程小部分几行
.altmacro
.macro SAVE_GP n
sd x\n, \n*8(sp)
.endm
.macro LOAD_GP n
ld x\n, \n*8(sp)
# 定义整个汇编连接到text吨,定义几个能全局访问到的函数与变量
.endm
.section .text
.globl __alltraps
.globl __restore
# 定义地址以2 ** 2 ,也就是与4对齐,即有效地址必须是4的倍数
.align 2
# 我们定义的第一个函数
#__alltraps是我们用户态调用系统函数转到系统态的函数,主要是括用户态程序上下文的保存
__alltraps:
# sp存储的是我们用户栈的栈顶指针,而sscratch存储的是内核栈的栈顶指针,这里为什么已经存储好了我们暂且不提
# csrrw的指令可以写为csrrw a,b,c, 将b内容存储在a中后c的内容存储在b中,这里是把sp与sscratch的存储内容互换
# 所以在这之后sp存的是内核栈栈顶指针,sscratch存储用户栈栈顶指针
csrrw sp, sscratch, sp
# now sp->kernel stack, sscratch->user stack
# allocate a TrapContext on kernel stack
# 分配34 *8 的栈帧用来存储寄存器的值。除了用户内核共享的32个寄存器,还有特殊的sstatus与spec寄存器
# 记住,这里的sp指向的是内核栈
addi sp, sp, -34*8
# save general-purpose registers
# 接下来就是把从x1到x32的寄存器全部存取完毕(一直到40行,调用了刚开始的宏),以及两个跟Trap有关的特殊寄存器sstatus, sepc
sd x1, 1*8(sp)
# skip sp(x2), we will save it later
sd x3, 3*8(sp)
# skip tp(x4), application does not use it
# save x5~x31
.set n, 5
.rept 27
SAVE_GP %n
.set n, n+1
.endr
# we can use t0/t1/t2 freely, because they were saved on kernel stack
#sstatus给出 Trap 发生之前 CPU 处在哪个特权级(S/U)等信息
# sepc当 Trap 是一个异常的时候,记录 Trap 发生之前执行的最后一条指令的地址
# 这里之所以先csrr 多此一举是因为这些Trap相关的特殊寄存器只能运行特殊的Trap指令,所以先移动到to0, t1中后再存到sp中
csrr t0, sstatus
csrr t1, sepc
sd t0, 32*8(sp)
sd t1, 33*8(sp)
# read user stack from sscratch and save it on the kernel stack
# 最后把用户栈的栈顶指针存储一下,然后调用trap_handler函数,我们需要在Rust中定义此函数
csrr t2, sscratch
sd t2, 2*8(sp)
# set input argument of trap_handler(cx: &mut TrapContext)
mv a0, sp
call trap_handler
# restore是系统太调用完成返回用户态,或者操作系统调用应用程序的函数
__restore:
# case1: start running app by __restore
# case2: back to U after handling trap
mv sp, a0
# now sp->kernel stack(after allocated), sscratch->user stack
# restore sstatus/sepc
# 从66行到79行负责用户态状态的恢复,先回复CSR再恢复通用寄存器
ld t0, 32*8(sp)
ld t1, 33*8(sp)
ld t2, 2*8(sp)
csrw sstatus, t0
csrw sepc, t1
csrw sscratch, t2
# restore general-purpuse registers except sp/tp
ld x1, 1*8(sp)
ld x3, 3*8(sp)
.set n, 5
.rept 27
LOAD_GP %n
.set n, n+1
.endr
# release TrapContext on kernel stack
# 我们把sp栈的增加的数据吐出来
addi sp, sp, 34*8
# now sp->kernel stack, sscratch->user stack
# 把用户栈和内核栈的指针换回来
csrrw sp, sscratch, sp
sret
/trap/mod.rs
// Trap是指用户太程序调用系统接口或者全校较低的系统程序调用权限较高的系统接口的一种机制,它将发生异常终止程序,或者说将所有权转给操作系统
// 当操作系统完成结果返回或判断程序非法调用接口杀死程序的这一过程,其中涉及到用户态程序转变位系统态的处理中断以及回复上下文的整个过程。
use core::arch::global_asm;
global_asm!(include_str!("trap.S"));
use crate::batch::TrapContext;
use riscv::register::{
mtvec::TrapMode,
scause::{self, Exception, Trap},
stval, stvec,
};
use crate::sbi::syscall;
use crate::batch;
// stvec 控制 Trap 处理代码的入口地址
//我们需要手动调整程序发生系统调用时候的运行的程序地址,也就是我们的__alltraps, 这段汇编将用户态程序切换到内核态运行
// 这种操作高级寄存器的接口由SBI+RISCV官方封装提供,我们需要下载包,详见toml. 这个东西要是让我们来写就完全超纲了好么
pub fn init() {
extern "C" { fn __alltraps(); }
unsafe {
stvec::write(__alltraps as usize, TrapMode::Direct);
}
}
#[no_mangle]
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
// 获取trap的原因与附加信息
let scause = scause::read();
let stval = stval::read();
// 之后按照枚举取运行
match scause.cause() {
Trap::Exception(Exception::UserEnvCall) => {
// 我们调用完ecall后需要运行ecall的下一条指令,(用户态),所以+4
cx.sepc += 4;
// 我们的x17 或者说a7寄存器保存syscall_id, 而我们的x10(a0),x11(a1),x12(a2)保存了指令的剩余信息
// 我们利用这些东西进行调用并把结果(有些系统调用需要结果)存储在a0中
cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
}
Trap::Exception(Exception::StoreFault) |
Trap::Exception(Exception::StorePageFault) => {
// 这里之所以需要在运行run_next_app()是因为没有从系统态回到用户态的入口,所以再次调用run_next_app借用里面的extren C回到用户态
// 也因此在源代码中采用了全局变量的方案运行程序
println!("[kernel] PageFault in application, kernel killed it.");
unsafe{batch::run_next_app();}
}
Trap::Exception(Exception::IllegalInstruction) => {
println!("[kernel] IllegalInstruction in application, kernel killed it.");
unsafe{batch::run_next_app();}
}
_ => {
panic!("Unsupported trap {:?}, stval = {:#x}!", scause.cause(), stval);
}
}
cx
}
// 我们在Trap中写完了用户态到系统态的调用,最后我们需要实现系统态返回用户态的调用
#[repr(C)]
pub struct TrapContext {
pub x: [usize; 32],
pub sstatus: Sstatus,
pub sepc: usize,
}
impl TrapContext {
// 获取用户态的栈顶指针
pub fn get_user_sp(&self) -> usize {
self.x[2]
}
// 我们程序初始化,也就是再之前没有用户态的时候我们的栈顶就是0,然后跑汇编__restore方法的时候就会访问未定义的内存,这是
//万万不行的,因此我们需要准备一个app_init初始化的方法。初始化的方法也很简单,只要我们的栈顶指针增加就可以了
// 最简单的方法就是初识化像结构体然后塞到KernelStack里面去。
// 为什么是KernelStack, 因为在汇编代码中我们运行时候的sp早就跟sscratch交换了,所以说sp指向的使内核,从内核人为站内有34 * 8的数据
// 这里的sp为了最后一句的互换应该仿制的是用户栈的栈顶, 而entry则是我们设定的程序第一条指令所在的地址,也就是0x80400000.
pub fn user_stack_init(entry: usize, sp: usize) -> TrapContext {
let mut sstatus = sstatus::read(); // CSR sstatus
sstatus.set_spp(SPP::User); //previous privilege mode: user mode
let mut cx = Self {
x: [0; 32],
sstatus,
sepc: entry, // entry point of app
};
cx.x[2] = sp; // app's user stack pointer
cx // return initial Trap Context of app
}
}
/batch.rs
use core::arch::asm;
use lazy_static::lazy_static;
pub trait sp {
fn get_sp(&self) -> usize;
}
// 定义用户栈与内核栈。我本来觉得只需要定义用户栈的,但是程序运行用户程序的话由于内粗气通用势必对内核栈造成影响,因此两个栈都是需要的
// 这两个栈更准确来说主要是给我们的上下文一个容纳空间的作用
// 而上面的获取栈顶是指,现在两个Stack的空间为空,就是没有存储任何数据,所以是数组的最高位地址
// 一旦存取数据,栈顶就会向下(/低地址的方向移动),这也就是为什么在官方代码中的self.get_sp() - core::mem::size_of::<TrapContext>()用的是减法
// 因为入栈了,栈顶下移了
pub struct User_Stack {
// 大小设为8Kib, u8正好是一字节,只不过我想不通为啥不用Uszie了
data: [u8; 4096 * 2],
}
pub struct Core_Stack {
// 大小设为8Kib, u8正好是一字节,只不过我想不通为啥不用Uszie了
data: [u8; 4096 * 2],
}
impl sp for User_Stack {
fn get_sp(&self) -> usize {
// 返回栈顶地址,这里用的加,但是我有点蒙,栈顶应该是低地址为什么要加呢?
return self.data.as_ptr() as usize + 4096 * 2;
}
}
impl sp for Core_Stack {
fn get_sp(&self) -> usize {
// 返回栈顶地址,这里用的加,但是我有点蒙,栈顶应该是低地址为什么要加呢?
return self.data.as_ptr() as usize + 4096 * 2;
}
}
impl Core_Stack {
pub fn push_context(&self, cx: TrapContext) -> &'static mut TrapContext {
// 进栈,栈顶像低地址,所以用减法
// 这里的*并不是解引用,而是指地址,和*const u8用法类似,不过从const变成了*mut,是一个裸指针指向未初始化可变类型
let cx_ptr = (self.get_sp() - core::mem::size_of::<TrapContext>()) as *mut TrapContext;
unsafe {
// 给指针指向的地址赋值。
*cx_ptr = cx;
}
unsafe { cx_ptr.as_mut().unwrap() }
}
}
// 保存上下文内容,x为寄存器x1,x2(用户栈顶指针sp) ,x3, x5..x31(这里我满打满算发现只有31个元素)
// 以及sstatus(原来运行的状态), spec (系统调用完返回程序下一条指令的地址)
// 这里之所以分配的空间比存储的多是为了防止x寄存器的序号与对应的角标混乱对不上,所以舍弃了一小点的空间换来代码可读性的方便
// 这里单纯是加载应用的部分
const MAX_APP_NUM: usize = 16;
const APP_BASE_ADDRESS: usize = 0x80400000;
const APP_SIZE_LIMIT: usize = 0x20000;
pub struct AppManager {
num_app: usize,
current_app: usize,
app_start: [usize; MAX_APP_NUM + 1],
}
impl AppManager {
pub fn print_app_info(&self) {
println!("[kernel] num_app = {}", self.num_app);
for i in 0..self.num_app {
println!(
"[kernel] app_{} [{:#x}, {:#x})",
i,
self.app_start[i],
self.app_start[i + 1]
);
}
}
unsafe fn load_app(&self, app_id: usize) {
if app_id >= self.num_app {
panic!("All applications completed!");
}
println!("[kernel] Loading app_{}", app_id);
// clear icache
// 清除掉缓存
asm!("fence.i");
// clear app area
// 有了下面init的注释,这里就好理解很多了,我们指定80400000(当然这个地址随你喜欢换成别的都没问题的)作为运行程序的地址,
// 然后对这一块空间做初始化清空,之后把我们的程序放进去
core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
// 之后我们拷贝当前要运行的程序的数据放到80400000这个地址
// 这一段代码看着有点别扭,是因为rust几乎不允许我们使用裸指针访问,所以我们需要用app_dst定义出这样的一块地址
// 之后使用copy_from_slice把整个数据拷贝进去
let app_src = core::slice::from_raw_parts(
self.app_start[app_id] as *const u8,
self.app_start[app_id + 1] - self.app_start[app_id],
);
let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
app_dst.copy_from_slice(app_src);
}
pub fn move_to_next_app(&mut self) {
self.current_app += 1;
}
}
pub unsafe fn create_app_ctrl() -> AppManager {
extern "C" {
fn _num_app();
}
// 好奇怪的地址,80204000
// 这句化的意思是获取我们汇编语言中的_num_app(我们可以看到它是一个块)的首地址,因为_num_app本身
// num_app as usize 是2149597184,也就是0x80204000的十进制表示,
// *const XX 类型是Rust的原始指针类型,我们能通过这个原始指针读取到响应内存中的数据
// 我们在汇编的第一句话使用的是.quad 2,在此地址存储了数据2.
// 在汇编中使用的是quad存储数据,我们在汇编第一行代码能看到.align 3 指令指挥强制命令地址必须是2**3的倍数。
// 至于为什么空间取这么大,因为riscv-64, 是64位为一个单位,按照每个字节八位的话刚好要8个地址。
// 这里一定至少要使用一次_num_app才会把我们汇编中定义的代码运行,才能在80204000中找到我们对应的东西
// 至于为什么是4000, 我们在汇编中定义的数据块不可修改且全局有效
// 至于为什么是从4000开始,因为我们定义的东西放到了data块没错吧,ALIGN(4K)规定了我们每个快的开始要与4K对齐
// 我们已经在bss块中放了一些东西,所以bss后面的东西从80200000地址变为80204000的起始地址。然后rodata中我们并没有防止任何数据,
// 此时地址不变的来到了.data块,于是乎理所当然的还是80204000。
let num_app_ptr = _num_app as usize as *const usize;
let num_app = num_app_ptr.read_volatile();
//获取app的数量
let mut app_start: [usize; MAX_APP_NUM + 1] = [0; MAX_APP_NUM + 1];
// 用这个函数读取我们整个_num_app块中的数据,不过有意思的是它能自动寻址,也能读取数据的个数
// add 1 是因为开始的app_num 2 我们读了不需要再度, num_app + 1是因为我们的数据是每个app的头地址 + 最后一个app的尾地址(+1)
let app_start_raw: &[usize] =
core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1);
// 然后copy过去就完成了初始化
app_start[..=num_app].copy_from_slice(app_start_raw);
AppManager {
num_app,
current_app: 0,
app_start,
}
}
// init batch subsystem
// run next app
static CORE_STACK: Core_Stack = Core_Stack {
data: [0; 4096*2],
};
static USER_STACK: User_Stack = User_Stack {
data: [0; 4096*2],
};
use core::cell::{RefCell, RefMut};
use lazy_static::*;
// 这里使用普通的RefCell告诉你不线程安全
pub struct UPSafeCell<T> {
/// inner data
inner: RefCell<T>,
}
unsafe impl<T> Sync for UPSafeCell<T> {}
impl<T> UPSafeCell<T> {
/// User is responsible to guarantee that inner struct is only used in
/// uniprocessor.
pub unsafe fn new(value: T) -> Self {
Self {
inner: RefCell::new(value),
}
}
/// Exclusive access inner data in UPSafeCell. Panic if the data has been borrowed.
pub fn exclusive_access(&self) -> RefMut<'_, T> {
self.inner.borrow_mut()
}
}
lazy_static! {
static ref APP_MANAGER: UPSafeCell<AppManager> = unsafe{
UPSafeCell::new(create_app_ctrl())
};
}
pub unsafe fn run_next_app() -> ! {
let mut app_ctrl = APP_MANAGER.exclusive_access();
println!("Load next app");
app_ctrl.load_app(app_ctrl.current_app);
app_ctrl.move_to_next_app();
// 在Trap调用的时候只能存在一个可变引用,所以必须drop掉
drop(app_ctrl);
//最后我们把代码合起来,restore会从系统态转变为用户态调用程序
extern "C" {
fn __restore(cx_addr: usize);
}
unsafe {
__restore(CORE_STACK.push_context(TrapContext::user_stack_init(
APP_BASE_ADDRESS,
USER_STACK.get_sp(),
)) as *const _ as usize);
}
//drop(app_manager);
// before this we have to drop local variables related to resources manually
// and release the resources
panic!("Unreachable in batch::run_current_app!");
}
更多原理参见rCore第一张批处理内容