第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 都对应一棵目录树
回到原视频 (00:16:00)
操作系统设计者首先要面对「文件系统」对象的管理。 至少有两套方案:
- Windows:所有操作系统对象的根
\(例如\Device\HarddiskVolume1、\Driver\Ntfs)——这个\不能cd,但可以用 API 访问:
HANDLE hDevice = CreateFile(
"\\\\.\\PhysicalDrive0", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
📷 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 出一棵只读目录树
回到原视频 (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 绑定镜像文件
回到原视频 (00:36:00)
📷 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
回到原视频 (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 编号 —— 同一个文件
- 文件也是有 ID 的(FID,用于文件系统内部实现);
- 删除文件的系统调用叫
unlink(引用计数--)——只有引用计数减到 0,文件对象才真正被回收; - 不能链接目录、不能跨文件系统(点和点是例外)。
📷 硬链接:`ls -i` 显示两个名字指向同一个 inode
回到原视频 (00:43:00)
编者说明:为什么不能链接目录?「很容易形成环——一个环从整个目录树里 detach 之后,引用计数就永远不为 0,那个环回收不了。」
2.3 软链接
(参考时间: 00:45)
软(symbolic)链接:在文件里存储一个「跳转提示」——软链接也是一个文件,里面以文本形式存着另一个文件的绝对/相对路径:
- 可以跨文件系统、可以链接目录、甚至可以链接自己、可以有环;
- 路径解析时,碰到符号链接就把它的内容拼到路径上继续找。
📷 软链接:在文件里存一段「跳转提示」文本,路径解析时替换
回到原视频 (00:47:00)
编者拓展|Windows「快捷方式」的复杂:它比你想象的复杂——文件重命名、移动、U 盘驱动器号改变……都能正确找到。「为解决一个问题带来了更多问题 😂」
用软链接制作文字冒险游戏(Galgame):Symlink Game——把每个状态做成一个目录,每个选项做成一个符号链接指向另一个状态目录。于是:
cd 2= 选择一个选项;cd ..= 时光回溯到上一层状态;pwd= 你所有的游戏历史(因为你是「一路cd进来的」);- 可以成环——「你看它成环了!」
📷 用符号链接实现的 Galgame 引擎:状态机 = 目录树,选项 = 符号链接
回到原视频 (00:50:00)
软链接还可以「伪造」文件系统——Nix:
- 把所有软件包的所有版本集中存储在
/nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1(由软件包 hash 决定,完全 deterministic); - 然后用符号链接构建一个完全虚拟的环境:
nix-shell -p python3 nodejs; - 这是一个 persistent data structure——还记得吗?Random read + append-only write = 任意数据结构:
/nix/store只增不减,符号链接就是 random read; - 随时回滚(
nix-env --switch-generation)、随地构建(nix-shell)。
📷 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
- Type:
d(directory)、l(link)、p(pipe)、c(char)、b(block); - Mode:
rwx(user, group, other)——例:0o755 = rwx(111) r-x(101) r-x(101); - Links:引用计数(硬链接,包括目录)。
📷 `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);
- 例子:macOS 的
com.apple.metadata会保存每个互联网下载文件的 url(还有下载进度、签名……); - 致命的缺陷:这是后加的特性——不是所有文件系统都支持,兼容性奇差(
cp需要--preserve=xattr才能保留 xattrs,否则「悄悄地消失」)。
📷 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
- 格式:
[d:]:name:perms; - root 会 bypass 这些检查——「从操作系统设计者角度,这是对的:如果 root 不能删除一个文件,那任何人都可以占满存储空间而最高权限的人都删不掉。」
📷 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 想到这一点。」
总结
- 从块设备到文件系统:内存 → 虚拟内存;磁盘 → 虚拟磁盘(文件);文件系统 = 在 block 上实现的数据结构;
- 目录树:层次索引结构(图书馆),利用信息的局部性;
./..是真实目录项;设备 = 目录树(mount);Windows 的对象根\与盘符; - loopback device:把文件变成块设备,从而可以被
mount; - 目录树 API:
mkdir/unlink/getdents+ globbing;硬链接(引用计数、unlink)与软链接(可跨文件系统、可成环、可做 Galgame、可伪造文件系统 → Nix); - 文件元数据:
ls -l的 Type/Mode/Links;xattr(任意 key-value,但兼容性差);ACL(细粒度访问控制); - Takeaway:目录(链接)和文件(对象)的简洁抽象赋予了我们管理对象的能力。 操作系统的设计者也扩展了文件的元数据,使我们能给文件打上复杂的标签,从而使应用程序(和操作系统)提供更好的服务。
编者拓展|「没有什么是理解不了的」:这一讲课上,讲师几乎对每一个演示都先
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/。视频画面与字幕版权归原作者所有。本书为课堂内容的学习性重构。