rCore系列文章学习笔记
老忘记学到什么地方了, 之前看过一点BlogOS的文章,但是没有看下去.
这次在阅读rCore时候,借助此笔记记录学习过程.
项目创建
默认创建的项目运行在Rust 标准库std环境下.
设置编译目标到”riscv64gc-unknown-none-elf”切换到no_std环境.
由于println!宏依赖标准库, 所以当切换到no_std环境下println!不可用.
在程序发生错误的时候,std下的panic!宏会打印出错的位置,而no_std下,panic!并没有具体实现.
要想让程序执行,需要手动实现panic!宏,
1 2 3 4 5 6
| use core::panic::PaincInfo;
#[panic_handler] fn panic(_info: &PanicInfo)->!{ loop{} }
|
此时cargo build –target riscv64gc-unknown-none-elf便可以正常编译.
使用file命令查看编译产物输出平台架构信息
1 2 3 4
| ➜ os git:(master) ✗ file target/riscv64gc-unknown-none-elf/debug/os target/riscv64gc-unknown-none-elf/debug/os: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped ➜ os git:(master) ✗ file target/riscv64gc-unknown-none-elf/release/os target/riscv64gc-unknown-none-elf/release/os: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, not stripped
|
使用rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os
查看更详细的信息
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 27 28 29 30 31 32
| File: target/riscv64gc-unknown-none-elf/debug/os Format: elf64-littleriscv Arch: riscv64 AddressSize: 64bit LoadName: <Not found> ElfHeader { Ident { Magic: (7F 45 4C 46) Class: 64-bit (0x2) DataEncoding: LittleEndian (0x1) FileVersion: 1 OS/ABI: SystemV (0x0) ABIVersion: 0 Unused: (00 00 00 00 00 00 00) } Type: Executable (0x2) Machine: EM_RISCV (0xF3) Version: 1 Entry: 0x0 ProgramHeaderOffset: 0x40 SectionHeaderOffset: 0x1760 Flags [ (0x5) EF_RISCV_FLOAT_ABI_DOUBLE (0x4) EF_RISCV_RVC (0x1) ] HeaderSize: 64 ProgramHeaderEntrySize: 56 ProgramHeaderCount: 4 SectionHeaderEntrySize: 64 SectionHeaderCount: 12 StringTableSectionIndex: 10 }
|
此时Entry: 0x0
入口地址是0, 导致无法被执行
回顾序章
序章中使用qemu运行了os, 当时执行的命令是
1 2 3 4 5
| qemu-system-riscv64 \ -machine virt \ -nographic \ -bios ../bootloader/rustsbi-qemu.bin \ -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000
|
其中
- -riscv64
- -machine
- -nographic
- -bios
- -device
- loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000
- loader=开机前载入文件
- file=设置载入文件路径, addr=载入到QEMU物理内存地址
程序内存布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| + + + + +--------------+---------+ High Address | | | stack | | | | ↓ | | | | | | | | ↑ | | | Data Meomory | heap | | | | .bss | | | | .data | | | | .rodata | | |--------------|---------| | | Code Meomory | .text | | |--------------|---------| Low Address | + + + +
|
.rodata和.data分别储存只读的全局数据(常数, 常量字符串)和可修改的全局数据
.bss储存未初始化的全局变量
heap储存运行时动态分配的数据, malloc/new分配的数据会放在堆, 向高地址增长
stack, 函数内的局部变量会放在此处, 向低地址增长.
编译流程
- 编译器(Compiler)
- 高级语言转化为汇编语言, 此时生成的文件仍然是文本文件
- 汇编器(Assembler)
- 上一步生成的汇编文本文件进一步转化为二进制目标文件
- 链接器(Linker)
- 将上一步得到的二进制目标文件,以及其他外部文件链接在一起形成完整的可执行文件
编写内核第一条指令
1 2 3 4 5
| # os/src/entry.asm .section .text.entry .globl _start _start: li x1, 100
|
li x1, 100
将一个立即数(100)加载到x1寄存器
1 2 3 4 5 6 7 8
| #![no_std] #![no_main]
mod lang_items;
use core::arch::global_asm; global_asm!(include_str!("entry.asm"));
|
在rust中嵌入上面的汇编代码
调整内核内存布局
1 2 3 4 5 6 7 8
| // os/.cargo/config [build] target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf] rustflags = [ "-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes" ]
|
手动制定了链接脚本为linker.ld, “-Cforce-frame-pointers=yes”强制打开 fp 选项,这样才会避免 fp 相关指令被编译器优化掉
linker.ld内容
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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.*) *(.srodata .srodata.*) }
. = ALIGN(4K); erodata = .; sdata = .; .data : { *(.data .data.*) *(.sdata .sdata.*) }
. = ALIGN(4K); edata = .; .bss : { *(.bss.stack) sbss = .; *(.bss .bss.*) *(.sbss .sbss.*) }
. = ALIGN(4K); ebss = .; ekernel = .;
/DISCARD/ : { *(.eh_frame) } }
|
运行
1 2 3 4 5 6 7 8 9 10
| cargo build --release
rust-objcopy --strip-all ./target/riscv64gc-unknown-none-elf/release/os -O binary ./target/riscv64gc-unknown-none-elf/release/os.bin
qemu-system-riscv64 \ -machine virt \ -nographic \ -bios ./bootloader/rustsbi-qemu.bin \ -device loader,file=./target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000
|
基于SBI服务完成输出和关机
可以注意到,在使用qemu运行os.bin时候, 指定了bios为rustsbi-qemu.bin,
rustsbi不仅在启动时候进行环境初始化工作,还会为内核提供服务.
为了使用rustsbi中的服务,需要引入sbi_rt依赖
1 2
| [dependencies] sbi-rt = { version = "0.0.2", features = ["legacy"] }
|
sbi_rt中的console_putchar可以输出字符
1 2 3 4 5
| pub fn console_putchar(c: usize) { #[allow(deprecated)] sbi_rt::legacy::console_putchar(c); }
|
而system_reset可以实现关机
1 2 3 4 5 6 7 8 9 10
| pub fn shutdown(failure: bool) -> ! { use sbi_rt::{system_reset, NoReason, Shutdown, SystemFailure}; if !failure { system_reset(Shutdown, NoReason); } else { system_reset(Shutdown, SystemFailure); } unreachable!() }
|
包装一下console_putchar就可以输出字符串了.
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 27 28 29 30 31 32
| 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(); }
#[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)+)?) => { $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)); } }
|
现在有了println!宏,可以愉快输出了.
回到之前的空panic, 在里面输出错误信息.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| use crate::sbi::shutdown; use core::panic::PanicInfo;
#[panic_handler] fn panic(info: &PanicInfo) -> ! { if let Some(location) = info.location() { println!( "Panicked at {}:{} {}", location.file(), location.line(), info.message().unwrap() ); } else { println!("Panicked: {}", info.message().unwrap()); } shutdown(true) }
|
人为的创建一个panic, 然后观察打印是否正常.
1 2 3 4 5 6 7
| #[no_mangle] pub fn rust_main() -> ! { clear_bss(); println!("Hello, world!"); panic!("Shutdown machine!"); }
|
每次运行都得build, strip, qemu-run, 过于麻烦,所以编写一个bash脚本方便运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #!/bin/bash cd "$(dirname "$0")"
cargo build --release if [ $? -ne 0 ]; then echo "cargo build 失败" exit 1 fi
rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/os -O binary target/riscv64gc-unknown-none-elf/release/os.bin if [ $? -ne 0 ]; then echo "rust-objcopy 失败" exit 1 fi
qemu-system-riscv64 \ -machine virt \ -nographic \ -bios ./bootloader/rustsbi-qemu.bin \ -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000
|
输出结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| [rustsbi] RustSBI version 0.3.1, 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) Hello, world! [ERROR] Paincked at src/main.rs:17 Shutdown machine!
|
添加彩色打印
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| 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(crate) fn print(args: fmt::Arguments) { Stdout.write_fmt(args).unwrap(); }
pub(crate) fn println(args: fmt::Arguments) { Stdout.write_fmt(args).unwrap(); Stdout.write_char('\n').unwrap(); }
#[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)+)?) => { $crate::console::println(format_args!($fmt $(, $($arg)+)?)); }; }
#[macro_export] macro_rules! error { ($fmt: literal $(, $($arg: tt)+)?) => { $crate::console::println(format_args!(concat!("\x1b[31m[ERROR]\t",concat!($fmt, "\x1b[0m")) $(, $($arg)+)?)); }; }
#[macro_export] macro_rules! warn { ($fmt: literal $(, $($arg: tt)+)?) => { $crate::console::println(format_args!(concat!("\x1b[93m[WARN]\t",concat!($fmt, "\x1b[0m")) $(, $($arg)+)?)); }; }
#[macro_export] macro_rules! info { ($fmt: literal $(, $($arg: tt)+)?) => { $crate::console::println(format_args!(concat!("\x1b[34m[INFO]\t",concat!($fmt, "\x1b[0m")) $(, $($arg)+)?)); }; }
#[macro_export] macro_rules! debug { ($fmt: literal $(, $($arg: tt)+)?) => { $crate::console::println(format_args!(concat!("\x1b[32m[DEBUG]\t",concat!($fmt, "\x1b[0m")) $(, $($arg)+)?)); }; }
#[macro_export] macro_rules! trace { ($fmt: literal $(, $($arg: tt)+)?) => { $crate::console::println(format_args!(concat!("\x1b[90m[TRACE]\t",concat!($fmt, "\x1b[0m")) $(, $($arg)+)?)); }; }
|