本章在第二章"批处理系统"的基础上,实现了一个多道程序操作系统(tg-ch3)。它支持多个用户程序同时驻留在内存中并发执行,通过时钟中断实现抢占式调度,通过 yield 系统调用支持协作式调度,并引入了时间管理功能。
通过本章的学习和实践,你将理解:
yield(让出 CPU)和 clock_gettime(获取时间)前置知识:建议先完成第一章(tg-ch1)和第二章(tg-ch2)的学习,理解裸机启动、Trap 处理、系统调用等基础概念。
ch3/ ├── .cargo/ │ └── config.toml # Cargo 配置:交叉编译目标和 QEMU runner ├── .gitignore # Git 忽略规则 ├── build.rs # 构建脚本:下载编译用户程序,生成链接脚本和 APP_ASM ├── Cargo.toml # 项目配置与依赖 ├── LICENSE # GPL v3 许可证 ├── README.md # 本文档 ├── rust-toolchain.toml # Rust 工具链配置 ├── test.sh # 自动测试脚本 └── src/ ├── main.rs # 内核源码:多道程序主循环、Trap 处理、系统调用 └── task.rs # 任务控制块(TCB)和调度事件定义
Linux / macOS / WSL:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
验证安装:
rustc --version # 要求 >= 1.85.0(支持 edition 2024)
cargo --version
rustup target add riscv64gc-unknown-none-elf
Ubuntu / Debian:
sudo apt update sudo apt install qemu-system-misc
macOS(Homebrew):
brew install qemu
验证:
qemu-system-riscv64 --version # 建议 >= 7.0
tg-ch3 的构建脚本需要 cargo-clone(用于自动下载用户程序 crate)和 rust-objcopy(用于将 ELF 转为二进制):
cargo install cargo-clone
# rust-objcopy 由 cargo-binutils 提供
cargo install cargo-binutils
rustup component add llvm-tools
方式一:只获取本实验
cargo clone tg-ch3
cd tg-ch3
方式二:获取所有实验
git clone https://github.com/rcore-os/rCore-Tutorial-in-single-workspace.git
cd rCore-Tutorial-in-single-workspace/ch3
在 ch3(或 tg-ch3)目录下执行:
cargo build
编译过程与第二章类似,build.rs 会自动完成以下工作:
tg_linker::NOBIOS_SCRIPT 生成内核的内存布局cargo clone 获取 tg-user crate(包含用户测试程序)cases.toml 中的 ch3 或 ch3_exercise 配置,为每个用户程序交叉编译环境变量说明:
TG_USER_DIR:指定本地 tg-user 源码路径(跳过自动下载)TG_USER_VERSION:指定 tg-user 版本(默认0.2.0-preview.1)TG_SKIP_USER_APPS:设置后跳过用户程序编译(生成空的占位 APP_ASM)LOG:设置日志级别(如LOG=INFO、LOG=TRACE,默认为info)
默认模式(抢占式调度):
cargo run
协作式调度模式:
cargo run --features coop
启用 coop feature 后,禁用时钟中断抢占,任务只能通过 yield 主动让出 CPU。
练习模式:
cargo run --features exercise
加载练习专用的测试用例(包含 sys_trace 相关测试)。
实际执行的 QEMU 命令等价于:
qemu-system-riscv64 \ -machine virt \ -nographic \ -bios none \ -kernel target/riscv64gc-unknown-none-elf/debug/tg-ch3
[tg-ch3 0.3.0-preview.1] Hello, world! [ INFO] .data [0x802xxxxx, 0x802xxxxx) [ WARN] boot_stack top=bottom=0x802xxxxx, lower_bound=0x802xxxxx [ERROR] .bss [0x802xxxxx, 0x802xxxxx) [ INFO] load app0 to 0x802xxxxx [ INFO] load app1 to 0x802xxxxx [ INFO] load app2 to 0x802xxxxx ... power_3 [10000/200000] power_3 [20000/200000] ... power_3 [200000/200000] 3^200000 = 871008973(MOD 998244353) Test power_3 OK! ... AAAAAAAAAA [1/5] BBBBBBBBBB [1/5] CCCCCCCCCC [1/5] ...(交替输出,体现时间片轮转) Test write A OK! Test write B OK! Test write C OK! ... Test sleep OK!
与第二章的串行输出不同,你会观察到多个用户程序的输出交替出现(如 power_3、power_5、write_a、write_b 交错),这就是抢占式调度的效果——时钟中断强制切换任务,实现了时间片轮转。
./test.sh # 运行全部测试(基础 + 练习)
./test.sh base # 仅运行基础测试
./test.sh exercise # 仅运行练习测试
测试脚本会同时在终端显示 cargo run 的完整输出,并通过 tg-checker 自动验证输出是否符合预期。
第二章的批处理系统串行执行用户程序:一个程序运行完毕后才加载下一个。这种方式的缺点是:当一个程序等待 I/O 或主动暂停时,CPU 处于空闲状态,造成资源浪费。
多道程序系统(Multiprogramming)解决了这个问题:
批处理系统 多道程序系统 ┌──────────────────┐ ┌──────────────────┐ │ App0 ██████████ │ │ App0 ██ ██ ██ │ │ App1 ████████│ │ App1 ██ ██ ██│ │ App2 ████│ │ App2 ██ ██ ██ │ │ ──────→ 时间│ │ ──────→ 时间 │ │ 串行执行,CPU │ │ 交替执行,CPU │ │ 利用率低 │ │ 利用率高 │ └──────────────────┘ └──────────────────┘
核心改进:
任务控制块(Task Control Block, TCB)是内核管理任务的核心数据结构。在 tg-ch3 中,每个 TCB 包含:
| 字段 | 类型 | 说明 |
|---|---|---|
ctx | LocalContext | 用户态上下文(所有通用寄存器 + CSR) |
finish | bool | 任务是否已完成 |
stack | [usize; 1024] | 独立的用户栈(8 KiB) |
与第二章相比,本章将用户上下文和栈空间封装到 TCB 中,使得多个任务可以独立管理,互不干扰。
任务状态变化:
init() [未初始化] ──→ [就绪] │ execute()│ ▼ [运行中] ╱ │ ╲ yield/ exit/ 异常/ 超时 退出 被杀死 ╱ │ ╲ ▼ ▼ ▼ [就绪] [已完成] [已完成]
任务切换是操作系统的核心机制。tg-ch3 使用 tg-kernel-context 库中的 LocalContext 实现:
ctx 中ctx 中恢复用户寄存器sret 指令返回到目标任务的用户态当前任务(App A) 下一个任务(App B) │ ▲ ▼ │ 触发 Trap sret 返回 │ ▲ ▼ │ 保存 A 的上下文到 TCB[A] 恢复 B 的上下文从 TCB[B] │ ▲ └──────── 内核调度决策 ───────────────┘
与第二章的 Trap 处理相比,本章增加了"不结束当前任务但切换到下一个"的逻辑。
协作式调度依赖任务主动让出 CPU。用户程序调用 yield 系统调用,告诉内核"我暂时不需要 CPU 了,可以去执行别的任务"。
典型使用场景:当程序需要等待外设完成 I/O 操作时,与其忙等浪费 CPU 时间,不如 yield 让出 CPU 给其他任务。
App A 发起 I/O 请求 App B 在运行 │ │ ├─ 调用 yield │ │ (ecall,a7=124) │ │ │ ▼ ▼ 内核处理: 继续执行 标记 A 为"就绪" │ 切换到 B │ │ │ ...(一段时间后轮转回 A)... │ │ │ ▼ │ A 继续执行 │ 检查 I/O 是否完成 │
在 tg-ch3 中,启用 coop feature 可以体验纯协作式调度——时钟中断被禁用,任务只能通过 yield 主动让出 CPU。
协作式调度的问题:如果一个任务永远不调用 yield(例如进入死循环),其他任务就永远得不到执行。
抢占式调度通过时钟中断解决这个问题:
App A 正在执行(可能是死循环) │ │ ← 12500 个时钟周期后 │ 时钟中断触发! ▼ 硬件自动陷入 S-mode │ ▼ scause = Interrupt::SupervisorTimer │ ▼ 内核处理: 1. 清除时钟中断(set_timer(u64::MAX)) 2. 切换到下一个任务 │ ▼ App B 开始执行
关键代码逻辑:
// 每次切换到用户程序前,设置时钟中断
tg_sbi::set_timer(time::read64() + 12500);
unsafe { tcb.execute() };
// Trap 返回后判断原因
match scause::read().cause() {
Trap::Interrupt(Interrupt::SupervisorTimer) => {
tg_sbi::set_timer(u64::MAX); // 清除中断
false // 不结束任务,切换到下一个
}
// ...
}
时间片轮转算法(Round-Robin):
tg-ch3 使用最简单的轮转算法:维护一个任务索引 i,每次时钟中断后 i = (i + 1) % n,循环执行各任务。每个任务获得相等的时间片(12500 个时钟周期 ≈ 1ms,在 QEMU 12.5MHz 时钟下)。
RISC-V 的时钟中断机制:
| 组件 | 说明 |
|---|---|
mtime 寄存器 | 硬件计数器,持续递增 |
mtimecmp 寄存器 | 比较值,当 mtime >= mtimecmp 时触发中断 |
sie.stie | S 特权级时钟中断使能位 |
set_timer() | 通过 SBI 调用设置 mtimecmp |
初始化步骤:
unsafe { sie::set_stimer() } —— 开启 S 特权级时钟中断set_timer(time::read64() + interval) —— 设置下次中断时间时钟中断到达后:
scause = Interrupt::SupervisorTimertg-ch3 在第二章的基础上新增了 yield 和 clock_gettime 两个系统调用:
| syscall ID | 名称 | 功能 |
|---|---|---|
| 64 | write | 将缓冲区数据写入文件描述符(fd=1 为标准输出) |
| 93 | exit | 退出当前任务 |
| 124 | sched_yield | 主动让出 CPU,切换到下一个任务 |
| 113 | clock_gettime | 获取当前时间(纳秒精度) |
| 410 | trace | 追踪系统调用信息(练习题,需自行实现) |
clock_gettime 的实现原理:
用户程序调用 clock_gettime(CLOCK_MONOTONIC, &ts) │ ▼ 内核读取 RISC-V time 寄存器 │ ▼ 将 tick 数转换为纳秒:time * 10000 / 125 = time * 80 ns │ ▼ 填充 TimeSpec { tv_sec, tv_nsec } 写回用户空间
tg-ch3 引入了 SchedulingEvent 枚举来统一描述系统调用的调度效果:
| 事件 | 含义 | 触发条件 |
|---|---|---|
None | 继续执行当前任务 | write、clock_gettime 等普通系统调用 |
Yield | 切换到下一个任务 | yield 系统调用 |
Exit(code) | 任务退出 | exit 系统调用 |
UnsupportedSyscall(id) | 杀死任务 | 不支持的系统调用 |
这种设计将系统调用的处理逻辑(在 handle_syscall 中)与调度决策(在主循环中)清晰分离。
程序结构分为五个部分:
模块文档与属性(第 1-22 行):
与第二章相同的 #![no_std]、#![no_main] 和条件编译属性。新增了 mod task 引入任务管理模块。
外部依赖引入(第 30-41 行):
与第二章类似,但增加了 task::TaskControlBlock 的引用。
启动与初始化(第 45-56 行):
global_asm!(include_str!(env!("APP_ASM"))):嵌入用户程序APP_CAPACITY = 32:最大支持 32 个应用boot0!(rust_main; stack = (APP_CAPACITY + 2) * 8192):分配 272 KiB 内核栈(因为 TCB 中包含用户栈)内核主函数 rust_main(第 60-153 行):
核心的多道程序循环:
// 初始化 → 加载所有应用到 TCB 数组
// → 开启时钟中断
// → 轮转执行:
while remain > 0 {
if !tcb.finish {
set_timer(...); // 设置时间片
tcb.execute(); // 切换到 U-mode
match scause {
Timer → 切换到下一个任务
UserEnvCall → 处理系统调用
Exception → 杀死任务
}
}
i = (i + 1) % n; // 轮转到下一个
}
shutdown()
接口实现模块 impls(第 164-237 行):
在第二章的 Console、IO、Process 基础上,新增了:
Scheduling:处理 yield 系统调用Clock:处理 clock_gettime 系统调用Trace:练习题的占位实现定义了两个核心类型:
TaskControlBlock:任务控制块
init(entry) —— 创建用户态上下文,分配独立用户栈execute() —— 切换到 U-mode 执行handle_syscall() —— 处理系统调用并返回调度事件SchedulingEvent:调度事件枚举
None / Yield / Exit(code) / UnsupportedSyscall(id)与第二章结构相同,但根据 exercise feature 选择不同的测试用例集:
let case_key = if env::var("CARGO_FEATURE_EXERCISE").is_ok() {
"ch3_exercise" // 练习模式测例
} else {
"ch3" // 基础模式测例
};
| 函数 | 功能 |
|---|---|
write_linker() | 生成链接脚本 |
ensure_tg_user() | 确保 tg-user 源码可用(本地或 cargo clone) |
build_apps() | 读取 cases.toml 配置,编译所有用户程序 |
build_user_app() | 编译单个用户程序 |
objcopy_to_bin() | 将 ELF 转为纯二进制 |
write_app_asm() | 生成汇编文件,嵌入用户程序二进制 |
write_dummy_app_asm() | 生成空的占位汇编(用于 publish --dry-run) |
Features:
| Feature | 说明 |
|---|---|
coop | 协作式调度:禁用时钟中断,任务需主动 yield |
exercise | 练习模式:加载练习测例 |
Dependencies:
| 依赖 | 说明 |
|---|---|
riscv | RISC-V CSR 寄存器访问(sie、scause、time) |
tg-sbi | SBI 调用封装,包括 set_timer 设置时钟中断 |
tg-linker | 链接脚本生成、内核布局定位、用户程序元数据 |
tg-console | 控制台输出(print! / println!)和日志 |
tg-kernel-context | 用户上下文 LocalContext,实现特权级切换 |
tg-syscall | 系统调用定义与分发(含 Scheduling、Clock、Trace trait) |
在 tg-ch3 中,我们的系统已经能够支持多个任务分时轮流运行。我们希望引入一个新的系统调用 sys_trace(ID 为 410)用来追踪当前任务系统调用的历史信息,并做对应的修改。定义如下:
fn trace(&self, _caller: tg_syscall::Caller, _trace_request: usize, _id: usize, _data: usize) -> isize
调用规范:
这个系统调用有三种功能,根据 trace_request 的值不同,执行不同的操作:
| trace_request | 功能 | 参数说明 | 返回值 |
|---|---|---|---|
| 0 | 读取用户内存 | id 视为 *const u8,读取该地址处 1 字节 | 该地址处的值(无符号) |
| 1 | 写入用户内存 | id 视为 *mut u8,写入 data 的最低字节 | 0 |
| 2 | 查询系统调用计数 | id 为系统调用编号 | 该系统调用的调用次数(本次调用也计入统计) |
| 其他 | 无效 | 忽略其他参数 | -1 |
说明:
TaskControlBlock::handle_syscall() 中统计。TaskControlBlock 结构来维护系统调用计数信息。unsafe 做类型转换,这在内核处理用户调用时是不可避免的。目录结构:
tg-ch3/ ├── Cargo.toml # 内核配置文件 ├── src/ # 内核源代码(需要修改) │ ├── main.rs # 内核主函数,包括系统调用接口实现 │ └── task.rs # 任务控制块(需要扩展) └── tg-user/ # 用户程序(运行时自动拉取,无需修改) └── src/bin # 测试用例
tg-user会在运行时自动拉取到tg-ch3/tg-user目录下,只需修改tg-ch3/src/目录下的内核代码。
运行练习测例:
cargo run --features exercise
测试练习测例:
./test.sh exercise
通过本章的学习和实践,你在第二章的基础上实现了重要的进化:
yield 主动让出 CPU,适用于 I/O 密集型场景clock_gettime 让用户程序获取系统时间在后续章节中,我们将引入地址空间,为每个任务提供独立的虚拟内存,进一步增强隔离性和安全性。
协作式 vs 抢占式调度的权衡? 协作式调度的优点和缺点分别是什么?在什么场景下协作式调度更合适?可以用 cargo run --features coop 体验协作式调度。
时间片大小的影响? tg-ch3 使用 12500 个时钟周期作为时间片。如果把时间片设得非常大(如 1 秒),系统行为会如何变化?如果设得非常小(如 10 个时钟周期),又会有什么问题?
为什么需要 SchedulingEvent 枚举? 如果不用枚举,直接在 handle_syscall 中决定是否切换任务,会有什么设计上的问题?
时钟中断和 sstatus.sie 的关系? 在 Trap 处理过程中,时钟中断会被屏蔽吗?为什么 RISC-V 默认这样设计?这与嵌套中断有什么关系?
如果一个用户程序的用户栈溢出,会发生什么? 在当前 tg-ch3 的设计中,栈溢出可能覆盖哪些数据?如何改进设计来检测栈溢出?
| 操作系统内核 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|---|---|---|---|
| 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.