文章寫的有點墨跡, 我在梳理梳理一下, 其實,就是一個技巧: 內核棧裏面放入的是用户態的數據。還有一點, 用户的程序放到一個位置, 內核去這個位置讀取數據
之前都是內核態,用的棧也是內核態的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
}