Linux /proc 文件系统深度实战:不用装任何工具,用 /proc 诊断生产环境 CPU、内存、IO、网络——最小依赖排查法(2026)

📝 619 字 · ☕ 2 分钟阅读

凌晨 3 点,容器里连 htop 都没有

凌晨 3:17,PagerDuty 响了。生产环境某个服务的 CPU 飙升到 95%,响应延迟从 80ms 跳到 3000ms。你 SSH 进容器——发现这是最小化镜像,没有 htop、没有 iotop、没有 netstat(现在连 ifconfig 都没了)。怎么办?

答案在 /proc。这个虚拟文件系统是 Linux 内核暴露的运行状态窗口,不需要额外安装任何工具——因为它是内核自己维护的。本文教你用 /proc 做一次完整的生产环境诊断,从 CPU 到内存到 IO 到网络,只靠 catls 和你自己的眼睛。

/proc 是什么:一行说清楚

/proc 不是真实磁盘上的文件。你 cat /proc/cpuinfo 的时候,内核当场生成内容返回给你。所以读取开销极低,而且永远是最新数据——没有缓存过期这回事。

/proc文件系统诊断优先级参考图
▲ /proc 诊断优先级参考:左侧评分 + 右侧决策流

内核版本不同,/proc 文件内容可能略有差异。本文基于 Linux 5.15+(Ubuntu 22.04/24.04 默认内核),但核心文件自 2.6 时代就没怎么变过。

CPU 诊断:比 top 更直接

/proc/cpuinfo — 快速看一眼 CPU 架构

别急着用 lscpu/proc/cpuinfo 给你每个逻辑核的完整信息:

$ grep -E "processor|model name|cpu MHz|cache size" /proc/cpuinfo | head -12
processor       : 0
model name      : Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz
cpu MHz         : 3499.998
cache size      : 55296 KB
processor       : 1
model name      : Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz
cpu MHz         : 3499.998
cache size      : 55296 KB

关键行:processor 是逻辑核编号(含超线程)、siblings 是同一物理核上的逻辑核数(判断超线程)、bogomips 在容器里通常不可靠,忽略就行。

/proc/loadavg — 负载一眼定生死

$ cat /proc/loadavg
2.85 2.10 1.67 3/1524 42891

前三个是 1/5/15 分钟负载均值。第四个字段 3/1524 表示「当前运行中的进程数 / 总进程数」。第五个是最近创建的 PID。

实战判断:如果 1 分钟负载 > CPU 核数 × 1.5,而且第四个字段里「运行中」的数字接近 CPU 核数——CPU 瓶颈无疑。如果是磁盘 IO 导致的负载(D 状态进程),CPU 可能很闲但负载很高。区分方法见下面 /proc/pid/status

/proc/stat — CPU 时间片的原始账本

$ head -1 /proc/stat
cpu  11824571 2934 8392104 148293847 111849 0 34458 0 0 0

从左到右:user、nice、system、idle、iowait、irq、softirq、steal(虚拟化偷走的时间)、guest、guest_nice。

实战脚本:采样两次差值计算瞬时 CPU 使用率,只有 4 行:

$ read cpu user nice system idle iowait irq softirq steal < <(head -1 /proc/stat | awk '{print $2,$3,$4,$5,$6,$7,$8,$9}')
$ sleep 1
$ read cpu2 user2 nice2 system2 idle2 iowait2 irq2 softirq2 steal2 < <(head -1 /proc/stat | awk '{print $2,$3,$4,$5,$6,$7,$8,$9}')
$ total=$((user2 - user + nice2 - nice + system2 - system + idle2 - idle + iowait2 - iowait + irq2 - irq + softirq2 - softirq + steal2 - steal))
$ echo "CPU: $((100 * (total - (idle2-idle) - (iowait2-iowait)) / total))%  iowait: $((100 * (iowait2-iowait) / total))%"

如果 iowait% > 15%,问题不是 CPU 慢,是磁盘慢——CPU 在等 IO。这时候该查的不是代码,是 /proc/diskstats

内存诊断:/proc/meminfo 全景图

$ cat /proc/meminfo | head -20
MemTotal:       32781128 kB
MemFree:          512436 kB
MemAvailable:   18234912 kB
Buffers:          234128 kB
Cached:         17302864 kB
SwapCached:            0 kB
Active:         10492136 kB
Inactive:       14823648 kB

别再盯着 MemFree 看了! Linux 会把空闲内存全拿去做 page cache(Buffers + Cached),所以 MemFree 低很正常。真正有用的指标是 MemAvailable——内核估算的「可立即分配给新进程的内存量」。如果 MemAvailable < 总内存的 10%,才需要紧张。

内存泄漏特征Active(anon) 持续增长不回落,Cached 没变化——说明是进程直接分配的内存(非文件缓存)在泄漏。检查 /proc/pid/smaps 定位具体进程。

进程诊断:/proc/pid/* 全家桶

/proc/pid/status — 进程身份证

$ cat /proc/$(pgrep -f "your-service")/status | grep -E "Name|State|VmRSS|Threads"
Name:   your-service
State:  S (sleeping)
VmRSS:     524288 kB
Threads:   24

State 字段定生死:

状态 含义 生产信号
S 可中断睡眠 正常等待(网络 IO、锁)
R 运行中 CPU 密集型任务
D 不可中断睡眠 ⚠️ 等待磁盘 IO,无法被 kill
Z 僵尸 ⚠️ 子进程已死但父进程未 wait
T 停止 SIGSTOP 暂停中

核心经验:如果大量进程在 D 状态,磁盘 IO 是瓶颈。如果 D 状态进程长时间不退——可能是 NFS 挂载点无响应,或磁盘控制器故障。

/proc/pid/fd/ — 文件描述符泄漏诊断

$ ls -l /proc/$(pgrep -f "your-service")/fd | wc -l
1247

1247 个文件描述符?这不对劲。看看是什么:

$ ls -l /proc/$(pgrep -f "your-service")/fd | tail -20
lrwx------ 1 app app 64 Aug  7 02:17 1228 -> 'socket:[9482713]'
lrwx------ 1 app app 64 Aug  7 02:17 1229 -> 'socket:[9482714]'
lrwx------ 1 app app 64 Aug  7 02:17 1230 -> 'socket:[9482715]'

大量未关闭的 socket——HTTP 客户端没复用连接池。定位代码里 requests.get() 是否忘记用 Session、数据库连接是否没放在 with 语句里。

快速统计 FD 类型分布:

$ ls -l /proc/PID/fd | awk '{print $NF}' | sed 's/.*\[//;s/\]//' | sort | uniq -c | sort -rn | head -5
   892 socket
   184 pipe
   102 eventfd
    45 anon_inode
    12 regular_file

892 个 socket 没释放——证据确凿。

/proc/pid/oom_score & oom_score_adj — OOM Killer 的生死簿

$ cat /proc/$(pgrep -f "your-service")/oom_score
57
$ cat /proc/$(pgrep -f "your-service")/oom_score_adj
0

oom_score 越高(最大 1000),越优先被杀。oom_score_adj 是手动调整值:-1000 完全豁免(给数据库用),1000 优先牺牲。

实战操作:保护关键进程不被 OOM Kill:

$ echo -500 > /proc/$(pgrep -f "postgres")/oom_score_adj

IO 诊断:/proc/diskstats

$ cat /proc/diskstats | grep -E "nvme|sda|vda"
   8       0 sda 2453824 129384 198273442 2384729 3847291 2957392 482940182 12938472 0 2847391 15323201 0 0 0 0

字段 4/8 是读写操作次数,字段 7/11 是读写耗费的毫秒数。

实战脚本——1 秒 IO 利用率:

$ awk '/sda /{r=$7;w=$11;print r,w}' /proc/diskstats; sleep 1; \
  awk '/sda /{r2=$7;w2=$11;printf "IO util: %d%%\n",((r2-r)+(w2-w))/10}' /proc/diskstats

持续 > 80% 说明磁盘是瓶颈。

网络诊断:/proc/net/*

/proc/net/tcp — TCP 连接状态一览

$ cat /proc/net/tcp | awk '{print $4}' | sort | uniq -c | sort -rn
    892 0A     # LISTEN 状态后的 ESTABLISHED——正常
     45 06     # TIME_WAIT——偏高但尚可
     12 07     # CLOSE——正在关闭
      3 08     # CLOSE_WAIT ⚠️ 关键!

状态码速查:01=ESTABLISHED、06=TIME_WAIT、07=CLOSE、08=CLOSE_WAIT。

CLOSE_WAIT 是红色警报:对方已经关闭连接,本端还没调 close()。这说明代码里没关闭 socket,最终会耗尽文件描述符。和上面 FD 泄漏的诊断完全对应上了。

/proc/net/dev — 网卡流量实时统计

$ cat /proc/net/dev | grep eth0
eth0:  48273918472  2384729    0    0    0     0          0         0 2394827394 29384728    0    0    0     0       0         0

第二个数字是接收字节数,第十个是发送字节数。采样差值就能算出实时带宽。

实战:三分钟诊断决策树

下次凌晨被叫醒,按这个顺序走,不用装任何工具:

  1. cat /proc/loadavg → 确认负载异常
  2. head -1 /proc/stat(两次采样)→ 区分 CPU 忙还是 IO 等待
  3. cat /proc/meminfo | grep Available → 排除内存不足
  4. for p in /proc/[0-9]*/status; do grep -H State "$p"; done | grep -c " D " → D 状态进程数
  5. cat /proc/net/tcp | ... → TIME_WAIT/CLOSE_WAIT 计数
  6. 定位嫌疑进程 → ls /proc/PID/fd | wc -l → 确认 FD 泄漏

常见问题

Q: /proc/meminfo 里 MemFree 只有 500MB 但 MemAvailable 有 18GB,内存到底够不够?

够。MemFree 低是 Linux 的正常行为——空闲内存会被用作 page cache。MemAvailable 才是内核估算的「可立即分配内存」,相信它而不是 MemFree。如果 MemAvailable 也在掉,才需要紧张。

Q: 进程状态 D(不可中断睡眠)过多久算有问题?

几秒是正常的(磁盘寻道)。持续超过 30 秒通常是存储问题——NFS 挂载超时、SAN 链路故障、磁盘控制器卡死。检查 dmesg 看内核日志里有没有 I/O error。

Q: 容器里 /proc/diskstats 显示的数据是宿主机全局的还是容器隔离的?

全局的。/proc/diskstats 在容器里显示的是宿主机所有磁盘的 IO 数据,没有被 namespaced 隔离。如果你看到其他容器的 IO 也在这一块盘上,它们是混在一起的。这也是为什么容器里用 iostat 看到的数据不一定是你自己的。

总结

/proc 是 Linux 最被低估的调试工具——因为它太朴素了,没有花哨的 TUI,没有彩色柱状图。但当你 SSH 进一个连 htop 都没有的最小化容器时,它是你唯一的武器。

核心记住这几条:

  • 负载异常 → /proc/loadavg → 区分 CPU vs IO 瓶颈 → /proc/stat 采样
  • 内存紧张 → /proc/meminfo 看 MemAvailable,别看 MemFree
  • FD 泄漏 → ls /proc/PID/fd | wc -l,配合 /proc/net/tcp 查 CLOSE_WAIT
  • OOM 风险 → /proc/PID/oom_score_adj 保护关键进程
  • D 状态进程 → 磁盘 IO 瓶颈或存储故障

下次凌晨被叫醒,先别急着谷歌「ubuntu 怎么装 htop」。/proc 就在那里,内核从你开机那一刻就在帮你记账了。


相关阅读:

📤 分享这篇文章