Skip to content

Commit cafcab1

Browse files
committed
doc: update README.md in chX
1 parent caf6666 commit cafcab1

8 files changed

Lines changed: 637 additions & 248 deletions

File tree

ch1/README.md

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,44 @@
1-
# 第一章
2-
3-
第一章旨在展示一个尽量简单的**特权态裸机应用程序**
4-
5-
- 只有[一个文件](src/main.rs)
6-
- 链接脚本在 [build.rs](build.rs),以免增加依赖;
7-
- 只依赖 [*sbi-rt*](https://crates.io/crates/sbi-rt) 以获得封装好的 SBI 调用;
8-
- 这个程序被 SEE 引导,工作在 S 态;
9-
- 这个程序不需要环境:
10-
- 从汇编进入并为 Rust 准备栈;
11-
- 依赖 SBI 提供的 `legacy::console_putchar` 打印 `Hello, world!`
12-
- 依赖 SBI 提供的 `system_reset` 调用关机;
13-
14-
它不配被称作一个操作系统,因为它没有操作(硬件),也不构造(执行用户程序的)系统;
15-
16-
## sbi-rt
17-
18-
这个库就是 kernel 的 libc。
19-
20-
它根据 [SBI 标准](https://github.com/riscv-non-isa/riscv-sbi-doc)封装了一系列函数,通过 `ecall` 命令调用 SBI 提供的响应功能。本章需要使用 `legacy::console_putchar` 向控制台打印字符,以及 `system_reset` 在程序运行完后关机。
21-
22-
## 定制链接脚本
23-
24-
build.rs 的用法见[文档](https://doc.rust-lang.org/cargo/reference/build-scripts.html)。这个定制的链接脚本是特殊的:
25-
26-
```ld
27-
OUTPUT_ARCH(riscv)
28-
SECTIONS {
29-
.text 0x80200000 : {
30-
*(.text.entry)
31-
*(.text .text.*)
32-
}
33-
.rodata : {
34-
*(.rodata .rodata.*)
35-
*(.srodata .srodata.*)
36-
}
37-
.data : {
38-
*(.data .data.*)
39-
*(.sdata .sdata.*)
40-
}
41-
.bss : {
42-
*(.bss.uninit)
43-
*(.bss .bss.*)
44-
*(.sbss .sbss.*)
45-
}
1+
# 第一章:应用程序与基本执行环境
2+
3+
本章实现了一个最简单的 RISC-V S 态裸机程序,展示操作系统的最小执行环境。
4+
5+
## 功能概述
6+
7+
- 使用 `_start` 裸函数汇编入口,初始化栈并跳转到 Rust
8+
- 通过 SBI 调用打印 `Hello, world!`
9+
- 调用 SBI 关机
10+
-`build.rs` 中生成链接脚本,将 `.text.entry` 放置在 `0x8020_0000`,确保被 SBI 正确引导
11+
12+
## 裸函数入口
13+
14+
本章使用 `#[naked]` 属性定义裸函数 `_start` 作为程序入口。裸函数不会生成函数序言和尾声,可以在没有栈的情况下执行:
15+
16+
```rust
17+
#[unsafe(naked)]
18+
#[link_section = ".text.entry"]
19+
unsafe extern "C" fn _start() -> ! {
20+
core::arch::naked_asm!(
21+
"la sp, {stack} + {stack_size}",
22+
"j {main}",
23+
// ...
24+
)
4625
}
4726
```
4827

49-
1. 为了被引导,它的 `.text` 在最前面。一般是 `.rodata` 在最前面。`.text` 的最前面是 `.text.entry`,有且只有一个汇编入口放在这个节,实现引导;
50-
2. 正常情况下,裸机应用程序需要清除自己的 `.bss` 节,所以需要定义全局符号以便动态定位 `.bss`。但这一章的程序并不依赖 清空的 `.bss`,所以没有导出符号。`.bss` 本身仍然需要,因为栈会放在里面。
28+
入口函数完成两件事:设置栈指针 `sp`,然后跳转到 Rust 主函数。链接脚本确保 `.text.entry` 位于 `0x8020_0000`,这是 SBI 引导后的跳转地址。
29+
30+
## Dependencies
31+
32+
| 依赖 | 说明 |
33+
|------|------|
34+
| `tg-sbi` | SBI 调用封装库,支持标准 RustSBI 模式和 nobios 模式 |
35+
36+
## Features
37+
38+
| Feature | 说明 |
39+
|---------|------|
40+
| `nobios` | 无需外部 SBI 实现,直接从 QEMU `-bios none` 模式启动 |
5141

52-
## 工作流程解读
42+
## License
5343

54-
1. SBI 初始化完成后,将固定跳转到 0x8020_0000 地址;
55-
2. 根据链接脚本,汇编入口函数被放置在这个地址。它叫做 `_start`,这个名字是特殊的!GNU LD 及兼容其脚本的链接器会将这个名字认为是默认的入口,否则需要指定。这个函数是一个 rust 裸函数([`#[naked]`](https://github.com/rust-lang/rust/issues/90957)),编译器不会为它添加任何序言和尾声,因此可以在没有栈的情况下执行。它将栈指针指向预留的栈空间,然后跳转到 `rust_main` 函数;
56-
3. `rust_main` 函数在一个最简单的循环打印调用 sbi 打印 `Hello, world!` 字符串,然后关机。
44+
Licensed under either of MIT license or Apache License, Version 2.0 at your option.

ch2/README.md

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,61 @@
1-
# 第二章
1+
# 第二章:批处理系统
22

3-
第二章实现的操作系统被称为“邓氏鱼”。邓氏鱼是这样一种生物:在它生存的年代,它最大、最强,非常有效的颌、眼睛和尾巴提供了充分强大的咬合力、视力和游泳能力,这些结构在泥盆纪都堪称先进。然而,在泥盆纪末灭绝事件中,以邓氏鱼为代表的所有盾皮鱼类都迅速而彻底地灭绝了。在今天的海洋里,我们没有发现什么与之在演化上接近的生物。演化就是这样短视。一时非常有效的设计,可能随着环境微妙的一点改变就完全不适用了,设计也是一样
3+
本章实现了一个批处理操作系统,支持特权级切换和 Trap 处理,能够依次加载并运行多个用户程序
44

5-
因此,在迈向更复杂的系统之前,就让我们任性地实现一个保持简单的单道批处理系统吧。如你所见,第二章依然只有一个 [main.rs](src/main.rs) 文件,而且算上注释也不到 150 行(当然,它使用了更多的依赖)。重点关注 [`syscall`](src/main.rs#L29)[`Context`](src/main.rs#L36) 的用法,这就是邓氏鱼的大脑和脊椎,这些真正成功的结构是不会随着某些物种的灭绝而消失的。
5+
## 功能概述
66

7-
> **注意** 如果修改了用户程序,需要 `cargo clean` 重新编译才能刷新,不知道怎么优化。
7+
- 用户态与内核态的特权级切换
8+
- Trap 上下文保存与恢复
9+
- 系统调用处理 (`write`, `exit`)
10+
- 批处理方式顺序执行用户程序
811

9-
## 实验
12+
## 用户程序加载
1013

11-
提高 `write` 系统调用安全性的实验只需要在第二章代码上做一点修改,没必要再做一个新的 bin crate 了。
14+
用户程序在编译时通过 `APP_ASM` 环境变量内联到内核镜像中,运行时依次加载执行。
15+
16+
## 系统调用
17+
18+
| 系统调用 | 功能 |
19+
|----------|------|
20+
| `write` | 向标准输出写入数据 |
21+
| `exit` | 退出当前程序 |
22+
23+
## 特权级切换与 Trap 处理
24+
25+
本章的核心是 `LocalContext` 结构,它保存用户态的完整寄存器状态。当用户程序发生异常或触发系统调用时:
26+
27+
1. 硬件自动切换到 S 态,跳转到 `stvec` 指向的 Trap 入口
28+
2. Trap 入口保存用户寄存器到 `LocalContext`
29+
3. 内核读取 `scause` 判断 Trap 类型,处理系统调用或异常
30+
4. 恢复 `LocalContext` 中的寄存器,执行 `sret` 返回用户态
31+
32+
```rust
33+
// 执行用户程序
34+
unsafe { ctx.execute() };
35+
// 从 Trap 返回后,检查原因
36+
match scause::read().cause() {
37+
Trap::Exception(Exception::UserEnvCall) => handle_syscall(&mut ctx),
38+
// ...
39+
}
40+
```
41+
42+
## Dependencies
43+
44+
| 依赖 | 说明 |
45+
|------|------|
46+
| `riscv` | RISC-V CSR 寄存器访问 |
47+
| `tg-sbi` | SBI 调用封装库 |
48+
| `tg-linker` | 链接脚本生成、内核布局定位、用户程序元数据 |
49+
| `tg-console` | 控制台输出 (`print!`/`println!`) 和日志 |
50+
| `tg-kernel-context` | 用户上下文 `LocalContext` 及特权级切换 |
51+
| `tg-syscall` | 系统调用定义与分发 |
52+
53+
## Features
54+
55+
| Feature | 说明 |
56+
|---------|------|
57+
| `nobios` | 无需外部 SBI 实现,直接从 QEMU `-bios none` 模式启动 |
58+
59+
## License
60+
61+
Licensed under either of MIT license or Apache License, Version 2.0 at your option.

ch3/README.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,70 @@
1-
# 第三章
1+
# 第三章:多道程序与分时多任务
2+
3+
本章实现了多道程序系统,支持协作式和抢占式调度,多个用户程序可以并发执行。
4+
5+
## 功能概述
6+
7+
- 任务控制块 (TCB) 管理任务状态和上下文
8+
- 时钟中断驱动的抢占式调度(默认模式)
9+
- 协作式调度(通过 `yield` 系统调用主动让出 CPU)
10+
- 轮转调度算法,依次执行各任务
11+
12+
## 用户程序加载
13+
14+
用户程序在编译时通过 `APP_ASM` 环境变量内联到内核镜像中,运行时依次加载执行。
15+
16+
## 系统调用
17+
18+
| 系统调用 | 功能 |
19+
|----------|------|
20+
| `write` | 向标准输出写入数据 |
21+
| `exit` | 退出当前任务 |
22+
| `sched_yield` | 主动让出 CPU |
23+
| `clock_gettime` | 获取当前时间 |
24+
25+
## 时钟中断与抢占式调度
26+
27+
本章通过 SBI 的 `set_timer` 设置时钟中断,实现抢占式调度。每次切换到用户程序前设置下一次中断时间:
28+
29+
```rust
30+
// 设置 12500 个时钟周期后触发中断
31+
tg_sbi::set_timer(time::read64() + 12500);
32+
unsafe { tcb.execute() };
33+
```
34+
35+
当时钟中断到达时,`scause``Interrupt::SupervisorTimer`,内核保存当前任务状态并切换到下一个任务,实现时间片轮转:
36+
37+
```rust
38+
Trap::Interrupt(Interrupt::SupervisorTimer) => {
39+
tg_sbi::set_timer(u64::MAX); // 清除中断
40+
false // 不结束任务,切换到下一个
41+
}
42+
```
43+
44+
启用 `coop` feature 可禁用时钟中断,任务需主动调用 `yield` 让出 CPU。
45+
46+
## Exercise
47+
48+
[Exercise](./exercise.md)
49+
50+
## Dependencies
51+
52+
| 依赖 | 说明 |
53+
|------|------|
54+
| `riscv` | RISC-V CSR 寄存器访问(`sie`, `scause`, `time`|
55+
| `tg-sbi` | SBI 调用封装库,包括 `set_timer` 设置时钟中断 |
56+
| `tg-linker` | 链接脚本生成、内核布局定位、用户程序元数据 |
57+
| `tg-console` | 控制台输出 (`print!`/`println!`) 和日志 |
58+
| `tg-kernel-context` | 用户上下文 `LocalContext` 及特权级切换 |
59+
| `tg-syscall` | 系统调用定义与分发 |
60+
61+
## Features
62+
63+
| Feature | 说明 |
64+
|---------|------|
65+
| `coop` | 协作式调度模式,禁用时钟中断抢占,任务需主动 `yield` |
66+
| `nobios` | 无需外部 SBI 实现,直接从 QEMU `-bios none` 模式启动 |
67+
68+
## License
69+
70+
Licensed under either of MIT license or Apache License, Version 2.0 at your option.

ch4/README.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,72 @@
1-
# 第四章
1+
# 第四章:地址空间
2+
3+
本章实现了基于 RISC-V Sv39 的虚拟内存管理,为每个进程提供独立的地址空间。
4+
5+
## 功能概述
6+
7+
- Sv39 三级页表管理,内核与用户地址空间隔离
8+
- ELF 程序加载到独立地址空间
9+
- 异界传送门 (`MultislotPortal`) 实现跨地址空间的上下文切换
10+
- 内核堆分配器初始化,支持动态内存分配
11+
- 系统调用中进行用户地址翻译和权限检查
12+
13+
## 用户程序加载
14+
15+
用户程序在编译时通过 `APP_ASM` 环境变量内联到内核镜像中,运行时解析 ELF 并映射到独立地址空间。
16+
17+
## 系统调用
18+
19+
| 系统调用 | 功能 |
20+
|----------|------|
21+
| `write` | 向标准输出写入数据(需地址翻译) |
22+
| `exit` | 退出当前进程 |
23+
| `sched_yield` | 主动让出 CPU |
24+
| `clock_gettime` | 获取当前时间 |
25+
| `sbrk` | 调整进程堆空间 |
26+
27+
## 异界传送门 (MultislotPortal)
28+
29+
当内核与用户程序使用不同的地址空间时,上下文切换变得复杂——切换 `satp` 后代码可能无法继续执行。`MultislotPortal` 解决这个问题:
30+
31+
1. 传送门页面同时映射到内核和所有用户地址空间的相同虚拟地址
32+
2. 切换时先跳转到传送门,在传送门内切换 `satp`
33+
3. 由于传送门在两个地址空间的虚拟地址相同,切换后代码仍能执行
34+
35+
```rust
36+
// 传送门位于虚拟地址空间最高页
37+
const PROTAL_TRANSIT: VPN<Sv39> = VPN::MAX;
38+
39+
// 用户地址空间共享内核的传送门页表项
40+
process.address_space.root()[portal_idx] = kernel_space.root()[portal_idx];
41+
42+
// 通过传送门执行用户程序
43+
unsafe { ctx.execute(portal, ()) };
44+
```
45+
46+
## Exercise
47+
48+
[Exercise](./exercise.md)
49+
50+
## Dependencies
51+
52+
| 依赖 | 说明 |
53+
|------|------|
54+
| `xmas-elf` | ELF 文件解析 |
55+
| `riscv` | RISC-V CSR 寄存器访问(`satp`, `scause`|
56+
| `tg-sbi` | SBI 调用封装库 |
57+
| `tg-linker` | 链接脚本生成、内核布局定位、用户程序元数据 |
58+
| `tg-console` | 控制台输出 (`print!`/`println!`) 和日志 |
59+
| `tg-kernel-context` | 用户上下文及异界传送门 `MultislotPortal`(启用 `foreign` feature) |
60+
| `tg-kernel-alloc` | 内核内存分配器 |
61+
| `tg-kernel-vm` | 虚拟内存管理 |
62+
| `tg-syscall` | 系统调用定义与分发 |
63+
64+
## Features
65+
66+
| Feature | 说明 |
67+
|---------|------|
68+
| `nobios` | 无需外部 SBI 实现,直接从 QEMU `-bios none` 模式启动 |
69+
70+
## License
71+
72+
Licensed under either of MIT license or Apache License, Version 2.0 at your option.

ch5/README.md

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,83 @@
1-
# 第五章
1+
# 第五章:进程
22

3-
#### 目前基本上实现了进程管理
4-
#### 存在的问题
5-
* `exit_code`:因为进程不存在内核栈,`exit` 进入内核之后,会直接删除 `PCB`,目前是直接写死的 333
6-
* `wait` 系统调用:等待任意的子进程结束,但是由于子进程 `exit` 之后会直接删除,父子关系也会直接断开,所以 `wait` 系统调用的语义产生了变化,导致 `fork` 相关的测例均不能通过
3+
本章实现了完整的进程管理,支持进程创建、执行、等待等操作。
4+
5+
## 功能概述
6+
7+
- 进程控制块 (PCB) 管理进程资源(地址空间、上下文、PID)
8+
- `fork` 创建子进程,复制父进程地址空间
9+
- `exec` 根据程序名加载并执行新程序
10+
- `wait` 等待子进程退出并回收资源
11+
- 进程树结构维护父子关系
12+
- 初始进程 `initproc` 作为所有用户进程的祖先
13+
14+
## 用户程序加载
15+
16+
用户程序在编译时通过 `APP_ASM` 环境变量内联到内核镜像,运行时通过 `APPS` 静态表按名称查找并加载。
17+
18+
## 系统调用
19+
20+
| 系统调用 | 功能 |
21+
|----------|------|
22+
| `fork` | 创建子进程(复制地址空间) |
23+
| `exec` | 加载并执行新程序 |
24+
| `wait` | 等待子进程退出 |
25+
| `exit` | 退出当前进程 |
26+
| `getpid` | 获取当前进程 PID |
27+
| `read` | 从标准输入读取 |
28+
| `write` | 向标准输出写入 |
29+
| `sbrk` | 调整进程堆空间 |
30+
31+
## fork 的实现
32+
33+
`fork` 创建子进程,复制父进程的地址空间。关键在于正确复制页表和物理页面:
34+
35+
```rust
36+
fn fork(&self) -> Option<Process> {
37+
// 复制地址空间(深拷贝所有映射的物理页)
38+
let address_space = self.address_space.clone_from(...);
39+
// 复制上下文
40+
let context = self.context.clone();
41+
// 分配新 PID
42+
let pid = ProcId::new();
43+
// ...
44+
}
45+
```
46+
47+
`fork` 返回后,父进程返回子进程 PID,子进程返回 0:
48+
49+
```rust
50+
// 在子进程上下文中设置返回值为 0
51+
*child_proc.context.context.a_mut(0) = 0;
52+
// 父进程返回子进程 PID
53+
pid.get_usize() as isize
54+
```
55+
56+
## Exercise
57+
58+
[Exercise](./exercise.md)
59+
60+
## Dependencies
61+
62+
| 依赖 | 说明 |
63+
|------|------|
64+
| `xmas-elf` | ELF 文件解析 |
65+
| `riscv` | RISC-V CSR 寄存器访问 |
66+
| `tg-sbi` | SBI 调用封装库 |
67+
| `tg-linker` | 链接脚本生成、内核布局定位、用户程序元数据 |
68+
| `tg-console` | 控制台输出 (`print!`/`println!`) 和日志 |
69+
| `tg-kernel-context` | 用户上下文及异界传送门(启用 `foreign` feature) |
70+
| `tg-kernel-alloc` | 内核内存分配器 |
71+
| `tg-kernel-vm` | 虚拟内存管理 |
72+
| `tg-syscall` | 系统调用定义与分发 |
73+
| `tg-task-manage` | 进程管理框架(启用 `proc` feature) |
74+
75+
## Features
76+
77+
| Feature | 说明 |
78+
|---------|------|
79+
| `nobios` | 无需外部 SBI 实现,直接从 QEMU `-bios none` 模式启动 |
80+
81+
## License
82+
83+
Licensed under either of MIT license or Apache License, Version 2.0 at your option.

0 commit comments

Comments
 (0)