文章写的有点墨迹, 我在梳理梳理一下, 其实,就是一个技巧: 内核栈里面放入的是用户态的数据。还有一点, 用户的程序放到一个位置, 内核去这个位置读取数据
之前都是内核态,用的栈也是内核态的sp.
extern "C" {
//汇编地址的入口
fn __restore(cx_addr: usize);
}
unsafe {
// 触发汇编的调用
__restore(KERNEL_STACK.push_context(TrapContext::app_init_context(
APP_BASE_ADDRESS,
USER_STACK.get_sp(),
)) as *const _ as usize);
}
参数参数是KERNEL_STACK.push_context的返回地址 。放到寄存器a0中(这个是规范)
看看KERNEL_STACK.push_context 返回的是啥?
impl KernelStack {
fn get_sp(&self) -> usize {
self.data.as_ptr() as usize + KERNEL_STACK_SIZE
}
pub fn push_context(&self, cx: TrapContext) -> &'static mut TrapContext {
let cx_ptr = (self.get_sp() - core::mem::size_of::<TrapContext>()) as *mut TrapContext;
unsafe {
*cx_ptr = cx;
}
unsafe { cx_ptr.as_mut().unwrap() }
}
}
返回的是一个固定内存地址减去一个上下文环境这么大的地址。从这个地址存入的是一个用户态的上下文。
再看看risc-v 汇编怎么用这个a0
__restore:
#sp 是内核栈减去一个app地址。之后的内存空间的都是用户态的上下文数据
#sp 是内核的栈,只不过里面放的用户态的数据,后面取出来的也是用户态数据而已。
mv sp, a0
# restore sstatus/sepc
ld t0, 32*8(sp)
ld t1, 33*8(sp)
ld t2, 2*8(sp)
csrw sstatus, t0
csrw sepc, t1
csrw sscratch, t2 #用户态的数据
ld x1, 1*8(sp)
ld x3, 3*8(sp)
.set n, 5
.rept 27
LOAD_GP %n
.set n, n+1
.endr
#sp的空间是自己维护,用完了在还原, 好让下一个app,可以在用这个地址空间
addi sp, sp, 34*8
#sscratch 指向了用户栈.
csrrw sp, sscratch, sp
#触发用户态执行了, pc=sepc,也会用到SPP。
sret
程序跳到sepc处执行, 这里存的是啥?sepc 是啥时候设置的呢?
pub fn app_init_context(entry: usize, sp: usize) -> Self {
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.set_sp(sp); // app's user stack pointer
cx // return initial Trap Context of app
}
entry是一定的固定的地址。这个地址放的是啥?一个程序,触发系统调用的程序的。exit(main)
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! {
clear_bss();
exit(main());//这里相当于一个模版, 可以退出一个程序,啥程序,你可以写一个main程序
panic!("unreachable after sys_exit!");
}
#[linkage = "weak"]
#[no_mangle]
fn main() -> i32 {
panic!("Cannot find main!");//大哥让你覆盖, 过来覆盖我吧。
}
一个用户的app,哥来覆盖你。 大哥也叫main这个名字。
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
#[no_mangle]
fn main() -> i32 {
println!("Hello, world!");
0
}
exit 这个程序怎么实现的呢?
pub fn sys_exit(exit_code: i32) -> isize {
syscall(SYSCALL_EXIT, [exit_code as usize, 0, 0])
}
触发了系统调用。回到了中断向量表。
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
asm!(
"ecall",
inlateout("a0") args[0] => ret, // 原来的 "x10"
in("a1") args[1], // 原来的 "x11"
in("a2") args[2], // 原来的 "x12"
in("a7") id // 原来的 "x17"
);
}
ret
}
启动的时候,已经配置了。
pub fn init() {
extern "C" {
fn __alltraps();
}
unsafe {
//这里只是准备了数据, 但是,并没有切换到过去
stvec::write(__alltraps as usize, TrapMode::Direct);
}
}
执行__alltraps的逻辑
__alltraps:
#sp用户栈, 换了一下,sp就是内核栈了。
csrrw sp, sscratch, sp
# 从那个固定的栈地址存储的是用户态的代码。
addi sp, sp, -34*8
# save general-purpose registers
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
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
csrr t2, sscratch
sd t2, 2*8(sp)
#内核态的栈顶,但是,里面存的用户态的信息。
mv a0, sp
call trap_handler
在看看trap_handler干了啥。scause,还是用户态。 所以,走到 Exception::UserEnvCall
#[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
let scause = scause::read(); // get trap cause
let stval = stval::read(); // get extra value
println!("scause={}", scause.bits());
match scause.cause() {
Trap::Exception(Exception::UserEnvCall) => {
cx.sepc += 4;//用户态的ret 哪一样。之后,执行真正的
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) => {
println!("[kernel] PageFault in application, kernel killed it.");
run_next_app();
}
Trap::Exception(Exception::IllegalInstruction) => {
println!("[kernel] IllegalInstruction in application, kernel killed it.");
run_next_app();
}
_ => {
panic!(
"Unsupported trap {:?}, stval = {:#x}!",
scause.cause(),
stval
);
}
}
cx
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。