本章实现了一个最简单的 RISC-V S 态裸机程序(tg-ch1),展示操作系统的最小执行环境。程序在 QEMU 模拟的 RISC-V 64 硬件上运行,不依赖 OpenSBI 或 RustSBI,通过 -bios none 模式直接启动,打印 Hello, world! 后关机。
通过本章的学习和实践,你将理解:
Hello, world! 并不简单ch1/ ├── .cargo/ │ └── config.toml # Cargo 配置:指定交叉编译目标和 QEMU runner ├── build.rs # 构建脚本:自动生成链接脚本 ├── Cargo.toml # 项目配置与依赖 ├── README.md # 本文档 └── src/ └── main.rs # 程序源码:入口、主函数、panic 处理
本项目使用 Rust 语言编写,需要通过 rustup 安装 Rust 工具链。
Linux / macOS / WSL:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
Windows:
从 https://rustup.rs 下载并运行 rustup-init.exe。
验证安装:
rustc --version # 应显示 rustc 1.xx.x
cargo --version # 应显示 cargo 1.xx.x
由于 tg-ch1 是面向 RISC-V 64 裸机平台的程序,需要添加对应的编译目标:
rustup target add riscv64gc-unknown-none-elf
这个目标三元组的含义是:
tg-ch1 在 QEMU 模拟的 RISC-V 64 虚拟机上运行,需要安装 qemu-system-riscv64(建议版本 >= 7.0)。
Ubuntu / Debian:
sudo apt update sudo apt install qemu-system-misc
macOS(Homebrew):
brew install qemu
验证安装:
qemu-system-riscv64 --version
方式一 只获取本实验
cargo clone tg-ch1
cd tg-ch1
获取所有8个实验和所依赖的tg-* crates. 方式二
git clone https://github.com/rcore-os/rCore-Tutorial-in-single-workspace.git
cd rCore-Tutorial-in-single-workspace/ch1
在 tg-ch1 或 ch1 目录下执行:
cargo build
这条命令实际上执行的是交叉编译——编译器在你的主机(如 x86_64)上运行,但生成的可执行文件是针对 riscv64gc-unknown-none-elf 平台的。这个目标平台由 .cargo/config.toml 中的配置自动指定:
[build]
target = "riscv64gc-unknown-none-elf"
编译过程中,build.rs 构建脚本会自动检测目标架构,为 RISC-V 64 生成链接脚本(linker.ld),控制程序的内存布局。
编译成功后,可执行文件位于 target/riscv64gc-unknown-none-elf/debug/tg-ch1。
cargo run
cargo run 在编译成功后会自动调用 .cargo/config.toml 中配置的 runner 来执行程序。实际执行的命令等价于:
qemu-system-riscv64 \ -machine virt \ -nographic \ -bios none \ -kernel target/riscv64gc-unknown-none-elf/debug/tg-ch1
QEMU 参数说明:
| 参数 | 说明 |
|---|---|
-machine virt | 使用 QEMU 的 virt 虚拟平台,这是一个通用的 RISC-V 虚拟机 |
-nographic | 无图形界面,所有输出通过串口重定向到终端 |
-bios none | 不加载任何 BIOS/SBI 固件,tg-ch1 自带 M-mode 启动代码 |
-kernel <文件> | 将 ELF 可执行文件加载到内存中作为内核启动 |
Hello, world!
输出一行 Hello, world! 后,QEMU 自动退出。这是因为程序通过 SBI 调用执行了关机操作。
以下内容帮助你理解 tg-ch1 代码背后的操作系统原理。
大多数程序员的职业生涯都从 Hello, world! 开始。然而,要在屏幕上打印一行字,并不像表面上那么简单。
在日常开发中,我们编写的应用程序运行在一个多层次的执行环境栈之上:
┌─────────────────────────┐ │ 应用程序 │ ← 你写的代码 ├─────────────────────────┤ │ 标准库 (std / libc) │ ← println! 等函数的实现 ├─────────────────────────┤ │ 操作系统内核 │ ← 系统调用:write, exit 等 ├─────────────────────────┤ │ 硬件抽象层 (SBI/BIOS) │ ← 固件,为内核提供基础服务 ├─────────────────────────┤ │ 硬件 (CPU/内存) │ ← 物理硬件 └─────────────────────────┘
每一层为上一层提供服务,层与层之间通过明确定义的接口交互:
ecall)请求操作系统服务当我们在 Linux 上执行 println!("Hello, world!") 时,实际经历了:println! → Rust 标准库 → libc 的 write() → Linux 内核 sys_write 系统调用 → 串口/终端驱动 → 硬件显示。
tg-ch1 做了什么? 它跳过了标准库和操作系统内核,直接在裸机上通过 SBI 接口输出字符。这就是"最小执行环境"的含义。
要让程序在裸机上运行,首先需要摆脱对操作系统的依赖。Rust 标准库 std 依赖操作系统提供的系统调用(如文件 I/O、内存分配、线程等),在没有操作系统的裸机上无法使用。
tg-ch1 在 src/main.rs 的开头使用了两个关键的属性标记:
#![no_std] —— 不使用标准库
告诉 Rust 编译器不链接标准库 std,改用核心库 core。核心库 core 是 Rust 语言的子集实现,不依赖任何操作系统功能,包含了基本类型、迭代器、Option/Result 等核心机制。
#![no_main] —— 不使用标准入口
标准的 main() 函数入口需要运行时环境(如 C runtime)进行初始化。在裸机环境中没有这些支持,所以我们告诉编译器不使用标准入口,自己定义程序的入口点 _start。
#[panic_handler] —— 自定义 panic 处理
标准库提供了 panic 时打印错误信息并终止程序的功能。使用 #![no_std] 后,需要自己实现 panic 处理函数。tg-ch1 中的实现是直接调用 SBI 关机:
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
shutdown(true) // 以异常状态关机
}
什么是交叉编译?
编译器运行在主机平台(如 x86_64-unknown-linux-gnu)上,但生成的可执行文件需要在目标平台(riscv64gc-unknown-none-elf)上运行,这种情况称为交叉编译(Cross Compile)。.cargo/config.toml 中的 target = "riscv64gc-unknown-none-elf" 配置使 cargo 自动进行交叉编译。
理解程序如何在裸机上启动,是操作系统学习的重要一步。
tg-ch1 采用 nobios 模式(-bios none),不依赖外部 SBI 固件,而是在 tg-sbi 库中自带了一个最小的 M-mode 启动代码。启动流程如下:
QEMU 加电 │ ▼ PC = 0x1000(QEMU 内置引导代码) │ ▼ 跳转到 0x80000000(M-mode 入口,tg-sbi 的 _m_start) │ ── 在 M-mode 下初始化硬件环境 │ ── 设置中断委托、PMP 等 ▼ 跳转到 0x80200000(S-mode 入口,tg-ch1 的 _start) │ ── 设置栈指针 sp ▼ 跳转到 rust_main() │ ── 打印 "Hello, world!" │ ── 调用 SBI shutdown 关机 ▼ QEMU 退出
关键地址:
0x80000000:M-mode 代码的起始地址,由链接脚本中的 M_BASE_ADDRESS 指定0x80200000:S-mode 代码的起始地址,由链接脚本中的 S_BASE_ADDRESS 指定,这是 _start 函数所在的位置链接脚本的作用
链接脚本控制程序各段在内存中的布局。build.rs 在编译时自动生成链接脚本,将程序组织为:
地址空间布局: 0x80000000 ┌────────────────────┐ │ .text.m_entry │ M-mode 入口代码(tg-sbi) │ .text.m_trap │ M-mode 中断处理 │ .bss.m_stack │ M-mode 栈空间 │ .bss.m_data │ M-mode 数据 │ ... │ 0x80200000 ├────────────────────┤ │ .text │ S-mode 代码段(含 .text.entry) │ .rodata │ 只读数据段 │ .data │ 可读写数据段 │ .bss │ 未初始化数据段(含栈) └────────────────────┘
栈空间初始化
在裸机环境中,没有操作系统帮我们设置栈。_start 是一个裸函数(#[unsafe(naked)]),它不会生成函数序言(prologue)和尾声(epilogue),可以在没有栈的情况下执行。它做的第一件事就是设置栈指针 sp,然后跳转到 Rust 函数 rust_main:
#[unsafe(naked)]
#[unsafe(no_mangle)]
#[unsafe(link_section = ".text.entry")]
unsafe extern "C" fn _start() -> ! {
const STACK_SIZE: usize = 4096;
#[unsafe(link_section = ".bss.uninit")]
static mut STACK: [u8; STACK_SIZE] = [0u8; STACK_SIZE];
core::arch::naked_asm!(
"la sp, {stack} + {stack_size}", // 将 sp 设置为栈顶地址
"j {main}", // 跳转到 rust_main
stack_size = const STACK_SIZE,
stack = sym STACK,
main = sym rust_main,
)
}
注意:Rust edition 2024 要求
no_mangle、link_section等 unsafe 属性必须用unsafe(...)包装,这与 edition 2021 的写法不同。
栈大小为 4096 字节(4 KiB),放置在 .bss.uninit 段中。la sp, STACK + 4096 将 sp 设置为栈顶地址(栈从高地址向低地址增长)。
RISC-V 特权级
RISC-V 定义了三个特权级(Privilege Level),从高到低:
| 特权级 | 缩写 | 说明 |
|---|---|---|
| Machine Mode | M-mode | 最高特权级,直接访问所有硬件资源 |
| Supervisor Mode | S-mode | 操作系统内核运行的特权级 |
| User Mode | U-mode | 应用程序运行的特权级 |
不同特权级之间通过 ecall(Environment Call)指令切换:
ecall → 陷入操作系统(S-mode):这是系统调用ecall → 陷入固件(M-mode):这是 SBI 调用虽然都是 ecall 指令,但因为所在特权级不同,产生的效果也不同。
SBI(Supervisor Binary Interface)
SBI 是 RISC-V 的标准规范,定义了 S-mode 软件(操作系统)向 M-mode 固件请求服务的接口。可以把 SBI 理解为"操作系统的操作系统"——它为操作系统提供最基本的硬件抽象服务。
tg-ch1 通过 use tg_sbi::{console_putchar, shutdown} 引入了两个 SBI 服务:
| 函数 | 说明 |
|---|---|
console_putchar(c) | 向控制台输出一个字符(通过串口) |
shutdown(fail) | 关闭虚拟机(fail=false 正常关机,fail=true 异常关机) |
rust_main 的实现非常简洁——逐字符输出 "Hello, world!\n",然后关机:
extern "C" fn rust_main() -> ! {
for c in b"Hello, world!\n" {
console_putchar(*c);
}
shutdown(false) // false 表示正常关机
}
nobios 模式的特殊之处
传统方案(如 rCore-Tutorial 旧版)使用外部 SBI 固件(如 RustSBI),需要将 SBI 固件和内核分别加载。tg-ch1 采用 tg-sbi 的 nobios 特性,将 M-mode 启动代码直接编译进同一个 ELF 文件中,因此可以用 -bios none -kernel 的方式一步加载,简化了启动流程。
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
runner = [
"qemu-system-riscv64",
"-machine", "virt",
"-nographic",
"-bios", "none",
"-kernel",
]
[build] target:设置默认编译目标为 RISC-V 64 裸机平台,每次 cargo build 自动交叉编译[target...] runner:设置运行器为 QEMU,cargo run 时自动在 QEMU 中执行编译产物[package]
name = "tg-ch1"
edition = "2024"
# ...
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
[dependencies]
tg-sbi = { version = "0.1.0-preview.1", features = ["nobios"] }
关键配置:
edition = "2024":使用 Rust 2024 edition,要求 unsafe 属性使用 unsafe(...) 包装panic = "abort":panic 时直接终止,不进行栈展开(unwinding),减少裸机程序的复杂度tg-sbi 依赖启用了 nobios 特性,使其内建 M-mode 启动代码fn main() {
use std::{env, fs, path::PathBuf};
if env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default() == "riscv64" {
let ld = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("linker.ld");
fs::write(&ld, LINKER_SCRIPT).unwrap();
println!("cargo:rustc-link-arg=-T{}", ld.display());
}
}
构建脚本在编译之前自动执行:
riscv64OUT_DIR/linker.ldcargo:rustc-link-arg 指示链接器使用该脚本链接脚本定义了两个关键地址:
M_BASE_ADDRESS = 0x80000000:M-mode 代码起始地址S_BASE_ADDRESS = 0x80200000:S-mode 代码起始地址(_start 所在位置)整个程序由五部分组成:
模块文档与属性标记(第 1-19 行):
//!)概述本章关键概念#![no_std]:不使用标准库#![no_main]:不使用标准入口cfg_attr:在 RISC-V 64 上启用严格警告,其他架构允许死代码(用于 cargo publish --dry-run 在主机上通过编译)SBI 引入(第 21-23 行):
use tg_sbi::{console_putchar, shutdown} 明确引入所需的两个 SBI 函数入口函数 _start(第 34-53 行):
riscv64 架构下编译(#[cfg(target_arch = "riscv64")]).text.entry 段,链接脚本将其安排在 0x80200000#[unsafe(no_mangle)]、#[unsafe(link_section = "...")] 语法sp 后跳转到 rust_main主函数 rust_main(第 59-64 行):
console_putchar 输出 "Hello, world!\n"shutdown(false) 正常关机panic 处理(第 69-72 行):
shutdown(true) 以异常方式关机非 RISC-V 占位模块 stub(第 78-95 行):
main、__libc_start_main 等符号,使得在非 RISC-V 平台上也能通过编译(用于 cargo publish --dry-run 验证)通过本章的学习和实践,你完成了从普通应用程序到裸机程序的蜕变过程:
Hello, world! 的背后并不简单#![no_std] 和 #![no_main],让 Rust 程序不再依赖操作系统_start 入口ecall 指令如何跨越特权级这是操作系统内核开发的第一步——在后续章节中,我们将在这个最小执行环境的基础上,逐步添加批处理、多道程序、内存管理、进程调度等操作系统核心功能。
为什么 _start 函数必须是裸函数(#[naked])? 如果不是裸函数会发生什么问题?提示:思考函数序言(prologue)需要什么前提条件。
ecall 指令在不同特权级中的效果有何不同? 为什么应用程序和操作系统都使用 ecall,却能产生不同的行为?
如果把链接脚本中的 S_BASE_ADDRESS 从 0x80200000 改为其他值(如 0x80100000),程序还能正常运行吗? 需要做哪些相应的修改?
| 依赖 | 说明 |
|---|---|
tg-sbi | SBI 调用封装库,支持 nobios 模式,内建 M-mode 启动代码 |
| 操作系统内核 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|---|---|---|---|
| tg-ch1 | 应用程序执行环境 裸机编程(Bare-metal) SBI(Supervisor Binary Interface) RISC-V 特权级(M/S-mode) 链接脚本(Linker Script) 内存布局(Memory Layout) Panic 处理 | 最小 S-mode 裸机程序 QEMU 直接启动(无 OpenSBI) 打印 "Hello, world!" 并关机 演示最基本的 OS 执行环境 | tg-sbi |
| tg-ch2 | 批处理系统(Batch Processing) 特权级切换(U-mode ↔ S-mode) Trap 处理(ecall / 异常) 上下文保存与恢复 系统调用(write / exit) 用户态 / 内核态 sret 返回指令 | 批处理操作系统 顺序加载运行多个用户程序 特权级切换和 Trap 处理框架 实现 write / exit 系统调用 | tg-sbi tg-linker tg-console tg-kernel-context tg-syscall |
| tg-ch3 | 多道程序(Multiprogramming) 任务控制块(TCB) 协作式调度(yield) 抢占式调度(Preemptive) 时钟中断(Clock Interrupt) 时间片轮转(Time Slice) 任务切换(Task Switch) 任务状态(Ready/Running/Finished) clock_gettime 系统调用 | 多道程序与分时多任务 多程序同时驻留内存 协作式 + 抢占式调度 时钟中断与时间管理 | tg-sbi tg-linker tg-console tg-kernel-context tg-syscall |
| tg-ch4 | 虚拟内存(Virtual Memory) Sv39 三级页表(Page Table) 地址空间隔离(Address Space) 页表项(PTE)与标志位 地址转换(VA → PA) 异界传送门(MultislotPortal) ELF 加载与解析 堆管理(sbrk) 恒等映射(Identity Mapping) 内存保护(Memory Protection) satp CSR | 引入 Sv39 虚拟内存 每个用户进程独立地址空间 跨地址空间上下文切换 进程隔离和内存保护 | tg-sbi tg-linker tg-console tg-kernel-context tg-kernel-alloc tg-kernel-vm tg-syscall |
| tg-ch5 | 进程(Process) 进程控制块(PCB) 进程标识符(PID) fork(地址空间深拷贝) exec(程序替换) waitpid(等待子进程) 进程树 / 父子关系 初始进程(initproc) Shell 交互式命令行 进程生命周期(Ready/Running/Zombie) 步幅调度(Stride Scheduling) | 引入进程管理 fork / exec / waitpid 系统调用 动态创建、替换、等待进程 Shell 交互式命令行 | tg-sbi tg-linker tg-console tg-kernel-context tg-kernel-alloc tg-kernel-vm tg-syscall tg-task-manage |
| tg-ch6 | 文件系统(File System) easy-fs 五层架构 SuperBlock / Inode / 位图 DiskInode(直接+间接索引) 目录项(DirEntry) 文件描述符表(fd_table) 文件句柄(FileHandle) VirtIO 块设备驱动 MMIO(Memory-Mapped I/O) 块缓存(Block Cache) 硬链接(Hard Link) open / close / read / write 系统调用 | 引入文件系统与 I/O 用户程序存储在磁盘镜像(fs.img) VirtIO 块设备驱动 easy-fs 文件系统实现 文件打开 / 关闭 / 读写 | tg-sbi tg-linker tg-console tg-kernel-context tg-kernel-alloc tg-kernel-vm tg-syscall tg-task-manage tg-easy-fs |
| tg-ch7 | 进程间通信(IPC) 管道(Pipe) 环形缓冲区(Ring Buffer) 统一文件描述符(Fd 枚举) 信号(Signal) 信号集(SignalSet) 信号屏蔽字(Signal Mask) 信号处理函数(Signal Handler) kill / sigaction / sigprocmask / sigreturn 命令行参数(argc / argv) I/O 重定向(dup) | 进程间通信-管道 异步事件通知(信号) 统一文件描述符抽象 信号发送 / 注册 / 屏蔽 / 返回 | tg-sbi tg-linker tg-console tg-kernel-context tg-kernel-alloc tg-kernel-vm tg-syscall tg-task-manage tg-easy-fs tg-signal tg-signal-impl |
| tg-ch8 | 同步互斥(Sync&Mutex) 线程(Thread)/ 线程标识符(TID) 进程-线程分离 竞态条件(Race Condition) 临界区(Critical Section) 互斥(Mutual Exclusion) 互斥锁(Mutex:自旋锁 vs 阻塞锁) 信号量(Semaphore:P/V 操作) 条件变量(Condvar) 管程(Monitor:Mesa 语义) 线程阻塞与唤醒(wait queue) 死锁(Deadlock)/ 死锁四条件 银行家算法(Banker's Algorithm) 双层管理器(PThreadManager) | 进程-线程分离 同一进程内多线程并发 互斥锁(MutexBlocking) 信号量(Semaphore) 条件变量(Condvar) 线程阻塞与唤醒机制 死锁检测(练习) | tg-sbi tg-linker tg-console tg-kernel-context tg-kernel-alloc tg-kernel-vm tg-syscall tg-task-manage tg-easy-fs tg-signal tg-signal-impl tg-sync |
| 功能组件 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|---|---|---|---|
| tg-sbi | SBI(Supervisor Binary Interface) console_putchar / console_getchar 系统关机(shutdown) RISC-V 特权级(M/S-mode) ecall 指令 | S→M 模式的 SBI 调用封装 字符输出 / 字符读取 系统关机 支持 nobios 直接操作 UART | 无 |
| tg-console | 控制台 I/O 格式化输出(print! / println!) 日志系统(Log Level) 自旋锁保护的全局控制台 | 可定制 print! / println! 宏 log::Log 日志实现 Console trait 抽象底层输出 | 无 |
| tg-kernel-context | 上下文(Context) Trap 帧(Trap Frame) 寄存器保存与恢复 特权级切换 stvec / sepc / scause CSR LocalContext(本地上下文) ForeignContext(跨地址空间上下文) 异界传送门(MultislotPortal) | 用户/内核态切换上下文管理 LocalContext 结构 ForeignContext(含 satp) MultislotPortal 跨地址空间执行 | 无 |
| tg-kernel-alloc | 内核堆分配器 伙伴系统(Buddy Allocation) 动态内存管理 #[global_allocator] | 基于伙伴算法的 GlobalAlloc 堆初始化(init) 物理内存转移(transfer) | 无 |
| tg-kernel-vm | 虚拟内存管理 页表(Page Table) Sv39 分页(三级页表) 虚拟地址(VAddr)/ 物理地址(PAddr) 虚拟页号(VPN)/ 物理页号(PPN) 页表项(PTE)/ 页表标志位(VmFlags) 地址空间(AddressSpace) PageManager trait 地址翻译(translate) | Sv39 页表管理 AddressSpace 地址空间抽象 虚实地址转换 页面映射(map / map_extern) 页表项操作 | 无 |
| tg-syscall | 系统调用(System Call) 系统调用号(SyscallId) 系统调用分发(handle) 系统调用结果(Done / Unsupported) Caller 抽象 IO / Process / Scheduling / Clock / Signal / Thread / SyncMutex trait 接口 | 系统调用 ID 与参数定义 trait 接口供内核实现 init_io / init_process / init_scheduling / init_clock / init_signal / init_thread / init_sync_mutex 支持 kernel / user feature | tg-signal-defs |
| tg-task-manage | 任务管理(Task Management) 调度(Scheduling) 进程管理器(PManager, proc feature) 双层管理器(PThreadManager, thread feature) ProcId / ThreadId 就绪队列(Ready Queue) Manage trait / Schedule trait 进程等待(wait / waitpid) 线程等待(waittid) 阻塞与唤醒(blocked / re_enque) | Manage 和 Schedule trait 抽象 proc feature:单层进程管理器(PManager) thread feature:双层管理器(PThreadManager) 进程树 / 父子关系 线程阻塞 / 唤醒 | 无 |
| tg-easy-fs | 文件系统(File System) SuperBlock / Inode / 位图(Bitmap) DiskInode(直接+间接索引) 块缓存(Block Cache) BlockDevice trait 文件句柄(FileHandle) 打开标志(OpenFlags) 管道(Pipe)/ 环形缓冲区 用户缓冲区(UserBuffer) FSManager trait | easy-fs 五层架构实现 文件创建 / 读写 / 目录操作 块缓存管理 管道环形缓冲区实现 FSManager trait 抽象 | 无 |
| tg-signal-defs | 信号编号(SignalNo) SIGKILL / SIGINT / SIGUSR1 等 信号动作(SignalAction) 信号集(SignalSet) 最大信号数(MAX_SIG) | 信号编号枚举定义 信号动作结构定义 信号集类型定义 为 tg-signal 和 tg-syscall 提供共用类型 | 无 |
| tg-signal | 信号处理(Signal Handling) Signal trait 接口 add_signal / handle_signals get_action_ref / set_action update_mask / sig_return / from_fork SignalResult(Handled / ProcessKilled) | Signal trait 接口定义 信号添加 / 处理 / 动作设置 屏蔽字更新 / 信号返回 fork 继承 | tg-kernel-context tg-signal-defs |
| tg-signal-impl | SignalImpl 结构 已接收信号位图(received) 信号屏蔽字(mask) 信号处理中状态(handling) 信号动作表(actions) 信号处理函数调用 上下文保存与恢复 | Signal trait 的参考实现 信号接收位图管理 屏蔽字逻辑 处理状态和动作表 | tg-kernel-context tg-signal |
| tg-sync | 互斥锁(Mutex trait: lock / unlock) 阻塞互斥锁(MutexBlocking) 信号量(Semaphore: up / down) 条件变量(Condvar: signal / wait_with_mutex) 等待队列(VecDeque<ThreadId>) UPIntrFreeCell | MutexBlocking 阻塞互斥锁 Semaphore 信号量 Condvar 条件变量 通过 ThreadId 与调度器交互 | tg-task-manage |
| tg-user | 用户态程序(User-space App) 用户库(User Library) 系统调用封装(syscall wrapper) 用户堆分配器 用户态 print! / println! | 用户测试程序运行时库 系统调用封装 用户堆分配器 各章节测试用例(ch2~ch8) | tg-console tg-syscall |
| tg-checker | 测试验证 输出模式匹配 正则表达式(Regex) 测试用例判定 | rCore-Tutorial CLI 测试输出检查工具 验证内核输出匹配预期模式 支持 --ch N 和 --exercise 模式 | 无 |
| tg-linker | 链接脚本(Linker Script) 内核内存布局(KernelLayout) .text / .rodata / .data / .bss / .boot 段 入口点(boot0! 宏) BSS 段清零 | 形成内核空间布局的链接脚本模板 用于 build.rs 工具构建 linker.ld 内核布局定位(KernelLayout::locate) 入口宏(boot0!) 段信息迭代 | 无 |
Licensed under GNU GENERAL PUBLIC LICENSE, Version 3.0.