第24讲 · 文件系统 API(1)

2026 南京大学《操作系统原理》第二十四讲 · 课堂实录重构

主讲 蒋炎岩(Yanyan Jiang)
视频 https://www.bilibili.com/video/BV1hAGr6vEi9/
课程主页 https://jyywiki.cn/OS/2026/
本讲讲义 https://jyywiki.cn/OS/2026/lect24.md
讲义 / 幻灯片作者 蒋炎岩,https://jyywiki.cn/
讲义 / 幻灯片许可 CC BY-NC 4.0
图片来源 书中插图为课堂演示与讲义渲染图;演示类截图为视频画面

对应讲次:第24讲(南京大学 操作系统原理)

制作模型:deepseek-flash(coding-agent / pi;据 ~/.pi/agent/settings.json)

编者说明:本书由课堂录像的语音字幕重构而成,术语、章节骨架与参考链接已按课程官方讲义(lect24.md)校准。讲师的口语风格、类比与案例被尽量保留;编辑性的补充一律以「编者说明」或「编者拓展」标出。


引子

(参考时间: 00:00)

存储(block)设备的抽象其实很简单:read_block(id) / write_block(id, data)——剩下的复杂性不用你操心(设备上专门有一台计算机来处理这些请求,FTL……)。

让进程直接访问设备文件?open("/dev/mmcblk0p1", O_RDWR) 然后 lseek——「那不就全乱套了吗!」(共享一个数据结构非常麻烦,Malloc Lab 已经教育过你们)。我们需要一个抽象层:文件系统。

编者说明:本次课讲人类使用的文件系统 API;下次课讲程序(AI Agent)使用的文件系统 API。


一、从块设备到文件系统

1.1 Abstract Data Type:从虚拟内存到虚拟磁盘

(参考时间: 00:03)

我们已经对内存做过一次抽象:

DRAM → (MMU) 虚拟地址空间 → malloc/free → list, set, map, …
      物理内存 random access char[]  → 虚拟内存 → 空洞管理 → 数据结构

既然可以在 RAM 上实现 Abstract Data Type,没道理不能在磁盘上实现——「无非就是 block v.s. word 访问的区别」。于是:内存 → 虚拟内存;磁盘 → 虚拟磁盘(字节序列 vector<char>)。「转了一圈,又回到 virtualization 了。」

文件(file):文字、符号固定于纸张等载体上的信息记录——在计算机里就是一个变长的字节序列,支持 read、write、lseek、ftruncate……

新需求:怎么管理系统中众多的文件?「对人类来说,PID 已经是一个很糟糕的抽象了(一个随机的数字,你不知道它是什么意思);如果再来一个 FID 就全乱套了。」

1.2 目录树:建一个图书馆

(参考时间: 00:09)

管理文件的方法:建一个图书馆! 背后的原理是信息的局部性——系统中的文件可以组织成层次索引结构(就像图书馆的中图法:文学区 → 虚构/非虚构 → ……)。

目录树:像图书馆中图法一样,用层次索引结构利用信息的局部性

📷 目录树:像图书馆中图法一样,用层次索引结构利用信息的局部性
回到原视频 (00:10:00)

.(当前目录)和 ..(上一级目录)是实现细节——本质上是一个数据结构。隐藏文件不是操作系统的行为,只是为了偷懒:busybox/coreutils 的 ls.c 里一行代码就实现了——

while ((entry = readdir(dir)) != NULL) {
    if (entry->d_name[0] == '.') {
        if (!(option_mask32 & (OPT_a|OPT_A))) continue;   // 就是这一行
        ...
    }
}
`.` 和 `..` 是真实的目录项,让目录树成为一个带环的数据结构

📷 `.` 和 `..` 是真实的目录项,让目录树成为一个带环的数据结构
回到原视频 (00:12:00)

编者拓展|「我已经把完整的 database 交给 AI 了」:讲师说,「我已经把所有数据都文本化了」——「我是谁、最近干了什么、所有和某件事相关的文档(比如学生守则),都放进文件系统。」他甚至设想:「每天晚上打开 Claude Code,问一句『我今天会被开除吗』——因为学生手册里有旷课到一定程度会被开除的条款。」用文件系统管理世界一切信息,而且很容易找到它。

1.3 设备 = 目录树

(参考时间: 00:16)

然而,系统中不止一个存储设备——每个设备、每个 partition 都是一个目录树:/dev/nvme0n1p1(Namespace 1, Partition 1)、优盘、网络(WebDAV)。

$ lsblk -f    # 列出所有 block device 及其 mount point
lsblk:系统中多个 block device,每个 partition 都对应一棵目录树

📷 lsblk:系统中多个 block device,每个 partition 都对应一棵目录树
回到原视频 (00:16:00)

操作系统设计者首先要面对「文件系统」对象的管理。 至少有两套方案:

HANDLE hDevice = CreateFile(
    "\\\\.\\PhysicalDrive0", GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
Windows 的对象根 `\`:盘符其实是 `\??\C:\` 的别名,`\\server\share` 是 UNC 网络路径

📷 Windows 的对象根 `\`:盘符其实是 `\??\C:\` 的别名,`\\server\share` 是 UNC 网络路径
回到原视频 (00:19:00)

编者拓展|为什么系统盘是 C 盘? 因为 A: 和 B: 是软盘(第一个/第二个软盘驱动器),在没有硬盘的时代就固定下来了。「所以硬盘出现时只能往后放,C 盘就是你们的系统盘。」而 \?? 是 per-user 的命名空间——「\\server\share 对两个不同用户可以解析成不同的东西。Windows 为用户隐藏了巨量的复杂性。」

从无到有的目录树:操作系统需要一个 API,把设备的目录树「放」到世界里——mount:

mount -t iso9660 /dev/cdrom /mnt/cdrom     # ISO 9660 (CD)
# UDF (DVD, Blue-Ray)
现场挂载一张 Win98 光盘:探测文件系统、ioctl 读容量,最终 mount 出一棵只读目录树

📷 现场挂载一张 Win98 光盘:探测文件系统、ioctl 读容量,最终 mount 出一棵只读目录树
回到原视频 (00:26:00)

编者说明:ISO 9660 就是一种「数据结构」——struct iso9660_primary_descriptor 里有 standard_id[5](必须是 "CD001")、system_id、volume_id……在字节序列的正确位置放一个 CD001 字符串,操作系统就能定位并解析它,把设备变成一个目录树。

1.4 Aside:挂载一个文件(loopback device)

(参考时间: 00:32)

mount 必须要求一个块设备——那 fs.img(一个文件)怎么办呢?

答案:创建一个 loopback(回环)设备——设备驱动把设备的 read/write 翻译成文件的 read/write:

// drivers/block/loop.c —— 实现的是 loop_mq_ops(不是 file_operations)
ioctl(3, LOOP_CTL_GET_FREE);      // 分配一个空闲的 loop 设备(返回编号)
ioctl(4, LOOP_SET_FD, 3);         // 绑定到镜像文件的 fd
strace 里的 loopback 创建:LOOP_CTL_GET_FREE 分配编号,LOOP_SET_FD 绑定镜像文件

📷 strace 里的 loopback 创建:LOOP_CTL_GET_FREE 分配编号,LOOP_SET_FD 绑定镜像文件
回到原视频 (00:36:00)

loopback 设备:把一个普通文件变成「块设备」,从而可以被 mount

📷 loopback 设备:把一个普通文件变成「块设备」,从而可以被 mount
回到原视频 (00:33:00)

编者说明:用 strace 观察挂载文件的流程:先以只读方式打开镜像探测文件系统类型(mount 系统调用本身要求你告诉它文件系统类型,否则直接拒绝)→ 打开 /dev/loop-control → ioctl(LOOP_CTL_GET_FREE) 拿到编号 0 → 访问 /dev/loop0 → 再执行真正的 mount。「你只要记住:操作系统会创建一个 loopback device——这句话到底意味着什么,strace 里全都有。」

编者拓展|FHS(Filesystem Hierarchy Standard):/usr、/home、/mnt……这些都是标准规定的。FHS enables software and user to predict the location of installed files and directories.「虽然现在这个标准显得有点过时,但依然让你感到安心。」——「macOS 内核其实是 UNIX(BSD),但不遵循 Linux FHS,所以体验不一样。」

编者说明:eject 命令也是 ioctl——它 open 打开 /dev/sr0,然后用 ioctl 触发 CD-ROM eject。

`eject`:用 ioctl 控制光驱弹出——设备控制几乎全部塞进 ioctl

📷 `eject`:用 ioctl 控制光驱弹出——设备控制几乎全部塞进 ioctl
回到原视频 (00:30:00)


二、目录树 API

2.1 增删改查与 globbing

(参考时间: 00:38)

int mkdirat(int dirfd, const char *pathname, mode_t mode);       // 创建目录
int unlinkat(int fd, const char *path, int flag);                // 删除
ssize_t getdents64(int fd, void *dirp, size_t count);            // 读取目录

Globbing(通配符)是 shell 的功能——echo /etc/**/* 会被 bash 转换成参数,所以直接 strace 看不到 globbing 过程,要把它包在 bash -c 里才能看到大量的 open + getdents64。

编者拓展|Agent 的「四大技能」:Read, Write, Grep, Glob 加上 Bash,就可以完成几乎所有的运维工作。讲师实现了 globbing 演示,并指出 fzf、find 背后都是同样的增删改查 API。

2.2 硬链接

(参考时间: 00:41)

需求:系统中可能有同一个运行库的多个版本(libc-2.27.so、libc-2.26.so……),还需要一个「当前版本的 libc」(程序需要链接 libc.so.6)——能否避免文件的一份拷贝?

硬(hard)链接:允许一个文件被多个目录引用(ls -i 可以看到 inode 编号):

$ ln a.txt b.txt     # 或 link/linkat 系统调用
$ ls -li a.txt b.txt
# 同一个 inode 编号 —— 同一个文件
硬链接:`ls -i` 显示两个名字指向同一个 inode

📷 硬链接:`ls -i` 显示两个名字指向同一个 inode
回到原视频 (00:43:00)

编者说明:为什么不能链接目录?「很容易形成环——一个环从整个目录树里 detach 之后,引用计数就永远不为 0,那个环回收不了。」

2.3 软链接

(参考时间: 00:45)

软(symbolic)链接:在文件里存储一个「跳转提示」——软链接也是一个文件,里面以文本形式存着另一个文件的绝对/相对路径:

软链接:在文件里存一段「跳转提示」文本,路径解析时替换

📷 软链接:在文件里存一段「跳转提示」文本,路径解析时替换
回到原视频 (00:47:00)

编者拓展|Windows「快捷方式」的复杂:它比你想象的复杂——文件重命名、移动、U 盘驱动器号改变……都能正确找到。「为解决一个问题带来了更多问题 😂」

用软链接制作文字冒险游戏(Galgame):Symlink Game——把每个状态做成一个目录,每个选项做成一个符号链接指向另一个状态目录。于是:

用符号链接实现的 Galgame 引擎:状态机 = 目录树,选项 = 符号链接

📷 用符号链接实现的 Galgame 引擎:状态机 = 目录树,选项 = 符号链接
回到原视频 (00:50:00)

软链接还可以「伪造」文件系统——Nix:

Nix:所有版本集中存于只增不减的 /nix/store,用符号链接拼出任意环境

📷 Nix:所有版本集中存于只增不减的 /nix/store,用符号链接拼出任意环境
回到原视频 (00:54:00)

编者说明:对比 apt——「所有的修改都是 in-place 的」。而 Nix 是 functional package manager,「只增不减」。「今天的版本答案你甚至不需要知道 Nix 的语法——直接打开 AI agent 说『帮我 create 一个隔离的 Python 环境 latest』就行。」


三、文件的元数据

3.1 为文件增加属性

(参考时间: 00:58)

软件 = 物理世界在信息世界的投影——我们还希望「byte array」有自己的「personality」:

$ ls -l example.txt
-rw-r--r-- 1     alice staff 1024 Mar 10 12:34 example.txt
mode       link  owner group size modified     name
`ls -l`:文件的 Type / Mode / Links / Owner / Size 等元数据

📷 `ls -l`:文件的 Type / Mode / Links / Owner / Size 等元数据
回到原视频 (01:01:00)

编者说明:chmod 777 让所有人都能读写执行;chmod 000 全部清掉;但 root(0 号用户)总是能访问所有文件——open 系统调用会返回 EACCES(permission denied)。FAT 文件系统没有权限概念(「所以软盘插到任何设备上任何人都能读写它」)。

编者拓展|隐藏其实是 procfs 的「欺骗」:/proc/<tid> 实际存在、可以 cd 进去、可以 cat status,但 ls 看不到它(getdents64 里不返回)。「因为我们想要的是只有我自己在用电脑时才显示学习资料,别人在旁边时隐藏——可现在计算机不支持表达这样的 policy。这就是革命的机会。」

3.2 Extended Attributes(xattr)

(参考时间: 01:10)

每个文件可以维护一个任意的 key-value dictionary:

ssize_t fgetxattr(int fd, const char *name, void value[.size], size_t size);
int fsetxattr(int fd, const char *name, const void value[.size], size_t size, int flags);
xattr:为文件维护一个任意 key-value 字典(`setfattr` / `getfattr`)

📷 xattr:为文件维护一个任意 key-value 字典(`setfattr` / `getfattr`)
回到原视频 (01:11:00)

编者拓展|vectorfs:「好用不火」的文件系统特性:「vfs search cat static/Photos/ | claude -p --summary——这不比 iPhone 的 Photo Search 好用多了?」讲师甚至断言:「目录树也是个妥协——图书馆的索引系统其实可以淘汰了,一本书的内容就是它的索引。路径 → 结构化查询(wildcard & globbing)→ 智能检索。」

3.3 Access Control List(ACL)

(参考时间: 01:16)

比 user/group/other 更精细的访问控制:

setfacl -m u:jyy:--- file      # 拒绝特定用户的访问
getfacl file
ACL:比 user/group/other 更细粒度的访问控制

📷 ACL:比 user/group/other 更细粒度的访问控制
回到原视频 (01:17:00)

编者拓展|面对未知(The Scaling Law):讲师给出一张幻灯片——Pre-training、SFT 和 test-time scaling(深度思考中),并引用了 Timothy Gowers 的「Kolmogorov complexity modulo experts」。核心态度是:「如果你希望实现一个『飞书文档』,ACL 就是一个很好的选择——你只要知道 fine-grained access control 是可以的,在需要的时候能启发 AI 想到这一点。」


总结

编者拓展|「没有什么是理解不了的」:这一讲课上,讲师几乎对每一个演示都先 strace、再让 AI 解释——「你只要记住:任何东西你都是可以打得开的。这些 API 要么是驱动帮你 take care 的,要么就是块设备的读写——这些东西加起来就变成了你在操作系统里看到的一切。」


附:官方参考与延伸阅读

本讲的讲义见课程主页,以下链接按书中出现顺序整理:

主题 链接
globbing(persistence 演示) https://jyywiki.cn/OS/demos/persistence/globbing
Symlink Game(Galgame 引擎) https://jyywiki.cn/OS/demos/persistence/ggmaker
Nix https://nixos.org
vectorfs https://vectorvfs.readthedocs.io/en/latest/
Filesystem Hierarchy Standard (FHS 3.0) http://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html
util-linux https://github.com/util-linux/util-linux
Linux loop.c(loopback 驱动) https://elixir.bootlin.com/linux/latest/source/drivers/block/loop.c
Windows 快捷方式(MS-SHLLINK) https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/
最小 Linux(virtualization 演示) https://jyywiki.cn/OS/demos/virtualization/linux-minimal
The kernel's command-line parameters https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
OSTEP(第 37 章 Files and Directories;第 55 章 Access Control) https://pages.cs.wisc.edu/~remzi/OSTEP/

阅读材料:读 OSTEP 第 37 章(Files and Directories)与第 55 章(Access Control);在自己的系统上 strace 一次 mount。

版权说明:本讲讲义与幻灯片作者为蒋炎岩,采用 Creative Commons BY-NC 4.0 许可,来源于 https://jyywiki.cn/OS/2026/。视频画面与字幕版权归原作者所有。本书为课堂内容的学习性重构。