# /proc 详解 —— 内核把自己"导出成文件"的窗口

## 这篇文档是做什么的

> 本仓库几乎每一篇工具文档都在读 `/proc` 里的某个文件:`top`/`pidstat` 读 `/proc/<pid>/stat`、`free` 读 `/proc/meminfo`、`mmap` 篇看 `/proc/<pid>/maps`、`core_pattern` 在 `/proc/sys/...`。**`/proc` 是这些工具共同的数据源**。本篇把它当成一个独立主题讲透:`/proc` 到底是什么(不是磁盘上的文件!)、目录怎么组织(进程级 vs 系统级)、每类文件对应内核里的什么、怎么用它排查、以及 `/proc/sys`(sysctl) 怎么改内核行为。

## 可以回答什么问题

| 问题 | 答案在哪 |
|------|---------|
| `/proc` 为什么文件大小是 0 却有内容？ | §零、§5.4——不是磁盘文件，是内核现场生成的文本 |
| `top`/`free`/`iostat` 的数据哪来的？ | §三——`/proc/stat`/`meminfo`/`diskstats` 文件 |
| 一个进程打开了哪些文件？内存在哪里？ | §二——`/proc/<pid>/fd`、`/proc/<pid>/maps` |
| `cat /proc/xxx` 时内核到底做了什么？ | §五——VFS → procfs → seq_file → 现场读内核数据 |
| `/proc/sys` 和 `sysctl` 是什么关系？ | §四——同一套东西，路径分隔符不同 |
| `/proc` 和 `/sys` 的分工是什么？ | §七——进程/内核信息 vs 设备/硬件拓扑 |
| 容器里的 `/proc` 可信任吗？ | §六——`cpuinfo`/`meminfo` 常显示宿主机值，看 cgroup 更准确 |


> 相关:进程级文件对应的内核结构见 [../process/task-struct.md](/concepts/process/task-struct.md);地址空间 `maps` 见 [../memory/mmap.md](/concepts/elf/mmap.md) 与 [../elf/memory-layout.md](/concepts/elf/memory-layout.md);各工具怎么用这些文件见 [top](/tools/cpu/top.md)/[pidstat](/tools/cpu/pidstat.md)/[vmstat](/tools/cpu/vmstat.md)/[free](/tools/memory/free.md)。

## 零、一句话认知：/proc 是一个"虚拟文件系统"，文件内容是内核实时生成的

`/proc` 里的东西**不在任何磁盘上**。它是一种 **虚拟文件系统(procfs)**:你 `cat` 一个文件的瞬间,内核**临时把当前状态格式化成文本**返回给你。文件大小几乎都是 0(还没读时没有内容),但一读就有实时数据。

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<u>> #E3F2FD
  BorderColor<<u>> #1976D2
  BackgroundColor<<k>> #C8E6C9
  BorderColor<<k>> #388E3C
  BackgroundColor<<d>> #FFCDD2
  BorderColor<<d>> #C62828
}
rectangle "用户态\ncat /proc/meminfo\nps / top / free ...\n(其实都是 open+read)" <<u>> as U
rectangle "procfs (内核里的虚拟文件系统)\nread 时**现场调用内核函数**\n把 task_struct / mm_struct /\n全局计数器 格式化成文本" <<k>> as K
rectangle "磁盘\n（**无关**：/proc 不落盘）" <<d>> as D
U -right-> K : ① open/read 系统调用
K -right-> U : ② 返回实时生成的文本
K -[#C62828,dashed]-> D : ✗ 不读写磁盘
note bottom of K : 每次 read 都是一次"内核状态快照"\n→ 值是活的,连读两次可能不同
@enduml
```

> **核心记忆**:`/proc` 是内核状态的**只读(大部分)文本视图**,读它 = 让内核现场生成一份快照。所以 `/proc` 里的文件"大小 0 却有内容"、"每次读都可能变"——它们是**接口**,不是数据文件。`/proc/sys` 下少数文件**可写**,写入 = 调节内核参数。

## 一、三大类：按 PID 的进程目录 / 系统全局 / 可调参数

`ls /proc` 看到的东西分三类：

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<a>> #E3F2FD
  BorderColor<<a>> #1976D2
  BackgroundColor<<b>> #C8E6C9
  BorderColor<<b>> #388E3C
  BackgroundColor<<c>> #FFE0B2
  BorderColor<<c>> #EF6C00
}
rectangle "① 数字目录 /proc/<pid>/\n每个进程一个(名字=PID)\n→ 那个进程的私有状态\n(status/stat/maps/fd/...)" <<a>> as A
rectangle "② 系统全局文件 /proc/xxx\nmeminfo/stat/cpuinfo/loadavg/\ninterrupts/diskstats/net/...\n→ 整机层面的状态与统计" <<b>> as B
rectangle "③ /proc/sys/**（sysctl）\nkernel/ vm/ net/ fs/...\n→ **可写**,调节内核运行参数" <<c>> as C
note bottom of A : 对应 task_struct 及其资源对象\n(见 task-struct.md)
note bottom of B : 对应内核全局计数器/子系统状态
note bottom of C : 等价于 sysctl 命令
@enduml
```

- **`/proc/self`**:一个特殊符号链接,永远指向**读它的那个进程自己**的 `/proc/<pid>`——脚本里不知道自己 PID 时很有用(`cat /proc/self/status`)。
- **`/proc/thread-self`**:指向当前**线程**的 `/proc/<pid>/task/<tid>`。

## 二、进程级：/proc/&lt;pid&gt;/ —— 一个进程的全部家当

这是 [task-struct.md](/concepts/process/task-struct.md) §四的展开。每个 `/proc/<pid>/` 子项对应内核里那个进程的某个资源对象：

| 路径 | 内容 | 对应内核结构 | 谁在用 |
|------|------|-------------|--------|
| `status` | 人类可读汇总:Name/State/Tgid/Pid/PPid/Threads/VmRSS/Uid/CapEff… | `task_struct` 本体 | `ps`、排查通用 |
| `stat` | 单行原始字段:state、utime/stime、min_flt/maj_flt、priority、starttime、第39字段=上次所在核 | `task_struct` | [top](/tools/cpu/top.md)/[pidstat](/tools/cpu/pidstat.md) |
| `statm` / `smaps` | 内存用量(页数)/ 每段详细 RSS/PSS/脏页 | `mm_struct` | 内存分析 |
| `maps` | VMA 列表:每段虚拟地址/权限/后备文件 | `mm_struct` 的 VMA | [mmap](/concepts/elf/mmap.md)/[memory-layout](/concepts/elf/memory-layout.md) |
| `fd/` | 打开的每个 fd → 指向的文件/socket | `files_struct` | [lsof](/tools/network/lsof.md) |
| `task/` | 线程组里每个线程一个 `<tid>/` 子目录 | 一组 `task_struct` | `top -H`/`ps -L` |
| `cwd`/`root`/`exe` | 当前目录/根目录/可执行文件(符号链接) | `fs_struct` / `mm` | 排查 |
| `cmdline`/`environ` | 启动命令行 / 环境变量 | — | 看进程身份 |
| `sched`/`schedstat` | vruntime、被调度次数、等待时间 | 调度实体 | [scheduling](/concepts/process/scheduling.md) |
| `limits` | 资源上限(rlimit) | `signal_struct` | 排查 ulimit |
| `ns/` | 所属各命名空间 | `nsproxy` | 容器排查 |
| `numa_maps` | 各内存段落在哪个 NUMA 节点 | `mm` + 内存策略 | [numa](/concepts/numa/numa.md) |
| `coredump_filter`/`dumpable` | core dump 行为控制 | — | [core-dump](/crash/core-dump.md) |

```bash
cat /proc/$$/status          # $$ = 当前 shell 的 PID
cat /proc/self/maps          # 读它的进程自己的地址空间布局
ls -l /proc/1234/fd          # 进程 1234 打开的所有 fd
cat /proc/1234/task/*/stat   # 该进程每个线程的调度状态
```

> `/proc/<pid>/` 展开的每一项,都是 [task-struct.md](/concepts/process/task-struct.md) 里那张"资源对象总表"的文件化投影。**要理解字段背后的结构,回那篇看。**

## 三、系统级：整机状态与统计

不带 PID 的顶层文件,反映**全系统**状态。挑最常用的：

| 文件 | 内容 | 相关工具 |
|------|------|---------|
| `/proc/meminfo` | 内存总量/可用/cache/buffers/swap/脏页/HugePage… | [free](/tools/memory/free.md) |
| `/proc/vmstat` | 内存管理细粒度计数:换页、缺页、回收… | [vmstat](/tools/cpu/vmstat.md) |
| `/proc/stat` | 全系统 CPU 时间(user/nice/system/idle/iowait/irq)、上下文切换总数、启动后中断数 | [top](/tools/cpu/top.md)/[mpstat](/tools/cpu/mpstat.md)/[vmstat](/tools/cpu/vmstat.md) |
| `/proc/loadavg` | 1/5/15 分钟平均负载 + 运行/总任务数 | uptime |
| `/proc/cpuinfo` | 每个逻辑 CPU 的型号/主频/缓存大小/标志位 | 查硬件 |
| `/proc/interrupts` | 每个中断号在每个 CPU 上的计数 | 中断亲和性([thread-affinity](/concepts/process/thread-affinity.md)) |
| `/proc/softirqs` | 软中断计数(NET_RX 等) | 网络排查 |
| `/proc/diskstats` | 每块设备的 IO 计数(次数/扇区/耗时) | [iostat](/tools/disk/iostat.md) |
| `/proc/net/*` | tcp/udp/dev 等网络栈状态 | [ss](/tools/network/ss.md) |
| `/proc/mounts`/`/proc/filesystems` | 挂载表 / 支持的文件系统 | 排查挂载 |
| `/proc/uptime` / `/proc/version` | 开机时长 / 内核版本 | — |

> **要点**:`top`/`free`/`vmstat`/`iostat` 这些工具**本身不神秘**——它们就是定期读上面这些文件、做差值、算速率、格式化显示。看懂 `/proc/stat`,就看懂了 CPU 利用率是怎么算出来的(两次采样求各态时间增量占比)。

## 四、/proc/sys —— 可写的内核旋钮（sysctl）

`/proc/sys/` 下的文件**可以写**,写入即刻改变内核行为。它和 `sysctl` 命令是**同一套东西**(路径 `/proc/sys/a/b/c` ↔ `sysctl a.b.c`)：

```bash
# 读
cat /proc/sys/vm/swappiness            # = sysctl vm.swappiness
# 临时改(重启失效),两种等价写法
echo 10 > /proc/sys/vm/swappiness
sysctl -w vm.swappiness=10
# 永久改:写进 /etc/sysctl.conf 或 /etc/sysctl.d/*.conf,然后 sysctl -p
```

本仓库出现过的几个 `/proc/sys` 旋钮：

| 路径 | 作用 | 出处 |
|------|------|------|
| `/proc/sys/kernel/core_pattern` | core dump 文件名/管道模板 | [core-dump](/crash/core-dump.md) |
| `/proc/sys/kernel/randomize_va_space` | ASLR 开关(0/1/2) | [memory-layout](/concepts/elf/memory-layout.md) |
| `/proc/sys/kernel/numa_balancing` | 自动 NUMA 均衡 | [numa](/concepts/numa/numa.md) |
| `/proc/sys/vm/overcommit_memory` | 内存超额分配策略 | [free](/tools/memory/free.md) |
| `/proc/sys/vm/drop_caches` | 手动释放 page cache(测试用) | 内存实验 |
| `/proc/sys/vm/swappiness` | 倾向换页的程度 | 内存调优 |

> **注意**:直接 `echo >` 只是**临时**改动,重启丢失;要持久化用 `/etc/sysctl.d/`。写这些旋钮多数需要 root,且改错(如 `overcommit`/`core_pattern`)会影响全系统稳定性——生产环境务必清楚含义再改。完整的配置入口地图(sysctl / /sys / 启动参数怎么选)见 [kernel-tuning.md](/tools/proc/kernel-tuning.md)。

## 五、原理：`cat /proc/xxx` 时内核到底做了什么

前面说"读它=内核现场生成快照"是结论,这一节讲**机制**:procfs 凭什么能让一个"文件"在被读时才产生内容。

### 5.1 procfs 是一个挂载的文件系统，接进了 VFS

Linux 一切文件操作都走 **VFS(虚拟文件系统层)**——它定义了 `open`/`read`/`write` 等操作的抽象接口,底下可以挂各种真实实现(ext4、xfs、tmpfs……)。**procfs 就是其中一种文件系统实现**,开机时被挂载到 `/proc`:

```bash
mount | grep proc        # proc on /proc type proc (rw,...)   ← 一个 type=proc 的文件系统
```

- 它注册了一个 `file_system_type`,挂载后 `/proc` 这棵目录树的每个节点(inode)都由 procfs 提供。
- 关键在于:procfs 的 inode 绑定的 `file_operations` 里,**`.read` 指向的不是"读磁盘块",而是内核自己的一个函数**。于是用户态 `read()` 经 VFS 分发,最终调到那个内核函数——**内容是函数当场算出来的,不是从存储里取的**。

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<u>> #E3F2FD
  BorderColor<<u>> #1976D2
  BackgroundColor<<v>> #FFF9C4
  BorderColor<<v>> #F9A825
  BackgroundColor<<p>> #C8E6C9
  BorderColor<<p>> #388E3C
  BackgroundColor<<k>> #FFE0B2
  BorderColor<<k>> #EF6C00
}
rectangle "用户态\nread(fd, buf, n)" <<u>> as U
rectangle "VFS 层\n按 fd→inode→file_operations\n分发 .read" <<v>> as V
rectangle "procfs 的 .read 实现\n(通常是 seq_file 框架)" <<p>> as P
rectangle "内核数据源\ntask_struct / mm_struct /\n全局计数器 / 子系统状态" <<k>> as K
U -down-> V : ① 系统调用
V -down-> P : ② 分发到 procfs\n(不是磁盘驱动!)
P -down-> K : ③ **现场读内核内存**,\n格式化成文本
K -up-> U : ④ 文本拷回用户 buf
note right of P : 没有任何磁盘 IO——\n"文件内容"是这次调用算出来的
@enduml
```

> **这就是"虚拟"的技术含义**:procfs 复用了 VFS 的文件抽象(所以你能 `open/read/cat`),但把"读数据"这个动作**替换成"调用一个内核函数生成数据"**。文件只是**接口外壳**,背后是活的内核状态。

### 5.2 seq_file：procfs 文件内容的标准生成框架

早期 procfs 的 `read` 处理器要自己管缓冲区、偏移、边界,很容易出 bug(尤其内容跨多次 read、或长度超过一页时)。现在绝大多数 `/proc` 文件用 **`seq_file`(sequence file)** 框架生成内容,它把"输出一段文本"抽象成**遍历一个序列**:

- 驱动方(某个 /proc 文件的实现)提供四个回调:`start()` / `next()` / `stop()` / `show()`。
- 内核把它们当成迭代器:`start` 定位到第一个元素 → `show` 把当前元素格式化成文本(用 `seq_printf` 往缓冲里写)→ `next` 移到下一个 → 直到 `stop`。
- **`seq_file` 自动处理**:缓冲区扩容、用户多次 `read` 的偏移续接、`lseek`——实现者只管"怎么把一个元素变成一行文本"。

以 `/proc/<pid>/status` 为例:它的 `show()` 就是**当场读那个进程的 `task_struct` 字段**(`state`、`pid`、`VmRSS`……),用 `seq_printf` 拼成 `Name:\t...\nState:\t...\n` 这样的文本。你每 `cat` 一次,`show()` 就重跑一次,读的是**此刻**的 `task_struct` —— 这就是"值是活的"的根源。

```plantuml
@startuml
skinparam shadowing false
skinparam sequence {
  ParticipantBackgroundColor #E3F2FD
  ParticipantBorderColor     #1976D2
  ArrowColor                 #37474F
}
participant "cat(用户)" as C
participant "seq_file 框架" as SF
participant "该文件的 show()\n(如读 task_struct)" as SH
C -> SF : read()
SF -> SH : start() 定位首元素
loop 每个元素
  SF -> SH : show(elem)\n→ seq_printf 现场格式化
  SF -> SH : next() 下一个
end
SF -> C : 把攒好的文本拷回用户缓冲
note over SH : 读的是**当前**内核状态\n→ 下次 cat 值可能不同
@enduml
```

### 5.3 目录树怎么建：proc_dir_entry 与动态生成

- **静态节点**:系统级文件(`/proc/meminfo`、`/proc/stat` 等)在内核初始化/模块加载时,通过 `proc_create()` 注册一个 **`proc_dir_entry`**(procfs 的目录项),绑定各自的 `file_operations`/`seq_operations`。内核子系统各自登记自己的那几个文件。
- **动态节点**:`/proc/<pid>/` 这类**不是预先建好的**——系统里进程时刻在增减,不可能为每个进程静态建目录。procfs 在你**查找/遍历 `/proc` 时动态生成**:`ls /proc` 时,procfs 遍历内核进程链表,为每个存在的 task 现场"变出"一个 `<pid>` 目录;`open /proc/1234/status` 时,先按 1234 找到对应 `task_struct`,再生成该文件。**进程没了,对应目录下次就查不到了**。
- 这也解释了 §零说的"文件大小是 0":`proc_dir_entry` 没有真实长度,`stat()` 拿不到大小,只有真去 `read` 触发 `show()` 才有内容。

### 5.4 由原理反推的几个现象

| 现象 | 原理解释 |
|------|---------|
| 文件大小 0 却有内容 | 没有后备存储,大小无意义;内容是 `read` 时 `show()` 现算的 |
| 连读两次值不同 | 每次 `read` 都重新读当前内核状态,不缓存 |
| `/proc/<pid>` 进程退出就消失 | 目录是查找时按 `task_struct` 动态生成的,进程没了就生成不出来 |
| 几乎没有磁盘 IO | procfs 的 `.read` 是内核函数,不碰块设备 |
| `/proc/sys` 可写 | 那些节点的 `.write` 回调绑到"设置某内核变量"的函数上,写入即赋值 |
| 容器里 meminfo 是宿主机的 | procfs 的 `show()` 默认读宿主机全局变量,不感知 cgroup(除非 lxcfs 拦截) |

> **一句话讲原理**:procfs 是一个挂在 VFS 下的**文件系统实现**,它把文件的 `read`/`write` 操作**接管**成"调用内核函数现场生成/接收数据"(大多用 `seq_file` 框架遍历内核结构、`seq_printf` 输出);进程目录 `/proc/<pid>` 则在被访问时按 `task_struct` **动态生成**。所以它有文件的外壳(能 open/read/cat)、却无文件的实体(不落盘、大小 0、值实时变)。

## 六、怎么读这些文件：注意事项

- **别用文件大小判断内容**:`ls -l /proc/meminfo` 显示 0 字节,但 `cat` 有满屏内容——大小是假的,必须真去 `read`。
- **值是"活的" **：连续读两次 `/proc/stat` 会不同。**利用率/速率类指标都要"采两次样求差值"**——这正是 `top`/`vmstat` 每隔 N 秒刷新的原因。
- **`stat` 字段靠位置**:`/proc/<pid>/stat` 是空格分隔的一长行,靠**字段序号**取值(如第 14/15 是 utime/stime、第 39 是所在 CPU)。字段含义查 `man 5 proc`。
- **`man 5 proc` 是权威手册**:每个文件的精确格式都在这里,拿不准就查它。
- **权限**:别的用户的 `/proc/<pid>/` 里敏感项(`environ`、`maps`)通常只有属主/root 能读。
- **容器里 /proc 可能是"部分虚假的" **：容器内 `/proc/cpuinfo`/`meminfo` 常显示宿主机的值(除非用 lxcfs 之类矫正),排查容器资源要看 cgroup 而非 /proc。

## 七、和 /sys、/proc 的分工（别混）

- **`/proc`**:历史上先有,混装"进程信息"+"内核杂项"+"可调参数(/proc/sys)"。
- **`/sys`(sysfs)**:后来引入,专门以更规整的层级导出**设备与内核对象模型**(块设备、CPU 拓扑、cgroup v1 的部分接口等)。
- 经验:**进程/传统内核信息 → `/proc`;设备与硬件拓扑 → `/sys`**。两者都是虚拟文件系统,读法一样。

## 八、和本仓库其他文档的关系

- **进程级文件的结构来源**:[../process/task-struct.md](/concepts/process/task-struct.md)(status/maps/fd/task 对应哪个内核对象)、[../process/scheduling.md](/concepts/process/scheduling.md)(sched/schedstat)。
- **地址空间**:[../memory/mmap.md](/concepts/elf/mmap.md)、[../elf/memory-layout.md](/concepts/elf/memory-layout.md)(maps/smaps/numa_maps)。
- **系统级文件的消费者**:[../cpu/top.md](/tools/cpu/top.md)/[pidstat](/tools/cpu/pidstat.md)/[mpstat](/tools/cpu/mpstat.md)/[vmstat](/tools/cpu/vmstat.md)、[scheduling-observation](/tools/cpu/scheduling-observation.md)、[../memory/free.md](/tools/memory/free.md)、[../disk/iostat.md](/tools/disk/iostat.md)、[iotop](/tools/disk/iotop.md)、[../network/ss.md](/tools/network/ss.md)、[../network/lsof](/tools/network/lsof.md)、[rdma](/tools/network/rdma.md)、[../numa/numactl.md](/tools/numa/numactl.md)、[numastat](/tools/numa/numastat.md)、[sar](/tools/history/sar.md)、[perf](/tools/code/perf.md)、[strace](/tools/code/strace.md)。
- **/proc/sys 旋钮**:[../crash/core-dump.md](/crash/core-dump.md)(core_pattern)、[../elf/memory-layout.md](/concepts/elf/memory-layout.md)(ASLR)、[../numa/numa.md](/concepts/numa/numa.md)、[../process/thread-affinity.md](/concepts/process/thread-affinity.md)(interrupts/中断亲和);配置入口全景见 [kernel-tuning.md](/tools/proc/kernel-tuning.md)。
- **原理相关**:[../elf/compile-link-load.md](/concepts/elf/compile-link-load.md)(open/read 走系统调用)、[../code/syscall.md](/concepts/process/syscall.md)(read 陷入内核)。

## 九、一句话总结

> **`/proc` 是一个不落盘的虚拟文件系统(procfs):你每次 read 它,内核就现场把 `task_struct`/`mm_struct`/全局计数器等状态格式化成文本返回——所以文件大小是 0、内容却是活的。它分三类:`/proc/<pid>/`(每进程一个目录,是 task_struct 及其资源对象的文件化投影)、系统级全局文件(meminfo/stat/cpuinfo/diskstats/net… 是 top/free/vmstat/iostat 的共同数据源,利用率靠采两次样求差值)、以及可写的 `/proc/sys`(sysctl 旋钮,调节内核行为但改错影响全系统)。设备与硬件拓扑则归 `/sys`。看懂 /proc,就看懂了本仓库几乎所有性能工具的数据从哪来。**
