在上篇Linux笔记1.1---一切皆文件(上):Linux 让所有 I/O 对象对外长得一样(都套同一套接口)。这篇讲内核怎么变出这个外壳、它的边界、以及为什么这么设计。
上篇说「不同对象套了同一个外壳」。这个外壳在内核里叫 VFS(Virtual File System,虚拟文件系统)。
VFS 是内核里的一层翻译官:上层(应用程序)只说「统一的话」(open/read/write),VFS 把这些话翻译成底层各种文件系统/驱动听得懂的指令。

关键的把戏叫 file_operations(操作表):VFS 规定「一个文件对象必须提供这组函数:open/read/write/...」。每种对象(ext4 文件、字符设备、管道)各自实现这组函数,但函数的签名(长什么样)完全一样。
于是 VFS 拿到一个文件对象,就能用同一句 obj->read(...) 调它——根本不管这个 read 背后是「从磁盘扇区读」「从网络包读」还是「内核现编一段数据给你」。
类比:万能遥控器的标准按键(播放/暂停/音量)。电视、音响、投影仪内部实现完全不同,但都按这个面板响应——你按「播放」,它们各自去干各自的事。VFS 就是这个标准按键面板,file_operations 就是「每个设备都得响应这几个键」的约定。
「一切皆文件」是设计理念,不是字面上的绝对规则。有些东西就不是文件:
open("/cpu")——它是调度的对象,不是 I/O 对象 | |
mmap 是另一条路,不是 read/write) | |
/proc/PID 下的文件,让你能 cat 查 | |
kill(),不走文件接口 | |
注意「进程」那一行——它体现了理念的弹性和分寸:进程本来不是文件,但 Linux 把它的状态映射成 /proc 里的虚拟文件,让你能用熟悉的 cat/grep 去查。这就是「尽可能」三个字:能套文件接口的就套、套了有好处的就套,套不上的不强求。
这正是 Unix/Linux 能这么灵活的根,也呼应了整个 shell 笔记系列:
A | B | C 里三者互相不知道对方是谁,靠管道(也是文件)连起来。详见 解析执行篇。cmd > f、cmd > /dev/null、cmd | grep,对象换了行为就变,代码不用改。cat、grep、wc 对任何能当文件读的东西都管用(磁盘文件、/proc、设备…),不用为每种对象写专用工具。file_operations 那组函数,套进 VFS 的壳,用户就能用 open/read 访问你的设备。一句话:因为接口统一,所以工具通用、命令可组合、行为可替换。
但「一切皆文件」有边界,不是万能药:
read/write 不够用,socket 有了自己的专用接口(bind/connect/accept)——虽然也用 fd,但接口比文件丰富。io_uring 这种新机制为了性能,不再局限于传统的 read/write 模型。这些不是「一切皆文件失败了」,而是任何抽象都有边界——当某个领域的需求超出了「顺序读写」这个模型,就需要专用接口。
最准确的说法是:
Linux 并没有把所有东西都变成磁盘文件,而是尽可能让各种 I/O 对象共享同一套抽象(VFS)和访问方式(
open/read/write)。真正统一的不是「文件」,而是「I/O 模型」。
三个关键词收口(也是整篇的骨架):
| 统一对象 | |
| 统一接口 | |
| 统一思想 |
那么,程序同时打开了一堆「文件」,怎么区分「我现在操作的是哪一个」?答案就是下一篇文件描述符(fd):一个编号,指向你那张私有的接线表。