这是2025秋季的开源操作系统训练营专业阶段的学习笔记。
精简版文档:learningos.cn/rCore-Tutor…
完整版文档:rcore-os.cn/rCore-Tutor…
Ch0 操作系统概述和环境配置
操作系统概念的介绍看文档。
环境配置上,我在Windows 10上安装了WSL-Ubuntu24.04,使用JetBrain的RustRover作为IDE进行开发(个人版本不收费)。
按文档要求安装软件:
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
在~/.cargo/config中配置cargo的源:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
安装rust的一些包:
rustup target add riscv64gc-unknown-none-elf
cargo install cargo-binutils
rustup component add llvm-tools-preview
rustup component add rust-src
安装qemu:
# 安装编译所需的依赖包
sudo apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \
gawk build-essential bison flex texinfo gperf libtool patchutils bc \
zlib1g-dev libexpat-dev pkg-config libglib2.0-dev libpixman-1-dev libsdl2-dev libslirp-dev \
git tmux python3 python3-pip ninja-build
# 下载源码包
# 如果下载速度过慢可以使用我们提供的百度网盘链接:https://pan.baidu.com/s/1dykndFzY73nqkPL2QXs32Q
# 提取码:jimc
wget https://download.qemu.org/qemu-7.0.0.tar.xz
# 解压
tar xvJf qemu-7.0.0.tar.xz
# 编译安装并配置 RISC-V 支持
cd qemu-7.0.0
./configure --target-list=riscv64-softmmu,riscv64-linux-user # 在第九章的实验中,可以有图形界面和网络。如果要支持图形界面,可添加 " --enable-sdl" 参数;如果要支持网络,可添加 " --enable-slirp" 参数
make -j$(nproc)
配置环境变量:
export PATH=$PATH:/path/to/qemu-7.0.0/build
注意检查版本:
qemu-system-riscv64 --version
qemu-riscv64 --version
Ch1 应用程序与基本执行环境
本章目标:基于RustSBI让应用程序输出Hello, world!字符串。
关于RustSBI,我的理解是:RustSBI = BootLoader + 一组的子程序,它完成了从磁盘加载内核到内存和CPU模式切换的任务,并为后续的开发共提供了一组直接操作硬件的子程序,提供输出字、关机、panic处理等功能,操作系统内核可以利用这些子程序构建自己的服务。
1. 创建项目并移除标准库依赖
首先创建一个Rust项目:
$ cargo new os
执行一下:
$ cd os
$ cargo run
warning: both `/home/minghan/.cargo/config` and `/home/minghan/.cargo/config.toml` exist. Using `/home/minghan/.cargo/config`
Compiling os v0.1.0 (/home/minghan/projs/rust-code/os)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.46s
Running `target/debug/os`
Hello, world!
Rust编译器将一个rust源程序转换为一个可执行文件时需要考虑标准库、操作系统和CPU架构的三元组组合,标准库指定了应用程序可以调用的函数接口,操作系统指定了系统调用的接口,CPU架构规定了生成二进制代码所需的指令集。
minghan@Minghan:~/projs/rust-code/os$ rustc --version --verbose
rustc 1.90.0 (1159e78c4 2025-09-14)
binary: rustc
commit-hash: 1159e78c4747b02ef996e55082b704c09b970588
commit-date: 2025-09-14
host: x86_64-unknown-linux-gnu
release: 1.90.0
LLVM version: 20.1.8
可以看到我的Rust编译器生成的目标CPU平台是x86,操作系统是Linux,标准库使用的是gnu。
现在尝试让Rust编译器生成一个可以在Risc-V CPU上运行的程序,首先我们在os目录下创建目录.cargo,然后在其中创建config文件,并写入一点内容,表示按照:
~/projs/rust-code/os$ mkdir .cargo
~/projs/rust-code/os$ vim .cargo/config
[build]
target = "riscv64gc-unknown-none-elf"
重新构建该项目:
minghan@Minghan:~/projs/rust-code/os$ cargo build
warning: `/home/minghan/projs/rust-code/os/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: both `/home/minghan/.cargo/config` and `/home/minghan/.cargo/config.toml` exist. Using `/home/minghan/.cargo/config`
Compiling os v0.1.0 (/home/minghan/projs/rust-code/os)
error[E0463]: can't find crate for `std`
|
= note: the `riscv64gc-unknown-none-elf` target may not support the standard library
= note: `std` is required by `os` because it does not declare `#![no_std]`
error: cannot find macro `println` in this scope
--> src/main.rs:2:5
|
2 | println!("Hello, world!");
| ^^^^^^^
error: `#[panic_handler]` function required, but not found
For more information about this error, try `rustc --explain E0463`.
error: could not compile `os` (bin "os") due to 3 previous errors
报错找不到标准库,为什么会这样?因为安装Rust时我们所在的平台是x86,标准库也是按x86来准备的,因此我们并没有支持Risc-V的rust标准库,而我们在程序中又使用了标准库提供的宏println!。
解决方法是注释掉println!,并在main.rs中添加\#![no_std],告知编译器不要链接标准库:
#![no_std]
fn main() {
// println!("Hello, world!");
}
重新构建:
minghan@Minghan:~/projs/rust-code/os$ cargo build
Compiling os v0.1.0 (/home/minghan/projs/rust-code/os)
error: `#[panic_handler]` function required, but not found
error: could not compile `os` (bin "os") due to 1 previous error
报错 #[panic_handler] function required,这是因为Rust编译器认为在编译程序时,从安全性考虑,需要有 panic! 宏的具体实现。标准库中提供了panic!的实现,但是现在我们不能使用标准库了,因此需要自己提供一个panic!宏。
创建一个文件lang_items.rs,在其中实现我们自己的panic!宏:
use crate::sbi::shutdown;
use core::panic::PanicInfo;
// #[panic_handler]用于告知编译器,采用我们的panic实现
#[panic_handler]
/// panic handler
fn panic(info: &PanicInfo) -> ! {
if let Some(location) = info.location() {
println!(
"[kernel] Panicked at {}:{} {}",
location.file(),
location.line(),
info.message().unwrap()
);
} else {
println!("[kernel] Panicked: {}", info.message().unwrap());
}
shutdown()
}
在main.rs中加入下面这两句:
#[macro_use]
mod lang_items;
重新构建:
minghan@Minghan:~/projs/rust-code/os$ cargo build
Compiling os v0.1.0 (/home/minghan/projs/rust-code/os)
error: using `fn main` requires the standard library
|
= help: use `#![no_main]` to bypass the Rust generated entrypoint and declare a platform specific entrypoint yourself, usually with `#[no_mangle]`
error: could not compile `os` (bin "os") due to 1 previous error
报错告诉我们使用main函数就需要标准库,并提示了我们使用#![no_main]来略过该错误。
#![no_std]
#![no_main]
mod lang_items;
// fn main() {
// println!("Hello, world!");
// }
再次构建,终于通过了编译:
minghan@Minghan:~/projs/rust-code/os$ cargo build
Compiling os v0.1.0 (/home/minghan/projs/rust-code/os)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
可以使用cargo-binutils工具集来分析以下刚刚编译生成的二进制文件:
minghan@Minghan:~/projs/rust-code/os$ 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
minghan@Minghan:~/projs/rust-code/os$ rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os
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: 0x1018
Flags [ (0x5)
EF_RISCV_FLOAT_ABI_DOUBLE (0x4)
EF_RISCV_RVC (0x1)
]
HeaderSize: 64
ProgramHeaderEntrySize: 56
ProgramHeaderCount: 4
SectionHeaderEntrySize: 64
SectionHeaderCount: 12
StringTableSectionIndex: 10
}
对于os文件,其文件格式为ELF、64位架构、LSB表示小端字节序,是一个可执行文件。目标架构位UCB Risc-V,使用了Risc-V压缩指令集(RVC),采用的是静态链接。
2. 使用Qemu模拟RISC-V CPU
现在已经可以在不使用标准库的情况下开发了针对RISC-V的程序了,但我们手上没有RISC-V CPU,必须通过qemu来模拟。在rCore的Makefile中可以看到:
run-inner: build
@qemu-system-riscv64 \
-machine virt \
-nographic \
-bios $(BOOTLOADER) \
-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)
我们使用qemu版本为qemu-system-riscv64,使用的bootloader为rustsbi-qemu.bin,内核会被加载到内存0x80200000处。启动的过程如下:
- 启动qemu后,将
rustsbi-qemu.bin(boot loader)和os.bin(内核)分别加载到内存0x80000000和0x80200000处,然后qemu的PC会被初始化为0x1000,即第一条指令的地址。在执行几条指令(qemu固件)后会跳转到0x80000000处,执行bootloader - bootloader在进行一些初始化工作后跳转到0x80200000,执行内核的第一条指令(这是我们使用的
rustsbi-qemu.bin规定的) - 执行内核程序!!!
OK,现在我们已经知道使用qemu启动程序后会经过qemu的固件、bootloader,最终执行我们编写的代码。在开始编写代码之前还要介绍一下链接器的作用:
- 将来自不同目标文件的段在目标内存布局中重新排布,合并为一个可执行文件
- 将各目标文件中使用的符号,替换为具体的地址,这个任务只能在链接时做,因为在连接之前符号的地址是没有确定的
现在我们来编写一个最简单的内核entry.asm`:
.section .text.entry
.globl _start
_start:
li x1, 100
这段汇编代码中,通过.section指明了该段的名称.text.entry,创建了一个全局符号_start来表示第一条指令li x1, 100的地址。
#![no_std]
#![no_main]
mod lang_items;
use core::arch::global_asm;
global_asm!(include_str!("entry.asm"));
将entry.asm嵌入到main.rs中,include_str!用于将文件展开为字符串,global_asm!用于将该字符串嵌入到当前文件中
由于链接器默认的内存布局不能满足qemu的要求,即内核的第一条指令必须在0x80200000处。我们自己来编写链接脚本,让链接器按照我们的要求来生成内核的内存布局。
往.cargo/config中添加配置,使用我们自己的链接脚本linker.ld:
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
]
OUTPUT_ARCH(riscv) # 指明目标平台
ENTRY(_start) # 入口地址的符号
BASE_ADDRESS = 0x80200000; # 设置入口地址为0x80200000
SECTIONS
{
. = BASE_ADDRESS; # 设置当前位置为0x80200000
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)
}
}
-
OUTPUT_ARCH(riscv):指定了链接器生成的目标文件是针对RISC-V平台的
-
ENTRY(_start):指定了程序的入口(第一条指令)为
_start所在的位置 -
BASE_ADDRESS = 0x80200000:定义了内核应该被加载到0x80200000处
-
SECTIONS中定义了多个目标文件(.o文件)中的段(.text, .data)是如何被组合的:
- .text:代码段
- .rodata:只读数据段
- .data:已初始化的数据段
- .bss:未初始化的数据段
总结一下,这个链接器脚本定义了一个RISC-V的裸机的内存布局:
- 内核从
0x80200000开始加载 _start代码始终位于内核代码的最前面- 所有主要的内存段(代码、只读数据、初始化数据、未初始化数据)都按照操作系统开发的要求,以 4KB 页面边界对齐
- 脚本定义了许多符号(如
skernel、etext、ebss),在之后的 Rust 代码(通常在启动代码中)中可以获取这些段的边界地址,以便进行内存初始化(例如,将.bss段清零)
在启动qemu执行我们的内核之前,还需要对内核的可执行文件进行一点处理。因为在编译链接生成可执行文件时,还会在该文件的头尾添加一些元数据,这些元数据无法被 Qemu 在加载文件时利用,且会使代码和数据段被加载到错误的位置,因此需要将这些元数据从内核文件中剔除,得到一个干净的内核文件。
rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/os -O binary target/riscv64gc-unknown-none-elf/release/os.bin
注意这里用到的是release版本的os文件,需要使用cargo build --release来得到,而不是cargo build,来看看剔除元数据前后的内核文件:
minghan@Minghan:~/projs/rust-code/os$ stat target/riscv64gc-unknown-none-elf/release/os
File: target/riscv64gc-unknown-none-elf/release/os
Size: 5456 Blocks: 16 IO Block: 4096 regular file
Device: 8,32 Inode: 67217 Links: 2
Access: (0755/-rwxr-xr-x) Uid: ( 1000/ minghan) Gid: ( 1000/ minghan)
Access: 2025-09-26 15:41:53.167816830 +0800
Modify: 2025-09-26 15:41:50.977802191 +0800
Change: 2025-09-26 15:41:50.987802261 +0800
Birth: 2025-09-26 15:41:50.977802191 +0800
minghan@Minghan:~/projs/rust-code/os$ stat target/riscv64gc-unknown-none-elf/release/os.bin
File: target/riscv64gc-unknown-none-elf/release/os.bin
Size: 4 Blocks: 8 IO Block: 4096 regular file
Device: 8,32 Inode: 67219 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 1000/ minghan) Gid: ( 1000/ minghan)
Access: 2025-09-26 15:41:53.167816830 +0800
Modify: 2025-09-26 15:41:53.187816964 +0800
Change: 2025-09-26 15:41:53.187816964 +0800
Birth: 2025-09-26 15:41:53.167816830 +0800
可以看到,携带元数据的内核文件有5456字节,而剔除掉元数据后仅剩下4字节(一条RISC-V指令的大小)。
是时候启动qemu来看看效果了,我们先来准备一个简单的Makefile:
.PHONY:
run:
@qemu-system-riscv64 \
-machine virt \
-nographic \
-bios bootloader/rustsbi-qemu.bin \
-device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000 \
-s -S
debug:
@gdb-multiarch \
-ex 'file target/riscv64gc-unknown-none-elf/release/os' \
-ex 'set arch riscv:rv64' \
-ex 'target remote localhost:1234'
- run用于启动硬件
- debug用于在另一个端口启动gdb调试
注意,在教程中使用riscv64-unknown-elf-gdb来调试,但是我自己执行老是会报错,将其换位gdb-multiarch就ok了。
The target architecture is set to "riscv:rv64".
Remote debugging using localhost:1234
0x0000000000001000 in ?? ()
(gdb) x/10i $pc
=> 0x1000: auipc t0,0x0
0x1004: addi a2,t0,40
0x1008: csrr a0,mhartid
0x100c: ld a1,32(t0)
0x1010: ld t0,24(t0)
0x1014: jr t0
0x1018: unimp
0x101a: .insn 2, 0x8000
0x101c: unimp
0x101e: unimp
可以看到,在启动后PC被初始化为0x1000,在执行若干条指令后会跳转到t0指向的地址:
(gdb) si
0x0000000000001004 in ?? ()
(gdb) si
0x0000000000001008 in ?? ()
(gdb) si
0x000000000000100c in ?? ()
(gdb) si
0x0000000000001010 in ?? ()
(gdb) si
0x0000000000001014 in ?? ()
(gdb) p/x $t0
$3 = 0x80000000
在ld t0,24(t0)执行完毕后t0的值为0x80000000,正是bootloader被加载到内存中的位置。
(gdb) si
0x0000000080000000 in ?? ()
(gdb) x/10i $pc
=> 0x80000000: csrw mie,zero
0x80000004: auipc sp,0xa
0x80000008: addi sp,sp,-2044
0x8000000c: lui t0,0x4
0x8000000e: csrr t1,mhartid
0x80000012: addi t1,t1,1
0x80000014: add sp,sp,t0
0x80000016: addi t1,t1,-1
0x80000018: bnez t1,0x80000014
0x8000001c: auipc ra,0x2
(gdb) b *0x80200000
Breakpoint 1 at 0x80200000
(gdb) c
Continuing.
Breakpoint 1, 0x0000000080200000 in stext ()
(gdb) x/5i $pc
=> 0x80200000 <stext>: li ra,100
0x80200004: unimp
0x80200006: unimp
0x80200008: unimp
0x8020000a: unimp
我们直接在0x80200000处设置断点,然后用continue命令,直接快进到内核,可以看到第一条指令正是我们在entry.asm中编写的li指令
3. 添加函数调用支持
在Computer System课程(UCB CS 61C或CMU15-213)我们已经学习过,函数调用是需要 栈(保存上下文) + 调用规范(调用者和被调用者) 来共同实现。在RISC-V中,函数的参数、返回值和返回地址均使用寄存器来保存(参数过多会放入栈),需要保护的上下文信息会存入栈中,用于后续上下文的恢复。在开始完成内核的功能之前,我们需要为其分配一块内存空间作为栈,使其具有函数调用的能力。修改entry.asm,分配一块64K大小的内存作为栈空间,初始化sp寄存器,然后调用函数rust_main:
.section .text.entry
.globl _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:
修改main.rs,添加两个函数,rust_main和clear_bss:
#![no_std]
#![no_main]
mod lang_items;
// fn main() {
// println!("Hello, world!");
// }
// 使用global_asm宏将entry.asm嵌入到代码中
use core::arch::global_asm;
global_asm!(include_str!("entry.asm"));
#[no_mangle]
pub fn rust_main() -> ! {
loop {}
}
fn clear_bss() {
extern "C" {
fn sbss();
fn ebss();
}
// 将从sbss到ebss这段内存清零
(sbss as usize..ebss as usize).for_each(|a| {
unsafe { (a as *mut u8).write_volatile(0)}
})
}
rust_main前面加上了#[no_mangle],用于告知编译器不要修改该名字,避免找不到该符号。clear_bss用于将bss(未初始化数据段)清零,
其中使用了在entry.asm中定义的符号sbss和ebss来获取bss段在内存中的范围。
现在我们已经将系统的控制权从汇编代码交到rust程序了!
4. 使用RustSBI输出字符串并关机
直接访问硬件打印字符串非常麻烦,需要了解硬件细节。RustSBI中封装了一些硬件的功能,为内核程序提供服务。内核运行在S态,而RustSBI运行在M态,Supervisor Binary Interface(SBI)定义了二者之间的接口,该接口的标准由 RISC-V 开源社区维护,RustSBI是SBI的实现之一, 按照 SBI spec 标准实现了需要支持的大多数功能。
由于RustSBI并没有和内核链接在一起,因此无法直接通过函数调用的方式来使用RustSBI提供的服务,我们需要先添加一个依赖sbi-rt,其封装了SBI服务的接口:
[package]
name = "os"
version = "0.1.0"
edition = "2024"
[dependencies]
sbi-rt = { version = "0.0.2", features = ["legacy"] }
创建文件sbi.rs,对SBI功能进行封装:
// 输出字符
pub fn console_putchar(c: usize) {
#[allow(deprecated)]
sbi_rt::legacy::console_putchar(c);
}
// 关机
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每次可以向终端输出一个字符,在此基础上创建console.rs文件,进一步实现字符串的输出:
use crate::sbi::console_putchar;
use core::fmt::{self, Write};
// 定义一个Stdout结构体
struct Stdout;
// 实现fmt中的Write接口
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)+)?));
}
}
这里定义了一个空结构体Stdout,实现了fmt包中的Write接口,详情见文档doc.rust-lang.org/stable/std/…
实现了print函数,调用Stdout.write_fmt打印字符串。实现了两个宏print!和println!,调用print进行格式化输出。
修改panic的实现,让其能够在系统遇到错误时打印错误消息并关机:
use crate::sbi::shutdown;
use core::panic::PanicInfo;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
// 从错误信息中获取location
if let Some(location) = info.location() {
// 打印位置信息和错误消息
println!("Panicked at {}:{} {}", location.file(), location.line(), info.message());
} else {
// 如果无法获取位置消息就直接打印错误信息
println!("Panicked: {}", info.message());
}
// 关机
shutdown(true)
}
最后更新rust_main:
#![no_std]
#![no_main]
use core::arch::global_asm;
#[macro_use]
mod console;
mod lang_items;
mod sbi;
// 使用global_asm宏将entry.asm嵌入到代码中
global_asm!(include_str!("entry.asm"));
#[unsafe(no_mangle)]
pub extern "C" fn rust_main() -> ! {
clear_bss();
println!("Hello, world {}!", "rCore");
panic!("Shutdown machine!");
}
fn clear_bss() {
unsafe extern "C" {
fn sbss();
fn ebss();
}
// 将从sbss到ebss这段内存清零
(sbss as usize..ebss as usize).for_each(|a| {
unsafe { (a as *mut u8).write_volatile(0)}
})
}
运行结果如下:
minghan@Minghan:~/projs/rust-code/os$ make run
[rustsbi] RustSBI version 0.3.0-alpha.4, 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 rCore!
Panicked at src/main.rs:30 Shutdown machine!
make: *** [Makefile:8: run] Error 1
总结
在Ch0中,介绍了操作系统的基本概念和历史发展,完成了rCore实验环境的配置。
在Ch1中,介绍了Qemu启动内核的过程;学习了程序的内存布局和编写链接脚本来控制链接器的行为,使生成的程序内存布局符合预期;学习了如何让rust程序不再依赖标准库,实现了自己的panic!宏;学会了利用RustSBI提供的服务来封装自己的格式化输出和关机函数。 最终实现了从裸机上启动内核并打印出"Hello, world rCore"字符串。