Revisit rCore 第二章
trap初始化
在主函数中清空bss
段后,执行了三个函数trap::init
、batch::init
、batch::run_next_app
。首先分析trap::init
,
pub fn init() {
extern "C" {
fn __alltraps();
}
unsafe {
stvec::write(__alltraps as usize, TrapMode::Direct)
}
}
这段代码首先引入了在汇编代码中说明的__alltraps
,这个是trap
的入口,之后通过unsafe
将这个值写入stvec
寄存器,并且模式设置为direct
,说明入口函数有且仅有一个。
batch初始化
batch
模块中的init
函数十分简单:
pub fn init() {
print_app_info();
}
之后我们可以看看是如何进行输出的,这个是当前模块中AppManager
中的pub
方法。
impl AppManager {
pub fn print_app_info(&self){
println!("[kernel] num_app = {}", self.num_app);
for i in 0..self.num_app {
println!(
"[kernel] app_{} [{:#x}, {:#x})",
i,
self.app_start[i],
self.app_start[i+1]
);
}
}
//...
}
这里对于AppManager
的结构进行遍历,输出了各个app
的地址范围。之所以没有初始化App
仍旧能够输出,这里是因为使用lazy_static
。
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[..=].copy_from_slice(app_start_raw);
AppManager {
num_app,
current_app: 0,
app_start,
}
})
};
}
在第一次使用时会进行初始化,这里根据地址值和长度生成一个引用的结构,因为rust
中引用是一个固定长度的结构,由一个地址和一个长度构成,指向堆区。我们读出地址值作为*const usize
,通过from_raw_part
我们可以直接构造一个引用,最后间数据拷贝到我们的app_start
中,最后对AppManager
的进行初始化。
调度
在main
函数中,执行完前两个初始化函数后就会执行run_next_app()
,它的内容如下:
pub fn run_next_app() -> !{
let mut app_manager = APP_MANAGER.exclusive_access();
let current_app = app_manager.get_cuurent_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!");
}
这里调用了AppManager
的exclusive_access
方法,这个方法用于解决实现全局可访问的可变变量的问题。它的结构如下:
UPSafeCell
use core::cell::{RefCell, RefMut};
pub struct UPSafeCell<T> {
inner: RefCell<T>,
}
unsafe impl<T> Sync for UPSafeCell<T> {}
impl<T> UPSafeCell<T> {
pub unsafe fn new(value: T) -> Self {
Self {
inner: RefCell::new(value),
}
}
pub fn exclusive_access(&self) -> RefMut<'_, T> {
self.inner.borrow_mut()
}
}
我们不使用static mut
这种使用方法是unsafe
的,所以我们使用RefCell
这个方法可以在运行时改变自己的可访问性,通过实现Sync
向编译器说明,由我们保证并发时的安全。然后,我们实现exclusive_access
方法,返回一个mut
,如果同时有两个mut
,就会发生panic
,所以我们使用必须在申请使用完后,马上进行销毁。
加载运行
获取到AppManager
的结构体后,可以使用load_app
方法将app
加载到对应的位置。我们可以查看这个函数的实现
unsafe fn load_app(&self, app_id: usize) {
if app_id >= self.num_app {
println!("All application completed!");
use crate::board::QEMUExit;
crate::board::QEMU_EXIT_HANDLE.exit_success();
}
println!("[kernel] Loading app_{}", app_id);
asm!("fence.i");
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);
}
这里构造了两个引用进行数据拷贝,又有一个引用负责进行将程序运行的区域进行初始化归零。第一个:
core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
将这个区域置零,由于我们的测试程序都不大,可以通过一个APP_SIZE_LIMIT
直接写死。
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);
直接程序拷贝到其所要运行的地址0x80400000
。
接着开始运行函数如下:
unsafe {
__restore(KERNEL_STACK.push_context(TrapContext::app_init_context(
APP_BASE_ADDRESS,
USER_STACK.get_sp(),
)) as *const _ as usize);
}
首先这里需要创建一个内核栈的上下文。
pub fn push_context(&self, cx: TrapContext) -> &'static mut TrapContext {
let cx_ptr: *mut TrapContext = (self.get_sp() - core::mem::size_of::<TrapContext>()) as *mut TrapContext;
unsafe {
*cx_ptr = cx;
}
unsafe { cx_ptr.as_mut().unwrap() }
}
这里在内核栈的空间上申请了一个上下文大小的空间,将这个空间赋值为cx
即我们传入的结构体,之后将这个结构体返回。
而这个结构体根据上方调用处的代码,是直接初始化的一个上下文结构。根据trap
模块中的代码可以直接还原其结构,其中x2
寄存器中存储内核栈的地址。
于是__restore
作为入口,通过a0
传入内核栈中的上下文地址,加载上下文中的内容到寄存器,最后交换内核栈的地址到sscratch
,并将用户栈指针写入sp
, 将执行流转移到用户程序。
系统调用
在我们的批处理操作系统中存在一个trap_handler
, 一旦用户程序进行ecall
,有的trap
会被委托到S
模式,于是根据scause
我们可以具体判断要进行哪些处理。这里在第二章中,只用考虑UserEnvCall
,首先sepc+4
因为,系统调用结束要执行下一条置零,之后分析具体是哪个系统调用。
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 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
);
}
}
}
具体是哪个系统系统调用的判断位于syscall
模块下,代码如下:
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),
}
}
根据系统调用号,可以具体判断执行哪个系统调用,write
是进行输出,并返回输出长度。而sys_exit
会进行run_next_app()
在运行的app
中,所有的app
都通过如下的模式进行,所以每个程序退出都会自动执行下一个程序。
pin extern "C" fn _start() -> !{
clear_bss();
exit(main());
panic!("unreachable after sys_exit!");
}
我们可以再次考虑在系统调用的情况下上下文是如何保存和恢复的。我们首先将内核栈的地址转移到sp
中,用户栈的地址转移到sscratch
中,接着在内核栈中存储app
的上下文。于是通过a0
做作为上下文指针就可以将整个结构体传递给trap_handler
。接着考虑如何进行恢复,返回时a0
寄存器中的值是上下文在核内栈中的地址,根据这个上下文进行恢复即可。最后一步再次交换内核栈和用户栈的指针,就可恢复到用户栈。
__alltraps:
csrrw sp, sscratch, sp
sd x1, 1*8(sp)
sd x3, 3*8(sp)
.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)
csrr t2, sscratch
sd t2, 2*8(sp)
mv a0, sp
call trap_handler
__restore:
mv sp, a0
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
addi sp, sp, 34*8
csrw sp, sscratch, sp
sret