2630 字
约 8 分钟
15
S15-X06 · 进程、端口与句柄:lsof/ss/ps 的取证分工

线上出现“服务卡死”时,最先拿到的往往只有一句描述:CPU 高、连不上、或者报了个 Too many open files。这三类症状对应三个不同的证据源——ps 给进程状态快照,ss 给连接与监听,lsof 与 /proc 给句柄与打开的文件。分不清谁负责哪层,就会在错误的工具里反复找同一个答案。

这篇按“进程 → 端口 → 句柄 → 内核上限”的顺序,把三个工具的取证边界划清楚。


1. ps aux 与 ps -ef:两种语法、两套列

$ ps aux | head -3
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1 167432 13208 ?        Ss   Nov08   0:04 /sbin/init
deploy    5123 12.4  3.8 4120336 611200 ?      Sl   04:11   3:22 java -jar app.jar
$ ps -ef | head -3
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Nov08 ?        00:00:04 /sbin/init
deploy    5123  5102 12 04:11 ?        00:03:22 java -jar app.jar
维度 ps aux(BSD 风格) ps -ef(POSIX 风格)
资源列 有 %CPU %MEM VSZ RSS 没有,要自己 -o 加
父进程 无 有 PPID
启动时间 START(相对或绝对) STIME
累计 CPU TIME TIME
适合 快速看谁吃资源 看进程树、看父子关系
兼容性 Linux/macOS 都认 更接近标准,脚本里更稳

结论很简单:看资源用 aux,看关系用 -ef,写脚本一律 -eo 显式指定列。


2. ps -o:把列裁成你需要的

$ ps -eo pid,ppid,stat,etime,pcpu,rss,args --sort=-pcpu | head -8
$ ps -o pid,rss,vsz,nlwp,cmd -p 5123
$ ps -eLf | grep 5123 | head        # 看线程(LWP)
列 含义 排障价值
stat 状态位:R/S/D/T/Z + 修饰符 D 不可中断睡眠 = 卡在 IO
etime 已运行时长 与 START 交叉验证是否被重启过
pcpu 平均 CPU 占用(不是瞬时) ps 的 %CPU 是生命周期均值,看瞬时要用 top
rss 常驻内存(KB) 含共享库,多进程会重复计
nlwp 线程数 与 top -H 对照看线程膨胀
wchan 内核等待点 D 状态时看它到底等什么

STAT 的修饰符同样有用:Ss 是会话首进程、Sl 表示多线程、R+ 在前台进程组。看到 Z 就是僵尸——它已经死了,只是父进程没 wait(),kill -9 对它无效,要处理父进程。


3. 进程树:pstree 与 --forest

$ pstree -ap 5102
java,5102 -jar app.jar
  ├─{java},5103
  └─{java},5104
$ ps -eo pid,ppid,cmd --forest | grep -B2 -A4 5123

pstree -p 带 PID、-a 带参数、-u 标出用户切换。排查“谁拉起了谁”时,进程树比 PPID 一列一列对快得多;systemd 环境下还能顺带看出 systemd → 服务 → worker 的层级。


4. top 的 %CPU 与 load average 不是一回事

$ uptime
 05:12:03 up 12 days,  3:41,  2 users,  load average: 0.82, 1.94, 2.31
$ nproc
4
指标 含义 读法
load average 1/5/15 分钟内可运行 + 不可中断睡眠的进程平均数 与 nproc 比:4 核长期 > 4 就是排队
%CPU(top 单进程) 该进程占用单核的百分比,可超 100 400% 在 4 核机器上等于跑满
us / sy 用户态 / 内核态 sy 高 = 系统调用或中断多
wa IO 等待 高说明磁盘是瓶颈,此时 CPU 反而闲
st 被宿主机“偷走”的时间 云主机 st 高 = 邻居抢资源
si / hi 软/硬中断 网络包量大时 si 会明显上升

关键点:load 包含 D 状态进程。一台 NFS 挂住或磁盘卡住的机器,CPU 可能只有 5%,但 load 冲到 50——这时盯着 CPU 找热点永远找不到。正确顺序是先看 wa 与 D 状态进程,再看 CPU。


5. /proc/<pid>/ 里有什么可用的

$ ls -l /proc/5123/ | head
dr-xr-xr-x 2 deploy deploy 0 Nov  9 05:10 fd
-r--r--r-- 1 deploy deploy 0 Nov  9 05:10 limits
lrwxrwxrwx 1 deploy deploy 0 Nov  9 05:10 cwd -> /srv/app
lrwxrwxrwx 1 deploy deploy 0 Nov  9 05:10 exe -> /usr/lib/jvm/java-17/bin/java
路径 内容 用法
fd/ 每个打开的 fd 一个符号链接 数量即句柄数;指向 socket:[...] 说明是连接
cwd 工作目录 确认服务在哪跑的,WorkingDirectory 是否生效
exe 实际可执行文件 与 which java 不一致时说明用了别的 JDK
root 进程视角的 / 容器/chroot 里看根目录差异
status State、VmRSS、Threads、CapEff 比 ps 更细,含能力集
limits 软/硬上限,含 Max open files 判句柄上限的唯一权威来源
cmdline 启动参数,NUL 分隔 tr '\0' ' ' < cmdline 才可读
environ 环境变量,NUL 分隔 查 JAVA_HOME、SPRING_PROFILES 是否注入成功
ns/ 命名空间链接 判断是否在独立 netns/mnt ns

cmdline 与 environ 是 NUL 分隔的,直接 cat 会挤成一行乱码;strings 或 tr 处理一下才能看。


6. 句柄泄漏:从 Too many open files 倒推

$ ls -l /proc/5123/fd | wc -l
$ sudo lsof -p 5123 | wc -l
$ sudo lsof -p 5123 | awk '{print $5}' | sort | uniq -c | sort -rn | head
现象 判断 动作
fd 数持续单调上升 泄漏 按类型聚合,看是 socket 还是普通文件
lsof 里大量 (deleted) 文件被删但 fd 未关 这是“磁盘不降”的主因
大量 socket:[...] 且指向同一远端 连接池没回收或没复用 查连接池上限与 keep-alive 配置
pipe 数量暴涨 子进程或线程间管道未关 查 popen/subprocess 是否 wait()
fd 数不涨但报 EMFILE 别的进程/host 级上限 cat /proc/sys/fs/file-nr 看系统总量

lsof -p 会顺带列出 mmap 的库,所以它的行数通常大于 /proc/PID/fd 的条目数。报数时要说清用的是哪个口径,否则两个人数出来对不上。


7. ulimit -n 与 /proc/PID/limits 为什么对不上

$ ulimit -n
1024
$ cat /proc/5123/limits | grep -i 'open files'
Max open files            65535                65535                files
$ systemctl show -p LimitNOFILE app.service
LimitNOFILE=65535
不一致的原因 说明
ulimit 是当前 shell 的软上限 服务由 systemd 直接拉起,不经过你的 shell
systemd 有独立默认 按 systemd 版本核对,常见 soft 1024 / hard 524288
LimitNOFILE= 覆盖 unit 里显式设定,优先于 pam_limits
pam_limits / limits.conf 只对走 PAM 的登录会话生效
进程自己 setrlimit 应用启动时改过,/proc 才反映最终值

判断“上限到底是多少”,只看 /proc/<pid>/limits;想改运行中进程可以用 prlimit:

$ sudo prlimit --pid 5123 --nofile=65535:65535
$ sudo prlimit --pid 5123 --nofile        # 查看当前值

注意软上限不能超过硬上限,硬上限只有 root 能抬。LimitNOFILE 改了要 systemctl daemon-reload + 重启服务才生效——daemon-reload 只重读 unit,不会把新上限注入已在跑的进程。


8. kill 的信号怎么选

$ kill -l | head -5
$ kill -TERM 5123
$ kill -HUP 1204        # nginx: 重开日志
$ kill -USR1 5123       # 应用自定义:常见于触发 dump 或轮转
$ kill -9 5123          # 最后手段
信号 编号 默认行为 适用
TERM 15 优雅退出,可被捕获 首选,让程序自己收尾
INT 2 同 Ctrl-C 前台调试
HUP 1 默认终止,但常被重载实现覆盖 nginx -HUP 重载配置/重开日志
QUIT 3 优雅退出 + core 需要栈现场时
USR1 / USR2 10/12 默认终止,实际多为应用自定义 触发日志轮转、平滑升级
KILL 9 不可捕获,内核直接回收 卡死且 TERM 无效时
STOP / CONT 19/18 暂停/继续 临时冻结进程保留现场

KILL 的代价是进程没有机会 flush 缓冲、删临时文件、释放锁;数据库类进程被 -9 之后可能需要恢复流程。所以顺序永远是 TERM → 等 → 再 KILL,等待时间按业务收尾耗时定,不是拍脑袋 3 秒。


9. 排错:六种“数据对不上”

现象 原因 处理
ps 的 %CPU 与 top 差很多 ps 是生命周期均值 用 top -b -n1 -p PID 或 pidstat 看瞬时
load 高但 CPU 空 D 状态进程堆积 ps -eo stat,pid,cmd | awk '$1 ~ /D/',再看 wchan
进程在但 ss 没它的端口 在别的 netns nsenter -t PID -n ss -tlnp
lsof 看不到别人进程 非 root sudo lsof
僵尸进程杀不掉 已死,等父进程回收 处理父进程,或重启其父
df 不降但文件已删 fd 仍持有 lsof +L1 找 deleted 文件

10. 边界:什么时候该换工具

需求 更合适的工具 理由
持续采样与告警 pidstat / node_exporter ps/top 只有当下
系统调用级定位 strace / bpftrace 进程状态不给调用细节
内存分配热点 pmap / smem / heaptrack RSS 是结果,不是归属
网络报文细节 tcpdump ss 只给连接状态
容器内外对照 crictl / docker top 宿主视角看不到容器内进程树

一句话收口:ps 回答“这个进程在什么状态”,ss 回答“它在和谁通信”,lsof 回答“它抓着哪些资源”,/proc/<pid>/limits 回答“它能抓多少”。 四问齐了,Too many open files 就不再是玄学。


11. 自测

  1. ps aux 与 ps -ef 各自的强项是什么?
  2. ps 里的 %CPU 为什么和 top 对不上?
  3. load average 高而 CPU 空闲,最可能是哪种进程状态造成的?
  4. lsof -p PID | wc -l 与 /proc/PID/fd 的条目数为什么不等?
  5. 服务由 systemd 拉起,ulimit -n 为什么不能代表它的真实上限?
  6. 什么情况下才应该用 kill -9,代价是什么?
S15-X06 · 进程、端口与句柄:lsof/ss/ps 的取证分工
https://www.clxhxhhr.top/posts/5708/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。