开源操作系统训练营-rCore-Ch0&Ch1

230 阅读10分钟

这是2025秋季的开源操作系统训练营专业阶段的学习笔记。

网址:opencamp.cn/os2edu/camp…

精简版文档: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处。启动的过程如下:

  1. 启动qemu后,将rustsbi-qemu.bin(boot loader)和os.bin(内核)分别加载到内存0x80000000和0x80200000处,然后qemu的PC会被初始化为0x1000,即第一条指令的地址。在执行几条指令(qemu固件)后会跳转到0x80000000处,执行bootloader
  2. bootloader在进行一些初始化工作后跳转到0x80200000,执行内核的第一条指令(这是我们使用的rustsbi-qemu.bin规定的)
  3. 执行内核程序!!!

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 页面边界对齐
  • 脚本定义了许多符号(如 skerneletextebss),在之后的 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"字符串。