rCore-ch1 环境执行 fn main () { println! ("hello,world" ); }
从修改hello world,使它不用系统自带的依赖开始
修改目标平台 目标为裸机平台,没有rust标准库和os支持的系统调用,为了方便换上了rust的core库
# os/.cargo/config [build] target = "riscv64gc-unknown-none-elf"
移除标准库依赖 在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
用户态 先前的main.rs里已经有了程序入口,可尝试给qemu-riscv64
跑一个看看
第一次跑段错误了,后面跟教程修改了一些才能够正常运行
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 ;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 ); }
此时如果想用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
处, 执行操作系统的第一条指令。
当在裸机环境运行时,需要有关机功能
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!" ); } #[no_mangle] extern "C" fn _start () { shutdown (); }
“ecall”和上以迎接不同在于特权级:
User Mode < Supervisor Mode < Machine Mode,分别对应”应用程序\操作系统\RustSBI”
编译结果如下:(ctlr C无法退出,用了类似kill的工具)
用rust-readobj看:(valid是后面通过qemu正常自动退出的结果,invalid是目前这个卡住的)
发现其入口地址不是 RustSBI 约定的 0x80200000
,需要修改内存布局
增加链接脚本: 首先在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) } }
初始化栈空间
再在同一文件夹新建一个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注释掉
再次编译并生成和运行,可以看到qemu成功退出
再清空一下.bss段,关于为什么
清除的方法即增加在main.rs中的clear_bss()
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了!
rCore-ch2 todo What? 说实话我在看到这一章的时候挺茫然的,满篇的东西都是在介绍,不知道我要干什么。
那么来总结一下这一章书上的内容先:
批处理系统:多个程序打包到一起输入计算机,程序结束自动执行下一个。为避免出错时整个环境崩溃,引入特权级,分开用户、内核。
应用程序: 项目文件里准备了bin文件夹对应各个应用程序。每个程序在main函数内实现了用户程序的功能。使用的依赖对应lib.rs(类似于一个标准库)。lib.rs中定义了_start
用于初始化应用程序
批处理: 内核通过link_app.S
获知应用程序的数量和位置,通过AppManager
来加载应用程序的二进制码.加载时需要清除缓存,清空内存,将二进制程序复制到正确位置.
特权转换: 特权被一些对应用的切换和监控操作需要,比如初始化,处理系统调用,应用出错的处理程序,结束程序时的切换
在看看程序跑一遍的结果:
[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)
#[no_mangle] pub fn rust_main () -> ! { extern "C" { fn stext (); fn etext (); fn srodata (); fn erodata (); fn sdata (); fn edata (); fn sbss (); fn ebss (); fn boot_stack_lower_bound (); fn boot_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
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
感谢逐行的注释
从 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书中实现批处理系统这一节对应上.
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的初始化内容:
声明了一个_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的结束地址存入切片
最后返回初始化成功的AppManager.这里的num_app
的类型我有点不明白,因为对它赋值时,使用的是泛型也就是前一行的usize,然而存储0-16范围的数也需要统一使用usize吗?
调用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); 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); 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); asm!("fence.i" ); }
load
本身在需要载入应用程序时,先清空约定区域的内存,将应用的二进制文件载入,再清空缓存
整体来看,调用一个app的功能实现:
借来一个appManager的可变引用,方便调用它的impl
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,以及几个熟悉的寄存器的出现,这个__restore
和alltraps
太像了,功能上和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!" );
功能解释:
TrapContext::app_init_context获取app起始地址和用户栈指针,用于创建TrapContext
KERNEL_STACK.push_context获取TrapContext本体,将它push进内核栈的栈顶,返回TrapContext的在内核栈中的可变引用
__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" ] 3 pub 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中发现了以下神奇代码
use crate::batch::run_next_app;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 (); let stval = stval::read (); 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