本章在第四章"地址空间"的基础上,引入了完整的 进程管理 机制,实现了 fork、exec、waitpid 等核心系统调用。进程是操作系统中最重要的抽象之一——它将"运行中的程序"封装为一个可管理的实体,使得用户可以动态创建、终止、等待进程,并通过 Shell 与操作系统交互。
通过本章的学习和实践,你将理解:
fork 如何复制父进程的地址空间创建子进程exec 如何用新程序替换当前进程的地址空间waitpid 如何等待子进程退出并回收资源前置知识:建议先完成第一章至第四章的学习,理解裸机启动、Trap 处理、系统调用、多任务调度和虚拟内存机制。
ch5/ ├── .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 # 内核主体:初始化、调度循环、系统调用实现 ├── process.rs # 进程结构:ELF 加载、fork、exec、堆管理 └── processor.rs # 处理器管理:进程管理器、调度队列
本章建议按“进程数据结构 -> 管理器 -> syscall 语义”阅读,重点把 fork/exec/wait 串起来。
| 阅读顺序 | 文件 | 重点问题 |
|---|---|---|
| 1 | src/process.rs | from_elf、fork、exec 分别如何改变进程执行映像? |
| 2 | src/processor.rs | ProcManager 如何维护就绪队列与实体映射? |
| 3 | src/main.rs 初始化路径 | initproc 如何被加载并进入调度体系? |
| 4 | src/main.rs Trap + syscall 分支 | exit/wait/exec 在内核中的状态迁移如何发生? |
配套建议:结合 tg-task-manage 的 PManager/ProcRel 注释阅读,可快速厘清父子进程关系与回收机制。
fork -> exec -> wait 的完整语义链路initproc 与 user_shell 在系统启动后的角色./test.sh base(练习时补充 ./test.sh exercise)| 核心概念 | 源码入口 | 自测方式(命令/现象) |
|---|---|---|
| 进程创建与替换 | ch5/src/process.rs 的 fork/exec/from_elf | 子进程 PID、父子返回值与预期一致 |
| 进程调度与实体管理 | ch5/src/processor.rs | 能解释 ready_queue 如何决定下一运行进程 |
| 退出与回收 | ch5/src/main.rs 的 syscall 分支(EXIT/WAIT) | waitpid 能拿到子进程退出码 |
| 启动进程链 | ch5/src/main.rs 初始化 initproc | 进入 shell 并可执行命令 |
遇到构建/运行异常可先查看根文档的“高频错误速查表”。
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
cargo install cargo-clone cargo install cargo-binutils rustup component add llvm-tools
方式一:只获取本实验
cargo clone tg-ch5
cd tg-ch5
方式二:获取所有实验
git clone https://github.com/rcore-os/tg-rcore-tutorial.git
cd tg-rcore-tutorial/ch5
cargo build
编译过程与前几章类似,build.rs 会自动下载 tg-user、编译用户程序并嵌入内核。
环境变量说明:
TG_USER_DIR:指定本地 tg-user 源码路径(跳过自动下载)TG_USER_VERSION:指定 tg-user 版本(默认0.2.0-preview.1)TG_SKIP_USER_APPS:设置后跳过用户程序编译LOG:设置日志级别(如LOG=INFO、LOG=TRACE)
基础模式:
cargo run
练习模式:
cargo run --features exercise
实际执行的 QEMU 命令等价于:
qemu-system-riscv64 \ -machine virt \ -nographic \ -bios none \ -kernel target/riscv64gc-unknown-none-elf/debug/tg-ch5
[tg-ch5 ...] Hello, world! [ INFO] .text ---> 0x80200000..0x8020xxxx [ INFO] .rodata ---> 0x8020xxxx..0x8020xxxx [ INFO] .data ---> 0x8020xxxx..0x8020xxxx [ INFO] (heap) ---> 0x8020xxxx..0x81a00000 Rust user shell >> ch5b_forktest_simple sys_wait without child process test passed! parent start, pid = 2! ready waiting on parent process! hello child process! child process pid = 3, exit code = 100 Shell: Process 2 exited with code 0 >> ch5b_forktree ...
与第四章不同,你会看到:
initproc 进程启动后 fork 出 user_shell 子进程fork/exec 组合动态创建和执行waitpid 回收子进程资源,Shell 打印退出码./test.sh # 运行全部测试(基础 + 练习)
./test.sh base # 仅运行基础测试
./test.sh exercise # 仅运行练习测试
在前几章中,我们管理的是"任务"(Task):内核预先加载所有用户程序,按调度策略切换执行。但任务有明显的局限性:
| 特性 | 任务(第三、四章) | 进程(第五章) |
|---|---|---|
| 创建方式 | 内核启动时全部加载 | 运行时动态创建(fork) |
| 程序替换 | 不支持 | exec 加载新程序 |
| 父子关系 | 无 | 完整的进程树 |
| 资源回收 | 内核自动回收 | 父进程通过 wait 回收 |
| 用户交互 | 无 | Shell 命令行 |
| 进程标识 | 无/内部编号 | PID(进程标识符) |
进程(Process)是操作系统对"运行中的程序"的抽象。每个进程拥有:
fork() 系统调用 ───────────────────────────────────── syscall ID: 220 功能:由当前进程复制出一个子进程 返回值: - 对于父进程:返回子进程的 PID - 对于子进程:返回 0
fork 的核心操作是深拷贝父进程的地址空间:
父进程地址空间 子进程地址空间(fork 后) ┌──────────────┐ ┌──────────────┐ │ .text │ ──复制──→ │ .text │ │ .data │ │ .data │ │ 堆空间 │ │ 堆空间 │ │ 用户栈 │ │ 用户栈 │ │ 传送门 │ ──共享──→ │ 传送门 │ └──────────────┘ └──────────────┘ 独立页表 独立页表 (不同物理页面) (不同物理页面)
fork 返回后,父子进程拥有相同的代码和数据,但在不同的地址空间中独立运行。区分父子进程的方式是 fork 的返回值:
let pid = fork();
if pid == 0 {
// 子进程分支
} else {
// 父进程分支,pid 是子进程的 PID
}
exec(path) 系统调用 ───────────────────────────────────── syscall ID: 221 功能:将当前进程的地址空间清空,加载并执行指定的 ELF 程序 参数:path 为程序名字符串 返回值:成功不返回(开始执行新程序),失败返回 -1
exec 的核心操作是替换地址空间:
exec 前(旧程序) exec 后(新程序) ┌──────────────┐ ┌──────────────┐ │ 旧 .text │ │ 新 .text │ │ 旧 .data │ ──替换──→ │ 新 .data │ │ 旧堆空间 │ │ 新堆空间 │ │ 旧用户栈 │ │ 新用户栈 │ └──────────────┘ └──────────────┘ PID 不变 PID 不变
exec 保留 PID 和父子关系,但完全替换了代码和数据。
waitpid(pid, exit_code) 系统调用 ───────────────────────────────────── syscall ID: 260 功能:等待子进程退出,回收资源,收集退出码 参数: - pid == -1:等待任意子进程 - pid > 0:等待指定 PID 的子进程 - exit_code:存放子进程退出码的用户空间指针 返回值: - 成功:返回退出的子进程 PID - 无符合条件的子进程:返回 -1 - 子进程尚未退出:返回 -2(由用户库循环等待)
| syscall ID | 名称 | 功能 |
|---|---|---|
| 63 | read | 从标准输入读取(需地址翻译) |
| 64 | write | 写入标准输出(需地址翻译) |
| 93 | exit | 退出当前进程,保存退出码 |
| 124 | sched_yield | 主动让出 CPU |
| 113 | clock_gettime | 获取系统时间(需地址翻译) |
| 172 | getpid | 获取当前进程 PID |
| 214 | sbrk | 调整堆大小 |
| 220 | fork | 创建子进程 |
| 221 | exec | 替换当前程序 |
| 260 | wait / waitpid | 等待子进程退出 |
| 400 | spawn | 创建新进程(练习题) |
| 140 | set_priority | 设置进程优先级(练习题) |
| 222 | mmap | 映射匿名内存(练习题) |
| 215 | munmap | 取消内存映射(练习题) |
fork 父进程 ──────────────→ 子进程(就绪态) │ exec(可选) │ ▼ 运行中 ←─── sched_yield / 时间片用完 │ ↑ │ 调度器选中 ▼ │ 就绪态 ──────────────┘ │ exit / 异常 │ ▼ 僵尸态(Zombie) │ 父进程 waitpid 回收 │ ▼ 资源释放,进程消亡
僵尸进程:进程退出后,其 PCB 和退出码仍然保留,等待父进程通过 waitpid 回收。如果父进程先退出,子进程会被挂到 initproc 下面,由 initproc 负责回收。
在 tg-ch5 中,进程控制块由 Process 结构体表示:
pub struct Process {
pub pid: ProcId, // 进程标识符
pub context: ForeignContext, // 用户态上下文 + satp
pub address_space: AddressSpace<Sv39, Sv39Manager>, // 独立地址空间
pub heap_bottom: usize, // 堆底
pub program_brk: usize, // 堆顶(sbrk)
}
与教科书中的 PCB 对比:
| 教科书 PCB 字段 | tg-ch5 对应 |
|---|---|
| PID | pid: ProcId |
| 寄存器状态 | context.context: LocalContext |
| 页表基地址 | context.satp |
| 地址空间 | address_space: AddressSpace |
| 父进程 / 子进程 | 由 ProcManager 维护 |
| 进程状态 | 由 PManager 管理 |
| 退出码 | 由 PManager 管理 |
进程管理分为两层:
ProcManager:负责进程实体的存储和调度队列管理
tasks: BTreeMap<ProcId, Process>:所有进程实体ready_queue: VecDeque<ProcId>:就绪队列(FIFO)PManager(来自 tg-task-manage 库):高层进程管理接口
add():添加进程find_next():取出下一个就绪进程make_current_exited():标记当前进程退出make_current_suspend():暂停当前进程wait():等待子进程当前调度算法是简单的 FIFO / 时间片轮转。练习题要求实现 stride 调度算法。
initproc 是内核创建的第一个用户进程:
内核 rust_main │ ▼ 加载 initproc(ELF) │ ▼ initproc 启动 │ ├── fork 子进程 │ │ │ ▼ │ exec("user_shell") → Shell 启动 │ │ │ 用户输入命令 │ │ │ fork + exec 执行命令 │ │ │ waitpid 等待命令完成 │ ▼ loop { wait() } // 回收僵尸进程
Shell(user_shell) 的工作流程:
>> read 系统调用)fork 出子进程exec 执行输入的程序名waitpid 等待子进程结束fork 的核心是深拷贝地址空间。在 tg-ch5 中:
pub fn fork(&mut self) -> Option<Process> {
let pid = ProcId::new();
// 1. 复制父进程的完整地址空间
let mut address_space = AddressSpace::new();
self.address_space.cloneself(&mut address_space);
// 2. 映射异界传送门
map_portal(&address_space);
// 3. 复制上下文(寄存器状态)
let context = self.context.context.clone();
let satp = (8 << 60) | address_space.root_ppn().val();
// 4. 子进程的 a0 = 0(fork 返回值)
// (由调用者设置)
Some(Self { pid, context: ForeignContext { context, satp }, address_space, ... })
}
cloneself 方法会:
exec 替换当前进程的地址空间:
pub fn exec(&mut self, elf: ElfFile) {
let proc = Process::from_elf(elf).unwrap();
self.address_space = proc.address_space; // 旧地址空间被释放
self.context = proc.context;
self.heap_bottom = proc.heap_bottom;
self.program_brk = proc.program_brk;
}
关键点:
当进程调用 exit 退出时:
父进程调用 waitpid 时:
启动流程 rust_main:
tg_kernel_alloc)initproc主调度循环:
系统调用实现(impls 模块):
IO:write(地址翻译后输出)、read(SBI 读字符)Process:fork(深拷贝地址空间)、exec(替换地址空间)、wait(回收子进程)、getpid、spawn(TODO)、sbrkScheduling:sched_yield、set_priority(TODO)Clock:clock_gettime(地址翻译后写入)Memory:mmap(TODO)、munmap(TODO)Process::from_elf(elf):从 ELF 创建进程
Process::fork():复制进程
Process::exec(elf):替换程序
Process::change_program_brk(size):实现 sbrk
Processor:全局处理器管理器
PManager,提供 get_mut() 访问接口ProcManager:进程管理器
tasks: BTreeMap:进程实体存储ready_queue: VecDeque:FIFO 就绪队列Manage trait(insert/get_mut/delete)Schedule trait(add/fetch)| 依赖 | 说明 |
|---|---|
xmas-elf | ELF 文件格式解析库 |
riscv | RISC-V CSR 寄存器访问(satp、scause) |
spin | 自旋锁(Lazy 延迟初始化) |
tg-sbi | SBI 调用封装(console、shutdown) |
tg-linker | 链接脚本生成、内核布局定位、用户程序元数据 |
tg-console | 控制台输出(print!/println!)和日志 |
tg-kernel-context | 用户上下文及异界传送门(foreign feature) |
tg-kernel-alloc | 内核堆分配器 |
tg-kernel-vm | 虚拟内存管理(地址空间、页表) |
tg-syscall | 系统调用定义与分发 |
tg-task-manage | 进程管理框架(proc feature,支持进程树) |
你仍需要迁移上一章的 mmap / munmap 以适应新的进程结构。
注意:从本章节开始,不再要求维护
trace这一系统调用。
mmap 定义:
fn mmap(&self, _caller: Caller, addr: usize, len: usize, prot: i32,
_flags: i32, _fd: i32, _offset: usize) -> isize
len 字节物理内存,映射到 addr 开始的虚存,属性为 protaddr:虚存起始地址(必须按页对齐)len:字节长度(可为 0,按页向上取整)prot:bit 0=可读,bit 1=可写,bit 2=可执行。其他位必须为 0munmap 定义:
fn munmap(&self, _caller: Caller, addr: usize, len: usize) -> isize
[addr, addr + len) 的映射大家一定好奇过为啥进程创建要用 fork + exec 这么一个奇怪的系统调用,就不能直接搞一个新进程吗?思而不学则殆,我们就来试一试!请实现一个完全 DIY 的系统调用 spawn,用以创建一个新进程。
spawn 系统调用定义(标准 spawn 看这里):
fn spawn(&self, _caller: Caller, path: usize, count: usize) -> isize
注意:虽然测例很简单,但提醒读者 spawn 不必像 fork 一样复制父进程的地址空间。spawn 直接从 ELF 创建新进程即可。
ch3 中我们实现的调度算法十分简单。现在我们要为我们的 OS 实现一种带优先级的调度算法:stride 调度算法。
算法描述:
为每个进程设置一个当前 stride,表示该进程当前已经运行的"长度"。另外设置其对应的 pass 值(只与进程的优先权有关系),表示对应进程在调度后,stride 需要进行的累加值。
每次需要调度时,从当前 runnable 态的进程中选择 stride 最小的进程调度。对于获得调度的进程 P,将对应的 stride 加上其对应的步长 pass。
一个时间片后,回到上一步骤,重新调度当前 stride 最小的进程。
可以证明,如果令 P.pass = BigStride / P.priority,其中 P.priority 表示进程的优先权(大于 1),而 BigStride 表示一个预先定义的大常数,则该调度方案为每个进程分配的时间将与其优先级成正比。
其他实验细节:
set_priority 系统调用:
fn set_priority(&self, _caller: Caller, prio: isize) -> isize
Process 中添加新字段(如 stride、priority)来支持优先级调度Process::from_elf 创建新进程目录结构说明:
tg-ch5/ ├── Cargo.toml(内核配置文件) ├── src/(内核源代码,需要修改) │ ├── main.rs(内核主函数,包括系统调用接口实现) │ ├── process.rs(进程结构) │ └── processor.rs(进程管理器和调度器) └── tg-user/(用户程序,运行时自动拉取,无需修改) └── src/bin(测试用例)
说明:
tg-user会在运行时自动拉取到tg-ch5/tg-user目录下- 只需修改
tg-ch5/src/目录下的内核代码
运行和测试:
运行练习测例:
cargo run --features exercise
然后在终端中输入 ch5_usertest 运行,这个测例打包了所有你需要通过的测例。你也可以通过修改这个文件调整本地测试的内容,或者单独运行某测例来纠正特定的错误。
测试练习测例:
./test.sh exercise
前向兼容:从本章开始,你的内核必须前向兼容,需要能通过前一章的所有测例(除了
ch3_trace和ch4_trace)。
通过本章的学习和实践,你完成了操作系统中最核心的抽象——进程:
在后续章节中,我们将在进程的基础上引入文件系统,实现持久化存储和文件 I/O。
fork 的效率问题? fork 需要复制整个地址空间,如果进程占用大量内存,开销很大。现代操作系统如何优化这个问题?(提示:Copy-on-Write)
为什么 fork + exec? UNIX 为什么选择 fork + exec 的组合而不是直接 spawn?这种设计有什么优缺点?Windows 的 CreateProcess 与之有何不同?
僵尸进程的问题? 如果父进程不调用 waitpid,子进程退出后会一直是僵尸态。这会导致什么问题?initproc 如何解决孤儿进程问题?
stride 调度的公平性? 为什么 stride 调度能保证与优先级成正比的时间分配?如果 BigStride 太小会怎样?太大会怎样?
spawn vs fork+exec? spawn 相比 fork+exec 有什么优势?在什么场景下 fork+exec 更灵活?
| 操作系统内核 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|---|---|---|---|
| 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.