rCore-ch1

环境执行

fn main() {
println!("hello,world");
}

从修改hello world,使它不用系统自带的依赖开始

  1. 修改目标平台
    目标为裸机平台,没有rust标准库和os支持的系统调用,为了方便换上了rust的core库
# os/.cargo/config
[build]
target = "riscv64gc-unknown-none-elf"
  1. 移除标准库依赖
    在main.rs中修改如下:

    #![no_std]
    #![no_main]
    mod lang_items;
    pub extern "C" fn _start() {
    loop {}
    }

    同级目录新建一个lang_items.rs

    use core::panic::PanicInfo;

    #[panic_handler]
    fn panic(_info: &PanicInfo) -> ! {
    loop{

    }
    }

    如此便移除了所有标准库依赖,可尝试cargo build看看全新的程序了

    file /path/to/elf
    # 可看文件格式
    rust-readobj -h /path/to/elf
    # 可看详细的文件头信息
    rust-objdump -S /path/to/elf
    # 可看反汇编导出的汇编程序

image-20240731152144771

用户态

先前的main.rs里已经有了程序入口,可尝试给qemu-riscv64跑一个看看

image-20240731183935657

第一次跑段错误了,后面跟教程修改了一些才能够正常运行

fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret;
unsafe {
core::arch::asm!(
"ecall",
inlateout("x10") args[0] => ret,
in("x11") args[1],
in("x12") args[2],
in("x17") id,
);
}
ret
}

这是一个神秘的函数,它通过使用内联汇编方便地使用core库提供给的系统调用.比如目前执行环境缺少一个退出机制:

const SYSCALL_EXIT: usize = 93;

//define syscall
fn syscall(id: usize,args: [usize;3]) -> isize {...}
pub fn sys_exit(xstate: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}

#[no_mangle]
extern "C" fn _start() {
sys_exit(9);
}

再传给qemu就能正常运行,还能通过$?参数接受退出码

如果要实现Stdout,也是类似的,复杂一点:

use core::fmt;
use core::fmt::Write;
const SYSCALL_WRITE: usize = 64;

pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}

再基于这个封装实现stdout

struct Stdout;

impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
sys_write(1, s.as_bytes());
Ok(())
}
}

pub fn print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}

其实此时已经可以通过print输出字符串了,就是类型上会有点别扭(毕竟没有标准库)

再来点看不懂的格式化宏

#[macro_export]
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
}
}

#[macro_export]
macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => {
print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
}
}
//可以看看输出效果了
#[no_mangle]
extern "C" fn _start() {
println!("Hello, world!");
sys_exit(9);
}

image-20240731191217937

此时如果想用objdump看看的话,就会发现多了特别多的依赖之类,而上面写的源码编译出来的东西(除了宏,宏的编译结果我没法直接看出来)只占其中一部分,我的objdump结果总共2000行,其中300行左右是我们的源码

裸机环境

用qemu的`qemu-system-riscv64模拟risc-v 64构建裸机环境,加载内核的命令:

  • -bios $(bootloader)加载bootloader,即rustSBI,
  • -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)表示硬件内存中的特定位置即$(KERNEL_ENTRY_PA)放置了操作系统的二进制程序$(KERNEL_BIN),entry_pa值是0x80200000

当我们执行包含上述启动参数的 qemu-system-riscv64 软件,就意味给这台虚拟的 RISC-V64 计算机加电了。 此时,CPU 的其它通用寄存器清零,而 PC 会指向 0x1000 的位置,这里有固化在硬件中的一小段引导代码, 它会很快跳转到 0x80000000 的 RustSBI 处。 RustSBI完成硬件初始化后,会跳转到 $(KERNEL_BIN) 所在内存位置 0x80200000 处, 执行操作系统的第一条指令。

当在裸机环境运行时,需要有关机功能

// bootloader/rustsbi-qemu.bin 直接添加的SBI规范实现的二进制代码,给操作系统提供基本支持服务

// os/src/sbi.rs
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
let mut ret;
unsafe {
core::arch::asm!(
"ecall",
...

const SBI_SHUTDOWN: usize = 8;

pub fn shutdown() -> ! {
sbi_call(SBI_SHUTDOWN, 0, 0, 0);
panic!("It should shutdown!");
}

// os/src/main.rs
#[no_mangle]
extern "C" fn _start() {
shutdown();
}

“ecall”和上以迎接不同在于特权级:

User Mode < Supervisor Mode < Machine Mode,分别对应”应用程序\操作系统\RustSBI”

编译结果如下:(ctlr C无法退出,用了类似kill的工具)

image-20240803214636705

用rust-readobj看:(valid是后面通过qemu正常自动退出的结果,invalid是目前这个卡住的)

image-20240803215319416

发现其入口地址不是 RustSBI 约定的 0x80200000,需要修改内存布局

  1. 增加链接脚本: 首先在cargo的配置文件中加入自己的链接脚本:
// os/.cargo/config
[build]
target = "riscv64gc-unknown-none-elf"

[target.riscv64gc-unknown-none-elf]
rustflags = [
"-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
]

然后在os/src/新建一linker.ld:

OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80200000;

SECTIONS
{
. = BASE_ADDRESS;
skernel = .;

stext = .;
.text : {
*(.text.entry)
*(.text .text.*)
}

. = ALIGN(4K);
etext = .;
srodata = .;
.rodata : {
*(.rodata .rodata.*)
}

. = ALIGN(4K);
erodata = .;
sdata = .;
.data : {
*(.data .data.*)
}

. = ALIGN(4K);
edata = .;
.bss : {
*(.bss.stack)
sbss = .;
*(.bss .bss.*)
}

. = ALIGN(4K);
ebss = .;
ekernel = .;

/DISCARD/ : {
*(.eh_frame)
}
}
  1. 初始化栈空间

再在同一文件夹新建一个entry.asm,用以初始化栈空间

    .section .text.entry
.globl _start
_start:
la sp, boot_stack_top
call rust_main

.section .bss.stack
.globl boot_stack
boot_stack:
.space 4096 * 16
.globl boot_stack_top
boot_stack_top:

可以看到此时在初始化空间之后直接call了一个新函数rust_main,于是我们也要在main.rs内补充同名函数以满足

core::arch::global_asm!(include_str!("entry.asm"));
#[no_mangle]
pub fn rust_main() -> ! {
shutdown();
}

再把原本的_start注释掉

  1. 再次编译并生成和运行,可以看到qemu成功退出

image-20240803220838568

再清空一下.bss段,关于为什么image-20240803221451061

清除的方法即增加在main.rs中的clear_bss()

// os/src/main.rs
fn clear_bss() {
extern "C" {
fn sbss();
fn ebss();
}
(sbss as usize..ebss as usize).for_each(|a| {
unsafe { (a as *mut u8).write_volatile(0) }
});
}

pub fn rust_main() -> ! {
clear_bss();
shutdown();
}

再经过一些对原项目的加工(Ctrl cv),终于能hello world了!

image-20240803232800786

rCore-ch2

todo What?

说实话我在看到这一章的时候挺茫然的,满篇的东西都是在介绍,不知道我要干什么。

那么来总结一下这一章书上的内容先:

  1. 批处理系统:多个程序打包到一起输入计算机,程序结束自动执行下一个。为避免出错时整个环境崩溃,引入特权级,分开用户、内核。
  2. 应用程序: 项目文件里准备了bin文件夹对应各个应用程序。每个程序在main函数内实现了用户程序的功能。使用的依赖对应lib.rs(类似于一个标准库)。lib.rs中定义了_start用于初始化应用程序
  3. 批处理: 内核通过link_app.S获知应用程序的数量和位置,通过AppManager来加载应用程序的二进制码.加载时需要清除缓存,清空内存,将二进制程序复制到正确位置.
  4. 特权转换: 特权被一些对应用的切换和监控操作需要,比如初始化,处理系统调用,应用出错的处理程序,结束程序时的切换

在看看程序跑一遍的结果:

[rustsbi] RustSBI version 0.3.0-alpha.4, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000ef2
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[ INFO] [kernel] .data [0x8020b000, 0x80228000)
[ WARN] [kernel] boot_stack top=bottom=0x80238000, lower_bound=0x80228000
[ERROR] [kernel] .bss [0x80238000, 0x80239000)
[kernel] num_app = 7
[kernel] app_0 [0x8020b048, 0x8020f0f0)
[kernel] app_1 [0x8020f0f0, 0x80213198)
[kernel] app_2 [0x80213198, 0x80217240)
[kernel] app_3 [0x80217240, 0x8021b2e8)
[kernel] app_4 [0x8021b2e8, 0x8021f390)
[kernel] app_5 [0x8021f390, 0x80223438)
[kernel] app_6 [0x80223438, 0x802274e0)
[kernel] Loading app_0
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_1
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_2
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_3
Hello, world from user mode program!
[kernel] Loading app_4
power_3 [10000/200000]
power_3 [20000/200000]
power_3 [30000/200000]
power_3 [40000/200000]
power_3 [50000/200000]
power_3 [60000/200000]
power_3 [70000/200000]
power_3 [80000/200000]
power_3 [90000/200000]
power_3 [100000/200000]
power_3 [110000/200000]
power_3 [120000/200000]
power_3 [130000/200000]
power_3 [140000/200000]
power_3 [150000/200000]
power_3 [160000/200000]
power_3 [170000/200000]
power_3 [180000/200000]
power_3 [190000/200000]
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
[kernel] Loading app_5
power_5 [10000/140000]
power_5 [20000/140000]
power_5 [30000/140000]
power_5 [40000/140000]
power_5 [50000/140000]
power_5 [60000/140000]
power_5 [70000/140000]
power_5 [80000/140000]
power_5 [90000/140000]
power_5 [100000/140000]
power_5 [110000/140000]
power_5 [120000/140000]
power_5 [130000/140000]
power_5 [140000/140000]
5^140000 = 386471875(MOD 998244353)
Test power_5 OK!
[kernel] Loading app_6
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
All applications completed!

再跟着代码看看运行的过程经历了什么(也就是看看源码,不想自己敲的懒狗发言XD)

// os/src/main.rs
#[no_mangle]
pub fn rust_main() -> ! {
extern "C" {
fn stext(); // begin addr of text segment
fn etext(); // end addr of text segment
fn srodata(); // start addr of Read-Only data segment
fn erodata(); // end addr of Read-Only data ssegment
fn sdata(); // start addr of data segment
fn edata(); // end addr of data segment
fn sbss(); // start addr of BSS segment
fn ebss(); // end addr of BSS segment
fn boot_stack_lower_bound(); // stack lower bound
fn boot_stack_top(); // stack top
}
clear_bss();
logging::init();
println!("[kernel] Hello, world!");
trace!(
"[kernel] .text [{:#x}, {:#x})",
stext as usize,
etext as usize
);
debug!(
"[kernel] .rodata [{:#x}, {:#x})",
srodata as usize, erodata as usize
);
info!(
"[kernel] .data [{:#x}, {:#x})",
sdata as usize, edata as usize
);
warn!(
"[kernel] boot_stack top=bottom={:#x}, lower_bound={:#x}",
boot_stack_top as usize, boot_stack_lower_bound as usize
);
error!("[kernel] .bss [{:#x}, {:#x})", sbss as usize, ebss as usize);
trap::init();
batch::init();
batch::run_next_app();
}

由于make run LOG=INFO的指令,可以看到从helloworld到error这三行是这里的输出.main之后是两个init和一个run_next

CodeReading-CorePart

1.trap

image-20240808221732668

// os/src/trap/mod.rs
pub fn init() {
extern "C" {
fn __alltraps();
}
unsafe {
stvec::write(__alltraps as usize, TrapMode::Direct);
}
}

这里的init原谅我不是很明白.在同级文件夹找到了__alltraps的具体内容

# os/src/trap/trap.S
__alltraps:
csrrw sp, sscratch, sp
# now sp->kernel stack, sscratch->user stack
# allocate a TrapContext on kernel stack
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
# we can use t0/t1/t2 freely, because they were saved on kernel stack
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)
# set input argument of trap_handler(cx: &mut TrapContext)
mv a0, sp
call trap_handler

感谢逐行的注释

image-20240808215516481

csrrw sp, sscratch, sp可知这个函数执行时将从用户态转向内核态,此时在做用户栈到内核栈的转换,分配空间存储trapText,保存一些寄存器的值到内核栈上:比如x1,x3,x5~x31,t0(对应sstatus 寄存器,包含当前 CPU 的状态),t1(对应sepc 寄存器,包含trap发生时的程序计数器值),t2(对应与用户空间的sp),最后将sp的值赋给a0,传给trap_handler.总的来说就是在初始化处理trap的条件和特权转换

返回看os/trap/mod.rs之中的stvec::write(__alltraps as usize, TrapMode::Direct);这一行,将这个trap处理的程序地址写入stvec,控制trap处理代码的入口地址.也就是在trap::init();之后,我们可以调用stvec来处理所有的trap了

2.batch

这一小节的内容能和tutorial书中实现批处理系统这一节对应上.

// os/src/batch.rs
pub fn init() {
print_app_info();
}

pub fn print_app_info() {
APP_MANAGER.exclusive_access().print_app_info();
}

lazy_static! {
static ref APP_MANAGER: UPSafeCell<AppManager> = unsafe {
UPSafeCell::new({
extern "C" {
fn _num_app();
}
let num_app_ptr = _num_app as usize as *const usize;
let num_app = num_app_ptr.read_volatile();
let mut app_start: [usize; MAX_APP_NUM + 1] = [0; MAX_APP_NUM + 1];
let app_start_raw: &[usize] =
core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1);
app_start[..=num_app].copy_from_slice(app_start_raw);
AppManager {
num_app,
current_app: 0,
app_start,
}
})
};
}

先看这样一点代码.

这里调用的过程是init -> print__app_info -> APP_MANAGER 实例 -> lazy_static 初始化 -> print_app_info 方法。

下面是涉及到的rust语法

lazy_static: 给静态变量延迟赋值的宏,在第一次被访问时完成赋值

UPsafeCell: 在单处理器中使用的数据结构,里面包含一个RefCell<T>,通过.exclusive_access()可访问这个数据的可变引用,能防止内部对象被重复借用

os::ptr::read_volatile: pub unsafe fn read_volatile<T>(src: *const T) -> T,在保持内存不变的同时易失性地读取src的值

core::slice::from_raw_parts: pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]根据指针和长度形成切片。len 参数是 元素 的数量,而不是字节数。

仔细看lazy_static的初始化内容:

  1. 声明了一个_num_app符号,在同级文件夹中的link_app.S中定义,这个文件在构建操作系统时,依据os/build.rs自动生成,内容类似下面:

    _num_app:
    .quad 7
    .quad app_0_start
    .quad app_1_start
    .quad app_2_start
    .quad app_3_start
    .quad app_4_start
    .quad app_5_start
    .quad app_6_start
    .quad app_6_end

    .section .data
    .global app_0_start
    .global app_0_end
    app_0_start:
    .incbin "../user/build/bin/ch2b_bad_address.bin"
    app_0_end:

    .section .data
    .global app_1_start
    .global app_1_end

    由于const MAX_APP_NUM: usize=16,为指针切片app_start赋值时,将容纳小于此数的app个数,并将每个app的起始地址和最后一个app的结束地址存入切片

  2. 最后返回初始化成功的AppManager.这里的num_app的类型我有点不明白,因为对它赋值时,使用的是泛型也就是前一行的usize,然而存储0-16范围的数也需要统一使用usize吗?

  3. 调用print_app_info,依次输出app个数和每个app的内存地址

3.run_next_app

和上一小节同样在batch.rs之中,下面是来自tutorial的一段话

AppManager 的方法中, print_app_info/get_current_app/move_to_next_app 都相当简单直接,需要说明的是 load_app

pub fn run_next_app() -> ! {
let mut app_manager = APP_MANAGER.exclusive_access();
let current_app = app_manager.get_current_app();
unsafe {
app_manager.load_app(current_app);
}
app_manager.move_to_next_app();
drop(app_manager);
// before this we have to drop local variables related to resources manually
// and release the resources
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);
}
panic!("Unreachable in batch::run_current_app!");
}

unsafe fn load_app(&self, app_id: usize) {
if app_id >= self.num_app {
println!("All applications completed!");
use crate::board::QEMUExit;
crate::board::QEMU_EXIT_HANDLE.exit_success();
}
println!("[kernel] Loading app_{}", app_id);
// clear app area
core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
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);
// Memory fence about fetching the instruction memory
// It is guaranteed that a subsequent instruction fetch must
// observes all previous writes to the instruction memory.
// Therefore, fence.i must be executed after we have loaded
// the code of the next app into the instruction memory.
// See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
asm!("fence.i");
}

load本身在需要载入应用程序时,先清空约定区域的内存,将应用的二进制文件载入,再清空缓存

image-20240810012942659

整体来看,调用一个app的功能实现:

  1. 借来一个appManager的可变引用,方便调用它的impl
  2. load 一个current 对应的app,再current+=1,drop掉appmanager

当我只看到这些时会觉得drop得有点早了,后面都在干什么?__restore的定义在哪里?找了一下在一个有点意外的位置:os/src/trap/trap.S

__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
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
addi sp, sp, 34*8
# now sp->kernel stack, sscratch->user stack
csrrw sp, sscratch, sp
sret

a0塞进sp,以及几个熟悉的寄存器的出现,这个__restorealltraps太像了,功能上和alltraps相反,是从内核态转到用户态用的.

在看一眼__restore被用去干嘛:

unsafe {
__restore(KERNEL_STACK.push_context(TrapContext::app_init_context(
APP_BASE_ADDRESS,
USER_STACK.get_sp(),
)) as *const _ as usize);
}
panic!("Unreachable in batch::run_current_app!");

功能解释:

  1. TrapContext::app_init_context获取app起始地址和用户栈指针,用于创建TrapContext
  2. KERNEL_STACK.push_context获取TrapContext本体,将它push进内核栈的栈顶,返回TrapContext的在内核栈中的可变引用
  3. __restore读取TrapContext的数据并将 CPU 切换到用户模式同时恢复应用程序的状态.

__restore后,程序就直接跳转到了应用程序的第一行二进制指令处,后面就暂时没有内核的事了.但是,应用结束任务再调用run_next_app的部分还没有看到,以及具体的应用出错的处理,特权转换也有点模糊(在我脑子里)

CodeReading-UserPart

1.__start

位于user/src/lib.rs中的函数,是用户库的入口函数,也是0x80400000地址对应的最先执行的程序部分。下面是tutorial里给的案例

1#[no_mangle]
2#[link_section = ".text.entry"]
3pub extern "C" fn _start() -> ! {
4 clear_bss();
5 exit(main());
6}

而我文件夹里的是这个样子:

#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start(argc: usize, argv: usize) -> ! {
clear_bss();
unsafe {
HEAP.lock()
.init(HEAP_SPACE.as_ptr() as usize, USER_HEAP_SIZE);
}
let mut v: Vec<&'static str> = Vec::new();
for i in 0..argc {
let str_start =
unsafe { ((argv + i * core::mem::size_of::<usize>()) as *const usize).read_volatile() };
let len = (0usize..)
.find(|i| unsafe { ((str_start + *i) as *const u8).read_volatile() == 0 })
.unwrap();
v.push(
core::str::from_utf8(unsafe {
core::slice::from_raw_parts(str_start as *const u8, len)
})
.unwrap(),
);
}
exit(main(argc, v.as_slice()));
}

多出的部分是在初始化堆、解析命令行参数(虽然现在应该没有应用需要)

后面exit之前进入了main函数,而因为pub extern "C" fn ,main不一定是同处于lib.rs的main,得根据实际链接时bin文件中的main符号对应的main函数来看,main之中的内容暂时不看

2.exit
pub fn exit(exit_code: i32) -> ! {
console::flush();
sys_exit(exit_code);
}

上一节的_start最后调用了这个函数。这个函数内部的sys_exit是包装好的sys_call,实际上是ecall的汇编指令(类似第一章但是有区别)

ecall在用户态会触发Environment call from U-mode的异常,会把特权从U提到S,同时直接跳转到对应的处理程序.遗憾的是还是没有发现哪里会切换到下一个应用

3.User -> Supervisor

然而exit有所谓的exit_code,再顺藤摸瓜看看谁接收这个码,发现在os/src/syscall/process.rs中发现了以下神奇代码

//! App management syscalls
use crate::batch::run_next_app;

/// task exits and submit an exit code
pub fn sys_exit(exit_code: i32) -> ! {
trace!("[kernel] Application exited with code {}", exit_code);
run_next_app()
}

那么问题来了,是谁调用了位于内核态的sys_exit?明明前面用户态调用的sys_exit是通过ecall实现的user/src/syscall.rs,这两个sys_exit是怎么最后联系在一起的?

再看看os/src/syscall/mod.rs

pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
match syscall_id {
SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]),
SYSCALL_EXIT => sys_exit(args[0] as i32),
_ => panic!("Unsupported syscall_id: {}", syscall_id),
}
}

可以看到syscall在内核态的包装方式,接下来在os的代码里找syscall

在os/src/trap/mod.rs中找到结果了

pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
let scause = scause::read(); // get trap cause
let stval = stval::read(); // get extra value
match scause.cause() {
Trap::Exception(Exception::UserEnvCall) => {
cx.sepc += 4;
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
}

这里发现调用了syscall

这个trap_handler就是每个应用的终点,不论是应用报错被alltraps接手,还是正常退出,从exit开始,最后都走到这里结束应用程序的生命周期.

整体和tutorial的内容算是一一对应,但是之前的rustlings没有这么多unsafe的指针操作和汇编,嗦实话这一章看的我好费劲T_T

rCore-ch3