服务器入坑指北:进程在等谁

举报
Tulpartner 发表于 2026/11/04 09:27:02 2026/11/04
【摘要】 服务卡住不动:占用不高、也不报错;按"谁 CPU 高"去找,找到的进程一切正常;把它杀掉重启,过一阵又卡住。你始终说不清它到底在等什么。 关键区分是:卡住的进程往往只是"受害者"——它在等一个资源,而持有资源的那一方可能看起来很闲,甚至根本不在"占用榜"上。所以"谁 CPU 高"这个入口天然找不到真凶。

服务卡住不动:占用不高、也不报错;按"谁 CPU 高"去找,找到的进程一切正常;把它杀掉重启,过一阵又卡住。你始终说不清它到底在等什么。

关键区分是:卡住的进程往往只是"受害者"——它在等一个资源,而持有资源的那一方可能看起来很闲,甚至根本不在"占用榜"上。所以"谁 CPU 高"这个入口天然找不到真凶。

这篇给一条可执行的反查链:读等待位置 → 反查持有者 → 再看持有者自己在等什么 → 才决定动作。三段只读命令,全部可以在生产上直接做;取值用占位符,路径、列名与命令名照写。

先认角色:受害者,还是阻塞来源

卡住的进程通常只是在等一个资源,而持有资源的一方可能看起来很闲——它不需要消耗 CPU,只要"拿着"就够了。于是"谁 CPU 高"这条路筛出来的永远是"正在消耗 CPU 的那一个",而真凶可能正在安静地持有某样东西。

top 这类按占用排序的视图,天生只显示"正在消耗"的那一侧——等锁、等 I/O 的进程在这一侧一个字都不会说。

顺序反了的代价很具体:直接杀掉"看起来卡住的那个",杀掉的是受害者——现场丢了、在途工作丢了,过一阵还会复现,因为持有那一方毫发无伤。

状态字:先读对状态,再谈方向

状态 含义 处置方向
R 正在跑或可运行 不是等待问题
S 可中断睡眠 可能在等锁、等对端、等定时
D 不可中断睡眠(多半等 I/O) 等它完成 / 让那次 I/O 失败
Z 僵尸,等待父进程回收 动作落在父进程上

在据此下结论之前,先把两条前提看清:

  1. 读等待位置受受保护进程的读取权限约束(权限检查基于 ptrace 访问模式);读不到,说明这一层不向你开放,而不是"它没在等";
  2. 远程挂载上的锁状态通常记在对端服务器(例如 NFS 挂载),本机看不到:在本机查不到不等于没有。

读状态本身很简单:ps 的 stat 列就是状态字;要看完整字段,读 /proc/<pid>/status 里的 State 行。

读等待位置块

ps -eo pid,stat,wchan:24,comm          # 一屏看状态与等待通道:stat 里的 D 就是不可中断等待
cat /proc/<pid>/wchan                  # 单进程:读它睡在内核的哪个位置(一个符号名)
cat /proc/<pid>/status                 # 读 State 等字段:确认它在 S 还是 D

判据:wchan 这一列给出的是内核里的一个位置名,它告诉你在等哪一类东西(锁、I/O、对端……)。读到的是一个符号名而不是中文解释——用它是来判断"等哪一类",不是让你背下这些名字;不同内核版本、不同子系统下的名字会有差异,以你在本机读到的为准。

反查持有者块

lslocks -o PID,COMMAND,TYPE,MODE,PATH  # 锁类:指定列一次读全(TYPE 与 MODE 决定怎么读)
lslocks                                # 默认列就是 COMMAND PID TYPE SIZE MODE M START END PATH
lslocks -o +BLOCKER                     # 追加一列:BLOCKER=阻塞该锁的进程的 PID(默认不显示)
lsof <路径>                            # 文件 / 套接字类:谁打开了这个对象
fuser -v <路径>                        # 反查占用者:输出更直接
ss -tnp                                # 套接字类:哪个进程占着这条连接

这一块是整篇的核心交付,怎么读:

  • 锁类资源靠"列定义"读,而不是靠一个叫持有者的列:lslocks 的默认列是 COMMAND PID TYPE SIZE MODE M START END PATH,而列定义写着 COMMAND 就是"持有该锁的进程的命令"——持有者就是该行自身;BLOCKER 是"阻塞该锁的进程的 PID",它不在默认输出里,要用 -o +BLOCKER 显式追加。于是"受害者 → 持有者"从推断变成直接读出来:先在这一行读到持有者,再追加 BLOCKER 看谁被它挡住;
  • TYPE 与 MODE 要一起看:这把锁是租约(lease)、FLOCK、POSIX 还是 OFD 类型,会影响你怎么读"持有者";对某些锁类型,持有者并非唯一(同一个打开文件描述可被多个进程共享);
  • -o 用来指定输出列:PID / COMMAND / PATH 这几列是读反查结果的最小集合;fuser 的 -v 是详细输出;
  • 文件 / 套接字类则用"谁打开了这个对象"反查:lsof <路径> 与 fuser -v <路径> 一对;套接字用 ss -tnp。

判据:能指认"谁持有"——指认不出来,说明还没走到该走的下一环。lsof 的 -p 用来指定进程,需要看"这个进程自己打开了什么"时用它。

顺带一句机制:处于不可中断等待(D)的进程,连强制信号都送不进去——所以"杀掉它"这条路有时根本走不通。这一条的机制与处置在《进程卡死与杀不掉的进程》那篇,本篇不展开,只把它作为"为什么有时只能等"的依据。

三类动作与代价

动作 何时选 代价 前提
让持有者尽快释放 持有者本身有瓶颈、可以修 要改持有者,可能影响它的其它调用方 你能动到持有者
隔离受害者 需要先止血、根因一时修不了 被隔离的那部分业务受损,根因仍在 有可用的隔离手段(挪走流量、降级)
直接杀掉 持有者已经无法自救 会丢现场与在途工作,而且可能复发 已确认没有更温和的路

为什么先动持有者:持有者通常才是根因;只动受害者,等于把症状按下去,过一阵它还会卡在同一个地方。

ps -eo pid,ppid,stat,cmd               # 僵尸(Z)找父进程:ppid 与 cmd 用来确认父子关系
cat /proc/<pid>/wchan                  # 再看持有者自己在等什么:链条可能还有一层
lsof -p <pid>                          # 换一个入口看这个进程自己打开了什么

僵尸这一路的动作对象是父进程:父进程先退出时,孤儿的回收会由 PID 1 接管——所以对僵尸本身发信号没有任何意义。

停手判据:查到哪层停哪层

等待链可能跨进程、跨服务,甚至跨到内核态。判据是三条:

  1. 等待位置读不到(权限不开放)→ 到此为止,说明"这一层不向你开放";
  2. 持有者在别的层次(宿主侧、对端,或 NFS 这类远程挂载的对端)→ 到此为止,把问题交到那一层;
  3. 等待对象是外部依赖 → 到此为止,动作落在"联系对端 / 降级"上。

共同纪律:没有证据时不要假定更深的成因。不要把"锁等待"直接说成"存储故障",也不要把"等对端响应"说成本机内核有问题。能查到哪一层,就停在哪一层,并说明为什么停在这里。

现象到动作的判定表

现象 结论 动作
占用不高却不响应 它在等一个资源 读 wchan 与 stat,先定"等哪一类"
杀掉重启后又卡住 真凶没被碰到 走反查链:该行的 COMMAND/PID 就是持有者,再加 -o +BLOCKER 看谁被挡
读不到等待位置 权限受限 承认这一层不开放;改用可读的证据
持有者在远程挂载或对端 本机看不到锁状态 停在这一层,交到对端处理
分不清等的是锁还是文件 状态与列没读全 lslocks -o PID,COMMAND,TYPE,MODE,PATH 与 lsof <路径> 各读一次

改完怎么验证:每项一条判据

  • 反查链能走到"持有者"这一层(能指认出来);
  • 持有者被处理后受害者恢复(不是靠重启掩盖);
  • 没有再出现同类阻塞(观察一段时间);
  • 本次动作与回滚点有记录;
  • 若只能止损,明确写清"根因未除"。

这篇不解决什么

  • 讲进程卡死与杀不掉的进程的那篇管状态与信号语义(为什么杀不掉、该找谁、会话工具起的进程为什么不受管);本篇管等待的方向性与三类动作,不写信号语义——遇到"为什么杀不掉",请转去那篇。
  • 讲负载高不等于 CPU 忙的那篇判断整机是否在排队;本篇判断具体是谁在等——两篇互相指路。
  • 讲云盘变慢或报 I/O 错误的那篇是I/O 异常分支的取证与迁移;本篇的"等磁盘 I/O"只作为反查链里的一类等待对象出现,不重复块层取证。

踩坑清单

  • 按"谁 CPU 高"找真凶:持有者可能很闲,压根不在榜上。
  • 直接杀受害者不看持有者:丢掉现场,还会复发。
  • 把"锁等待"直接说成"存储故障":越层归因。
  • 不看被阻塞那一方的 MODE 标记:把"谁在等"和"谁持有"读反。
  • 远程挂载上的锁在本机找不到就以为没有:状态记在对端。
  • 忽略"持有者可能不唯一":某些锁类型下同一个打开文件描述可被多进程共享。
  • 读到 wchan 看不懂也不承认看不懂:符号名随版本与子系统而异。
  • 不知道读等待位置受权限约束:读不到被当成"没在等"。
  • 拿引导式救援去救阻塞问题:那是另一条路径。
  • 只止损不记录根因:下次还得从头查一遍。
  • 只看 ps -eo pid,stat,wchan:24,comm 不看 /proc/<pid>/status:滞后与瞬时状态会混在一起。
  • 把 Z 状态也当成"在等资源":僵尸已经结束,动作在父进程。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。