tg-rcore-tutorial-ch6 0.4.8

Chapter 6 of rCore Tutorial: File system with easy-fs and virtio block device.
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
# 第六章:文件系统

本章在第五章"进程管理"的基础上,引入了 **文件系统** 支持。用户程序不再嵌入内核镜像,而是存放在 **磁盘镜像**(fs.img)中,内核通过 **VirtIO 块设备驱动** 和 **easy-fs 文件系统** 按名称加载和执行程序。同时,进程拥有了**文件描述符表**,可以通过 `open`/`close`/`read`/`write` 等标准接口操作文件。

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

- 什么是文件系统,为什么需要文件系统
- easy-fs 的五层架构(块设备 → 块缓存 → 磁盘数据结构 → 磁盘管理器 → Inode)
- 磁盘布局:SuperBlock、Inode Bitmap、Inode Area、Data Bitmap、Data Area
- 文件描述符表和文件句柄的设计
- VirtIO 块设备驱动的工作原理
- open/close/read/write 系统调用的实现
- 硬链接的概念和实现(练习题)

> **前置知识**:建议先完成第一章至第五章的学习,理解裸机启动、Trap 处理、系统调用、多任务调度、虚拟内存和进程管理。

## 练习任务(以教代学,学以致用):

- 学:读本文件,了解相关OS知识,在某个开发环境(在线或本地)中正确编译运行rcore-tutorial-ch6;根据本章的`exercise.md`完成作业练习。
- 教:分析并改进rcore-tutorial-ch6的文档和代码,让自己更高效地完成本章学习。
- 用:基于rcore-tutorial-ch6的源代码,实现用户态打砖块游戏应用,支持碰撞反弹、计分,及快捷键保存/恢复游戏进度等基本功能;并扩展操作系统内核功能,支持用户态打砖块游戏应用。[demo](https://github.com/rcore-os/tg-rcore-tutorial-game-demo/blob/main/ch6-breakout.gif)

注:与AI充分合作,并保存与AI合作的交互过程,总结如何做到与AI合作提升自己的操作系统知识与能力。

## 项目结构

```
ch6/
├── .cargo/
│   └── config.toml     # Cargo 配置:交叉编译目标和 QEMU runner(含块设备参数)
├── .gitignore           # Git 忽略规则
├── build.rs            # 构建脚本:编译用户程序,打包 easy-fs 磁盘镜像
├── Cargo.toml          # 项目配置与依赖
├── LICENSE             # GPL v3 许可证
├── README.md           # 本文档
├── rust-toolchain.toml # Rust 工具链配置
├── test.sh             # 自动测试脚本
└── src/
    ├── main.rs         # 内核主体:初始化、调度循环、系统调用实现
    ├── fs.rs           # 文件系统管理:easy-fs 封装
    ├── process.rs      # 进程结构:含文件描述符表
    ├── processor.rs    # 处理器管理:进程管理器
    └── virtio_block.rs # VirtIO 块设备驱动
```

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

## 源码阅读导航索引

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

本章建议按“块设备 -> 文件系统 -> fd_table -> 文件 syscall”逐层阅读。

| 阅读顺序 | 文件 | 重点问题 |
|---|---|---|
| 1 | `src/virtio_block.rs` | VirtIO 驱动如何把块设备能力暴露给 easy-fs? |
| 2 | `src/fs.rs` | `FS`、`open`、`read_all` 如何构成“按文件加载程序”的路径? |
| 3 | `src/process.rs` | 进程的 `fd_table` 如何初始化并在 `fork` 时继承? |
| 4 | `src/main.rs` 的 `impls::IO` | `open/read/write/close` 如何经由 fd 映射到具体文件对象? |
| 5 | `src/main.rs` 的 `kernel_space` | MMIO 映射为何是块设备可用的前提? |

配套建议:结合 `tg-rcore-tutorial-easy-fs` 的 `layout/efs/vfs` 注释阅读,能更完整理解“磁盘块 -> 文件语义”的抽象过程。

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

- [ ] 能说明“用户程序从内嵌镜像迁移到 fs.img”的核心变化
- [ ] 能解释 VirtIO MMIO 映射为何是文件系统可用前提
- [ ] 能从代码追踪 `open/read/write/close` 经 fd_table 到具体文件对象的路径
- [ ] 能在 shell 中运行至少一个文件读写相关用户程序并解释结果
- [ ] 能执行 `./test.sh base`(练习时补充 `./test.sh exercise`)

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

| 核心概念 | 源码入口 | 自测方式(命令/现象) |
|---|---|---|
| 块设备驱动接入 | `tg-rcore-tutorial-ch6/src/virtio_block.rs` | 启动日志出现 VirtIO MMIO 映射,文件系统可读写 |
| 文件系统封装 | `tg-rcore-tutorial-ch6/src/fs.rs` 的 `FS/open/read_all` | `initproc` 能从文件系统成功加载 |
| 进程 fd 表 | `tg-rcore-tutorial-ch6/src/process.rs` 的 `fd_table` | `fork` 后子进程可继承并使用已打开 fd |
| 文件 syscall 实现 | `tg-rcore-tutorial-ch6/src/main.rs` 的 `impls::IO` | `open/read/write/close` 行为与期望一致 |

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

## 一、环境准备

### 1.1 安装 Rust 工具链

```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 安装额外工具

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

### 1.5 获取源代码

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

```bash
cargo clone tg-rcore-tutorial-ch6
cd tg-rcore-tutorial-ch6
```

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

```bash
git clone --recurse-submodules https://github.com/rcore-os/tg-rcore-tutorial.git
cd tg-rcore-tutorial-ch6
```

## 二、编译与运行

### 2.1 编译

```bash
cargo build
```

编译过程与前几章类似,但 `build.rs` 有重要变化:
1. 下载并编译 `tg-rcore-tutorial-user` 用户程序
2. **不再**将用户程序嵌入内核镜像,而是打包到 **easy-fs 磁盘镜像** `fs.img` 中

> 环境变量说明:
> - `TG_USER_DIR`:指定本地 tg-rcore-tutorial-user 源码路径
> - `TG_USER_VERSION`:指定 tg-rcore-tutorial-user 版本(默认 `0.2.0-preview.1`)
> - `TG_SKIP_USER_APPS`:跳过用户程序编译
> - `LOG`:设置日志级别

### 2.2 运行

**基础模式:**

```bash
cargo run
```

**练习模式:**

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

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

```bash
qemu-system-riscv64 \
    -machine virt \
    -nographic \
    -bios none \
    -drive file=target/riscv64gc-unknown-none-elf/debug/fs.img,if=none,format=raw,id=x0 \
    -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 \
    -kernel target/riscv64gc-unknown-none-elf/debug/tg-rcore-tutorial-ch6
```

注意与第五章不同:QEMU 命令中多了 `-drive` 和 `-device` 参数,用于挂载 `fs.img` 磁盘镜像作为 VirtIO 块设备。

### 2.3 预期输出

```
[tg-rcore-tutorial-ch6 ...] Hello, world!
[ INFO] .text    ---> 0x80200000..0x8023xxxx
[ INFO] .rodata  ---> 0x8023xxxx..0x8024xxxx
[ INFO] .data    ---> 0x8024xxxx..0x81exxxxx
[ INFO] .boot    ---> 0x81exxxxx..0x81exxxxx
[ INFO] (heap)   ---> 0x81exxxxx..0x83200000
[ INFO] MMIO range -> 0x10001000..0x10002000

Rust user shell
>> ch5b_forktest_simple
...
Shell: Process 2 exited with code 0
>> 
```

与第五章不同,你会看到:
- MMIO 地址范围的映射信息(VirtIO 块设备)
- 用户程序从磁盘镜像(而非内核内嵌)加载和执行
- Shell 交互功能与第五章相同

### 2.4 运行测试

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

---

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

### 3.1 为什么需要文件系统?

在前几章中,用户程序直接嵌入内核镜像(通过 `APP_ASM` 或 `APPS` 表)。这存在明显的局限性:

| 问题 | 说明 |
|------|------|
| **耦合性** | 程序与内核绑定,修改用户程序需要重新编译内核 |
| **灵活性** | 无法在运行时动态创建、修改、删除文件 |
| **持久性** | 数据仅存在于内存中,关机后丢失 |
| **标准化** | 没有统一的文件操作接口(open/read/write/close) |

**文件系统** 通过在磁盘上组织数据,解决了这些问题:
- 程序和数据以文件形式存储在磁盘上
- 内核通过文件系统接口访问磁盘
- 提供标准的文件操作 API
- 数据在重启后仍然存在

### 3.2 easy-fs 文件系统架构

easy-fs 是一个简化的类 UNIX inode 文件系统,采用五层架构:

```
┌─────────────────────────────────┐
│  第 5 层:Inode(虚拟文件系统)   │  文件/目录操作接口
│  find / create / read / write    │
├─────────────────────────────────┤
│  第 4 层:磁盘管理器              │  文件系统全局管理
│  EasyFileSystem                  │  inode/数据块分配
├─────────────────────────────────┤
│  第 3 层:磁盘数据结构            │  SuperBlock / DiskInode
│  Bitmap / DirEntry               │  DiskInode 索引结构
├─────────────────────────────────┤
│  第 2 层:块缓存                  │  BlockCache + CacheManager
│  缓存磁盘块到内存                 │  自动回写脏块
├─────────────────────────────────┤
│  第 1 层:块设备接口              │  BlockDevice trait
│  read_block / write_block        │  由 VirtIO 驱动实现
└─────────────────────────────────┘
```

### 3.3 磁盘布局

easy-fs 将磁盘划分为五个区域:

```
+------------+--------------+------------+-------------+-----------+
| SuperBlock | Inode Bitmap | Inode Area | Data Bitmap | Data Area |
+------------+--------------+------------+-------------+-----------+
   1 块        若干块          若干块        若干块         若干块
```

| 区域 | 作用 |
|------|------|
| **SuperBlock** | 文件系统元信息(魔数、总块数、各区域大小) |
| **Inode Bitmap** | inode 分配位图,每 bit 对应一个 inode |
| **Inode Area** | 存储 DiskInode(文件元数据:大小、类型、数据块索引) |
| **Data Bitmap** | 数据块分配位图 |
| **Data Area** | 存储文件实际数据和目录项 |

**DiskInode 索引结构(支持大文件):**

```
DiskInode(128 字节)
├── 28 个直接索引块(每块 512 字节 → 14 KiB)
├── 1 个一级间接索引(128 个块 → 64 KiB)
└── 1 个二级间接索引(128 × 128 个块 → 8 MiB)
最大文件大小 ≈ 8 MiB + 64 KiB + 14 KiB
```

**目录项(DirEntry,32 字节):**

```
┌──────────────────┬──────────┐
│  文件名(28 字节) │ inode 号  │
│  含 '\0' 终止符    │  4 字节   │
└──────────────────┴──────────┘
每个 512 字节磁盘块可存储 16 个目录项
```

### 3.4 块缓存层

为了减少磁盘 I/O,easy-fs 使用**块缓存**(BlockCache):

```
程序读写请求
     │
     ▼
查找块缓存 ──命中──→ 直接读写内存缓冲区
     │
   未命中
     │
     ▼
从磁盘读取块到缓存 ──→ 读写内存缓冲区
     │
  缓存满时
     │
     ▼
淘汰最早的缓存块(若脏则回写磁盘)
```

块缓存的关键设计:
- 每个缓存项包含 512 字节缓冲区 + 修改标记
- 全局缓存管理器限制最大缓存数量
- FIFO 淘汰策略
- Drop 时自动回写脏块

### 3.5 VirtIO 块设备驱动

VirtIO 是 QEMU 使用的虚拟化 I/O 标准。在 tg-rcore-tutorial-ch6 中:

```
QEMU 宿主机
┌──────────────────────────────────┐
│  fs.img ──→ VirtIO 后端          │
│              ▲                    │
│              │ MMIO(0x10001000) │
│              ▼                    │
│  VirtIO 前端(Guest 内核驱动)     │
└──────────────────────────────────┘
```

内核需要:
1. 在地址空间中映射 MMIO 地址 `0x10001000`
2. 实现 `Hal` trait(DMA 内存分配、地址转换)
3. 实现 `BlockDevice` trait,将 read_block/write_block 转发给 VirtIO 驱动

### 3.6 文件描述符表

本章为进程引入了**文件描述符表**(fd_table):

```rust
pub struct Process {
    pub fd_table: Vec<Option<Mutex<FileHandle>>>,
    // ... 其他字段
}
```

预留的标准文件描述符:

| fd | 名称 | 说明 |
|----|------|------|
| 0 | stdin | 标准输入(SBI console_getchar) |
| 1 | stdout | 标准输出(SBI console_putchar) |
| 2 | stderr | 标准错误(同 stdout) |
| 3+ | 普通文件 | 通过 open 系统调用分配 |

**文件操作流程:**

```
用户程序                    内核
   │                         │
   │── open("test.txt") ───→ │ 1. 地址翻译读取文件名
   │                         │ 2. easy-fs 查找/创建文件
   │                         │ 3. 分配 fd,插入 fd_table
   │←── 返回 fd = 3 ────────│
   │                         │
   │── write(3, buf, len) ──→│ 1. 查找 fd_table[3]
   │                         │ 2. 地址翻译获取用户缓冲区
   │                         │ 3. 通过 FileHandle 写入文件
   │←── 返回写入字节数 ──────│
   │                         │
   │── close(3) ────────────→│ 1. fd_table[3] = None
   │←── 返回 0 ─────────────│
```

### 3.7 系统调用

| syscall ID | 名称 | 功能 |
|-----------|------|------|
| 56 | `open` | 打开文件(**新增**) |
| 57 | `close` | 关闭文件描述符(**新增**) |
| 63 | `read` | 读取文件或标准输入(**扩展**:支持文件 fd) |
| 64 | `write` | 写入文件或标准输出(**扩展**:支持文件 fd) |
| 93 | `exit` | 退出进程 |
| 124 | `sched_yield` | 让出 CPU |
| 113 | `clock_gettime` | 获取时间 |
| 172 | `getpid` | 获取 PID |
| 214 | `sbrk` | 调整堆 |
| 220 | `fork` | 创建子进程(**扩展**:复制 fd_table) |
| 221 | `exec` | 替换程序(**变化**:从文件系统加载) |
| 260 | `wait` | 等待子进程 |
| 37 | `linkat` | 创建硬链接(**练习题**) |
| 35 | `unlinkat` | 删除链接(**练习题**) |
| 80 | `fstat` | 获取文件状态(**练习题**) |

### 3.8 从内嵌程序到文件系统加载

对比第五章和第六章的程序加载方式:

```
第五章:程序嵌入内核
────────────────────
build.rs 编译用户程序 → 生成 APP_ASM → 嵌入内核镜像
exec 时:APPS.get(name) → 内存中的 ELF 数据

第六章:程序存储在文件系统
────────────────────────
build.rs 编译用户程序 → 打包到 fs.img → QEMU 挂载为块设备
exec 时:FS.open(name) → read_all() → 从磁盘读取 ELF 数据
```

---

## 四、代码解读

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

**启动流程(与第五章类似,新增 MMIO 映射):**
1. 清零 BSS 段 → 初始化控制台 → 初始化堆
2. 创建异界传送门 → 建立内核地址空间
3. **新增**:映射 VirtIO MMIO 地址 `0x10001000`
4. 初始化系统调用 → 从**文件系统**加载 initproc → 进入调度循环

**IO 系统调用的变化:**
- `write`/`read`:先检查是否为标准 I/O fd,否则通过 fd_table 查找文件句柄读写
- `open`:从用户空间读取文件路径字符串 → easy-fs 打开文件 → 分配 fd
- `close`:将 fd_table 对应项设为 None
- `exec`:从 `FS.open(name)` + `read_all()` 加载 ELF,而非 `APPS.get(name)`

### 4.2 `src/fs.rs` —— 文件系统管理

**`FS`**:全局文件系统实例,通过 `BLOCK_DEVICE`(VirtIO)打开 easy-fs

**`FileSystem` 实现 `FSManager` trait:**
- `open()`:支持 CREATE(创建)、TRUNC(清空)等标志
- `find()`:在根目录中查找文件
- `readdir()`:列出所有文件名
- `read_all()`:辅助函数,读取整个文件内容

### 4.3 `src/process.rs` —— 进程管理

与第五章相比新增 `fd_table` 字段:
- `from_elf()`:初始化时预留 fd 0/1/2
- `fork()`:深拷贝父进程的 fd_table(子进程继承已打开文件)
- `exec()`:保留 fd_table 不变

### 4.4 `src/virtio_block.rs` —— VirtIO 驱动

- `BLOCK_DEVICE`:全局块设备实例
- `VirtIOBlock`:封装 virtio-drivers 库的 VirtIOBlk
- `VirtioHal`:DMA 内存分配和地址转换(恒等映射下很简单)

### 4.5 `Cargo.toml` —— 依赖说明

| 依赖 | 说明 |
|------|------|
| `virtio-drivers` | VirtIO 设备驱动库(**本章新增**) |
| `tg-rcore-tutorial-easy-fs` | easy-fs 文件系统实现(**本章新增**) |
| `xmas-elf` | ELF 文件解析 |
| `riscv` | RISC-V CSR 寄存器访问 |
| `spin` | 自旋锁(Lazy、Mutex) |
| `tg-rcore-tutorial-sbi` | SBI 调用封装 |
| `tg-rcore-tutorial-linker` | 链接脚本和内核布局 |
| `tg-rcore-tutorial-console` | 控制台输出和日志 |
| `tg-rcore-tutorial-kernel-context` | 用户上下文及异界传送门 |
| `tg-rcore-tutorial-kernel-alloc` | 内核堆分配器 |
| `tg-rcore-tutorial-kernel-vm` | 虚拟内存管理 |
| `tg-rcore-tutorial-syscall` | 系统调用定义与分发 |
| `tg-rcore-tutorial-task-manage` | 进程管理框架 |

---

## 五、编程练习

### 5.1 硬链接

硬链接要求两个不同的目录项指向同一个文件,在我们的文件系统中也就是两个不同名称目录项指向同一个磁盘块。

本节要求实现三个系统调用 `linkat`、`unlinkat`、`fstat`。

#### linkat

- syscall ID: 37
- 功能:创建一个文件的硬链接([linkat 标准接口](https://linux.die.net/man/2/linkat))

```rust
fn linkat(&self, _caller: Caller, _olddirfd: i32, oldpath: usize,
          _newdirfd: i32, newpath: usize, _flags: u32) -> isize
```

- 参数:
  - olddirfd, newdirfd: 仅为兼容性考虑,始终为 AT_FDCWD (-100),可忽略
  - flags: 仅为兼容性考虑,始终为 0,可忽略
  - oldpath:原有文件路径
  - newpath: 新的链接文件路径
- 说明:
  - 不考虑新文件路径已存在的情况(属于未定义行为)
  - 新旧名字一致时返回 -1
- 返回值:成功 0,错误 -1

#### unlinkat

- syscall ID: 35
- 功能:取消一个文件路径到文件的链接([unlinkat 标准接口](https://linux.die.net/man/2/unlinkat))

```rust
fn unlinkat(&self, _caller: Caller, _dirfd: i32, path: usize, _flags: u32) -> isize
```

- 参数:
  - dirfd: 始终为 AT_FDCWD (-100),可忽略
  - flags: 始终为 0,可忽略
  - path:文件路径
- 说明:使用 unlink 彻底删除文件时,需要回收 inode 及其数据块
- 返回值:成功 0,错误 -1
- 可能的错误:文件不存在

#### fstat

- syscall ID: 80
- 功能:获取文件状态

```rust
fn fstat(&self, _caller: Caller, fd: usize, st: usize) -> isize
```

- 参数:
  - fd: 文件描述符
  - st: 文件状态结构体指针

```rust
#[repr(C)]
pub struct Stat {
    pub dev: u64,        // 磁盘驱动器号(写死为 0)
    pub ino: u64,        // inode 编号
    pub mode: StatMode,  // 文件类型(FILE 或 DIR)
    pub nlink: u32,      // 硬链接数量(初始为 1)
    pad: [u64; 7],
}

bitflags! {
    pub struct StatMode: u32 {
        const NULL  = 0;
        const DIR   = 0o040000;   // 目录
        const FILE  = 0o100000;   // 普通文件
    }
}
```

### 5.2 实现提示

- `linkat` 和 `unlinkat` 的文件路径读取可参考 `src/main.rs` 中 `open` 系统调用的实现
- `fstat` 的 Stat 结构体写入可参考 `clock_gettime` 对 TimeSpec 的写入方式(地址翻译后写入)
- 需要拉取 `tg-rcore-tutorial-easy-fs` 到本地并修改以支持硬链接:
  ```bash
  cd tg-rcore-tutorial-ch6
  cargo clone tg-rcore-tutorial-easy-fs
  ```
  然后修改 `Cargo.toml`:
  ```toml
  [dependencies]
  tg-rcore-tutorial-easy-fs = { path = "./tg-rcore-tutorial-easy-fs" }
  ```

### 5.3 实验要求

**目录结构:**

```
tg-rcore-tutorial-ch6/
├── Cargo.toml(需要修改依赖配置)
├── src/(需要修改)
│   ├── main.rs
│   ├── fs.rs
│   ├── process.rs
│   ├── processor.rs
│   └── virtio_block.rs
├── tg-rcore-tutorial-easy-fs/(需拉取到本地并修改以支持硬链接)
│   └── src/
└── tg-rcore-tutorial-user/(自动拉取,无需修改)
```

**运行和测试:**

```bash
cargo run --features exercise    # 运行练习测例
./test.sh exercise               # 测试练习测例
```

然后在终端中输入 `tg-rcore-tutorial-ch6_usertest` 运行所有练习测例。

> **前向兼容**:你的内核必须前向兼容,需要能通过前一章的所有测例。

---

## 六、本章小结

通过本章的学习和实践,你完成了操作系统中的重要基础设施——文件系统:

1. **文件系统概念**:通过 easy-fs 理解了 inode 文件系统的基本原理
2. **五层架构**:块设备接口 → 块缓存 → 磁盘数据结构 → 磁盘管理器 → Inode
3. **磁盘布局**:SuperBlock、Bitmap、Inode Area、Data Area 的组织方式
4. **VirtIO 驱动**:通过 MMIO 访问虚拟块设备,连接文件系统与磁盘
5. **文件描述符表**:统一管理标准 I/O 和普通文件的抽象
6. **文件操作接口**:open/close/read/write 系统调用的实现
7. **程序加载方式的变化**:从内核内嵌到文件系统动态加载

在后续章节中,我们将在文件系统的基础上引入**进程间通信**(管道等)机制。

## 七、思考题

1. **为什么需要块缓存?** 如果每次读写都直接访问磁盘,性能会怎样?块缓存的淘汰策略(FIFO vs LRU)对性能有什么影响?

2. **DiskInode 的索引设计?** 为什么 easy-fs 的 DiskInode 使用直接 + 一级间接 + 二级间接的三级索引结构?如果只有直接索引,最大文件大小是多少?

3. **文件描述符表的继承?** fork 时子进程复制了父进程的 fd_table。如果父进程打开了一个文件然后 fork,父子进程写入同一个文件会发生什么?

4. **硬链接 vs 软链接?** 硬链接和软链接有什么区别?为什么硬链接不能跨文件系统?删除一个硬链接后,文件何时真正被删除?

5. **exec 后的 fd_table?** 本实现中 exec 不清除 fd_table。这意味着什么?UNIX 系统中 exec 如何处理文件描述符(提示:close-on-exec 标志)?

## 参考资料

- [rCore-Tutorial-Guide 第六章](https://learningos.github.io/rCore-Tutorial-Guide/)
- [rCore-Tutorial-Book 第六章](https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter6/index.html)
- [VirtIO 规范](https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html)
- [UNIX 文件系统设计](https://en.wikipedia.org/wiki/Unix_File_System)
- [Linux VFS 层](https://www.kernel.org/doc/html/latest/filesystems/vfs.html)

---

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

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

| 操作系统内核 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|:-----|:------------|:---------|:---------------|
| **tg-rcore-tutorial-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-rcore-tutorial-sbi |
| **tg-rcore-tutorial-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-rcore-tutorial-sbi<br>tg-rcore-tutorial-linker<br>tg-rcore-tutorial-console<br>tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-syscall |
| **tg-rcore-tutorial-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-rcore-tutorial-sbi<br>tg-rcore-tutorial-linker<br>tg-rcore-tutorial-console<br>tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-syscall |
| **tg-rcore-tutorial-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-rcore-tutorial-sbi<br>tg-rcore-tutorial-linker<br>tg-rcore-tutorial-console<br>tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-kernel-alloc<br>tg-rcore-tutorial-kernel-vm<br>tg-rcore-tutorial-syscall |
| **tg-rcore-tutorial-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-rcore-tutorial-sbi<br>tg-rcore-tutorial-linker<br>tg-rcore-tutorial-console<br>tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-kernel-alloc<br>tg-rcore-tutorial-kernel-vm<br>tg-rcore-tutorial-syscall<br>tg-rcore-tutorial-task-manage |
| **tg-rcore-tutorial-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-rcore-tutorial-sbi<br>tg-rcore-tutorial-linker<br>tg-rcore-tutorial-console<br>tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-kernel-alloc<br>tg-rcore-tutorial-kernel-vm<br>tg-rcore-tutorial-syscall<br>tg-rcore-tutorial-task-manage<br>tg-rcore-tutorial-easy-fs |
| **tg-rcore-tutorial-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-rcore-tutorial-sbi<br>tg-rcore-tutorial-linker<br>tg-rcore-tutorial-console<br>tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-kernel-alloc<br>tg-rcore-tutorial-kernel-vm<br>tg-rcore-tutorial-syscall<br>tg-rcore-tutorial-task-manage<br>tg-rcore-tutorial-easy-fs<br>tg-rcore-tutorial-signal<br>tg-rcore-tutorial-signal-impl |
| **tg-rcore-tutorial-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-rcore-tutorial-sbi<br>tg-rcore-tutorial-linker<br>tg-rcore-tutorial-console<br>tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-kernel-alloc<br>tg-rcore-tutorial-kernel-vm<br>tg-rcore-tutorial-syscall<br>tg-rcore-tutorial-task-manage<br>tg-rcore-tutorial-easy-fs<br>tg-rcore-tutorial-signal<br>tg-rcore-tutorial-signal-impl<br>tg-rcore-tutorial-sync |

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

| 功能组件 | 所涉及核心知识点 | 主要完成功能 | 所依赖的组件 |
|:-----|:------------|:---------|:----------------------|
| **tg-rcore-tutorial-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-rcore-tutorial-console** | 控制台 I/O<br>格式化输出(print! / println!)<br>日志系统(Log Level)<br>自旋锁保护的全局控制台 | 可定制 print! / println! 宏<br>log::Log 日志实现<br>Console trait 抽象底层输出 | 无 |
| **tg-rcore-tutorial-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-rcore-tutorial-kernel-alloc** | 内核堆分配器<br>伙伴系统(Buddy Allocation)<br>动态内存管理<br>#[global_allocator] | 基于伙伴算法的 GlobalAlloc<br>堆初始化(init)<br>物理内存转移(transfer) | 无 |
| **tg-rcore-tutorial-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-rcore-tutorial-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-rcore-tutorial-signal-defs |
| **tg-rcore-tutorial-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-rcore-tutorial-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-rcore-tutorial-signal-defs** | 信号编号(SignalNo)<br>SIGKILL / SIGINT / SIGUSR1 等<br>信号动作(SignalAction)<br>信号集(SignalSet)<br>最大信号数(MAX_SIG) | 信号编号枚举定义<br>信号动作结构定义<br>信号集类型定义<br>为 tg-rcore-tutorial-signal 和 tg-rcore-tutorial-syscall 提供共用类型 | 无 |
| **tg-rcore-tutorial-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-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-signal-defs |
| **tg-rcore-tutorial-signal-impl** | SignalImpl 结构<br>已接收信号位图(received)<br>信号屏蔽字(mask)<br>信号处理中状态(handling)<br>信号动作表(actions)<br>信号处理函数调用<br>上下文保存与恢复 | Signal trait 的参考实现<br>信号接收位图管理<br>屏蔽字逻辑<br>处理状态和动作表 | tg-rcore-tutorial-kernel-context<br>tg-rcore-tutorial-signal |
| **tg-rcore-tutorial-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-rcore-tutorial-task-manage |
| **tg-rcore-tutorial-user** | 用户态程序(User-space App)<br>用户库(User Library)<br>系统调用封装(syscall wrapper)<br>用户堆分配器<br>用户态 print! / println! | 用户测试程序运行时库<br>系统调用封装<br>用户堆分配器<br>各章节测试用例(ch2~ch8) | tg-rcore-tutorial-console<br>tg-rcore-tutorial-syscall |
| **tg-rcore-tutorial-checker** | 测试验证<br>输出模式匹配<br>正则表达式(Regex)<br>测试用例判定 | rCore-Tutorial CLI 测试输出检查工具<br>验证内核输出匹配预期模式<br>支持 --ch N 和 --exercise 模式 | 无 |
| **tg-rcore-tutorial-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.