“一切皆文件”是Linux内核通过struct file和file_operations统一资源接口的抽象,以文件描述符(FD)为运行时载体,覆盖设备、进程、网络等;/dev、/proc、/sys为虚拟文件系统实现;socket作为FD突破传统文件语义,体现抽象的弹性与定制性。

因为内核用统一的 struct file 和 file_operations 接口封装了所有资源,让键盘、磁盘、网络连接、进程状态等都能走 read()/write()/open()/close() 这一套系统调用路径。
文件描述符(FD)是“一切皆文件”的运行时载体
每个打开的资源——不管是 /dev/ttyS0、/proc/1234/status 还是 TCP socket——在进程的 files_struct 里都占一个整数下标(比如 3、7、12)。这个数字就是 FD,它不关心背后是硬盘还是网卡,只负责把 read(fd, ...) 转发给对应 struct file->f_op->read 函数指针。
- 0、1、2 号 FD 固定绑定 stdin/stdout/stderr,但你用
dup2(5, 1)就能把 stdout 重定向到某个设备文件,无需改代码逻辑 - 父进程
fork()后,子进程默认继承全部 FD —— 所以管道|和重定向>才能天然生效 - 误关掉 FD 2(stderr)可能导致日志静默丢失,而
strace -e trace=write,read可快速定位哪段代码在往哪个 FD 写
/dev、/proc、/sys 是“文件化”的三大实现场所
它们不是真存储数据的磁盘文件,而是 VFS 层挂载的虚拟文件系统,靠内核动态生成内容:
-
/dev/input/event0:读它返回 struct input_event 二进制流,本质是轮询或中断触发的事件队列 -
/proc/[pid]/mem:对它read()实际调用的是ptrace逻辑,权限受ptrace_scope限制 -
/sys/class/net/eth0/operstate:写"down"会触发内核 netdevice 状态机,等价于ip link set eth0 down
注意:/proc 下多数文件不可 mmap();/sys 文件通常只支持单次 read 或 write,多次读可能返回空或报错 EINVAL。
socket 为什么“是文件却不完全像文件”?
socket 是 FD,但它的行为突破了传统文件语义边界:
- 不能用
open("/path/to/socket")创建,必须调socket(AF_INET, SOCK_STREAM, 0)—— 因为 socket 不存在于文件系统命名空间中(除非是 Unix domain socket,如/tmp/mysock) -
read(fd)对 TCP socket 可能只返回部分数据,且不保证消息边界;而普通文件read()总是按字节偏移精确读取 - 关闭 TCP 连接需先
shutdown(fd, SHUT_WR)发 FIN,再close(fd);直接close()可能导致 RST 被发,对方收不到完整数据
这说明“一切皆文件”不是强行套壳,而是有弹性的抽象:VFS 提供统一入口,但具体实现可深度定制 —— 比如 socket 的 f_op->poll 会注册到 epoll 红黑树,而磁盘文件的 poll 只是简单返回就绪。
真正容易被忽略的,是 struct file 中的 f_mode 和 f_flags 如何影响行为:比如以 O_NONBLOCK 打开的设备文件,read() 会立即返回 EAGAIN;而同样 FD 若来自 pipe(),非阻塞读则取决于缓冲区是否有数据。同一套接口,背后控制逻辑千差万别 —— 这正是“抽象但不失控”的设计分寸所在。










