tg-ch3 0.4.2-preview.1

Chapter 3 of rCore Tutorial: Multiprogramming and time-sharing multitasking.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
# 第三章:多道程序与分时多任务

本章在第二章"批处理系统"的基础上,实现了一个**多道程序操作系统**(tg-ch3)。它支持多个用户程序同时驻留在内存中并发执行,通过时钟中断实现**抢占式调度**,通过 `yield` 系统调用支持**协作式调度**,并引入了时间管理功能。

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

- 什么是多道程序系统,它与批处理系统有何区别
- 任务控制块(TCB)如何管理任务的状态和上下文
- 协作式调度与抢占式调度的原理和实现
- 时钟中断的工作机制和时间片轮转算法
- 新增的系统调用:`yield`(让出 CPU)和 `clock_gettime`(获取时间)

> **前置知识**:建议先完成第一章(tg-ch1)和第二章(tg-ch2)的学习,理解裸机启动、Trap 处理、系统调用等基础概念。

## 项目结构

```
ch3/
├── .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         # 内核源码:多道程序主循环、Trap 处理、系统调用
    └── task.rs         # 任务控制块(TCB)和调度事件定义
```

<a id="source-nav"></a>

## 源码阅读导航索引

[返回根文档导航总表](../README.md#chapters-source-nav-map)

本章建议按“任务模型 -> 调度循环 -> 时钟中断/系统调用”顺序阅读。

| 阅读顺序 | 文件 | 重点问题 |
|---|---|---|
| 1 | `src/task.rs` | `TaskControlBlock` 如何封装上下文、栈和任务状态? |
| 2 | `src/main.rs` 的主循环 | 轮转调度如何在多任务之间切换? |
| 3 | 时钟中断分支 | 抢占式调度中,时间片到期后发生了什么? |
| 4 | `yield` 与 syscall 分支 | 协作式让出与普通 syscall 返回路径有何区别? |

配套建议:结合 `tg-sbi::set_timer` 与 `clock_gettime` 实现,串起“硬件时钟 -> 内核调度 -> 用户可见时间”的链路。

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

- [ ] 能运行 `cargo run` 并说明抢占式调度(时钟中断)发生的证据
- [ ] 能运行 `cargo run --features coop` 并说明协作式调度与抢占式差异
- [ ] 能解释 `TaskControlBlock` 中“上下文/栈/完成状态”的作用
- [ ] 能从 Trap 分支区分 `SupervisorTimer` 与 `UserEnvCall` 两类事件
- [ ] 能完成 `./test.sh base`(以及练习时 `./test.sh exercise`)

## 概念-源码-测试三联表

| 核心概念 | 源码入口 | 自测方式(命令/现象) |
|---|---|---|
| 任务控制块(TCB) | `ch3/src/task.rs` | 能说清 `init/execute/handle_syscall` 的职责 |
| 抢占式调度 | `ch3/src/main.rs` 的时钟中断分支 | 日志出现 timeout/轮转切换行为 |
| 协作式调度 | `ch3/src/main.rs` 的 `Event::Yield` 分支 | `--features coop` 下由用户主动让出 CPU |
| 时间系统调用 | `ch3/src/main.rs` 的 `Clock` 实现 | 用户态 `clock_gettime` 返回时间单调递增 |

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

## 一、环境准备

### 1.1 安装 Rust 工具链

**Linux / macOS / WSL:**

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
```

验证安装:

```bash
rustc --version    # 要求 >= 1.85.0(支持 edition 2024)
cargo --version
```

### 1.2 添加 RISC-V 64 编译目标

```bash
rustup target add riscv64gc-unknown-none-elf
```

### 1.3 安装 QEMU 模拟器

**Ubuntu / Debian:**

```bash
sudo apt update
sudo apt install qemu-system-misc
```

**macOS(Homebrew):**

```bash
brew install qemu
```

验证:

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

### 1.4 安装额外工具

tg-ch3 的构建脚本需要 `cargo-clone`(用于自动下载用户程序 crate)和 `rust-objcopy`(用于将 ELF 转为二进制):

```bash
cargo install cargo-clone
# rust-objcopy 由 cargo-binutils 提供
cargo install cargo-binutils
rustup component add llvm-tools
```

### 1.5 获取源代码

**方式一:只获取本实验**

```bash
cargo clone tg-ch3
cd tg-ch3
```

**方式二:获取所有实验**

```bash
git clone https://github.com/rcore-os/rCore-Tutorial-in-single-workspace.git
cd rCore-Tutorial-in-single-workspace/ch3
```

## 二、编译与运行

### 2.1 编译

在 `ch3`(或 `tg-ch3`)目录下执行:

```bash
cargo build
```

编译过程与第二章类似,`build.rs` 会自动完成以下工作:

1. **生成链接脚本**:使用 `tg_linker::NOBIOS_SCRIPT` 生成内核的内存布局
2. **下载用户程序**:自动通过 `cargo clone` 获取 `tg-user` crate(包含用户测试程序)
3. **编译用户程序**:根据 `cases.toml` 中的 `ch3` 或 `ch3_exercise` 配置,为每个用户程序交叉编译
4. **生成 APP_ASM**:生成汇编文件,将所有用户程序的二进制数据内联到内核镜像中

> 环境变量说明:
> - `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`,默认为 `info`)

### 2.2 运行

**默认模式(抢占式调度):**

```bash
cargo run
```

**协作式调度模式:**

```bash
cargo run --features coop
```

启用 `coop` feature 后,禁用时钟中断抢占,任务只能通过 `yield` 主动让出 CPU。

**练习模式:**

```bash
cargo run --features exercise
```

加载练习专用的测试用例(包含 `sys_trace` 相关测试)。

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

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

### 2.3 预期输出

```
[tg-ch3 0.3.0-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
[ INFO] load app1 to 0x802xxxxx
[ INFO] load app2 to 0x802xxxxx
...

power_3 [10000/200000]
power_3 [20000/200000]
...
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
...
AAAAAAAAAA [1/5]
BBBBBBBBBB [1/5]
CCCCCCCCCC [1/5]
...(交替输出,体现时间片轮转)
Test write A OK!
Test write B OK!
Test write C OK!
...
Test sleep OK!
```

与第二章的串行输出不同,你会观察到多个用户程序的输出交替出现(如 power_3、power_5、write_a、write_b 交错),这就是抢占式调度的效果——时钟中断强制切换任务,实现了时间片轮转。

### 2.4 运行测试

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

测试脚本会同时在终端显示 `cargo run` 的完整输出,并通过 `tg-checker` 自动验证输出是否符合预期。

---

## 三、操作系统核心概念

### 3.1 从批处理到多道程序

**第二章的批处理系统**串行执行用户程序:一个程序运行完毕后才加载下一个。这种方式的缺点是:当一个程序等待 I/O 或主动暂停时,CPU 处于空闲状态,造成资源浪费。

**多道程序系统**(Multiprogramming)解决了这个问题:

```
批处理系统                          多道程序系统
┌──────────────────┐               ┌──────────────────┐
│ App0 ██████████  │               │ App0 ██  ██  ██  │
│ App1     ████████│               │ App1   ██  ██  ██│
│ App2         ████│               │ App2  ██  ██  ██ │
│      ──────→ 时间│               │      ──────→ 时间 │
│  串行执行,CPU   │               │  交替执行,CPU     │
│  利用率低        │               │  利用率高         │
└──────────────────┘               └──────────────────┘
```

核心改进:
- **一次性加载**:所有用户程序在启动时同时加载到内存,减少切换开销
- **任务切换**:内核可以在多个任务之间快速切换,保证每个任务都能得到执行
- **调度算法**:决定何时切换、切换到哪个任务

### 3.2 任务控制块(TCB)

**任务控制块**(Task Control Block, TCB)是内核管理任务的核心数据结构。在 tg-ch3 中,每个 TCB 包含:

| 字段 | 类型 | 说明 |
|------|------|------|
| `ctx` | `LocalContext` | 用户态上下文(所有通用寄存器 + CSR) |
| `finish` | `bool` | 任务是否已完成 |
| `stack` | `[usize; 1024]` | 独立的用户栈(8 KiB) |

与第二章相比,本章将用户上下文和栈空间封装到 TCB 中,使得多个任务可以独立管理,互不干扰。

**任务状态变化:**

```
          init()
  [未初始化] ──→ [就绪]
                   │
           execute()│
                   ▼
                [运行中]
               ╱   │    ╲
         yield/  exit/   异常/
         超时   退出   被杀死
             ╱     │      ╲
           ▼      ▼      ▼
        [就绪] [已完成] [已完成]
```

### 3.3 任务切换机制

任务切换是操作系统的核心机制。tg-ch3 使用 `tg-kernel-context` 库中的 `LocalContext` 实现:

1. **保存当前任务上下文**:将所有用户寄存器保存到当前 TCB 的 `ctx` 中
2. **恢复目标任务上下文**:从目标 TCB 的 `ctx` 中恢复用户寄存器
3. **切换执行**:通过 `sret` 指令返回到目标任务的用户态

```
当前任务(App A)                    下一个任务(App B)
     │                                    ▲
     ▼                                    │
  触发 Trap                           sret 返回
     │                                    ▲
     ▼                                    │
  保存 A 的上下文到 TCB[A]     恢复 B 的上下文从 TCB[B]
     │                                    ▲
     └──────── 内核调度决策 ───────────────┘
```

与第二章的 Trap 处理相比,本章增加了"不结束当前任务但切换到下一个"的逻辑。

### 3.4 协作式调度(yield)

**协作式调度**依赖任务主动让出 CPU。用户程序调用 `yield` 系统调用,告诉内核"我暂时不需要 CPU 了,可以去执行别的任务"。

**典型使用场景**:当程序需要等待外设完成 I/O 操作时,与其忙等浪费 CPU 时间,不如 yield 让出 CPU 给其他任务。

```
App A 发起 I/O 请求                App B 在运行
     │                                 │
     ├─ 调用 yield                     │
     │   (ecall,a7=124)             │
     │                                 │
     ▼                                 ▼
  内核处理:                        继续执行
  标记 A 为"就绪"                       │
  切换到 B                             │
     │                                 │
     ...(一段时间后轮转回 A)...       │
     │                                 │
     ▼                                 │
  A 继续执行                           │
  检查 I/O 是否完成                    │
```

在 tg-ch3 中,启用 `coop` feature 可以体验纯协作式调度——时钟中断被禁用,任务只能通过 yield 主动让出 CPU。

### 3.5 抢占式调度(时钟中断)

**协作式调度的问题**:如果一个任务永远不调用 yield(例如进入死循环),其他任务就永远得不到执行。

**抢占式调度**通过时钟中断解决这个问题:

```
App A 正在执行(可能是死循环)
     │
     │  ← 12500 个时钟周期后
     │     时钟中断触发!
     ▼
  硬件自动陷入 S-mode
     │
     ▼
  scause = Interrupt::SupervisorTimer
     │
     ▼
  内核处理:
  1. 清除时钟中断(set_timer(u64::MAX))
  2. 切换到下一个任务
     │
     ▼
  App B 开始执行
```

**关键代码逻辑:**

```rust
// 每次切换到用户程序前,设置时钟中断
tg_sbi::set_timer(time::read64() + 12500);
unsafe { tcb.execute() };

// Trap 返回后判断原因
match scause::read().cause() {
    Trap::Interrupt(Interrupt::SupervisorTimer) => {
        tg_sbi::set_timer(u64::MAX);  // 清除中断
        false  // 不结束任务,切换到下一个
    }
    // ...
}
```

**时间片轮转算法(Round-Robin):**

tg-ch3 使用最简单的轮转算法:维护一个任务索引 `i`,每次时钟中断后 `i = (i + 1) % n`,循环执行各任务。每个任务获得相等的时间片(12500 个时钟周期 ≈ 1ms,在 QEMU 12.5MHz 时钟下)。

### 3.6 时钟中断的实现

RISC-V 的时钟中断机制:

| 组件 | 说明 |
|------|------|
| `mtime` 寄存器 | 硬件计数器,持续递增 |
| `mtimecmp` 寄存器 | 比较值,当 `mtime >= mtimecmp` 时触发中断 |
| `sie.stie` | S 特权级时钟中断使能位 |
| `set_timer()` | 通过 SBI 调用设置 `mtimecmp` |

初始化步骤:
1. `unsafe { sie::set_stimer() }` —— 开启 S 特权级时钟中断
2. 每次执行用户程序前:`set_timer(time::read64() + interval)` —— 设置下次中断时间

时钟中断到达后:
1. 硬件自动陷入 S-mode
2. `scause = Interrupt::SupervisorTimer`
3. 内核清除中断并切换任务

### 3.7 系统调用

tg-ch3 在第二章的基础上新增了 `yield` 和 `clock_gettime` 两个系统调用:

| syscall ID | 名称 | 功能 |
|-----------|------|------|
| 64 | `write` | 将缓冲区数据写入文件描述符(fd=1 为标准输出) |
| 93 | `exit` | 退出当前任务 |
| 124 | `sched_yield` | 主动让出 CPU,切换到下一个任务 |
| 113 | `clock_gettime` | 获取当前时间(纳秒精度) |
| 410 | `trace` | 追踪系统调用信息(**练习题**,需自行实现) |

**clock_gettime 的实现原理:**

```
用户程序调用 clock_gettime(CLOCK_MONOTONIC, &ts)
       │
       ▼
内核读取 RISC-V time 寄存器
       │
       ▼
将 tick 数转换为纳秒:time * 10000 / 125 = time * 80 ns
       │
       ▼
填充 TimeSpec { tv_sec, tv_nsec } 写回用户空间
```

### 3.8 调度事件机制

tg-ch3 引入了 `SchedulingEvent` 枚举来统一描述系统调用的调度效果:

| 事件 | 含义 | 触发条件 |
|------|------|---------|
| `None` | 继续执行当前任务 | write、clock_gettime 等普通系统调用 |
| `Yield` | 切换到下一个任务 | yield 系统调用 |
| `Exit(code)` | 任务退出 | exit 系统调用 |
| `UnsupportedSyscall(id)` | 杀死任务 | 不支持的系统调用 |

这种设计将系统调用的处理逻辑(在 `handle_syscall` 中)与调度决策(在主循环中)清晰分离。

---

## 四、代码解读

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

程序结构分为五个部分:

**模块文档与属性(第 1-22 行):**
与第二章相同的 `#![no_std]`、`#![no_main]` 和条件编译属性。新增了 `mod task` 引入任务管理模块。

**外部依赖引入(第 30-41 行):**
与第二章类似,但增加了 `task::TaskControlBlock` 的引用。

**启动与初始化(第 45-56 行):**
- `global_asm!(include_str!(env!("APP_ASM")))`:嵌入用户程序
- `APP_CAPACITY = 32`:最大支持 32 个应用
- `boot0!(rust_main; stack = (APP_CAPACITY + 2) * 8192)`:分配 272 KiB 内核栈(因为 TCB 中包含用户栈)

**内核主函数 `rust_main`(第 60-153 行):**

核心的多道程序循环:

```rust
// 初始化 → 加载所有应用到 TCB 数组
// → 开启时钟中断
// → 轮转执行:
while remain > 0 {
    if !tcb.finish {
        set_timer(...);        // 设置时间片
        tcb.execute();         // 切换到 U-mode
        match scause {
            Timer     → 切换到下一个任务
            UserEnvCall → 处理系统调用
            Exception → 杀死任务
        }
    }
    i = (i + 1) % n;         // 轮转到下一个
}
shutdown()
```

**接口实现模块 `impls`(第 164-237 行):**
在第二章的 `Console`、`IO`、`Process` 基础上,新增了:
- `Scheduling`:处理 yield 系统调用
- `Clock`:处理 clock_gettime 系统调用
- `Trace`:练习题的占位实现

### 4.2 `src/task.rs` —— 任务管理

定义了两个核心类型:

**`TaskControlBlock`**:任务控制块
- `init(entry)` —— 创建用户态上下文,分配独立用户栈
- `execute()` —— 切换到 U-mode 执行
- `handle_syscall()` —— 处理系统调用并返回调度事件

**`SchedulingEvent`**:调度事件枚举
- `None` / `Yield` / `Exit(code)` / `UnsupportedSyscall(id)`

### 4.3 `build.rs` —— 构建脚本

与第二章结构相同,但根据 `exercise` feature 选择不同的测试用例集:

```rust
let case_key = if env::var("CARGO_FEATURE_EXERCISE").is_ok() {
    "ch3_exercise"   // 练习模式测例
} else {
    "ch3"            // 基础模式测例
};
```

| 函数 | 功能 |
|------|------|
| `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) |

### 4.4 `Cargo.toml` —— 配置与依赖

**Features:**

| Feature | 说明 |
|---------|------|
| `coop` | 协作式调度:禁用时钟中断,任务需主动 yield |
| `exercise` | 练习模式:加载练习测例 |

**Dependencies:**

| 依赖 | 说明 |
|------|------|
| `riscv` | RISC-V CSR 寄存器访问(`sie`、`scause`、`time`) |
| `tg-sbi` | SBI 调用封装,包括 `set_timer` 设置时钟中断 |
| `tg-linker` | 链接脚本生成、内核布局定位、用户程序元数据 |
| `tg-console` | 控制台输出(`print!` / `println!`)和日志 |
| `tg-kernel-context` | 用户上下文 `LocalContext`,实现特权级切换 |
| `tg-syscall` | 系统调用定义与分发(含 `Scheduling`、`Clock`、`Trace` trait) |

---

## 五、编程练习:实现 `sys_trace`

### 5.1 题目描述

在 tg-ch3 中,我们的系统已经能够支持多个任务分时轮流运行。我们希望引入一个新的系统调用 `sys_trace`(ID 为 410)用来追踪当前任务系统调用的历史信息,并做对应的修改。定义如下:

```rust
fn trace(&self, _caller: tg_syscall::Caller, _trace_request: usize, _id: usize, _data: usize) -> isize
```

**调用规范:**

这个系统调用有三种功能,根据 `trace_request` 的值不同,执行不同的操作:

| trace_request | 功能 | 参数说明 | 返回值 |
|--------------|------|---------|--------|
| 0 | 读取用户内存 | `id` 视为 `*const u8`,读取该地址处 1 字节 | 该地址处的值(无符号) |
| 1 | 写入用户内存 | `id` 视为 `*mut u8`,写入 `data` 的最低字节 | 0 |
| 2 | 查询系统调用计数 | `id` 为系统调用编号 | 该系统调用的调用次数(**本次调用也计入统计**) |
| 其他 | 无效 | 忽略其他参数 | -1 |

**说明:**
- 读写操作在未实现地址空间前并不安全,使用不当可能导致崩溃。**不要求实现安全检查机制,只需通过测试用例即可**。
- 本章只要求追踪自身任务的信息,在后续章节引入进程、线程等概念后才会扩展到追踪其他任务。

### 5.2 实现提示

- **大胆修改已有框架!** 除了配置文件,你几乎可以随意修改已有框架的内容。
- **系统调用次数**可以考虑在 `TaskControlBlock::handle_syscall()` 中统计。
- 可以**扩展 `TaskControlBlock` 结构**来维护系统调用计数信息。
- 不要害怕使用 `unsafe` 做类型转换,这在内核处理用户调用时是不可避免的。
- 在实现时,可以把系统调用参数中前缀的下划线去掉,这样更清晰。

### 5.3 实验要求

**目录结构:**

```
tg-ch3/
├── Cargo.toml          # 内核配置文件
├── src/                # 内核源代码(需要修改)
│   ├── main.rs         # 内核主函数,包括系统调用接口实现
│   └── task.rs         # 任务控制块(需要扩展)
└── tg-user/            # 用户程序(运行时自动拉取,无需修改)
    └── src/bin         # 测试用例
```

> `tg-user` 会在运行时自动拉取到 `tg-ch3/tg-user` 目录下,只需修改 `tg-ch3/src/` 目录下的内核代码。

**运行练习测例:**

```bash
cargo run --features exercise
```

**测试练习测例:**

```bash
./test.sh exercise
```

---

## 六、本章小结

通过本章的学习和实践,你在第二章的基础上实现了重要的进化:

1. **从串行到并发**:批处理系统一次只运行一个程序,多道程序系统让多个程序交替执行,大幅提高 CPU 利用率
2. **任务控制块(TCB)**:将任务的上下文、状态和栈空间封装到统一的数据结构中,是后续章节进程管理的基础
3. **协作式调度**:任务通过 `yield` 主动让出 CPU,适用于 I/O 密集型场景
4. **抢占式调度**:时钟中断强制切换任务,保证公平性,防止任务独占 CPU
5. **时间片轮转**:最基本的调度算法,每个任务获得相等的时间片
6. **时间管理**:通过 `clock_gettime` 让用户程序获取系统时间

在后续章节中,我们将引入**地址空间**,为每个任务提供独立的虚拟内存,进一步增强隔离性和安全性。

## 七、思考题

1. **协作式 vs 抢占式调度的权衡?** 协作式调度的优点和缺点分别是什么?在什么场景下协作式调度更合适?可以用 `cargo run --features coop` 体验协作式调度。

2. **时间片大小的影响?** tg-ch3 使用 12500 个时钟周期作为时间片。如果把时间片设得非常大(如 1 秒),系统行为会如何变化?如果设得非常小(如 10 个时钟周期),又会有什么问题?

3. **为什么需要 `SchedulingEvent` 枚举?** 如果不用枚举,直接在 `handle_syscall` 中决定是否切换任务,会有什么设计上的问题?

4. **时钟中断和 `sstatus.sie` 的关系?** 在 Trap 处理过程中,时钟中断会被屏蔽吗?为什么 RISC-V 默认这样设计?这与嵌套中断有什么关系?

5. **如果一个用户程序的用户栈溢出,会发生什么?** 在当前 tg-ch3 的设计中,栈溢出可能覆盖哪些数据?如何改进设计来检测栈溢出?

## 参考资料

- [rCore-Tutorial-Guide 第三章](https://learningos.github.io/rCore-Tutorial-Guide/)
- [rCore-Tutorial-Book 第三章](https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter3/index.html)
- [RISC-V Privileged Specification](https://riscv.org/specifications/privileged-isa/)
- [RISC-V Reader 中文版](http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf)

---

## 附录:rCore-Tutorial 组件分析表

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

| 操作系统内核 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|:-----|:------------|:---------|:---------------|
| **tg-ch1** | 应用程序执行环境<br>裸机编程(Bare-metal)<br>SBI(Supervisor Binary Interface)<br>RISC-V 特权级(M/S-mode)<br>链接脚本(Linker Script)<br>内存布局(Memory Layout)<br>Panic 处理 | 最小 S-mode 裸机程序<br>QEMU 直接启动(无 OpenSBI)<br>打印 "Hello, world!" 并关机<br>演示最基本的 OS 执行环境 | tg-sbi |
| **tg-ch2** | 批处理系统(Batch Processing)<br>特权级切换(U-mode ↔ S-mode)<br>Trap 处理(ecall / 异常)<br>上下文保存与恢复<br>系统调用(write / exit)<br>用户态 / 内核态<br>`sret` 返回指令 | 批处理操作系统<br>顺序加载运行多个用户程序<br>特权级切换和 Trap 处理框架<br>实现 write / exit 系统调用 | tg-sbi<br>tg-linker<br>tg-console<br>tg-kernel-context<br>tg-syscall |
| **tg-ch3** | 多道程序(Multiprogramming)<br>任务控制块(TCB)<br>协作式调度(yield)<br>抢占式调度(Preemptive)<br>时钟中断(Clock Interrupt)<br>时间片轮转(Time Slice)<br>任务切换(Task Switch)<br>任务状态(Ready/Running/Finished)<br>clock_gettime 系统调用 | 多道程序与分时多任务<br>多程序同时驻留内存<br>协作式 + 抢占式调度<br>时钟中断与时间管理 | tg-sbi<br>tg-linker<br>tg-console<br>tg-kernel-context<br>tg-syscall |
| **tg-ch4** | 虚拟内存(Virtual Memory)<br>Sv39 三级页表(Page Table)<br>地址空间隔离(Address Space)<br>页表项(PTE)与标志位<br>地址转换(VA → PA)<br>异界传送门(MultislotPortal)<br>ELF 加载与解析<br>堆管理(sbrk)<br>恒等映射(Identity Mapping)<br>内存保护(Memory Protection)<br>satp CSR | 引入 Sv39 虚拟内存<br>每个用户进程独立地址空间<br>跨地址空间上下文切换<br>进程隔离和内存保护 | tg-sbi<br>tg-linker<br>tg-console<br>tg-kernel-context<br>tg-kernel-alloc<br>tg-kernel-vm<br>tg-syscall |
| **tg-ch5** | 进程(Process)<br>进程控制块(PCB)<br>进程标识符(PID)<br>fork(地址空间深拷贝)<br>exec(程序替换)<br>waitpid(等待子进程)<br>进程树 / 父子关系<br>初始进程(initproc)<br>Shell 交互式命令行<br>进程生命周期(Ready/Running/Zombie)<br>步幅调度(Stride Scheduling) | 引入进程管理<br>fork / exec / waitpid 系统调用<br>动态创建、替换、等待进程<br>Shell 交互式命令行 | tg-sbi<br>tg-linker<br>tg-console<br>tg-kernel-context<br>tg-kernel-alloc<br>tg-kernel-vm<br>tg-syscall<br>tg-task-manage |
| **tg-ch6** | 文件系统(File System)<br>easy-fs 五层架构<br>SuperBlock / Inode / 位图<br>DiskInode(直接+间接索引)<br>目录项(DirEntry)<br>文件描述符表(fd_table)<br>文件句柄(FileHandle)<br>VirtIO 块设备驱动<br>MMIO(Memory-Mapped I/O)<br>块缓存(Block Cache)<br>硬链接(Hard Link)<br>open / close / read / write 系统调用 | 引入文件系统与 I/O<br>用户程序存储在磁盘镜像(fs.img)<br>VirtIO 块设备驱动<br>easy-fs 文件系统实现<br>文件打开 / 关闭 / 读写 | tg-sbi<br>tg-linker<br>tg-console<br>tg-kernel-context<br>tg-kernel-alloc<br>tg-kernel-vm<br>tg-syscall<br>tg-task-manage<br>tg-easy-fs |
| **tg-ch7** | 进程间通信(IPC)<br>管道(Pipe)<br>环形缓冲区(Ring Buffer)<br>统一文件描述符(Fd 枚举)<br>信号(Signal)<br>信号集(SignalSet)<br>信号屏蔽字(Signal Mask)<br>信号处理函数(Signal Handler)<br>kill / sigaction / sigprocmask / sigreturn<br>命令行参数(argc / argv)<br>I/O 重定向(dup) | 进程间通信-管道 <br>异步事件通知(信号)<br>统一文件描述符抽象<br>信号发送 / 注册 / 屏蔽 / 返回 | tg-sbi<br>tg-linker<br>tg-console<br>tg-kernel-context<br>tg-kernel-alloc<br>tg-kernel-vm<br>tg-syscall<br>tg-task-manage<br>tg-easy-fs<br>tg-signal<br>tg-signal-impl |
| **tg-ch8** | 同步互斥(Sync&Mutex)<br>线程(Thread)/ 线程标识符(TID)<br>进程-线程分离<br>竞态条件(Race Condition)<br>临界区(Critical Section)<br>互斥(Mutual Exclusion)<br>互斥锁(Mutex:自旋锁 vs 阻塞锁)<br>信号量(Semaphore:P/V 操作)<br>条件变量(Condvar)<br>管程(Monitor:Mesa 语义)<br>线程阻塞与唤醒(wait queue)<br>死锁(Deadlock)/ 死锁四条件<br>银行家算法(Banker's Algorithm)<br>双层管理器(PThreadManager) | 进程-线程分离<br>同一进程内多线程并发<br>互斥锁(MutexBlocking)<br>信号量(Semaphore)<br>条件变量(Condvar)<br>线程阻塞与唤醒机制<br>死锁检测(练习) | tg-sbi<br>tg-linker<br>tg-console<br>tg-kernel-context<br>tg-kernel-alloc<br>tg-kernel-vm<br>tg-syscall<br>tg-task-manage<br>tg-easy-fs<br>tg-signal<br>tg-signal-impl<br>tg-sync |

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

| 功能组件 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|:-----|:------------|:---------|:----------------------|
| **tg-sbi** | SBI(Supervisor Binary Interface)<br>console_putchar / console_getchar<br>系统关机(shutdown)<br>RISC-V 特权级(M/S-mode)<br>ecall 指令 | S→M 模式的 SBI 调用封装<br>字符输出 / 字符读取<br>系统关机<br>支持 nobios 直接操作 UART | 无 |
| **tg-console** | 控制台 I/O<br>格式化输出(print! / println!)<br>日志系统(Log Level)<br>自旋锁保护的全局控制台 | 可定制 print! / println! 宏<br>log::Log 日志实现<br>Console trait 抽象底层输出 | 无 |
| **tg-kernel-context** | 上下文(Context)<br>Trap 帧(Trap Frame)<br>寄存器保存与恢复<br>特权级切换<br>stvec / sepc / scause CSR<br>LocalContext(本地上下文)<br>ForeignContext(跨地址空间上下文)<br>异界传送门(MultislotPortal) | 用户/内核态切换上下文管理<br>LocalContext 结构<br>ForeignContext(含 satp)<br>MultislotPortal 跨地址空间执行 | 无 |
| **tg-kernel-alloc** | 内核堆分配器<br>伙伴系统(Buddy Allocation)<br>动态内存管理<br>#[global_allocator] | 基于伙伴算法的 GlobalAlloc<br>堆初始化(init)<br>物理内存转移(transfer) | 无 |
| **tg-kernel-vm** | 虚拟内存管理<br>页表(Page Table)<br>Sv39 分页(三级页表)<br>虚拟地址(VAddr)/ 物理地址(PAddr)<br>虚拟页号(VPN)/ 物理页号(PPN)<br>页表项(PTE)/ 页表标志位(VmFlags)<br>地址空间(AddressSpace)<br>PageManager trait<br>地址翻译(translate) | Sv39 页表管理<br>AddressSpace 地址空间抽象<br>虚实地址转换<br>页面映射(map / map_extern)<br>页表项操作 | 无 |
| **tg-syscall** | 系统调用(System Call)<br>系统调用号(SyscallId)<br>系统调用分发(handle)<br>系统调用结果(Done / Unsupported)<br>Caller 抽象<br>IO / Process / Scheduling / Clock /<br>Signal / Thread / SyncMutex trait 接口 | 系统调用 ID 与参数定义<br>trait 接口供内核实现<br>init_io / init_process / init_scheduling /<br>init_clock / init_signal /<br>init_thread / init_sync_mutex<br>支持 kernel / user feature | tg-signal-defs |
| **tg-task-manage** | 任务管理(Task Management)<br>调度(Scheduling)<br>进程管理器(PManager, proc feature)<br>双层管理器(PThreadManager, thread feature)<br>ProcId / ThreadId<br>就绪队列(Ready Queue)<br>Manage trait / Schedule trait<br>进程等待(wait / waitpid)<br>线程等待(waittid)<br>阻塞与唤醒(blocked / re_enque) | Manage 和 Schedule trait 抽象<br>proc feature:单层进程管理器(PManager)<br>thread feature:双层管理器(PThreadManager)<br>进程树 / 父子关系<br>线程阻塞 / 唤醒 | 无 |
| **tg-easy-fs** | 文件系统(File System)<br>SuperBlock / Inode / 位图(Bitmap)<br>DiskInode(直接+间接索引)<br>块缓存(Block Cache)<br>BlockDevice trait<br>文件句柄(FileHandle)<br>打开标志(OpenFlags)<br>管道(Pipe)/ 环形缓冲区<br>用户缓冲区(UserBuffer)<br>FSManager trait | easy-fs 五层架构实现<br>文件创建 / 读写 / 目录操作<br>块缓存管理<br>管道环形缓冲区实现<br>FSManager trait 抽象 | 无 |
| **tg-signal-defs** | 信号编号(SignalNo)<br>SIGKILL / SIGINT / SIGUSR1 等<br>信号动作(SignalAction)<br>信号集(SignalSet)<br>最大信号数(MAX_SIG) | 信号编号枚举定义<br>信号动作结构定义<br>信号集类型定义<br>为 tg-signal 和 tg-syscall 提供共用类型 | 无 |
| **tg-signal** | 信号处理(Signal Handling)<br>Signal trait 接口<br>add_signal / handle_signals<br>get_action_ref / set_action<br>update_mask / sig_return / from_fork<br>SignalResult(Handled / ProcessKilled) | Signal trait 接口定义<br>信号添加 / 处理 / 动作设置<br>屏蔽字更新 / 信号返回<br>fork 继承 | tg-kernel-context<br>tg-signal-defs |
| **tg-signal-impl** | SignalImpl 结构<br>已接收信号位图(received)<br>信号屏蔽字(mask)<br>信号处理中状态(handling)<br>信号动作表(actions)<br>信号处理函数调用<br>上下文保存与恢复 | Signal trait 的参考实现<br>信号接收位图管理<br>屏蔽字逻辑<br>处理状态和动作表 | tg-kernel-context<br>tg-signal |
| **tg-sync** | 互斥锁(Mutex trait: lock / unlock)<br>阻塞互斥锁(MutexBlocking)<br>信号量(Semaphore: up / down)<br>条件变量(Condvar: signal / wait_with_mutex)<br>等待队列(VecDeque\<ThreadId\>)<br>UPIntrFreeCell | MutexBlocking 阻塞互斥锁<br>Semaphore 信号量<br>Condvar 条件变量<br>通过 ThreadId 与调度器交互 | tg-task-manage |
| **tg-user** | 用户态程序(User-space App)<br>用户库(User Library)<br>系统调用封装(syscall wrapper)<br>用户堆分配器<br>用户态 print! / println! | 用户测试程序运行时库<br>系统调用封装<br>用户堆分配器<br>各章节测试用例(ch2~ch8) | tg-console<br>tg-syscall |
| **tg-checker** | 测试验证<br>输出模式匹配<br>正则表达式(Regex)<br>测试用例判定 | rCore-Tutorial CLI 测试输出检查工具<br>验证内核输出匹配预期模式<br>支持 --ch N 和 --exercise 模式 | 无 |
| **tg-linker** | 链接脚本(Linker Script)<br>内核内存布局(KernelLayout)<br>.text / .rodata / .data / .bss / .boot 段<br>入口点(boot0! 宏)<br>BSS 段清零 | 形成内核空间布局的链接脚本模板<br>用于 build.rs 工具构建 linker.ld<br>内核布局定位(KernelLayout::locate)<br>入口宏(boot0!)<br>段信息迭代 | 无 |
## License

Licensed under GNU GENERAL PUBLIC LICENSE, Version 3.0.