第九十一章:procfs
斩灵期涉及内核源码:
一
林小源走进一片由半透明光膜构成的林间空地。这里的树不是真的树——树干是光柱,叶片是流动的符文,整棵树都在不断闪烁变化,仿佛有什么东西在它体内飞速运转。
"这……这棵树是活的?"林小源伸手触碰树干,手指穿过了光膜,什么也没摸到。
"我可不是树。"一个声音从四面八方传来,像是无数个窗口同时开口说话。光膜凝聚成一个模糊的人形,面容不断切换——时而是一串数字,时而是一段文字,时而是一张表格。"我叫 procfs。你看到的这些,都是我从内核各处收集来的信息。"
林小源后退一步:"你不是真正的文件系统?"
"聪明。"procfs 的声音里带着一丝自嘲,"我没有磁盘,没有数据块,不存储任何东西。我只是……一扇窗。"它挥了挥手,面前浮现出密密麻麻的光幕——/proc/cpuinfo、/proc/meminfo、/proc/[pid]/status,每一个都在实时刷新数据。
"用户空间想看内核的什么信息,就来读我的文件。"procfs 说,"但那些文件不是真的文件——它们是函数。你读 /proc/cpuinfo 的时候,我调用一个函数,把 CPU 信息拼成文本返回给你。"
林小源看着光幕上滚动的 CPU 信息,若有所思:"那写入呢?我看到 /proc/sys/ 下有好多文件……"
procfs 的表情变得严肃起来:"那是我的另一面。/proc/sys/ 下的文件对应 参数——你写入一个值,就能改变内核的行为。比如 echo 1 > /proc/sys/net/ipv4/ip_forward,就能开启 IP 转发。"
"一个窗口,既能看,又能改?"
"双向的窗口。"procfs 说,"但要小心——改错了参数,整个系统都可能出问题。"
二
林小源跟着 procfs 走进它的内部世界。这里没有文件,没有目录,只有无数条光线在空中交织,每条光线的末端都连接着内核中的某个数据结构。
"你看到的 /proc/1/status,"procfs 指着一条光线说,"那不是存储在磁盘上的文件。当有人读它的时候,我去找进程 1 的 ,把它的状态、内存、PID 等信息拼成一段文本返回。"
林小源沿着光线看去,果然看到一个进程结构体在光线末端闪烁。"那 /proc/1/fd/ 呢?"
"那是进程打开的文件描述符。"procfs 打开一个光幕,上面列出了进程 1 打开的所有文件,"每个数字对应一个 结构体。我遍历进程的文件描述符表,动态生成这个列表。"
"所以你本质上是一个……翻译器?"林小源问。
procfs 笑了:"你可以这么理解。内核的数据结构对用户空间来说太复杂了—— 有几百个字段,普通人根本看不懂。我把它们翻译成人类可读的文本,通过文件接口暴露出去。"
"那为什么用文件接口?"
"因为 Linux 中一切都是文件。"procfs 说,"用户空间只需要 open、read、write、close,不需要学习新的 API。这是最简单的接口。"
三
林小源注意到 procfs 的身体在微微颤抖——某些光线变得暗淡,另一些则异常明亮。
"你怎么了?"他问。
"我在承受压力。"procfs 的声音有些疲惫,"每秒钟有成千上万的进程在读取 /proc 下的文件——、ps、、,它们都在不停地访问我。每次访问,我都要调用函数、生成文本、返回数据。"
林小源皱眉:"那你不会崩溃吗?"
"不会,但我需要小心。"procfs 说,"如果某个读取函数耗时太长,会阻塞其他访问。如果返回的数据太多,会占用太多内核内存。我必须在信息量和性能之间找到平衡。"
"那你和 sysfs 呢?"林小源想起了另一个虚拟文件系统,"你们有什么区别?"
procfs 的表情变得微妙:"sysfs 是设备模型的文件系统,专门暴露设备、驱动、总线的信息。而我更全面——进程信息、内核参数、硬件信息,什么都有。但我们有一个共同点:都不是真正的文件系统,都不存储数据,都是内核向用户空间说话的嘴巴。"
"两扇不同的窗户。"
"对。"procfs 说,"一扇看进程和内核,一扇看设备和驱动。"
/*
* procfs 的结构:
*
* /proc/
* ├── [pid]/ — 进程信息
* │ ├── status — 进程状态
* │ ├── maps — 内存映射
* │ ├── fd/ — 打开的文件描述符
* │ └── cmdline — 命令行参数
* ├── cpuinfo — CPU 信息
* ├── meminfo — 内存信息
* ├── mounts — 挂载信息
* ├── version — 内核版本
* └── sys/ — 内核参数
*
* procfs 的特点:
* - 不占用磁盘空间
* - 内核动态生成内容
* - 可以通过写入修改内核参数
*/
printf("=== procfs — 内核的信息窗口 ===\n\n");
printf("procfs 目录结构:\n");
printf(" /proc/\n");
printf(" ├── [pid]/ 进程信息\n");
printf(" │ ├── status 进程状态\n");
printf(" │ ├── maps 内存映射\n");
printf(" │ ├── fd/ 文件描述符\n");
printf(" │ └── cmdline 命令行参数\n");
printf(" ├── cpuinfo CPU 信息\n");
printf(" ├── meminfo 内存信息\n");
printf(" ├── mounts 挂载信息\n");
printf(" ├── version 内核版本\n");
printf(" └── sys/ 内核参数\n\n");
printf("--- 进程信息 ---\n");
printf("/proc/[pid]/status:\n");
printf(" Name: bash\n");
printf(" State: S (sleeping)\n");
printf(" Pid: 1234\n");
printf(" PPid: 1\n");
printf(" VmSize: 12345 kB\n\n");
printf("/proc/[pid]/maps:\n");
printf(" 7f0000000-7f0010000 r-xp /lib/libc.so\n");
printf(" 7fff00000-7fff002000 rw-p [stack]\n\n");
printf("--- 系统信息 ---\n");
printf("/proc/cpuinfo:\n");
printf(" processor: 0\n");
printf(" model name: Intel Core i7\n");
printf(" cpu MHz: 3000.000\n\n");
printf("/proc/meminfo:\n");
printf(" MemTotal: 16384000 kB\n");
printf(" MemFree: 8192000 kB\n");
printf(" MemAvailable: 12288000 kB\n\n");
printf("--- 内核参数 ---\n");
printf("/proc/sys/:\n");
printf(" kernel/hostname\n");
printf(" net/ipv4/ip_forward\n");
printf(" vm/swappiness\n\n");
printf("修改内核参数:\n");
printf(" echo 1 > /proc/sys/net/ipv4/ip_forward\n");
printf(" sysctl -w kernel.hostname=myhost\n");#include <stdio.h>
/*
* procfs 的结构:
*
* /proc/
* ├── [pid]/ — 进程信息
* │ ├── status — 进程状态
* │ ├── maps — 内存映射
* │ ├── fd/ — 打开的文件描述符
* │ └── cmdline — 命令行参数
* ├── cpuinfo — CPU 信息
* ├── meminfo — 内存信息
* ├── mounts — 挂载信息
* ├── version — 内核版本
* └── sys/ — 内核参数
*
* procfs 的特点:
* - 不占用磁盘空间
* - 内核动态生成内容
* - 可以通过写入修改内核参数
*/
int main() {
printf("=== procfs — 内核的信息窗口 ===\n\n");
printf("procfs 目录结构:\n");
printf(" /proc/\n");
printf(" ├── [pid]/ 进程信息\n");
printf(" │ ├── status 进程状态\n");
printf(" │ ├── maps 内存映射\n");
printf(" │ ├── fd/ 文件描述符\n");
printf(" │ └── cmdline 命令行参数\n");
printf(" ├── cpuinfo CPU 信息\n");
printf(" ├── meminfo 内存信息\n");
printf(" ├── mounts 挂载信息\n");
printf(" ├── version 内核版本\n");
printf(" └── sys/ 内核参数\n\n");
printf("--- 进程信息 ---\n");
printf("/proc/[pid]/status:\n");
printf(" Name: bash\n");
printf(" State: S (sleeping)\n");
printf(" Pid: 1234\n");
printf(" PPid: 1\n");
printf(" VmSize: 12345 kB\n\n");
printf("/proc/[pid]/maps:\n");
printf(" 7f0000000-7f0010000 r-xp /lib/libc.so\n");
printf(" 7fff00000-7fff002000 rw-p [stack]\n\n");
printf("--- 系统信息 ---\n");
printf("/proc/cpuinfo:\n");
printf(" processor: 0\n");
printf(" model name: Intel Core i7\n");
printf(" cpu MHz: 3000.000\n\n");
printf("/proc/meminfo:\n");
printf(" MemTotal: 16384000 kB\n");
printf(" MemFree: 8192000 kB\n");
printf(" MemAvailable: 12288000 kB\n\n");
printf("--- 内核参数 ---\n");
printf("/proc/sys/:\n");
printf(" kernel/hostname\n");
printf(" net/ipv4/ip_forward\n");
printf(" vm/swappiness\n\n");
printf("修改内核参数:\n");
printf(" echo 1 > /proc/sys/net/ipv4/ip_forward\n");
printf(" sysctl -w kernel.hostname=myhost\n");
return 0;
}道藏笔记
内核启示
procfs 不是一个真正的文件系统——它没有磁盘,不存储数据。它是一扇窗户,让内核把信息暴露给用户空间。
你读 /proc/cpuinfo,procfs 调用一个函数,把 CPU 信息拼成文本返回给你。你读 /proc/[pid]/status,它去找对应进程的 task_struct,把状态、内存、PID 等信息翻译成人类可读的文本。文件在磁盘上吗?不在。它们是函数,读的时候才生成内容。
/proc/sys/ 下的文件更有意思——你写入一个值就能改变内核行为。比如 echo 1 > /proc/sys/net/ipv4/ip_forward 就能开启 IP 转发。双向的窗户,既能看,又能改。
为什么用文件接口?因为 Linux 中一切都是文件。用户空间只需要 open、read、write、close,不需要学新的 API。top、ps、free、htop 这些工具全靠 procfs 活着。
procfs 之试
本章举例通过 procfs 修改内核参数时,开启 IPv4 转发写入的是哪条命令?