Articles

第四章:地址空间 · rCore 2024S 随记

唉。他妈的排课,都没什么时间做 rCore 了。我现在学校的课程从早 8 排到晚 22。简直是高中。 前言 实际实践参考的文档:rCore Tutorial Guide 2024S。 拥有更为详细细节介绍的 rCore OS 官方文档:rCore-Tutorial-Book-v3。 第四章:地址空间 引言 试着运行本章代码。 $ git checkout ch4 Already on 'ch4' Your branch is up to date with 'origin/ch4'. ~/2024s-rcore-255doesnotexist$ cd os ~/2024s-rcore-255doesnotexist/os$ make run (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add riscv64gc-unknown-none-elf riscv64gc-unknown-none-elf (installed) cargo install cargo-binutils Updating crates.io index Ignored package `cargo-binutils v0.3.6` is already installed, use --force to override rustup component add rust-src info: component 'rust-src' is up to date rustup component add llvm-tools-preview info: component 'llvm-tools' for target 'x86_64-unknown-linux-gnu' is up to date make[1]: Entering directory '/home/ezra/2024s-rcore-255doesnotexist/user' Removed 1342 files, 15.

5月7日随记:天杀的哪个b排的课早上8点上到晚上22点

如题。今天他娘的一点都没有空闲干别的。 ADD 05/09/2024: 还是一样。rCore Lab 都没空做了。

第三章:多道程序与分时多任务 · rCore 2024S 随记

本部分将试着毛估估的完成 ch3。由于本人同时在学习 RISC-V 汇编,涉及的内容可能解释的不那么详细。 前言 实际实践参考的文档:rCore Tutorial Guide 2024S。 拥有更为详细细节介绍的 rCore OS 官方文档:rCore-Tutorial-Book-v3。 第三章:多道程序与分时多任务 引言 概述 本章的目标是实现分时多任务系统,它能并发地执行多个用户程序,并调度这些程序。为此需要实现: 一次性加载所有用户程序,减少任务切换开销; 支持任务切换机制,保存切换前后程序上下文; 支持程序主动放弃处理器,实现 yield 系统调用; 以时间片轮转算法调度用户程序,实现资源的时分复用。 在 qemu 模拟器上运行本章代码: $ cd os $ make run 运行代码,看到用户程序交替输出信息: $ make run (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add riscv64gc-unknown-none-elf riscv64gc-unknown-none-elf (installed) cargo install cargo-binutils Updating crates.io index Ignored package `cargo-binutils v0.3.6` is already installed, use --force to override rustup component add rust-src info: component 'rust-src' is up to date rustup component add llvm-tools-preview info: component 'llvm-tools' for target 'x86_64-unknown-linux-gnu' is up to date make[1]: Entering directory '/home/ezra/2024s-rcore-255doesnotexist/user' Removed 138 files, 3.

第二章:批处理系统 · rCore 2024S 随记

由于 ch2 已经被实现好了,本部分基本为摘抄。 前言 实际实践参考的文档:rCore Tutorial Guide 2024S。 拥有更为详细细节介绍的 rCore OS 官方文档:rCore-Tutorial-Book-v3。 第二章:应用程序与基本执行环境 引言 批处理系统 (Batch System) 出现于计算资源匮乏的年代,其核心思想是: 将多个程序打包到一起输入计算机;当一个程序运行结束后,计算机会 自动 执行下一个程序。 实践体验 本章我们引入了用户程序。为了将内核与应用解耦,我们将二者分成了两个仓库,分别是存放内核程序的 rCore-Tutorial-Code-20xxx (下称代码仓库,最后几位 x 表示学期)与存放用户程序的 rCore-Tutorial-Test-20xxx (下称测例仓库)。 你首先需要进入代码仓库文件夹并 clone 用户程序仓库(如果已经执行过该步骤则不需要再重复执行): $ git clone https://github.com/LearningOS/rCore-Tutorial-Code-2024S.git $ cd rCore-Tutorial-Code-2024S $ git checkout ch2 $ git clone https://github.com/LearningOS/rCore-Tutorial-Test-2024S.git user 上面的指令会将测例仓库克隆到代码仓库下并命名为 user ,注意 /user 在代码仓库的 .gitignore 文件中,因此不会出现 .git 文件夹嵌套的问题,并且你在代码仓库进行 checkout 操作时也不会影响测例仓库的内容。 在 qemu 模拟器上运行本章代码: $ cd os $ make run LOG=INFO (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add riscv64gc-unknown-none-elf riscv64gc-unknown-none-elf (installed) cargo install cargo-binutils Updating crates.

第一章:应用程序与基本执行环境 · rCore 2024S 随记

前言 实际实践参考的文档:rCore Tutorial Guide 2024S。 拥有更为详细细节介绍的 rCore OS 官方文档:rCore-Tutorial-Book-v3。 第一章:应用程序与基本执行环境 引言 本章预期代码树: ├── bootloader (内核依赖的运行在 M 特权级的 SBI 实现,本项目中我们使用 RustSBI) │ └── rustsbi-qemu.bin ├── os │ ├── Cargo.toml (cargo 项目配置文件) │ ├── Makefile │ └── src │ ├── console.rs (将打印字符的 SBI 接口进一步封装实现更加强大的格式化输出) │ ├── entry.asm (设置内核执行环境的的一段汇编代码) │ ├── lang_items.rs (需要我们提供给 Rust 编译器的一些语义项,目前包含内核 panic 时的处理逻辑) │ ├── linker.ld (控制内核内存布局的链接脚本以使内核运行在 qemu 虚拟机上) │ ├── logging.rs (为本项目实现了日志功能) │ ├── main.rs (内核主函数) │ └── sbi.

第零章:实验环境配置 · rCore 2024S 随记

前言 实际实践参考的文档:rCore Tutorial Guide 2024S。 拥有更为详细细节介绍的 rCore OS 官方文档:rCore-Tutorial-Book-v3。 第零章:实验环境配置 OS 环境配置 OS环境 Ubuntu Server 22.04 amd64。 Rust 开发环境配置 通过 rustup 安装的当前最新版工具链。 curl https://sh.rustup.rs -sSf | sh Qemu 模拟器安装 参考文档,从源码安装 Qemu 7.0.0。 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 git tmux python3 wget https://download.qemu.org/qemu-7.0.0.tar.xz # 解压 tar xvJf qemu-7.0.0.tar.xz # 编译安装并配置 RISC-V 支持 cd qemu-7.

Hello

Hello.

第一章:应用程序与基本执行环境 · rCore 2024S 随记

Planted April 19, 2024

前言

实际实践参考的文档:rCore Tutorial Guide 2024S

拥有更为详细细节介绍的 rCore OS 官方文档:rCore-Tutorial-Book-v3

第一章:应用程序与基本执行环境

引言

本章预期代码树:

├── bootloader (内核依赖的运行在 M 特权级的 SBI 实现,本项目中我们使用 RustSBI)
│   └── rustsbi-qemu.bin
├── os
│   ├── Cargo.toml (cargo 项目配置文件)
│   ├── Makefile
│   └── src
│       ├── console.rs (将打印字符的 SBI 接口进一步封装实现更加强大的格式化输出)
│       ├── entry.asm (设置内核执行环境的的一段汇编代码)
│       ├── lang_items.rs (需要我们提供给 Rust 编译器的一些语义项,目前包含内核 panic 时的处理逻辑)
│       ├── linker.ld (控制内核内存布局的链接脚本以使内核运行在 qemu 虚拟机上)
│       ├── logging.rs (为本项目实现了日志功能)
│       ├── main.rs (内核主函数)
│       └── sbi.rs (封装底层 SBI 实现提供的 SBI 接口)
└── rust-toolchain (整个项目的工具链版本)

cloc os
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Rust                             5             25              6            155
make                             1             11              4             34
Assembly                         1              1              0             11
TOML                             1              2              1              7
-------------------------------------------------------------------------------
SUM:                             8             39             11            207
-------------------------------------------------------------------------------

应用程序执行环境与平台支持

执行应用程序

按文档要求,我们切回主分支 main。执行命令。

cargo new os
$ tree
.
├── os
│   ├── Cargo.lock
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── README.md

3 directories, 4 files
$ cd os/
$ cargo run
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/os`
Hello, world!

我们在屏幕上看到了一行 Hello, world! ,但为了打印出 Hello, world!,我们需要的不止几行源代码。

理解应用程序执行环境

这有一张图

我们的应用程序通过调用标准库或第三方库提供的接口,仅需少量源代码就能完成复杂的功能; Hello, world! 程序调用的 println! 宏就是由 Rust 标准库 std 和 GNU Libc 等提供的。 这些库属于应用程序的 执行环境 (Execution Environment),而它们的实现又依赖于操作系统提供的系统调用。

  • 所以我们需要剥离标准库,才能在裸机上执行。操作系统则提供关于标准库的系统调用。

平台与目标三元组

编译器在编译、链接得到可执行文件时需要知道,程序要在哪个 平台 (Platform) 上运行, 目标三元组 (Target Triplet) 描述了目标平台的 CPU 指令集、操作系统类型和标准运行时库。

我们研究一下现在 Hello, world! 程序的目标三元组是什么:

  • 在我的环境下:
$ rustc --verbose --version
rustc 1.77.2 (25ef9e3d8 2024-04-09)
binary: rustc
commit-hash: 25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04
commit-date: 2024-04-09
host: x86_64-unknown-linux-gnu
release: 1.77.2
LLVM version: 17.0.6
  • 也就是 x86_64 (amd64)、Linux、GNU glibc。

接下来,我们希望把 Hello, world! 移植到 RICV 目标平台 riscv64gc-unknown-none-elf 上运行。

  • 注意,接下来没有标准库可供调用。

修改目标平台

将程序的目标平台换成 riscv64gc-unknown-none-elf,试试看会发生什么:

$ cargo run --target riscv64gc-unknown-none-elf
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
error[E0463]: can't find crate for `std`
  |
  = note: the `riscv64gc-unknown-none-elf` target may not be installed
  = help: consider downloading the target with `rustup target add riscv64gc-unknown-none-elf`

error: cannot find macro `println` in this scope
 --> src/main.rs:2:5
  |
2 |     println!("Hello, world!");
  |     ^^^^^^^

error: requires `sized` lang_item

For more information about this error, try `rustc --explain E0463`.
error: could not compile `os` (bin "os") due to 3 previous errors

报错的原因是目标平台上确实没有 Rust 标准库 std,也不存在任何受 OS 支持的系统调用。 这样的平台被我们称为 裸机平台 (bare-metal)。

幸运的是,除了 std 之外,Rust 还有一个不需要任何操作系统支持的核心库 core, 它包含了 Rust 语言相当一部分核心机制,可以满足本门课程的需求。 有很多第三方库也不依赖标准库 std,而仅仅依赖核心库 core。

为了以裸机平台为目标编译程序,我们要将对标准库 std 的引用换成核心库 core。

移除标准库依赖

  • 编辑 os/.cargo/config 文件,加入以下内容:
[build]
target = "riscv64gc-unknown-none-elf"
  • 这将会持久化设定目标平台的设定为 riscv64gc。

这将使 cargo 工具在 os 目录下默认会使用 riscv64gc-unknown-none-elf 作为目标平台。 这种编译器运行的平台(x86_64)与可执行文件运行的目标平台不同的情况,称为 交叉编译 (Cross Compile)。

移除 println! 宏

我们在 main.rs 的开头加上一行 #![no_std], 告诉 Rust 编译器不使用 Rust 标准库 std 转而使用核心库 core。重新编译,报错如下:

$ cargo run
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
error[E0463]: can't find crate for `core`
  |
  = note: the `riscv64gc-unknown-none-elf` target may not be installed
  = help: consider downloading the target with `rustup target add riscv64gc-unknown-none-elf`

error[E0463]: can't find crate for `compiler_builtins`

error: cannot find macro `println` in this scope
 --> src/main.rs:3:5
  |
3 |     println!("Hello, world!");
  |     ^^^^^^^

error: requires `sized` lang_item

For more information about this error, try `rustc --explain E0463`.
error: could not compile `os` (bin "os") due to 4 previous errors
  • 注意到环境配置似乎有些问题,没有安装 riscv64gc-unknown-none-elf 的 target。解决方案如下:
$ rustup target add riscv64gc-unknown-none-elf

println! 宏是由标准库 std 提供的,且会使用到一个名为 write 的系统调用。 无论如何,我们先将这行代码注释掉。

提供语义项 panic_handler

标准库 std 提供了 Rust 错误处理函数 #[panic_handler],其大致功能是打印出错位置和原因并杀死当前应用。 但核心库 core 并没有提供这项功能,得靠我们自己实现。

  • 例子:移除了 println! 后,依然存在以下错误。
$ cargo run
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
error: `#[panic_handler]` function required, but not found

error: could not compile `os` (bin "os") due to 1 previous error

新建一个子模块 os/src/lang_items.rs,在里面编写 panic 处理函数,通过标记 #[panic_handler] 告知编译器采用我们的实现:

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
  • 不要忘了在 main.rs 里引用你写的 panic。 mod lang_items; 即可。

移除 main 函数

  • 接下来重新编译,提示在入口点又出现了问题。
$ cargo run
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/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.rs 中加入 #![no_main] 即可。

至此,我们终于移除了所有标准库依赖。

  • 不过直接执行它是行不通的。不要忘记你使用的是 x86_64 的机器。
  • 错误信息如下。
$ cargo run
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
warning: function `main` is never used
 --> src/main.rs:4:4
  |
4 | fn main() {
  |    ^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: `os` (bin "os") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/riscv64gc-unknown-none-elf/debug/os`
target/riscv64gc-unknown-none-elf/debug/os: 1: ELF�@�@8@
                                                        : not found
target/riscv64gc-unknown-none-elf/debug/os: 2: Syntax error: "(" unexpected

分析被移除标准库的程序

我们可以通过一些工具来分析目前的程序:

  • file 获取简略的文件类型信息。
$ 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
  • rust-readbj 读取详细符号信息。
$ rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os
Could not find tool: readobj
at: /home/ezra/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-readobj
Consider `rustup component add llvm-tools-preview
  • 哎哟忘记装了,按提示装一下吧。
$ rustup component add llvm-tools-preview
info: downloading component 'llvm-tools'
info: installing component 'llvm-tools'
 29.6 MiB /  29.6 MiB (100 %)  11.3 MiB/s in  2s ETA:  0s
  • 装好以后可以读取出以下符号信息。
$ 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: 0x1788
  Flags [ (0x5)
    EF_RISCV_FLOAT_ABI_DOUBLE (0x4)
    EF_RISCV_RVC (0x1)
  ]
  HeaderSize: 64
  ProgramHeaderEntrySize: 56
  ProgramHeaderCount: 4
  SectionHeaderEntrySize: 64
  SectionHeaderCount: 12
  StringTableSectionIndex: 10
}
  • 反汇编导出汇编程序信息。
$ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os

target/riscv64gc-unknown-none-elf/debug/os:     file format elf64-littleriscv

通过 file 工具对二进制程序 os 的分析可以看到,它好像是一个合法的 RV64 执行程序, 但 rust-readobj 工具告诉我们它的入口地址 Entry 是 0。 再通过 rust-objdump 工具把它反汇编,没有生成任何汇编代码。 可见,这个二进制程序虽然合法,但它是一个空程序,原因是缺少了编译器规定的入口函数 _start 。

从下一节开始,我们将着手实现本节移除的、由用户态执行环境提供的功能。

构建用户态执行环境

用户态最小化执行环境

执行环境初始化

首先我们要给 Rust 编译器编译器提供入口函数 _start() , 在 main.rs 中添加如下内容:

#[no_mangle]
extern "C" fn _start() {
    loop{};
}
  • 和 C、C++ 很像,在程序启动前都存在一个最初的入口点。
  • 通过入口点,我们可以为用户态初始化一些必要的资源。

~~ 先写到这,形势与政策课 iYanDa 校园网不好,连不上实验实例了。下节课再写。 ~~

  • 重新执行 cargo build 构建 os 程序,程序正常编译,反编译后可以导出两行死循环汇编。正是我们需要的效果 。
$ cargo build
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
warning: function `main` is never used
  --> src/main.rs:10:4
   |
10 | fn main() {
   |    ^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `os` (bin "os") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
$ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os

target/riscv64gc-unknown-none-elf/debug/os:     file format elf64-littleriscv

Disassembly of section .text:

0000000000011158 <_start>:
;     loop{};
   11158: 09 a0         j       0x1115a <_start+0x2>
   1115a: 01 a0         j       0x1115a <_start+0x2>

反汇编出的两条指令就是一个死循环, 这说明编译器生成的已经是一个合理的程序了。 用 qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os 命令可以执行这个程序。

$ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os
^C (that loop stuck here so send SIGINT to exist)
$ 
  • 如果注释 _start 中的死循环,再次构建运行。将会触发段错误。这是因为我们还没有一个健全的返回机制。
$ cargo build
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
warning: function `main` is never used
  --> src/main.rs:10:4
   |
10 | fn main() {
   |    ^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `os` (bin "os") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.14s
$ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os
Segmentation fault

目前的执行环境还缺了一个退出机制,我们需要操作系统提供的 exit 系统调用来退出程序。

  • 用了很多内联汇编,看文字的意思大概是暂时不需要理解。
// os/src/main.rs

const SYSCALL_EXIT: usize = 93;

fn syscall(id: usize, args: [usize; 3]) -> isize {
    let mut ret;
    unsafe {
        core::arch::asm!(
            "ecall",
            inlateout("x10") args[0] => ret,
            in("x11") args[1],
            in("x12") args[2],
            in("x17") id,
        );
    }
    ret
}

pub fn sys_exit(xstate: i32) -> isize {
    syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}

#[no_mangle]
extern "C" fn _start() {
    sys_exit(9);
}
  • 再次构建 os 并执行。这次执行时就没有出现段错误了。
$ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os
$ 

有显示支持的用户态执行环境

没有 println 输出信息,终究觉得缺了点啥。

Rust 的 core 库内建了以一系列帮助实现显示字符的基本 Trait 和数据结构,函数等,我们可以对其中的关键部分进行扩展,就可以实现定制的 println! 功能。 实现输出字符串的相关函数

首先封装一下对 SYSCALL_WRITE 系统调用。

const SYSCALL_WRITE: usize = 64;

pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
  syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}

然后实现基于 Write Trait 的数据结构,并完成 Write Trait 所需要的 write_str 函数,并用 print 函数进行包装。

struct Stdout;

impl Write for Stdout {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        sys_write(1, s.as_bytes());
        Ok(())
    }
}

pub fn print(args: fmt::Arguments) {
    Stdout.write_fmt(args).unwrap();
}

最后,基于 print 函数,实现 Rust 语言格式化宏 ( formatting macros )。

#[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)+)?) => {
        print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
    }
}

接下来,我们调整一下应用程序,让它发出显示字符串和退出的请求:

#[no_mangle]
extern "C" fn _start() {
    println!("Hello, world!");
    sys_exit(9);
}

现在,我们编译并执行一下,可以看到正确的字符串输出,且程序也能正确退出!

$ cargo build --target riscv64gc-unknown-none-elf
   Compiling os v0.1.0 (/home/ezra/rCore-Tutorial-Code-2024S/os)
warning: function `main` is never used
  --> src/main.rs:65:4
   |
65 | fn main() {
   |    ^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `os` (bin "os") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s
$ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os; echo $?
Hello, world!
9

构建裸机执行环境

裸机启动过程

用 QEMU 软件 qemu-system-riscv64 来模拟 RISC-V 64 计算机。加载内核程序的命令如下:

qemu-system-riscv64 \
            -machine virt \
            -nographic \
            -bios $(BOOTLOADER) \
            -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)

-bios $(BOOTLOADER) 意味着硬件加载了一个 BootLoader 程序,即 RustSBI

-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) 表示硬件内存中的特定位置 $(KERNEL_ENTRY_PA) 放置了操作系统的二进制代码 $(KERNEL_BIN) 。 $(KERNEL_ENTRY_PA) 的值是 0x80200000 。

当我们执行包含上述启动参数的 qemu-system-riscv64 软件,就意味给这台虚拟的 RISC-V64 计算机加电了。 此时,CPU 的其它通用寄存器清零,而 PC 会指向 0x1000 的位置,这里有固化在硬件中的一小段引导代码, 它会很快跳转到 0x80000000 的 RustSBI 处。 RustSBI完成硬件初始化后,会跳转到 $(KERNEL_BIN) 所在内存位置 0x80200000 处, 执行操作系统的第一条指令。

  • 接下来用内置汇编实现 sbi_call,类似 syscall。

应用程序访问操作系统提供的系统调用的指令是 ecall ,操作系统访问 RustSBI提供的SBI调用的指令也是 ecall , 虽然指令一样,但它们所在的特权级是不一样的。 简单地说,应用程序位于最弱的用户特权级(User Mode), 操作系统位于内核特权级(Supervisor Mode), RustSBI位于机器特权级(Machine Mode)。 下一章会进一步阐释具体细节。

  • 然后设置链接脚本 os/src/linker.ld,标记入口点为 _start、基地址为 0x80200000。需要可以直接看 ch1 的相关文件。

  • 然后写 entry.asm 用于声明 64KiB 的 boot 栈空间。栈顶地址标记为 boot_stack_top,栈底则是 boot_stack。

  • la sp, boot_stack_top 把栈顶地址赋给经典的 sp 寄存器。

  • 在 main.rs 中用全局内联汇编嵌入 entry.asm。

  • 利用 #[no_mangle] 声明不被混淆的 rust_main 导出函数。

  • 在 rust_main 中调用 sbi_call 中的 SHUTDOWN。

  • 可见 qemu 被合理关闭了。

  • 然而并没有清空内存 .bss 段。

fn clear_bss() {
    extern "C" {
        fn sbss();
        fn ebss();
    }
    (sbss as usize..ebss as usize).for_each(|a| {
        unsafe { (a as *mut u8).write_volatile(0) }
    });
}

pub fn rust_main() -> ! {
    clear_bss();
    shutdown();
}
  • 利用链接脚本中 sbss 和 ebss 给出的位置即可完成清空工作。

  • 略作修改用户态程序,执行 make run LOG=TRACE

$ make run LOG=TRACE
(rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add riscv64gc-unknown-none-elf
riscv64gc-unknown-none-elf (installed)
cargo install cargo-binutils
    Updating crates.io index
     Ignored package `cargo-binutils v0.3.6` is already installed, use --force to override
rustup component add rust-src
info: component 'rust-src' is up to date
rustup component add llvm-tools-preview
info: component 'llvm-tools' for target 'x86_64-unknown-linux-gnu' is up to date
Platform: qemu
    Finished `release` profile [optimized + debuginfo] target(s) in 0.01s
[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)
[kernel] Hello, world!
[TRACE] [kernel] .text [0x80200000, 0x80202000)
[DEBUG] [kernel] .rodata [0x80202000, 0x80203000)
[ INFO] [kernel] .data [0x80203000, 0x80204000)
[ WARN] [kernel] boot_stack top=bottom=0x80214000, lower_bound=0x80204000
[ERROR] [kernel] .bss [0x80214000, 0x80215000)