本章在第三章"多道程序与分时多任务"的基础上,引入了 RISC-V Sv39 虚拟内存机制,为每个用户进程提供独立的地址空间(tg-ch4)。这是操作系统实现进程隔离和内存保护的关键一步。
通过本章的学习和实践,你将理解:
前置知识:建议先完成第一章至第三章的学习,理解裸机启动、Trap 处理、系统调用和多任务调度。
ch4/ ├── .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 加载、堆管理
本章建议按“地址空间建立 -> 进程装载 -> 跨地址空间执行 -> 用户指针翻译”阅读。
| 阅读顺序 | 文件 | 重点问题 |
|---|---|---|
| 1 | src/main.rs 的 kernel_space | 内核恒等映射、堆映射、传送门映射分别解决什么问题? |
| 2 | src/process.rs 的 new | ELF 如何被映射到用户地址空间,用户栈与 satp 如何初始化? |
| 3 | src/main.rs 的 schedule | 异界传送门如何支撑跨地址空间执行与返回? |
| 4 | src/main.rs 的 impls | 系统调用里 translate() 如何做权限检查与地址翻译? |
| 5 | src/process.rs 的 change_program_brk | sbrk 如何驱动堆页映射的扩张与回收? |
配套建议:先读本章再回看 tg-kernel-vm 与 tg-kernel-context/foreign,会更容易理解抽象设计。
translate() 在 syscall 中如何完成权限检查与地址翻译sbrk 扩容/缩容时页映射范围如何变化./test.sh base(练习时补充 ./test.sh exercise)| 核心概念 | 源码入口 | 自测方式(命令/现象) |
|---|---|---|
| 内核地址空间建立 | ch4/src/main.rs 的 kernel_space | 启动日志出现 .text/.rodata/.data/(heap) 映射信息 |
| ELF 装载到用户空间 | ch4/src/process.rs 的 Process::new | 用户程序入口为虚拟地址(如 0x10000)并可运行 |
| 跨地址空间执行 | ch4/src/main.rs 的 schedule + MultislotPortal | 用户态与内核态可正常往返,无地址空间切换崩溃 |
| 用户指针翻译与检查 | ch4/src/main.rs 的 impls(translate) | 非法用户地址会被拒绝而不是直接越界访问 |
遇到构建/运行异常可先查看根文档的“高频错误速查表”。
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-ch4
cd tg-ch4
方式二:获取所有实验
git clone https://github.com/rcore-os/tg-rcore-tutorial.git
cd tg-rcore-tutorial/ch4
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-ch4
[tg-ch4 ...] Hello, world! [ INFO] .text ---> 0x80200000..0x8020xxxx [ INFO] .rodata ---> 0x8020xxxx..0x8020xxxx [ INFO] .data ---> 0x8020xxxx..0x8020xxxx [ INFO] (heap) ---> 0x8020xxxx..0x81a00000 [ INFO] detect app[0]: 0x8020xxxx..0x8020xxxx [ INFO] process entry = 0x10000, heap_bottom = 0xxxxx [ INFO] detect app[1]: ... ... Hello, world from user mode program! Test power OK! ... Test sbrk OK!
与前几章不同,你会看到:
0x10000),而非物理地址./test.sh # 运行全部测试(基础 + 练习)
./test.sh base # 仅运行基础测试
./test.sh exercise # 仅运行练习测试
在前几章中,所有用户程序直接使用物理地址,存在严重问题:
| 问题 | 说明 |
|---|---|
| 安全性 | 用户程序可以读写任意物理地址,包括内核数据 |
| 隔离性 | 一个程序的 bug 可能破坏其他程序的内存 |
| 灵活性 | 程序必须加载到特定的物理地址,无法重定位 |
虚拟内存通过在 CPU 和物理内存之间加入一层地址翻译解决这些问题:
用户程序 MMU(地址翻译) 物理内存 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 虚拟地址 │ ───────→ │ 页表查找 │ ───────→ │ 物理地址 │ │ 0x10000 │ │ Sv39 三级 │ │ 0x80400000│ └──────────┘ └──────────┘ └──────────┘
每个进程拥有独立的页表,看到的虚拟地址空间完全相同(都从 0x10000 开始),但映射到不同的物理页面。
Sv39 是 RISC-V 定义的 39 位虚拟地址的分页方案:
| 参数 | 值 |
|---|---|
| 虚拟地址宽度 | 39 位(512 GiB 地址空间) |
| 物理地址宽度 | 56 位 |
| 页大小 | 4 KiB(12 位页内偏移) |
| 页表级数 | 3 级(每级 9 位,共 27 位 VPN) |
| 页表项大小 | 8 字节(64 位) |
| 每页页表项数 | 512 个 |
虚拟地址结构:
38 30 29 21 20 12 11 0 ┌──────────┬──────────┬──────────┬──────────┐ │ VPN[2] │ VPN[1] │ VPN[0] │ Offset │ │ 9 bits │ 9 bits │ 9 bits │ 12 bits │ └──────────┴──────────┴──────────┴──────────┘
三级页表查找过程:
satp 寄存器 → 根页表物理地址 │ ▼ ┌─ 根页表 ─┐ │ VPN[2] 索引│ ──→ 得到二级页表地址 └──────────┘ │ ▼ ┌─ 二级页表 ─┐ │ VPN[1] 索引│ ──→ 得到三级页表地址 └──────────┘ │ ▼ ┌─ 三级页表 ─┐ │ VPN[0] 索引│ ──→ 得到物理页号 PPN └──────────┘ │ ▼ 物理地址 = PPN × 4096 + Offset
页表项(PTE)标志位:
| 标志 | 含义 |
|---|---|
| V (Valid) | 有效位,必须为 1 |
| R (Read) | 可读 |
| W (Write) | 可写 |
| X (Execute) | 可执行 |
| U (User) | 用户态可访问 |
| G (Global) | 全局映射(不随 TLB 刷新) |
| A (Accessed) | 已访问 |
| D (Dirty) | 已修改 |
satp 寄存器:
63 60 59 44 43 0 ┌──────┬──────────────┬──────────────────────┐ │ MODE │ ASID │ PPN │ │ 4bit │ 16 bit │ 44 bit │ └──────┴──────────────┴──────────────────────┘ MODE=8 表示 Sv39 根页表的物理页号
tg-ch4 的内核地址空间使用恒等映射(Identity Mapping):虚拟地址 == 物理地址。
内核地址空间 ┌────────────────────────────────────┐ 高地址 │ 传送门(PORTAL_TRANSIT = VPN::MAX)│ ← 虚拟地址空间最高页 ├────────────────────────────────────┤ │ 调度栈(2 页) │ ├────────────────────────────────────┤ │ ... │ ├────────────────────────────────────┤ │ 堆区域(恒等映射) │ ← layout.end() ~ start+MEMORY ├────────────────────────────────────┤ │ .bss(恒等映射) │ │ .data(恒等映射) │ │ .rodata(恒等映射) │ │ .text(恒等映射) │ └────────────────────────────────────┘ 0x80200000
恒等映射的优势:内核可以直接通过虚拟地址访问任意物理内存,简化页表管理代码。
每个用户进程拥有独立的地址空间,通过 ELF 解析创建:
用户地址空间 ┌────────────────────────────────────┐ 高地址 │ 传送门(与内核共享同一物理页) │ ← VPN::MAX ├────────────────────────────────────┤ │ ... │ ├────────────────────────────────────┤ │ 用户栈(2 页 = 8 KiB) │ ← VPN[(1<<26)-2, 1<<26) ├────────────────────────────────────┤ │ ... │ ├────────────────────────────────────┤ │ 堆区域(通过 sbrk 动态扩展) │ ← heap_bottom ~ program_brk ├────────────────────────────────────┤ │ .bss / .data / .rodata / .text │ ← 从 ELF LOAD 段映射 └────────────────────────────────────┘ 低地址(如 0x10000)
关键特性:
sbrk 管理)核心问题:当内核和用户程序使用不同的页表时,切换 satp 后当前指令所在的虚拟地址可能变为无效,导致 CPU 无法继续执行。
解决方案:异界传送门——一个特殊的代码页面,同时映射到内核和所有用户地址空间的相同虚拟地址。
内核地址空间 用户地址空间 ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ 内核代码 │ │ 用户代码 │ │ │ │ │ ├──────────────┤ ├──────────────┤ │ 传送门页面 │ ──── 同一 ──→ │ 传送门页面 │ │ (VPN::MAX) │ 物理页面 │ (VPN::MAX) │ └──────────────┘ └──────────────┘
切换流程:
内核态(内核地址空间) │ ▼ 跳转到传送门虚拟地址 │ ▼ 在传送门内:切换 satp → 用户地址空间 │ (传送门在两个地址空间的虚拟地址相同,所以不会崩溃) ▼ 恢复用户寄存器,sret → U-mode │ ▼ 用户程序执行... │ ▼ Trap → 传送门入口 │ ▼ 在传送门内:切换 satp → 内核地址空间 │ ▼ 跳转到内核 Trap 处理代码
本章不再将用户程序作为原始二进制加载,而是解析 ELF 格式:
ELF 文件 ├── ELF Header(入口地址、程序头表位置) ├── Program Header 1(LOAD: .text, 虚拟地址 0x10000, RX) ├── Program Header 2(LOAD: .data, 虚拟地址 0x20000, RW) └── ...
加载过程:
引入地址空间后,系统调用的实现发生了根本变化:用户传入的指针是虚拟地址,内核无法直接访问。
以 write 系统调用为例:
第三章(无虚拟内存) 第四章(有虚拟内存) ───────────────── ───────────────── 用户传入 buf = 0x80400000 用户传入 buf = 0x10200 │ │ ▼ ▼ 内核直接读取 buf 地址 内核通过 translate() 查页表 │ │ ▼ ▼ 输出数据 得到物理地址 0x80500200 │ ▼ 输出数据
translate() 方法会:
None,系统调用返回 -1本章新增了 sbrk 系统调用,允许用户程序动态调整堆大小:
堆底(heap_bottom) 堆顶(program_brk) │ │ ▼ ▼ ┌──────────────────────────────┐ │ 已分配的堆空间 │ └──────────────────────────────┘ sbrk(+4096) → 扩展堆,映射新的物理页面 sbrk(-4096) → 收缩堆,取消映射物理页面 sbrk(0) → 返回当前堆顶地址
| syscall ID | 名称 | 功能 |
|---|---|---|
| 64 | write | 写入数据(需地址翻译) |
| 93 | exit | 退出当前进程 |
| 124 | sched_yield | 让出 CPU |
| 113 | clock_gettime | 获取时间(需地址翻译) |
| 214 | sbrk | 调整堆大小 |
| 410 | trace | 追踪系统调用(练习题) |
| 222 | mmap | 映射内存(练习题) |
| 215 | munmap | 取消映射(练习题) |
启动流程 rust_main:
tg_kernel_alloc)schedule() 函数调度函数 schedule:
页表管理器 Sv39Manager:
PageManager<Sv39> traitOWNED 标志位标记内核分配的页面地址翻译在系统调用中的应用(如 write、clock_gettime):
READABLE、WRITABLE)address_space.translate() 翻译用户地址并检查权限Process::new(elf):从 ELF 创建进程
change_program_brk(size):实现 sbrk
| 依赖 | 说明 |
|---|---|
xmas-elf | ELF 文件格式解析库 |
riscv | RISC-V CSR 寄存器访问(satp、scause) |
tg-sbi | SBI 调用封装 |
tg-linker | 链接脚本生成、内核布局定位 |
tg-console | 控制台输出和日志 |
tg-kernel-context | 用户上下文及异界传送门 MultislotPortal(foreign feature) |
tg-kernel-alloc | 内核堆分配器 |
tg-kernel-vm | 虚拟内存管理(地址空间、页表、页面管理) |
tg-syscall | 系统调用定义与分发 |
引入虚存机制后,原来内核的 trace 函数实现就无效了。请你重写这个系统调用的代码,恢复其正常功能。
由于本章有了地址空间作为隔离机制,trace 需要考虑额外的情况:
trace_request 为 0)时,如果对应地址用户不可见或不可读,则返回值应为 -1(isize 格式的 -1,而非 u8)。trace_request 为 1)时,如果对应地址用户不可见或不可写,则返回值应为 -1。mmap 在 Linux 中主要用于在内存中映射文件,本次实验简化它的功能,仅用于申请内存。
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) 的映射VmFlags::build_from_str() 构建,如 "U_WRV" 表示用户态可写可读有效prot 参数的区别U(用户态可访问)标志trace 时,可参考 main.rs 中 clock_gettime 的实现,使用 translate 方法进行地址翻译和权限检查tg-ch4/ ├── Cargo.toml # 内核配置(需修改依赖配置) ├── src/ # 内核源代码(需修改) │ ├── main.rs │ └── process.rs ├── tg-kernel-vm/ # 虚拟内存模块(需拉取到本地并修改) │ └── src/ │ ├── lib.rs │ └── space/mod.rs └── tg-user/ # 用户程序(自动拉取,无需修改)
注意:
tg-kernel-vm需要拉取到本地才能修改:cd tg-ch4 cargo clone tg-kernel-vm然后修改
Cargo.toml:[dependencies] tg-kernel-vm = { path = "./tg-kernel-vm" }
运行和测试:
cargo run --features exercise # 运行练习测例
./test.sh exercise # 测试练习测例
通过本章的学习和实践,你完成了操作系统中最重要的抽象之一——地址空间:
sbrk 实现动态堆扩展/收缩在后续章节中,我们将在地址空间的基础上引入进程概念,实现 fork/exec/waitpid 等系统调用。
恒等映射 vs 非恒等映射? 内核使用恒等映射(VPN == PPN),用户使用非恒等映射。这样设计的好处是什么?如果内核也使用非恒等映射,会带来什么复杂性?
为什么需要异界传送门? 如果不使用传送门,直接在切换 satp 后执行下一条指令,会发生什么?能否用其他方式解决这个问题?
页表项的 U 标志的作用? 如果一个页面没有设置 U 标志,用户态程序访问它会发生什么?内核态呢?
translate() 在系统调用中的必要性? 为什么 write 系统调用不能直接用用户传入的指针?如果省略 translate 检查,可能导致什么安全问题?
sbrk 与 mmap 的关系? sbrk 只能线性扩展堆,而 mmap 可以在任意地址映射内存。现代操作系统中,malloc 通常同时使用两者。为什么?
| 操作系统内核 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|---|---|---|---|
| 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.