← cd ../posts

一切皆文件:Linux 最经典的一句口号到底说了什么

2026-06-16

这是 Linux 系列的第 3 篇。前两篇讲了为什么是 Linux 和它怎么组织自己。这篇讲它世界观的核心比喻——也是它能用如此简洁的 API 控制如此复杂硬件的真正原因。

0. 一句话的重量

你的 Linux 机器上有大约 三百多种不同类型的"东西":硬盘、SSD、CPU 寄存器、GPU、键盘、麦克风、USB 摄像头、网卡、显示器、声卡、内核参数、进程内存、TCP 连接、管道、共享内存……

如果每种东西都设计自己的 API——你要学 300 套。

Unix 1970 年代做出了一个决定,简单到惊人:让它们全部假装是"文件"。统一用 open / read / write / close 操作

这个决定的副产品是:

# 把一段音频写进音响(早期 OSS 时代)
$ cat song.wav > /dev/dsp

# 显示器闪一下
$ echo -e '\033[1;31m hello \033[0m'

# 给另一个进程"发消息"
$ echo 'cmd' > /proc/123/cmdline  # (实际现代内核不让写,但 fd 抽象一致)

# 读 CPU 温度
$ cat /sys/class/thermal/thermal_zone0/temp
52000   # 52.0 °C

# 看正在跑的所有 TCP 连接
$ cat /proc/net/tcp

每条命令都用同一个动词(cat / echo),操作完全不同的硬件和子系统。这就是 一切皆文件


1. "文件"到底有几种

ls -l 第一列的字符你肯定见过:

$ ls -l /
total 76
drwxr-xr-x  2 root root  4096 Dec 15 14:22 bin
drwxr-xr-x  4 root root  4096 Dec 15 14:22 boot
drwxr-xr-x 19 root root  4060 Dec 15 14:22 dev
drwxr-xr-x 91 root root  4096 Dec 15 14:22 etc
drwxr-xr-x  3 root root  4096 Dec 15 14:22 home
lrwxrwxrwx  1 root root     7 Dec 15 14:22 lib -> usr/lib
drwxr-xr-x  2 root root  4096 Dec 15 14:22 proc
...

第一个字符就是文件类型,一共 7 种:

字符 类型 例子
- 普通文件(regular file) README.md
d 目录(directory) /etc
l 符号链接(symlink) /lib -> usr/lib
c 字符设备(character device) /dev/tty/dev/random
b 块设备(block device) /dev/sda/dev/nvme0n1
s UNIX socket /var/run/docker.sock
p 命名管道(named pipe / FIFO) mkfifo myfifo 后的产物

这 7 种都用同一套 syscall(open / read / write / close / ioctl 等)操作。


2. 三类特殊文件,每一类都打开一扇门

① 设备文件:/dev

$ ls -l /dev/ | head
brw-rw---- 1 root disk     8,   0 Dec 15 14:22 sda          # 整块硬盘
brw-rw---- 1 root disk     8,   1 Dec 15 14:22 sda1         # 第一个分区
crw-rw-rw- 1 root tty      5,   0 Dec 15 14:22 tty          # 当前终端
crw-rw-rw- 1 root root     1,   3 Dec 15 14:22 null         # 黑洞
crw-rw-rw- 1 root root     1,   5 Dec 15 14:22 zero         # 无尽 0
crw-rw-rw- 1 root root     1,   8 Dec 15 14:22 random       # 随机字节
crw-rw-rw- 1 root root     1,   9 Dec 15 14:22 urandom      # 同上,不阻塞
crw-rw-rw- 1 root root    10, 200 Dec 15 14:22 net/tun      # 虚拟网卡

/dev 下每一个"文件"背后都是一个驱动。你 read 它就是在调驱动。

立刻能用的几个:

# 把任何东西丢进黑洞(/dev/null)
$ command_with_noisy_output > /dev/null 2>&1

# 生成一个 100MB 的零字节测试文件
$ dd if=/dev/zero of=test.bin bs=1M count=100

# 生成 16 字节随机十六进制(密码生成器)
$ head -c 16 /dev/urandom | xxd -p

# 直接读硬盘前 512 字节(MBR 引导扇区)
$ sudo dd if=/dev/sda bs=512 count=1 | xxd | head

/proc:内核状态的实时窗口

/proc 不在磁盘上,是内核动态生成的"虚拟文件系统"(procfs)。每次 cat,内核当场把对应数据序列化成文本。

最常用的几个:

$ cat /proc/cpuinfo       # CPU 型号 / 核数 / cache / flags
$ cat /proc/meminfo       # 内存详细情况
$ cat /proc/version       # 内核版本
$ cat /proc/uptime        # 开机多久了(秒数)
$ cat /proc/loadavg       # 1/5/15 分钟负载

# 每个进程都是一个目录
$ ls /proc/$$/            # $$ 是当前 shell 的 pid
cmdline  cwd  environ  exe  fd  maps  mem  status  ...

$ cat /proc/$$/status | head -10
Name:   bash
Pid:    12345
PPid:   12340
Uid:    1000  1000  1000  1000
VmRSS:  4512 kB                # 这个进程占了多少物理内存

$ ls -l /proc/$$/fd/           # 这个进程打开了哪些文件
lr-x------ 1 you you 64 ... 0 -> /dev/pts/0    # stdin
lrwx------ 1 you you 64 ... 1 -> /dev/pts/0    # stdout
lrwx------ 1 you you 64 ... 2 -> /dev/pts/0    # stderr
lr-x------ 1 you you 64 ... 255 -> /home/you   # 你 cd 过的目录

每个进程的 stdin/stdout/stderr 都是 fd 0/1/2,都通过 /proc/<pid>/fd/N 暴露。后面"重定向"章会大量用到。

/sys:硬件的现代窗口

/sys/proc 的现代化版本(sysfs),暴露硬件和驱动的状态。比 /proc 结构更整齐:

# 看 CPU 频率
$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
3800000   # 3.8 GHz

# 看电池
$ cat /sys/class/power_supply/BAT0/capacity
87

# 看键盘灯(如果有的话)
$ echo 1 | sudo tee /sys/class/leds/input*::capslock/brightness

# 看磁盘是 SSD 还是 HDD
$ cat /sys/block/sda/queue/rotational
0   # 0=SSD, 1=机械硬盘

# 看网卡链路速度
$ cat /sys/class/net/eth0/speed
1000   # Mbps

很多 GUI 监控工具(电池图标、CPU 频率显示)背后都只是在 cat /sys 下的文件。


3. 一个真实的 5 行小练习

监控当前机器的 CPU 温度 + 负载,每秒打印一次:

while true; do
  temp=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null)
  load=$(cut -d' ' -f1 /proc/loadavg)
  printf '[%s] temp=%s°C load=%s\n' "$(date +%T)" "$((temp/1000))" "$load"
  sleep 1
done

5 行 shell,零依赖,比下载一个 GUI 监控软件快 100 倍。这就是"一切皆文件"给你的杠杆


4. socket / pipe 也是"文件"

不止物理硬件——抽象的通信机制也套这套接口:

# UNIX socket:进程间通信
$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Dec 15 14:22 /var/run/docker.sock
# Docker CLI 跟 Docker daemon 通信,就是 read/write 这个 socket

# 命名管道:跨进程文本流
$ mkfifo /tmp/mypipe
$ cat /tmp/mypipe &              # 一个进程在另一端等
$ echo "hi" > /tmp/mypipe        # 另一边写就触发那一头读出来

# 匿名管道(shell 的 |):内核里一段缓冲区,两个 fd 一头读一头写
$ ls | wc -l

每一种通信形式都长得跟普通文件一样——fd(file descriptor)这个数字就是通用句柄。无论它背后是磁盘文件、socket、管道、还是设备,对程序来说就是同一种东西。

这就是为什么 epoll / select 这类 I/O 多路复用器能"同时盯一万个 socket 和文件"——因为对它们来说没区别


5. 例外:不是所有东西都精确是文件

为了诚实,得说一下:

  • 网络命名空间 / cgroup:是文件系统对象但语义复杂,不是纯 read/write
  • /dev/shm:共享内存其实是 tmpfs 上的"文件",但通常用 mmap 而不是 read
  • GPU 计算:CUDA / OpenCL 用 ioctl 而不是 read/write(性能要求太高,read/write 太啰嗦)
  • 现代异步 I/O(io_uring):fd 还在,但是不再走 read/write,走 ring buffer 队列

但这些都是为了性能做的特例。绝大部分操作仍然是文件接口。


6. 这条哲学的延伸:现代的"配置即文件"

"一切皆文件"还延伸出 Linux 系统配置的另一种风格——配置即文本文件

/etc/passwd          # 用户列表
/etc/hosts           # DNS 短路
/etc/fstab           # 启动时挂载哪些盘
/etc/systemd/system/ # 所有自启服务
/etc/cron.d/         # 定时任务
/etc/sysctl.conf     # 内核参数

每一项都是纯文本——你能 grep、能 diff、能 git commit、能 ansible playbook、能 sed 批量改。

对比 Windows 那个 hkey_local_machine 注册表二进制黑箱,Linux 这条"文件第一"的路让运维自动化天生顺畅 10 倍。


7. 现在做一件事

打开你的终端,把下面这条命令的输出花 1 分钟读懂:

$ for n in cpuinfo meminfo uptime loadavg version; do
    echo "==== /proc/$n ===="
    head -3 /proc/$n
    echo
  done

你刚才用同一个动词(head)一口气读了 CPU、内存、运行时间、负载、内核版本——5 个看起来八竿子打不着的系统状态。

这就是这条哲学的全部魔力。


下一篇Shell 是粘合剂——| 这一个字符为什么是 Unix 最大的发明,以及为什么"很多小工具组合"会比"一个大工具"更强。