之前在做rCore的时候发现里面的panic()
会直接调用sbi_call直接实现shutdown()
。由于这次我自己写的OS自己写的bootloader没有使用SBI,于是想去自己去实现一个shutdown
的功能。
由于我用的是QEMU
来做硬件模拟,于是我去看了一下RustSBI关于shutdown
的处理部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| pub fn handle_ecall(extension: usize, function: usize, param: [usize; 5]) -> SbiRet { match extension { EXTENSION_RFENCE => rfence::handle_ecall_rfence(function, param[0], param[1], param[2], param[3], param[4]), EXTENSION_TIMER => match () { #[cfg(target_pointer_width = "64")] () => timer::handle_ecall_timer_64(function, param[0]), #[cfg(target_pointer_width = "32")] () => timer::handle_ecall_timer_32(function, param[0], param[1]), }, EXTENSION_IPI => ipi::handle_ecall_ipi(function, param[0], param[1]), EXTENSION_BASE => base::handle_ecall_base(function, param[0]), EXTENSION_HSM => hsm::handle_ecall_hsm(function, param[0], param[1], param[2]), EXTENSION_SRST => srst::handle_ecall_srst(function, param[0], param[1]), LEGACY_SET_TIMER => match () { #[cfg(target_pointer_width = "64")] () => legacy::set_timer_64(param[0]), #[cfg(target_pointer_width = "32")] () => legacy::set_timer_32(param[0], param[1]), } .legacy_void(param[0], param[1]), LEGACY_CONSOLE_PUTCHAR => legacy::console_putchar(param[0]).legacy_void(param[0], param[1]), LEGACY_CONSOLE_GETCHAR => legacy::console_getchar().legacy_return(param[1]), LEGACY_SEND_IPI => legacy::send_ipi(param[0]).legacy_void(param[0], param[1]), LEGACY_SHUTDOWN => legacy::shutdown().legacy_void(param[0], param[1]), _ => SbiRet::not_supported(), }
|
1 2 3 4
| #[inline] pub fn shutdown() -> SbiRet { crate::reset::legacy_reset() }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| pub(crate) fn system_reset(reset_type: usize, reset_reason: usize) -> SbiRet { if let Some(obj) = &*RESET.lock() { return obj.system_reset(reset_type, reset_reason); } SbiRet::not_supported() }
pub(crate) fn legacy_reset() -> ! { if let Some(obj) = &*RESET.lock() { obj.legacy_reset() } unreachable!("no reset handler available; this is okay if your platform didn't declare a legacy reset handler") }
|
根据源码追溯我们可以看出来RustSBI
使用system_reset()
来处理关机,而不同平台对应不同的system_reset()
功能。
由于我使用的是QEMU
,所以我们可以从平台里面找到对应的函数接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| impl rustsbi::Reset for Reset { fn system_reset(&self, reset_type: usize, reset_reason: usize) -> rustsbi::SbiRet { const VIRT_TEST: *mut u32 = 0x10_0000 as *mut u32; let mut value = match reset_type { rustsbi::reset::RESET_TYPE_SHUTDOWN => TEST_PASS, rustsbi::reset::RESET_TYPE_COLD_REBOOT => TEST_RESET, rustsbi::reset::RESET_TYPE_WARM_REBOOT => TEST_RESET, _ => TEST_FAIL, }; if reset_reason == rustsbi::reset::RESET_REASON_SYSTEM_FAILURE { value = TEST_FAIL; }; unsafe { core::ptr::write_volatile(VIRT_TEST, value); } unreachable!() } }
|
可以看到,我们只需要向VIRT_TEST
这个物理地址里面写一些值即可实现关机。
明白了RustSBI实现关机的原理,然后回到我们OS中来。
由于实现shutdown()
这必须要求我们使用ecall
从S Mode切换到M Mode。但幸运的是我们可以通过设置mideleg
和medeleg
寄存器来将M态的中断代理到S态:
1 2 3 4
| medeleg::write(0xffff); mideleg::write(0xffff); sie::write(sie::read() | sie::SIE::SEIE as usize | sie::SIE::STIE as usize | sie::SIE::SSIE as usize);
|
然后我们就可以在S态的中断来处理Kernel syscall
了。
1
| Trap::Exception(Exception::KernelEnvCall) => handler_kernel_syscall(arg7),
|
在中断时我们可以匹配到KernelEnvCall
来对内核的调用进行处理,这里我们遵循SBI的标准,arg7即表示中断的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| pub fn handler_kernel_syscall(result: usize) { unsafe{ satp::write(0); } match result { SHUTDOWN => { println!("\x1b[1;31mshutdown! Bye~ \x1b[0m"); system_reset( RESET_TYPE_SHUTDOWN, RESET_REASON_NO_REASON ); }
_ => { println!("Unresolved Kernel Syscall"); } } }
|
首先我们必须把satp
寄存器置0,否则当我们写入物理地址的时候会直接发生Page Fault
。
以上就是内核的处理流程。
而在panic
的具体实现中,我们需要去ecall
来传入参数:
1 2 3 4 5 6
| #[panic_handler] fn panic(info: &PanicInfo<'_>) -> ! { println!("\x1b[1;31mpanic: '{}'\x1b[0m", info); kernel_syscall(SHUTDOWN, 0, 0, 0); loop {} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #[inline] pub fn kernel_syscall( which: usize, arg0: usize, arg1: usize, arg2: usize, ) -> usize { let mut ret; unsafe { llvm_asm!("ecall" : "={x10}" (ret) : "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which) : "memory" : "volatile" ); } ret }
|
这样我们即可在内核中实现关机。