上一章有了 pipe 和 fd,现在进程可以互相传数据了。但还有一个问题:用户程序没有办法查询"系统里现在有哪些进程"——这类信息只存在内核里,外面拿不到。
加专用 syscall 当然可以,但 Linux 选择了一个更优雅的方案:procfs。
一切皆文件
procfs 的思路是:把内核状态伪装成文件,挂在 /proc/ 目录下。
/proc/0/status → 进程 0 的信息
/proc/1/status → 进程 1 的信息
用户程序用完全一样的 open/read/close 就能读到,不需要学新 API:
int fd = open("/proc/1/status");
read(fd, buf, 128);
// buf 里是 "pid: 1\nstate: running\nparent: 0\n"
这就是 Linux “一切皆文件” 哲学的体现——统一接口,背后实现可以完全不同。
虚拟文件和真实文件的区别
普通文件的 read:从磁盘读数据。
procfs 文件的 read:内核现场生成内容,没有磁盘,没有 inode,数据就是 procs[] 数组里的字段格式化出来的字符串。
open("/proc/1/status")
→ vfs_open 识别 "/proc/" 前缀
→ procfs_open 解析 pid,格式化状态字符串存入内核 buf
→ 返回 VFile(type=VFILE_PROC)
read(fd, buf, len)
→ vfs_read 识别 VFILE_PROC
→ 从内核 buf 按 offset 拷贝给用户
实现
procfs 内核结构
typedef struct {
int used;
uint32_t pid;
char buf[256]; // open 时格式化好的字符串
uint32_t buf_len;
uint32_t offset; // 支持分段 read
} proc_fd_t;
open 时一次性生成完整字符串,后续 read 只是按 offset 切片拷贝——和普通文件行为完全一致,可以多次 read。
VFS 扩展
只需要三处改动:
1. 新增类型常量
#define VFILE_PROC 3
2. vfs_open 识别前缀
int vfs_open(const char *path) {
if (str_startswith(path, "/proc/"))
return vfs_open_proc(path + 5); // 传 "/1/status"
// 原有 ext2 逻辑...
}
3. vfs_read / vfs_close 分发
if (f->type == VFILE_PROC)
return procfs_read(f->proc_fd, buf, len);
路径解析
/proc/1/status 经过 VFS 剥掉 /proc 后,procfs_open 收到 /1/status:
- 跳过
/ - 解析数字
1→ pid - 遇到
/status停止(目前只支持 status,路径后缀忽略)
验证
用户程序:
mov rax, SYS_OPEN
lea rdi, [rel proc_path] ; "/proc/1/status"
syscall
mov rax, SYS_READ
mov rdi, [rel fd_val]
lea rsi, [rel buf]
mov rdx, 128
syscall
mov rax, SYS_WRITE
lea rdi, [rel buf]
syscall
输出:
pid: 1
state: running
parent: 0
小结
procfs 的核心洞察是:文件接口是通用的,不代表背后一定是磁盘。
VFS 把 read(fd) 和"从磁盘读"解耦了——只要在 VFile 的 type 字段上加一种新类型,就能接入任意数据源。内核状态、设备、网络连接……都可以用同一套 fd 接口暴露给用户程序。