本章在第一章"最小执行环境"的基础上,实现了一个批处理操作系统(tg-ch2)。它能够依次加载并运行多个用户程序,支持特权级切换和 Trap 处理,并实现了 write 和 exit 两个系统调用。
通过本章的学习和实践,你将理解:
ecall 到内核态处理前置知识:建议先完成第一章(tg-ch1)的学习,理解
#![no_std]、裸机启动、SBI 等基础概念。
ch2/ ├── .cargo/ │ └── config.toml # Cargo 配置:交叉编译目标和 QEMU runner ├── build.rs # 构建脚本:下载编译用户程序,生成链接脚本和 APP_ASM ├── Cargo.toml # 项目配置与依赖 ├── README.md # 本文档 ├── test.sh # 自动测试脚本 └── src/ └── main.rs # 内核源码:批处理主循环、Trap 处理、系统调用
本章建议围绕 src/main.rs 建立“批处理 + Trap + 系统调用”主线。
| 阅读顺序 | 位置 | 重点问题 |
|---|---|---|
| 1 | rust_main | 批处理循环如何逐个装载并执行用户程序? |
| 2 | Trap 分支(scause 匹配) | 用户态 ecall 与异常进入内核后,分支逻辑如何区分? |
| 3 | handle_syscall | a7/a0~a5/a0 的系统调用寄存器约定如何落到代码中? |
| 4 | impls 模块 | IO / Process trait 如何与 syscall 分发层对接? |
配套建议:结合 tg-kernel-context 和 tg-syscall 的注释阅读,理解上下文切换与 syscall 分发的职责边界。
ch2 目录运行 cargo run,观察多个用户程序被依次装载与执行ecall 触发 Trap 的基本路径a0~a5)与 syscall 号来源(a7)sepc += 4(跳过 ecall 指令)./test.sh base 并通过基础测试| 核心概念 | 源码入口 | 自测方式(命令/现象) |
|---|---|---|
| 批处理主循环 | ch2/src/main.rs 的 rust_main | 日志中按顺序出现 app 装载与退出信息 |
| Trap 分发 | ch2/src/main.rs 中 scause::read().cause() 匹配分支 | 非法行为可被识别并输出错误日志 |
| 系统调用参数约定 | ch2/src/main.rs 的 handle_syscall | write/exit 行为与预期一致 |
| syscall trait 对接 | ch2/src/main.rs 的 impls 模块 | STDOUT 可输出,非法 fd 被拒绝 |
遇到构建/运行异常可先查看根文档的“高频错误速查表”。
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-ch2 的构建脚本需要 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-ch2
cd tg-ch2
方式二:获取所有实验
git clone https://github.com/rcore-os/tg-rcore-tutorial.git
cd tg-rcore-tutorial/ch2
在 ch2(或 tg-ch2)目录下执行:
cargo build
编译过程比第一章复杂,build.rs 会自动完成以下工作:
tg_linker::NOBIOS_SCRIPT 生成内核的内存布局cargo clone 获取 tg-user crate(包含用户测试程序)环境变量说明:
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)
cargo run
实际执行的 QEMU 命令等价于:
qemu-system-riscv64 \ -machine virt \ -nographic \ -bios none \ -kernel target/riscv64gc-unknown-none-elf/debug/tg-ch2
[tg-ch2 0.3.1-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 Hello world from user mode program! [ INFO] app0 exit with code 0 [ INFO] load app1 to 0x802xxxxx ...(更多用户程序输出)...
批处理系统依次加载并运行每个用户程序:
exit 系统调用退出./test.sh
结果
运行 ch2 基础测试... ========== Testing ch2 base ========== Expected patterns: 4, Not expected: 1 [PASS] found <Hello, world from user mode program!> [PASS] found <Test power_3 OK!> [PASS] found <Test power_5 OK!> [PASS] found <Test power_7 OK!> [PASS] not found <FAIL: T.T>
Test PASSED: 5/5 ✓ ch2 基础测试通过
批处理系统(Batch System)是最早期的操作系统形态,出现于计算资源匮乏的年代。其核心思想是:将多个程序打包到一起输入计算机,当一个程序运行结束后,计算机自动执行下一个程序。
tg-ch2 实现的批处理系统工作流程:
内核启动 │ ▼ 初始化(清零 BSS、初始化控制台和系统调用) │ ▼ ┌─→ 加载第 i 个用户程序 │ │ │ ▼ │ 创建用户上下文(设置入口地址、用户栈、U-mode) │ │ │ ▼ │ execute() → sret 切换到 U-mode 运行用户程序 │ │ │ ▼ │ 用户程序触发 Trap(ecall 或异常) │ │ │ ▼ │ 内核处理 Trap(系统调用 / 杀死出错程序) │ │ │ ├─ 系统调用 write → 输出数据,继续运行 │ ├─ 系统调用 exit → 程序退出 │ └─ 异常 → 杀死程序 │ │ │ ▼ └── 加载下一个用户程序(i++) │ ▼ 所有程序完成 → 关机
为什么需要特权级? 如果用户程序的错误(如访问非法地址、执行特权指令)能够影响内核的运行,那整个系统就不可靠了。特权级机制将用户程序和内核隔离,确保出错的用户程序只会被杀死,而不会破坏内核。
RISC-V 定义了三个特权级,本章重点关注 U-mode 和 S-mode 之间的切换:
| 特权级 | 缩写 | 运行的软件 | 能做什么 |
|---|---|---|---|
| Machine Mode | M-mode | SBI 固件 | 访问所有硬件资源 |
| Supervisor Mode | S-mode | 操作系统内核 | 管理内存、处理 Trap |
| User Mode | U-mode | 用户程序 | 仅能执行普通指令 |
特权级切换的方向:
ecall 或发生异常时,CPU 自动陷入 S-modesret 指令返回 U-mode 继续运行用户程序Trap 是 CPU 从低特权级陷入高特权级的机制,触发原因包括:
ecall 指令Trap 相关的 CSR(控制状态寄存器):
| CSR | 功能 |
|---|---|
stvec | Trap 处理入口地址 |
sepc | Trap 发生前最后一条指令的地址(异常)或下一条指令地址(中断) |
scause | Trap 原因(系统调用、非法指令、页错误等) |
stval | Trap 附加信息(如出错的地址) |
sstatus | SPP 字段记录 Trap 前的特权级 |
Trap 处理流程:
用户程序执行 ecall │ ▼ ┌── 硬件自动完成 ──┐ │ 1. sstatus.SPP ← U │ (记录 Trap 前的特权级) │ 2. sepc ← ecall 地址 │ (记录 Trap 前的 PC) │ 3. scause ← 原因 │ (如 UserEnvCall) │ 4. PC ← stvec │ (跳转到 Trap 入口) │ 5. 特权级 ← S-mode │ (切换到内核态) └──────────────────────┘ │ ▼ Trap 入口(__alltraps) ── 保存所有用户寄存器到内核栈(Trap 上下文) ── 跳转到 Rust 的 trap_handler │ ▼ trap_handler 处理 ── 读取 scause 判断 Trap 类型 ── 系统调用:处理后 sepc += 4(跳过 ecall 指令) ── 异常:杀死程序 │ ▼ __restore ── 从内核栈恢复用户寄存器 ── 执行 sret 返回 U-mode │ ▼ 用户程序从 ecall 的下一条指令继续执行
为什么 sepc 要加 4? 因为 ecall 指令本身占 4 字节。硬件将 sepc 设为 ecall 的地址,如果不加 4,sret 后会再次执行 ecall,陷入无限循环。
上下文保存与恢复
进入 Trap 时必须保存用户态的全部寄存器状态(称为 Trap 上下文),否则内核代码的执行会破坏用户寄存器的值。tg-ch2 使用 tg-kernel-context 库中的 LocalContext 结构体来管理上下文:
LocalContext::user(entry) —— 创建一个用户态上下文,设置入口地址和 sstatus.SPP = Userctx.execute() —— 恢复寄存器并执行 sret,切换到 U-modeexecute() 的下一行系统调用是用户程序请求内核服务的唯一合法途径。用户程序将参数放入寄存器,执行 ecall,内核读取参数并处理。
RISC-V 系统调用约定:
| 寄存器 | 用途 |
|---|---|
a7 | syscall ID |
a0 - a5 | 参数 |
a0 | 返回值 |
tg-ch2 支持的系统调用:
| syscall ID | 名称 | 功能 |
|---|---|---|
| 64 | write | 将缓冲区数据写入文件描述符(fd=1 为标准输出) |
| 93 | exit | 退出当前用户程序 |
用户程序中的系统调用过程(以 write 为例):
用户程序调用 println!("Hello") │ ▼ 用户库将其转为 sys_write(fd=1, buf, len) │ ▼ 内嵌汇编:a7=64, a0=1, a1=buf, a2=len, ecall │ ▼ Trap 进入内核 → handle_syscall │ ▼ 内核读取 a7=64 → 调用 write 处理函数 │ ▼ 将 buf 指向的数据通过 SBI 输出到控制台 │ ▼ 返回值写入 a0,sepc += 4,sret 回到用户态
与第一章不同,本章需要将多个用户程序嵌入到内核中。build.rs 在编译时完成以下工作:
tg-user crate(包含用户测试程序的源码)rust-objcopy 将 ELF 转为纯二进制格式(.bin)app.asm,用 .incbin 指令将所有 .bin 文件嵌入到内核的 .data 段运行时,内核通过 tg_linker::AppMeta::locate() 获取用户程序的元数据(数量、位置、大小),然后依次加载到内存中执行。
程序结构分为六个部分:
模块文档与属性(第 1-21 行):
与第一章相同的 #![no_std]、#![no_main] 和条件编译属性。
外部依赖引入(第 23-38 行):
tg_console:print! / println! 宏和日志功能riscv::register::*:访问 CSR 寄存器(如 scause)tg_kernel_context::LocalContext:用户上下文管理tg_syscall:系统调用分发框架启动与数据嵌入(第 42-47 行):
global_asm!(include_str!(env!("APP_ASM"))):将用户程序二进制数据嵌入内核tg_linker::boot0!(rust_main; stack = 8 * 4096):定义入口,分配 32 KiB 内核栈内核主函数 rust_main(第 51-107 行):
核心的批处理循环:初始化 → 遍历用户程序 → 创建上下文 → execute → 处理 Trap → 下一个
系统调用处理 handle_syscall(第 121-142 行):
从上下文提取 syscall ID 和参数,分发到 tg_syscall::handle,将返回值写回 a0
接口实现模块 impls(第 146-194 行):
Console:通过 SBI 实现字符输出SyscallContext:实现 write 和 exit 系统调用这是本章最复杂的文件,负责在编译期完成用户程序的获取、编译和打包。关键函数:
| 函数 | 功能 |
|---|---|
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) |
| 依赖 | 说明 |
|---|---|
riscv | RISC-V CSR 寄存器访问库 |
tg-sbi | SBI 调用封装,提供 nobios 模式启动 |
tg-linker | 链接脚本生成、内核布局定位、用户程序元数据 |
tg-console | 控制台输出(print! / println!)和日志 |
tg-kernel-context | 用户上下文 LocalContext,实现特权级切换 |
tg-syscall | 系统调用定义与分发框架 |
通过本章的学习和实践,你在第一章的基础上迈出了重要的一步:
ecall 触发到硬件自动保存 CSR,再到软件保存/恢复上下文write 和 exit 是用户程序与内核交互的最基本接口在后续章节中,我们将从批处理系统演进为多道程序系统和分时共享系统,实现多任务切换和时间片调度。
为什么需要内核栈和用户栈分离? 如果 Trap 处理时仍然使用用户栈,会有什么安全问题?
sepc 在系统调用和异常时的值有何不同? 为什么处理系统调用时需要将 sepc 加 4,而处理异常时不需要?
fence.i 指令的作用是什么? 在批处理系统中,为什么在加载下一个用户程序前需要执行这条指令?提示:思考指令缓存(i-cache)和数据缓存(d-cache)的区别。
如果用户程序执行了 S-mode 的特权指令(如 sret),会发生什么? 从特权级机制的角度解释这个行为。
| 操作系统内核 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|---|---|---|---|
| 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-linker | 链接脚本(Linker Script) 内核内存布局(KernelLayout) .text / .rodata / .data / .bss / .boot 段 入口点(boot0! 宏) BSS 段清零 | 形成内核空间布局的链接脚本模板 用于 build.rs 工具构建 linker.ld 内核布局定位(KernelLayout::locate) 入口宏(boot0!) 段信息迭代 | 无 |
| 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.