博客 / 詳情

返回

risc-v+rust-- 加載用户的app的底層原理?

文章寫的有點墨跡, 我在梳理梳理一下, 其實,就是一個技巧: 內核棧裏面放入的是用户態的數據。還有一點, 用户的程序放到一個位置, 內核去這個位置讀取數據

之前都是內核態,用的棧也是內核態的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
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.