logo
0
0
WeChat Login
prepare for tg-rcore-tutorial

第四章:地址空间

本章在第三章"多道程序与分时多任务"的基础上,引入了 RISC-V Sv39 虚拟内存机制,为每个用户进程提供独立的地址空间(tg-ch4)。这是操作系统实现进程隔离内存保护的关键一步。

通过本章的学习和实践,你将理解:

  • 什么是虚拟内存,为什么需要地址空间隔离
  • RISC-V Sv39 三级页表的结构和地址翻译过程
  • 内核地址空间与用户地址空间的布局
  • 异界传送门(MultislotPortal)如何解决跨地址空间切换问题
  • ELF 文件如何被解析并加载到独立地址空间
  • 系统调用中如何进行用户地址翻译和权限检查
  • 堆内存管理(sbrk 系统调用)

前置知识:建议先完成第一章至第三章的学习,理解裸机启动、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 加载、堆管理

源码阅读导航索引

返回根文档导航总表

本章建议按“地址空间建立 -> 进程装载 -> 跨地址空间执行 -> 用户指针翻译”阅读。

阅读顺序文件重点问题
1src/main.rskernel_space内核恒等映射、堆映射、传送门映射分别解决什么问题?
2src/process.rsnewELF 如何被映射到用户地址空间,用户栈与 satp 如何初始化?
3src/main.rsschedule异界传送门如何支撑跨地址空间执行与返回?
4src/main.rsimpls系统调用里 translate() 如何做权限检查与地址翻译?
5src/process.rschange_program_brksbrk 如何驱动堆页映射的扩张与回收?

配套建议:先读本章再回看 tg-kernel-vmtg-kernel-context/foreign,会更容易理解抽象设计。

DoD 验收标准(本章完成判据)

  • 能说明本章为何必须引入 Sv39 与每进程独立地址空间
  • 能从代码解释“内核恒等映射 + 用户地址空间映射 + 传送门映射”三者关系
  • 能说明 translate() 在 syscall 中如何完成权限检查与地址翻译
  • 能解释 sbrk 扩容/缩容时页映射范围如何变化
  • 能执行 ./test.sh base(练习时补充 ./test.sh exercise

概念-源码-测试三联表

核心概念源码入口自测方式(命令/现象)
内核地址空间建立ch4/src/main.rskernel_space启动日志出现 .text/.rodata/.data/(heap) 映射信息
ELF 装载到用户空间ch4/src/process.rsProcess::new用户程序入口为虚拟地址(如 0x10000)并可运行
跨地址空间执行ch4/src/main.rsschedule + MultislotPortal用户态与内核态可正常往返,无地址空间切换崩溃
用户指针翻译与检查ch4/src/main.rsimplstranslate非法用户地址会被拒绝而不是直接越界访问

遇到构建/运行异常可先查看根文档的“高频错误速查表”。

一、环境准备

1.1 安装 Rust 工具链

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

1.2 添加 RISC-V 64 编译目标

rustup target add riscv64gc-unknown-none-elf

1.3 安装 QEMU 模拟器

Ubuntu / Debian:

sudo apt update sudo apt install qemu-system-misc

macOS(Homebrew):

brew install qemu

验证:

qemu-system-riscv64 --version # 建议 >= 7.0

1.4 安装额外工具

cargo install cargo-clone cargo install cargo-binutils rustup component add llvm-tools

1.5 获取源代码

方式一:只获取本实验

cargo clone tg-ch4 cd tg-ch4

方式二:获取所有实验

git clone https://github.com/rcore-os/tg-rcore-tutorial.git cd tg-rcore-tutorial/ch4

二、编译与运行

2.1 编译

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=INFOLOG=TRACE

2.2 运行

基础模式:

cargo run

练习模式:

cargo run --features exercise

实际执行的 QEMU 命令等价于:

qemu-system-riscv64 \ -machine virt \ -nographic \ -bios none \ -kernel target/riscv64gc-unknown-none-elf/debug/tg-ch4

2.3 预期输出

[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!

与前几章不同,你会看到:

  • 内核地址空间的段映射信息(恒等映射)
  • 每个进程的入口地址是 ELF 中的虚拟地址(如 0x10000),而非物理地址
  • 用户程序在独立地址空间中运行,堆管理(sbrk)正常工作

2.4 运行测试

./test.sh # 运行全部测试(基础 + 练习) ./test.sh base # 仅运行基础测试 ./test.sh exercise # 仅运行练习测试

三、操作系统核心概念

3.1 为什么需要虚拟内存?

在前几章中,所有用户程序直接使用物理地址,存在严重问题:

问题说明
安全性用户程序可以读写任意物理地址,包括内核数据
隔离性一个程序的 bug 可能破坏其他程序的内存
灵活性程序必须加载到特定的物理地址,无法重定位

虚拟内存通过在 CPU 和物理内存之间加入一层地址翻译解决这些问题:

用户程序 MMU(地址翻译) 物理内存 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 虚拟地址 │ ───────→ │ 页表查找 │ ───────→ │ 物理地址 │ │ 0x10000 │ │ Sv39 三级 │ │ 0x80400000│ └──────────┘ └──────────┘ └──────────┘

每个进程拥有独立的页表,看到的虚拟地址空间完全相同(都从 0x10000 开始),但映射到不同的物理页面。

3.2 RISC-V Sv39 页表

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 根页表的物理页号

3.3 内核地址空间

tg-ch4 的内核地址空间使用恒等映射(Identity Mapping):虚拟地址 == 物理地址。

内核地址空间 ┌────────────────────────────────────┐ 高地址 │ 传送门(PORTAL_TRANSIT = VPN::MAX)│ ← 虚拟地址空间最高页 ├────────────────────────────────────┤ │ 调度栈(2 页) │ ├────────────────────────────────────┤ │ ... │ ├────────────────────────────────────┤ │ 堆区域(恒等映射) │ ← layout.end() ~ start+MEMORY ├────────────────────────────────────┤ │ .bss(恒等映射) │ │ .data(恒等映射) │ │ .rodata(恒等映射) │ │ .text(恒等映射) │ └────────────────────────────────────┘ 0x80200000

恒等映射的优势:内核可以直接通过虚拟地址访问任意物理内存,简化页表管理代码。

3.4 用户地址空间

每个用户进程拥有独立的地址空间,通过 ELF 解析创建:

用户地址空间 ┌────────────────────────────────────┐ 高地址 │ 传送门(与内核共享同一物理页) │ ← VPN::MAX ├────────────────────────────────────┤ │ ... │ ├────────────────────────────────────┤ │ 用户栈(2 页 = 8 KiB) │ ← VPN[(1<<26)-2, 1<<26) ├────────────────────────────────────┤ │ ... │ ├────────────────────────────────────┤ │ 堆区域(通过 sbrk 动态扩展) │ ← heap_bottom ~ program_brk ├────────────────────────────────────┤ │ .bss / .data / .rodata / .text │ ← 从 ELF LOAD 段映射 └────────────────────────────────────┘ 低地址(如 0x10000)

关键特性:

  • 所有映射都带有 U(User)标志,允许用户态访问
  • 传送门页面在内核和用户地址空间映射到相同虚拟地址
  • 每个进程有独立的堆(通过 sbrk 管理)

3.5 异界传送门(MultislotPortal)

核心问题:当内核和用户程序使用不同的页表时,切换 satp 后当前指令所在的虚拟地址可能变为无效,导致 CPU 无法继续执行。

解决方案:异界传送门——一个特殊的代码页面,同时映射到内核和所有用户地址空间的相同虚拟地址

内核地址空间 用户地址空间 ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ 内核代码 │ │ 用户代码 │ │ │ │ │ ├──────────────┤ ├──────────────┤ │ 传送门页面 │ ──── 同一 ──→ │ 传送门页面 │ │ (VPN::MAX) │ 物理页面 │ (VPN::MAX) │ └──────────────┘ └──────────────┘

切换流程:

内核态(内核地址空间) │ ▼ 跳转到传送门虚拟地址 │ ▼ 在传送门内:切换 satp → 用户地址空间 │ (传送门在两个地址空间的虚拟地址相同,所以不会崩溃) ▼ 恢复用户寄存器,sret → U-mode │ ▼ 用户程序执行... │ ▼ Trap → 传送门入口 │ ▼ 在传送门内:切换 satp → 内核地址空间 │ ▼ 跳转到内核 Trap 处理代码

3.6 ELF 加载

本章不再将用户程序作为原始二进制加载,而是解析 ELF 格式

ELF 文件 ├── ELF Header(入口地址、程序头表位置) ├── Program Header 1(LOAD: .text, 虚拟地址 0x10000, RX) ├── Program Header 2(LOAD: .data, 虚拟地址 0x20000, RW) └── ...

加载过程:

  1. 解析 ELF 头,验证是 RISC-V 64 位可执行文件
  2. 遍历 LOAD 类型的程序头
  3. 为每个段分配物理页面,设置对应的权限标志(R/W/X + U)
  4. 将段数据从 ELF 复制到新分配的页面
  5. 记录最高虚拟地址作为堆底

3.7 地址翻译与系统调用

引入地址空间后,系统调用的实现发生了根本变化:用户传入的指针是虚拟地址,内核无法直接访问

write 系统调用为例:

第三章(无虚拟内存) 第四章(有虚拟内存) ───────────────── ───────────────── 用户传入 buf = 0x80400000 用户传入 buf = 0x10200 │ │ ▼ ▼ 内核直接读取 buf 地址 内核通过 translate() 查页表 │ │ ▼ ▼ 输出数据 得到物理地址 0x80500200 │ ▼ 输出数据

translate() 方法会:

  1. 通过进程的页表将虚拟地址翻译为物理地址
  2. 检查页表项的权限标志(如可读、可写)
  3. 权限不足时返回 None,系统调用返回 -1

3.8 堆内存管理(sbrk)

本章新增了 sbrk 系统调用,允许用户程序动态调整堆大小:

堆底(heap_bottom) 堆顶(program_brk) │ │ ▼ ▼ ┌──────────────────────────────┐ │ 已分配的堆空间 │ └──────────────────────────────┘ sbrk(+4096) → 扩展堆,映射新的物理页面 sbrk(-4096) → 收缩堆,取消映射物理页面 sbrk(0) → 返回当前堆顶地址

3.9 系统调用

syscall ID名称功能
64write写入数据(需地址翻译)
93exit退出当前进程
124sched_yield让出 CPU
113clock_gettime获取时间(需地址翻译)
214sbrk调整堆大小
410trace追踪系统调用(练习题
222mmap映射内存(练习题
215munmap取消映射(练习题

四、代码解读

4.1 src/main.rs —— 内核主体

启动流程 rust_main

  1. 清零 BSS 段
  2. 初始化控制台和日志
  3. 初始化内核堆分配器(tg_kernel_alloc
  4. 分配并创建异界传送门
  5. 建立内核地址空间(恒等映射 + 传送门映射),激活 Sv39 分页
  6. 解析 ELF 加载用户进程,共享传送门页表项
  7. 创建调度线程,进入 schedule() 函数

调度函数 schedule

  • 初始化传送门和系统调用
  • 循环执行:通过传送门切换到用户进程 → Trap 返回 → 处理系统调用/异常
  • 所有进程完成后关机

页表管理器 Sv39Manager

  • 实现 PageManager<Sv39> trait
  • 提供物理页面的分配、映射和地址转换
  • 使用 OWNED 标志位标记内核分配的页面

地址翻译在系统调用中的应用(如 writeclock_gettime):

  • 构建权限标志(如 READABLEWRITABLE
  • 调用 address_space.translate() 翻译用户地址并检查权限
  • 翻译失败时返回 -1

4.2 src/process.rs —— 进程管理

Process::new(elf):从 ELF 创建进程

  • 验证 ELF 头 → 创建地址空间 → 映射 LOAD 段 → 分配用户栈 → 创建 ForeignContext

change_program_brk(size):实现 sbrk

  • size > 0:扩展堆,映射新页面
  • size < 0:收缩堆,取消映射
  • 返回旧的 break 地址

4.3 Cargo.toml —— 依赖说明

依赖说明
xmas-elfELF 文件格式解析库
riscvRISC-V CSR 寄存器访问(satpscause
tg-sbiSBI 调用封装
tg-linker链接脚本生成、内核布局定位
tg-console控制台输出和日志
tg-kernel-context用户上下文及异界传送门 MultislotPortalforeign feature)
tg-kernel-alloc内核堆分配器
tg-kernel-vm虚拟内存管理(地址空间、页表、页面管理)
tg-syscall系统调用定义与分发

五、编程练习

5.1 重写 trace 系统调用

引入虚存机制后,原来内核的 trace 函数实现就无效了。请你重写这个系统调用的代码,恢复其正常功能。

由于本章有了地址空间作为隔离机制,trace 需要考虑额外的情况

  • 在读取(trace_request 为 0)时,如果对应地址用户不可见或不可读,则返回值应为 -1(isize 格式的 -1,而非 u8)。
  • 在写入(trace_request 为 1)时,如果对应地址用户不可见或不可写,则返回值应为 -1。

5.2 实现 mmap 和 munmap 匿名映射

mmap 在 Linux 中主要用于在内存中映射文件,本次实验简化它的功能,仅用于申请内存。

mmap 定义:

fn mmap(&self, _caller: Caller, addr: usize, len: usize, prot: i32, _flags: i32, _fd: i32, _offset: usize) -> isize
  • syscall ID:222
  • 申请 len 字节物理内存,映射到 addr 开始的虚存,属性为 prot
  • 参数:
    • addr:虚存起始地址(必须按页对齐
    • len:字节长度(可为 0,按页向上取整)
    • prot:bit 0=可读,bit 1=可写,bit 2=可执行。其他位必须为 0
  • 返回:成功 0,错误 -1
  • 可能的错误:addr 未对齐、prot 无效、地址已映射、物理内存不足

munmap 定义:

fn munmap(&self, _caller: Caller, addr: usize, len: usize) -> isize
  • syscall ID:215
  • 取消 [addr, addr + len) 的映射
  • 错误:存在未被映射的虚存

5.3 实现提示

  • 页表项权限使用 VmFlags::build_from_str() 构建,如 "U_WRV" 表示用户态可写可读有效
  • 注意 RISC-V 页表项格式与 prot 参数的区别
  • 别忘了添加 U(用户态可访问)标志
  • 实现 trace 时,可参考 main.rsclock_gettime 的实现,使用 translate 方法进行地址翻译和权限检查

5.4 实验要求

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 # 测试练习测例

六、本章小结

通过本章的学习和实践,你完成了操作系统中最重要的抽象之一——地址空间:

  1. 虚拟内存:通过 Sv39 三级页表将虚拟地址映射到物理地址,CPU 的每次内存访问都经过地址翻译
  2. 进程隔离:每个进程拥有独立的页表,无法访问其他进程或内核的内存
  3. 异界传送门:巧妙地解决了跨地址空间切换时代码无法执行的问题
  4. ELF 加载:不再使用原始二进制,而是解析标准的 ELF 格式,按段设置权限
  5. 地址翻译:系统调用必须将用户虚拟地址翻译为物理地址才能访问
  6. 堆管理:通过 sbrk 实现动态堆扩展/收缩

在后续章节中,我们将在地址空间的基础上引入进程概念,实现 fork/exec/waitpid 等系统调用。

七、思考题

  1. 恒等映射 vs 非恒等映射? 内核使用恒等映射(VPN == PPN),用户使用非恒等映射。这样设计的好处是什么?如果内核也使用非恒等映射,会带来什么复杂性?

  2. 为什么需要异界传送门? 如果不使用传送门,直接在切换 satp 后执行下一条指令,会发生什么?能否用其他方式解决这个问题?

  3. 页表项的 U 标志的作用? 如果一个页面没有设置 U 标志,用户态程序访问它会发生什么?内核态呢?

  4. translate() 在系统调用中的必要性? 为什么 write 系统调用不能直接用用户传入的指针?如果省略 translate 检查,可能导致什么安全问题?

  5. sbrk 与 mmap 的关系? sbrk 只能线性扩展堆,而 mmap 可以在任意地址映射内存。现代操作系统中,malloc 通常同时使用两者。为什么?

参考资料


附录:rCore-Tutorial 组件分析表

表 1:tg-ch1 ~ tg-ch8 操作系统内核总体情况描述表

操作系统内核所涉及核心知识点主要完成功能所依赖的组件
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

表 2:tg-ch1 ~ tg-ch8 操作系统内核所依赖组件总体情况描述表

功能组件所涉及核心知识点主要完成功能所依赖的组件
tg-sbiSBI(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-implSignalImpl 结构
已接收信号位图(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!)
段信息迭代

License

Licensed under GNU GENERAL PUBLIC LICENSE, Version 3.0.