排查 Linux 故障这几年,我见过太多人一上来就装工具。top、ps、vmstat、strace、perf 装了一堆,最后发现真正有用的线索早就躺在 /proc 里了,只是很少有人愿意从头把它啃明白。/proc 是内核对外暴露的一整套虚拟接口,读它就是在直接问内核"你现在到底什么状态"。这篇文章不打算铺开讲 /proc 下面几百个文件的清单,而是从故障排查的角度,把它拆成几个真实场景:系统级体检怎么做、内存数字怎么读才不被骗、进程卡死和僵尸怎么查、/proc/sys 调参有哪些坑、生产环境里怎么给 /proc 上锁。运维、SRE、嵌入式开发,还有刚开始碰内核源码的人,应该都能从这里找到能直接拿去用的东西。
1. /proc 到底是什么:一个不占磁盘的"文件系统"
1.1 为什么说 /proc 的文件是"假"的
第一次用 ls -l /proc 的人都会愣一下:meminfo 大小是 0,cmdline 大小也是 0,cat 却能出来一大段内容。这不是什么 bug,而是 /proc 本来就不是磁盘上的文件。
你 cat /proc/meminfo 的时候,内核并没有去硬盘上找一个叫 meminfo 的文件,而是现场调用了一段内核代码,把内存管理模块里当前的数据结构整理成文本返回给你。也就是说,这些"文件"的数据是每次读取时实时生成的,而不是存储在介质上的。
所以 df -h /proc 会显示 Size 是 0,du 跑在 /proc 上更是毫无意义。有人看到 du -sh /proc/* 报了很大的数字就以为系统被什么日志吃满了,实际上那只是内核在生成目录项和文件属性时统计到的"虚拟大小",跟磁盘占用没有半毛钱关系。
这个理解是后面所有排查动作的地基。一旦你意识到 /proc 是"读的时候才生成"的,很多怪现象就能解释通了:同一秒内连续 cat 两次 /proc/meminfo,数字会不一样;某些文件用大块读取会得到奇怪的截断;往 /proc 里的文件写东西,本质是在触发内核里的一个处理函数,而不是在改磁盘上的字节。
1.2 它在内核里是怎么挂起来的
从内核视角看,/proc 是一个注册在 VFS(虚拟文件系统)层上的文件系统类型,模块名叫 procfs。它没有底层的块设备,不需要格式化,挂载的时候也不需要指定设备名,直接 mount -t proc proc /proc 就行。
VFS 给所有文件系统提供了一套统一的操作接口,procfs 只需要实现 open、read、write、iterate 这些回调。重点在这里:procfs 里每个节点不是一个固定的磁盘 inode,而是一组回调函数。你打开 /proc/meminfo,VFS 调用的是 meminfo_proc_show 这类函数;你往 /proc/sys/vm/swappiness 里写数字,调用的是对应的 sysctl 处理函数。
这也是为什么 /proc 下文件的大小总是 0,但权限却非常讲究——因为"文件"的元数据(大小、修改时间)都是虚拟的,权限却是真实按内核安全策略控制的。理解这层回调机制,你就明白了:凡是文档里说"写这个文件可以调参"的,本质是在调用一个内核函数,参数合法性由内核代码管,写错了一般会返回 EINVAL,而不是像普通文件那样先写进去再说。
1.3 /proc 使用中的常见误区
排查经验多了以后,我发现新手在 /proc 上翻车的点基本是固定的,列出来给你避雷:
- 试图 rm /proc 下的条目:绝大多数删除操作要么直接报错,要么什么都不发生。你在 /proc 里删掉的是一个"接口的目录项",不是把底层数据删了,重启或者重新触发后它又会出现。
- 用普通文本文件的思维读 /proc:某些 /proc 文件对单次 read 的缓冲区大小有要求,缓冲区太小时可能只返回部分内容,或者行为异常。大文件建议用
cat或dd bs=4096,不要想当然地head -c 1。 - 把 /proc/kcore 当成"可以 cat 的内存镜像":那是一个 ELF core 格式的虚拟文件,代表了物理内存的映射。直接拿 cat 去读,轻则卡死终端,重则把系统 IO 拖垮。要看内存内容,老老实实用 crash 或者 gdb 挂上去读。
- 混淆 /proc 和 /sys:/proc 早期承载了大量内核状态和调参接口,后来内核把设备、总线、驱动相关的信息逐渐搬到 /sys 下。遇到硬件和驱动问题,优先去 /sys 找;/proc 主要看进程、内存、网络、内核全局状态。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 故障现场取证:用 /proc 给系统拍一张"体检片"
2.1 五分钟内的系统级速查清单
接到一个"不知道哪里有问题"的告警时,我一般不会立刻上 perf 或者抓包,而是先花两分钟把 /proc 的几个关键文件扫一遍,建立基线。顺序基本是:
bash复制cat /proc/loadavg
cat /proc/uptime
cat /proc/stat | head -20
cat /proc/interrupts
cat /proc/softirqs
cat /proc/diskstats
cat /proc/meminfo
这套组合拳能快速回答三个问题:系统整体压力是上行还是下行、压力主要落在 CPU 还是 IO、有没有明显异常的硬件中断。
先说 loadavg。很多人把 load average 当成"CPU 使用率",这是典型的误读。/proc/loadavg 的第一列是 1 分钟内的平均活跃任务数,这里的"活跃"包括正在运行的进程和处于不可中断睡眠(D 态)的进程。也就是说,即使 CPU 完全空闲,只要有一堆进程卡在磁盘 IO 上,load 一样会飙得很高。所以看到 load 高的时候,先别急着下结论说 CPU 不够,去查查 D 态进程和磁盘再定性。
/proc/uptime 也是被严重低估的文件。第一列是系统开机秒数,第二列是"所有 CPU 核空闲时间的总和"。注意是总和,所以在多核机器上第二列会比第一列大得多,这是正常现象。用它可以直接估算整机的平均空闲率:空闲率约等于第二列除以(第一列乘以核数)。如果这个比例长时间低于 0.1,说明机器基本一直在干活。
2.2 CPU 时间都去哪了:读 /proc/stat 的正确姿势
/proc/stat 里以 cpu 开头的那一行,是整机 CPU 时间的累计值,单位是 USER_HZ(通常 100 分之一秒)。字段顺序是 user、nice、system、idle、iowait、irq、softirq、steal、guest、guest_nice。
排查时我最关心的不是 user 和 system,而是 iowait 和 steal。iowait 高说明 CPU 在等 IO 完成,这时候加 CPU 核数没有意义,瓶颈在磁盘;steal 高则说明宿主机上其他虚拟机在抢 CPU,这是云上"机器突然变慢"的高频原因。
软中断的分布用 /proc/softirqs 看。这里面每一列代表一个 CPU 核,行名包括 HI、TIMER、NET_TX、NET_RX、BLOCK、IRQ_POLL、TASKLET、SCHED 等。你如果发现 NET_RX 全部堆在 CPU0 上,说明网卡中断没有做多队列绑定,流量一大 CPU0 就会被打满,而其他核在旁边看热闹。配合 /proc/interrupts 里每个中断号在各 CPU 上的计数,能直接定位中断倾斜问题。
硬中断的均衡可以通过 /proc/irq/<中断号>/smp_affinity 调整,它是一个十六进制 CPU 掩码。比如想把这个中断绑定到 CPU2 和 CPU3,就写入 c。这个操作对网卡多队列场景特别有用,但改之前一定要确认驱动支持,不然中断虽然绑过去了,队列还是走老的 CPU,白忙一场。
2.3 磁盘 IO 的原始账本:/proc/diskstats
iostat 好用,但它的原始数据来源就是 /proc/diskstats。手动读一遍这个文件,你会发现字段远比你想象的多,而且埋了不少雷。
标准情况下,一个块设备条目大约有 14 个字段:读完成次数、读合并次数、读扇区数、读耗时、写完成次数、写合并次数、写扇区数、写耗时、正在进行中的 IO 数、IO 总耗时、加权 IO 总耗时。这里的"耗时"单位是毫秒,而且是累计值,不是单次 IO 的延迟。
最容易踩的坑是分区条目。sda 是整块盘,sda1 是分区,两者的字段数量经常不一样,分区条目可能只有前面几个计数。写脚本解析的时候如果不区分块设备和分区,字段位置全错,算出来的 IO 延迟离谱到没法看。
快速判断磁盘是否饱和的方法:持续观察第 12 列(正在进行中的 IO 数),如果长期不为 0,说明有请求排队;再看第 13 列除以采样间隔的"IO 时间占比",接近 1 就是满负荷。真正要定位是哪个进程在写,得去 /proc/
另外提一句,排查挂载问题时要多信 /proc/mounts 而不是 /etc/mtab。/etc/mtab 在某些异常卸载、容器迁移场景下会过期,/proc/mounts 是内核实时生成的挂载表,永远反映真实状态。
3. 内存异常排查:MemAvailable 不是 MemFree,别被数字骗了
3.1 MemTotal、MemFree、MemAvailable 三兄弟的区别
内存类的故障告警里,最普遍的一个误判是:MemFree 剩下几百兆,就觉得内存不够了。实际上现代 Linux 的内存管理哲学是"内存闲着就是浪费",内核会把空闲内存大量用作 page cache,所以 MemFree 长期偏低反而是正常状态。
/proc/meminfo 里的 MemFree 只代表"完全没有被使用的物理页",而 MemAvailable 才是内核估算的、在不触发交换的前提下还能安全分配给新程序的物理内存量。这个估算考虑了 page cache 的可回收性、内存水位线、以及回收时可能带来的性能开销。所以判断"这台机器还能不能扛得住新进程",看 MemAvailable 比看 MemFree 靠谱得多。
举个例子:一台 16G 的机器,MemFree 只有 300M,MemAvailable 却有 12G,因为中间差了大量的 page cache,它们随时可以被回收。但你也要注意,MemAvailable 是"估算值",不是保票。在极端内存碎片化或者 cgroup 限制的场景下,实际分配可能比估算值更早失败。
Cached 和 Shmem 的差别也容易搞混。Cached 里的 file-backed page cache 可以轻松回收;但 Shmem(tmpfs、共享内存)虽然长得像 cache,回收它却要走 swap 或者显式删除,不能像普通 page cache 那样直接丢弃。所以看到 Shmem 很大的时候,别指望 drop_caches 能把它清掉。
3.2 OOM 前后,/proc 里留下的"凶手"痕迹
生产环境最痛的内存事故就是进程被 OOM Killer 干掉。事后复盘时,/proc 里的好几个文件能帮你还原现场。
首先是 /proc/<pid>/oom_score。内核给每个进程算了一个分数,这个数字越大,OOM 时越容易被选中干掉。实际影响它的是 oom_score_adj,范围是 -1000 到 1000。注意 oom_score_adj 为 -1000 的进程(通常是系统关键进程)会被 OOM Killer 完全跳过,这么做是为了自保,但也可能逼着内核去杀其他更重要的进程。
复盘的一次典型事故:一台服务器上跑着 Java 应用,OOMKilled 反复发生,但宿主机 free -h 看起来还有好几个 G 空闲。后来一查,问题根本不在宿主机内存总量,而是容器 cgroup 的内存上限太小。这时候 /proc/meminfo 是宿主机的全局视角,看不到容器限制,必须结合 /sys/fs/cgroup/memory.max 和 /proc/<pid>/cgroup 才能还原真相。
内存碎片化也是 OOM 的隐形推手。/proc/buddyinfo 能看出每个内存区域的页块分布,如果高阶页块(order 越高代表越大的连续内存)长时间为 0,而低阶页块也不稳定,说明内存在持续碎片化。配合 /proc/zoneinfo 里的水位线数据,可以判断是不是频繁在高低水位之间震荡。
3.3 drop_caches 的两个常见误用
echo 3 > /proc/sys/vm/drop_caches 可以说是 /proc 里被滥用最多的操作。它的作用是释放 page cache、dentries 和 inodes,但很多人把它当成"清理内存"的万能药。实际上它清不掉匿名内存(进程真正占用的堆栈),也解决不了 cgroup 内存超限的问题。更要命的是,清完 cache 之后,所有被释放的缓存文件下次访问都要重新读磁盘,整机性能反而可能跳水。
所以我的建议是:drop_caches 只用于性能测试前清空缓存、或者验证"去掉缓存后业务是否还正常"这类场景,千万别把它写进定时任务。真觉得内存紧张,先看 /proc/vmstat 里的 nr_dirty 和 /proc/meminfo 里的 Writeback,如果脏页堆积说明磁盘写回跟不上;再看 /proc/pressure/memory,这个文件读出来的数值直接反映内存回收对业务的压力,比盯着 MemFree 猜靠谱多了。
4. 进程卡死与僵尸:/proc/ 里的整条线索链
4.1 进程到底卡在哪:wchan、status、stack 三件套
一条应用告警过来说"进程卡住了",第一步不是重启,而是去看进程在内核里等什么。/proc/<pid>/wchan 给出的是进程当前在内核中的等待通道,也就是阻塞在内核哪个函数上。比如卡在 pipe_read,说明它在等管道对端写数据;卡在 wait_woken,大概率在等网络事件。
/proc/<pid>/status 的价值在于里面的 State 字段和上下文切换计数。State 为 R 是在跑或者可运行,S 是可中断睡眠,D 是不可中断睡眠。D 态最让人头疼,因为它连 kill -9 都杀不掉,典型的成因是内核线程在等待某个 IO 操作返回,比如 NFS 挂死、磁盘控制器卡住。
再看 voluntary_ctxt_switches 和 nonvoluntary_ctxt_switches 两个字段。voluntary 多说明进程经常主动让出 CPU(比如等在等 IO),nonvoluntary 多说明时间片被抢占。如果一个进程 nonvoluntary 疯狂增长,说明它在抢 CPU 且经常被调度器打断,结合 CPU 占用率就能判断是不是忙等。
root 可以进一步看 /proc/<pid>/stack,这是内核栈的回溯,能看到进程在内核里完整的调用路径。但这玩意不是免费午餐:它需要内核开启 CONFIG_STACKTRACE,而且某些场景下读取它自身也会引起短暂阻塞。我一般只在 D 态进程排查时用,平时不碰。
还有一个容易被忽略的文件是 /proc/<pid>/syscall,它显示进程当前执行的系统调用号和参数。如果显示"running",说明进程正在用户态跑;如果显示一个系统调用号但进程又不在正常干活,那它多半卡在了这个系统调用的内核路径里。配合 strace 能定位到用户态还是内核态出的问题。
4.2 僵尸进程:杀不掉的"尸体"和真正的解法
僵尸进程(Zombie)在 /proc 里表现为 /proc/<pid>/status 的 State 是 Z,用 ps 看会带 <defunct> 标记。僵尸的意思是进程已经死了,但它没有真正从进程表里消失,因为父进程还没调用 wait() 来收尸。内核为了保留退出状态给父进程查询,不得不留一个最小化的 task_struct。
这里有个反直觉的事实:僵尸进程不消耗 CPU、不占内存,但它占着 PID,而且 PID 是有限的,大量僵尸会拖垮系统。更关键的是,僵尸进程杀不掉——kill -9 对它无效,因为"尸体"已经没有可执行的代码了。唯一的解法是让它的父进程调用 wait(),或者直接把父进程干掉,让僵尸被挂到 init/systemd 或者其他 subreaper 进程下面,由它们统一回收。
容器场景有个经典坑:容器里的 PID 1 是一个自定义的 shell 脚本或者普通二进制,它没有实现"收养并回收子进程"的逻辑。于是容器里的孤儿进程全变成了僵尸挂在 PID 1 下面,容器越跑越久,僵尸越来越多。解决办法是让容器的 PID 1 使用 tini、s6 这类 init 程序,或者把父进程逻辑里加上处理 SIGCHLD 并 wait 的代码。
定位僵尸的父进程很简单:/proc/<pid>/stat 的第 4 列是 PPid。当你发现大量僵尸的 PPid 都是同一个进程时,问题就清晰了——去修那个父进程,而不是试图 kill 僵尸本身。
4.3 文件句柄泄漏:/proc 里最容易被忽略的"软故障"
文件句柄泄漏的特点是慢刀割肉:系统一开始很正常,跑了一两个月后突然所有新连接都建立不起来,报 "Too many open files"。这种问题用 /proc/<pid>/fd 查最快。
每个进程的 /proc/<pid>/fd 目录下,一个文件描述符就是一个符号链接,链接指向它打开的对象。快速统计句柄数量用 ls /proc/<pid>/fd | wc -l,但注意 ls 是逐个 stat 的,进程 fd 特别多的时候这个命令本身会有点慢。
符号链接里最值得关注的是带着 (deleted) 后缀的条目。这说明进程打开了一个文件,但这个文件已经被 unlink 了。遇到这种情况,即使你 df -h 看到磁盘没满,也要小心:这类被删除但还被进程占用的文件,空间不会真正释放。典型场景是日志文件被 logrotate 轮转后,老进程还握着旧日志的 fd,日积月累把磁盘写满。定位到具体是哪个进程之后,处理办法通常只有重启进程或者让进程重新打开日志。
网络连接和 fd 的关系也能查。ls -l /proc/<pid>/fd 里会看到 socket:[123456] 这样的链接,中括号里是 socket 的 inode 号。拿着这个号去 /proc/net/tcp 或者 /proc/net/tcp6 里找,就能把这个 fd 对应到具体的四元组连接,排查"连接被谁占着不释放"非常有用。
5. /proc/sys 调参与常见翻车点
5.1 往 /proc/sys 写值,等于在给内核现场改参数
/ proc/sys 目录下的文件是内核 sysctl 参数的接口。往里面写值不是改配置文件的文本,而是直接把值交给内核的 sysctl 处理函数,立刻生效。这就带来两个后果:第一,写法必须符合内核的解析规则,非法值会直接报 EINVAL;第二,这些修改只对当前运行的内核有效,重启后归零。
所以我调参数的习惯是:先 sysctl -a | grep 关键字 看当前值,再决定要不要改;改之前把原值记下来,最好写成一行注释存到本地。生产环境永远不要裸奔着改,谁知道你是不是把某个值改成了让系统起不来的程度。
持久化修改要用 /etc/sysctl.conf 或者 /etc/sysctl.d/ 下的文件,执行 sysctl --system 让它生效。这里有个隐蔽的小坑:/etc/sysctl.conf 里写了很多参数,但实际生效值可能被某个 /etc/sysctl.d/ 下的文件覆盖了,系统加载的时候按文件名顺序处理,后面的覆盖前面的。排查"明明改了却不生效"的问题时,先查一遍有没有其他文件覆盖。
5.2 高频调参项:值是怎么算出来的
vm.swappiness 是我被问得最多的参数。它的范围在新内核里是 0 到 200,默认 60。很多人以为设成 0 就不会用 swap,实际上它只是降低内核使用 swap 的倾向,并不会完全禁用。真正要理解的是它的语义:这是一个权重,影响内核在回收匿名页和文件缓存之间怎么选。所以生产环境遇到"swap 用了很多但内存看起来还有余量",不要只调 swappiness,先看 /proc/pressure/memory 是不是真的在 stall。
vm.overcommit_memory 是个危险的开关。默认 0 是启发式模式,内核会估算一下再决定是否允许大的内存分配;1 是永远允许超额分配;2 是禁止超过 CommitLimit 的分配,配合 overcommit_ratio 用。很多数据库启动前会检查内存,如果 overcommit_memory 被设成 2 而 ratio 又小,malloc 一大块内存就会失败,数据库直接起不来。血的教训:不要为了"防 OOM"就把这个值改成 2,改之前先想清楚你的应用对内存分配失败怎么处理。
网络参数方面,net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog 决定 accept 队列和 SYN 队列的容量。Nginx、Redis 这类服务如果监听队列溢出,客户端表现就是连接变慢、偶尔连接失败,服务端 dmesg 里会有 "listen queue" 相关提示。调整时记住一个原则:应用层的 backlog 设置和内核的 somaxconn 要对齐,应用设了 1024,内核只有 128,那上限就是 128。
net.ipv4.ip_local_port_range 管的是本地发起连接时用的临时端口范围,默认通常是 32768 到 60999。如果你的服务作为客户端大量向外建连,而 TIME_WAIT 又堆积,临时端口耗尽后新连接会报 "Cannot assign requested address"。这种场景可以把这个范围扩大,但别扩到 1024 以下,那些端口可能被服务端监听占用,容易踩到意料之外的冲突。
5.3 一次调参翻车的完整复盘
分享一次我自己的翻车经历。当时线上服务出现周期性延迟毛刺,我怀疑是内存回收过于激进,于是把 swappiness 从 60 临时改成了 0。结果第二天毛刺更严重了,因为匿名内存回收不出来,page cache 被反复挤压,磁盘读放大,性能比之前还差。后来我把 swappiness 改回 60,再配合 /proc/pressure/memory 观察,发现真正的瓶颈是 cgroup 内存上限太小,和 swappiness 毫无关系。
这个教训是:/proc/sys 的每个参数都是牵一发动全身的,不能凭直觉乱调。改之前先问自己三个问题:这个参数的默认值为什么是这个数?我期望它产生的效果是什么?如果效果和预期不符,我怎么快速回滚?回滚这件事最值得强调——直接 sysctl -w 参数=原值 能当场恢复,但如果你连原值都没记,就只能靠重启内核来重置了。
另外提醒一句,容器里改 /proc/sys 经常失败,因为很多文件对容器的命名空间是只读的,或者容器没有对应的 CAP_NET_ADMIN、CAP_SYS_ADMIN 权限。遇到 "Read-only file system" 或者 "Operation not permitted" 不要硬刚,先确认你到底有没有权限改,再看是不是要在宿主机层面操作。
6. 权限、安全与容器场景:生产环境里 /proc 的"自保"问题
6.1 hidepid:别让用户一眼看光所有进程
/ proc 默认是全局可见的,任何用户都能列出所有进程的 /proc/
内核提供了 hidepid 挂载选项来收紧这个口子。hidepid=1 表示隐藏其他用户进程的目录,未授权的用户无法列出也看不到详情;hidepid=2 则连"这个 PID 是否存在"都不让你知道,安全性最高。挂载方式是在 /etc/fstab 里给 proc 这一行加选项:
code复制proc /proc proc defaults,hidepid=2 0 0
或者临时生效用 mount -o remount,hidepid=2 /proc。注意这个操作要非常谨慎,因为很多监控程序(比如某些 agent、JMX 采集器)是靠遍历 /proc 拿进程信息的,开了 hidepid=2 之后它们会大量报错。我见过不少团队开了 hidepid 之后监控全红的案例,所以上线前一定要把监控组件的兼容性测清楚。
6.2 敏感的 /proc 接口和内核地址保护
/ proc 里有几个文件自带敏感属性,生产环境要看紧。/proc/kallsyms 是内核符号表,里面包含函数地址。这个地址一旦泄露,对内核漏洞利用来说是重大助攻。内核对这个做了 kptr_restrict 控制:设成 1 后,非特权用户看到的符号地址会被置 0;设成 2 则更严格。同理还有 kernel.dmesg_restrict,限制普通用户读取内核日志,避免攻击者从日志里提取内存布局信息。
/proc/kcore 是物理内存的 ELF core 映射,默认权限就应该是 root 才能读。你在生产环境做安全巡检时,可以顺手看看这几个文件的权限:ls -l /proc/kcore /proc/kallsyms。如果发现权限异常宽松,先查是不是有自定义的 systemd 配置或者安全策略把默认权限放宽了。
/proc/sysrq-trigger 也要特别注意。这个文件往里面写特定字母会触发内核紧急操作,比如写 s 是同步数据,写 b 是立即重启。它必须只允许 root 操作,任何非 root 账号能写它都是重大事故。曾经有机器被入侵后,攻击者往 /proc/sysrq-trigger 里写 b 把系统直接重启,这也是排查"服务器神秘重启"时要检查的一个点。
6.3 容器里的 /proc 是最容易误解的 namespace 边界
容器场景下,/proc 的 namespace 语义混合得让人头疼。容器自己 mount 了一个新的 /proc 实例,所以 /proc 下能看到容器内的进程;但很多全局文件的数据仍然来自宿主机内核。典型例子是 /proc/meminfo,容器里看到的 MemTotal 还是宿主机全部内存,根本不是 cgroup 给容器分配的上限。这就是为什么容器里跑 free -h 会疑惑:怎么显示的内存比我的 limit 大这么多?
/ proc/loadavg 在容器里默认也是宿主机全局的 load,不是容器自己的负载。要拿到容器视角的负载,得读 cgroup 的文件,比如 /sys/fs/cgroup/cpu.pressure 和 /sys/fs/cgroup/memory.pressure。这也是 lxcfs 这类工具存在的意义——它把宿主机的 /proc 文件重映射成容器 cgroup 视角的数据,让容器里的 free、top 看着"正常"。
还有一个让我印象深刻的生产问题:某个容器里排查网络连接,读取 /proc/net/tcp 发现看不到宿主机其他进程的连接,这是因为每个网络 namespace 有自己的 /proc/net,容器里只能看到自己网络命名空间的 socket。这其实是隔离的体现,但很多人不理解,误以为"容器能看到一切网络",然后怀疑自己被入侵了。反过来,/proc/interrupts 和 /proc/softirqs 这类硬件的全局视图,在容器里看到的依然和宿主机一致,毕竟中断属于物理硬件,不属于任何 namespace。
最后说一个我自己的习惯:任何一次 /proc 排查开始之前,我都会先把关键文件存一份基线快照。这个习惯救过我很多次——当你不确定自己的调参是否引入了新问题时,翻出快照一对比,答案立刻浮现。/proc 这个文件系统看起来繁琐,但它永远是内核最诚实的"证人",只要你会读,它就能告诉你系统每一个异常背后的真实原因。
