上一章有了 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 接口暴露给用户程序。