Revisit rCore 第一章
入口
我们要在裸机上进行编程,所以在main.rs
中要加入#![no_std]
,表示我们不适用标准库,其中!
表示编译器内置相关的特性,需要加载文件的一开始。在编写main
之前,先介绍这里的链接文件,我们不适用默认的链接模式,因为这时我们编程是要考虑操作系统不存在的情况。
OUTPUT_ARCH(riscv)
ENTRY_ADDRESS(_start)
BASE_ADDRESS = 0x80200000;
这里设置一个变量BASE_ADDRESS
, 将我们的kernel
加载的位置设置为0x80200000。下面是我们空间分布:
这里每一个segment
都是4k对齐的,通过导出设置在linker
中的变量,我们可以知道各个segment
的大小和偏移。由于我们在裸机上启动程序,并没有运行时为我们设置堆栈,随意我们需要自己设置。
.section .text.entry
.global _start
_start:
la sp, boot_stack_top
call rust_main
.section .bss.stack
.global boot_stack_lower_bound
boot_stack_lower_bound:
.space 4096*16
.global boot_stack_top
boot_stack_top:
设置完sp
后,就可以跳转到main
中。在main.rs
中,我们引入这段代码:
#![no_std]
#![no_main]
//...
use core::arch::global_asm;
global_asm!(include_str!("entry.asm"));
#[no_mangle]
pub fn rust_main() -> !{
extern "C" {
fn stext();
fn etext();
fn srodata();
fn erodata();
fn sdata();
fn edata();
fn boot_stack_lower_bound();
fn boot_stack_top()
}
//...
}
通过core::arch::global_asm
引入这段汇编代码。在rust_main
之前有个#[no_mangle]
是为了防止编译器对函数名进行一个再命令,之后通过调用的形式,将我们需要的地址引入。
输出
这里我们通过opensbi
进行输出,sbi
是通过对ecall
进行封装,这样我们就可以通过函数请求opensbi
进行字符输出。
use core::arch::asm;
#[inline(always)]
fn sbi_call(which:usize, arg0:usize, arg1:usize, arg2:usize) -> usize {
let mut ret: usize;
unsafe {
asm!(
"ecall",
inlateout("x10") arg0 => ret,
in("x11") arg1,
in("x12") arg2,
in("x17") which,
);
}
ret
}
于是通过sbi_call
进一步我们封装具体的功能。
pub fn console_putchar(c: usize) {
sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0);
}
我们需要对print
宏进行编写,这样我们才能通过print
进行输出。
use crate::sbi::console_putchar;
use core::fmt::{self, Write};
struct Stdout;
impl Write for Stdout {
fn write_str(&mut self, s:&str) -> fmt::Result {
for c in s.chars() {
console_putchar(c as usize);
}
Ok(())
}
}
pub fn print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}
这里的Write
作为一个Trait
只有一个方法write_str
,编译器会帮我们解析格式化字符,我们只要进行输出就可以了。
在rust
中,我们调用的println
实际是一个宏,再去除了标准库后,我们需要自己编写println
。
#[macro_export]
macro_rules! print{
($fmt:literal $(, $($(arg:tt)+)?)) => {
$crate:console::print(format_args!($fmt $(, $($arg:tt)+)?));
}
}
这里我们匹配的意思是,前面一个字面量作为格式化字符,后面是一串的token
用,
进行隔开,之后将需要格式化输出的内容放入print
中。
panic
我们还需要编写panic
相关的信息。
use core::panic::PanicInfo;
use crate::sbi::shutdown;
#[panic_handler]
fn panic(info: &PanicInfo) -> !{
if let Some(location: &Location) = info.location(){
println!(
"Panicked at {}:{} {}",
location.file(),
location.line(),
info.message().unwrap()
)
} else {
println!("Panicked: {}", info.message().unwrap());
}
shutdown()
}
发生panic
时我们这里进行输出,通过#[panic_handler]
说明这时panic
发生时进行处理的办法,之后通过sbi
进行关机
revisit main
#![feature(panic_info_message)]
//...
#[macro_use]
mod console;
mod land_item;
mod sbi;
#[no_managle]
pub fn rust_main() -> !{
//...
clear_bss();
println!("Hello OS");
println!(".text [{:#x}, {:#x})", stext as usize, etext as usize);
//...
shutdown()
}
fn clear_bss(){
extern "C" {
fn sbss();
fn ebss();
}
(sbss as usize ..ebss as usize).foreach|x:size| {
unsafe{ (x as *mut u8).write_volatile(0)}
};
}
#![feature(panic_info_message)]
说明如果我们发生panic
可以输出信息。之后添加#[macro_use]
导入宏,之后清空bss
段,之后输出我们的段信息,最后关机。