rCore系列文章学习笔记0x01

rCore系列文章学习笔记

老忘记学到什么地方了, 之前看过一点BlogOS的文章,但是没有看下去.
这次在阅读rCore时候,借助此笔记记录学习过程.

项目创建

1
cargo new os --bin

默认创建的项目运行在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
    • 代表64位RISC-V
  • -machine
    • 名称virt
  • -nographic
    • 无需图形界面
  • -bios
    • bios使用的rustsbi-qemu.bin
  • -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
// os/src/main.rs
#![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
// os/src/sbi.rs
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
// os/src/sbi.rs
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
// os/src/console.rs
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
// os/src/lang_item.rs
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
// os/src/main.rs
#[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")"
# 编译 Release 版本
cargo build --release
if [ $? -ne 0 ]; then
echo "cargo build 失败"
exit 1 # 退出脚本并返回错误码 1
fi

# 使用 rust-objcopy 将目标文件转换为二进制
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 运行
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)+)?));
};
}