CPU 飙高、内存吃满、服务卡死不动,排查到最后总要走一步:找到那个进程,然后结束它。这一步看起来简单,实际上是最容易产生次生事故的地方——kill -9 下去,数据库可能来不及刷盘,订单可能停在半路,锁文件没清理导致下次起不来。
这篇文章讲清楚怎么看进程(ps、pstree、pgrep、top 和 /proc/ 目录),怎么正确地结束进程(kill、killall、pkill 与不同信号的区别),以及三类特殊进程该怎么处理:僵尸进程、孤儿进程和 D 状态进程。核心观点只有一句:kill -9 是最后手段,不是第一选择。
先读懂进程状态
看进程之前,必须先能看懂状态列,否则你连"这个进程是不是卡死了"都判断不了。
ps -eo pid,ppid,user,stat,%cpu,%mem,etime,comm
STAT 列由若干字符组成,第一位是主状态:
| 字符 | 含义 | 说明 |
|---|---|---|
R |
Running / Runnable | 正在运行或在运行队列等待 |
S |
Interruptible Sleep | 可中断睡眠,等待事件,最常见 |
D |
Uninterruptible Sleep | 不可中断睡眠,通常在等磁盘 I/O |
T |
Stopped | 被作业控制信号暂停,如 SIGSTOP |
t |
Tracing stop | 被调试器跟踪而暂停 |
X |
Dead | 已死亡,理论上看不到 |
Z |
Zombie | 僵尸进程,已退出但父进程未回收 |
I |
Idle | 空闲的内核线程 |
后面的附加字符同样重要:
| 字符 | 含义 |
|---|---|
< |
高优先级 |
N |
低优先级(nice) |
L |
有页面被锁在内存中 |
s |
会话首进程(session leader) |
l |
多线程进程 |
+ |
前台进程组的进程 |
常见的组合如 Ss(睡眠状态的会话首进程)、Ssl(多线程的会话首进程)、R+(前台运行)、D(等 I/O)。
判断要点: 看到大量 R 说明 CPU 有压力;看到 D 要警惕,它通常意味着进程在等磁盘或网络文件系统,且无法被信号打断,包括 kill -9;看到 Z 是僵尸,需要关注它的数量是否增长;T 状态的进程是被暂停的,可能有人误按了 Ctrl+Z 或被调试器挂起。
ps:查看进程的基础命令
ps 有两种参数风格,BSD 风格不带短横线,System V 风格带短横线,两者输出列不同:
ps aux # BSD 风格,最常用
ps -ef # System V 风格,显示 PPID
ps -eLf # 显示线程(LWP)
ps aux --sort=-%cpu | head -n 15
ps aux --sort=-%mem | head -n 15
ps aux 各列含义:
| 列 | 含义 |
|---|---|
USER |
进程所有者 |
PID |
进程号 |
%CPU |
自启动以来的 CPU 平均占用,非瞬时值 |
%MEM |
物理内存占用百分比 |
VSZ |
虚拟内存大小(KB),含映射但未使用部分 |
RSS |
常驻物理内存(KB),这才是真实占用 |
TTY |
所属终端,? 表示无终端 |
STAT |
状态,见上节 |
START |
启动时间 |
TIME |
累计 CPU 时间 |
COMMAND |
命令,加 -ww 可避免截断 |
自定义输出列更实用:
ps -eo pid,ppid,user,stat,%cpu,%mem,rss,etime,args --sort=-%cpu | head -n 20
ps -eo pid,ppid,user,stat,etime,args --sort=start_time | tail -n 20 # 最近启动的
ps -eo pid,ppid,stat,args --forest | head -n 40 # 树状
etime 是进程已经运行的时间,格式为 [[DD-]hh:]mm:ss,用它一眼就能看出进程是刚起来的还是跑了很久。--sort=-%cpu 里的负号表示降序。
关于 %CPU 的重要提醒: ps 显示的是进程生命周期内的平均值,一个跑了三天的进程即使刚才占满 CPU,%CPU 也可能只有个位数。要看瞬时占用请用 top 或者 ps -eo 配合两次采样。
按名字或用户过滤:
ps -u nginx # 某用户的进程
ps -C nginx # 按命令名精确匹配
ps aux | grep -v grep | grep nginx
用 grep 过滤时记得排除 grep 自身,否则总会多出一行干扰结果。
pgrep 与 pstree:快速定位与关系梳理
pgrep 直接返回 PID,写脚本时比 ps | grep 干净得多:
pgrep nginx # 只输出 PID
pgrep -a nginx # 同时输出命令行
pgrep -u nginx java # 某用户的某进程
pgrep -f 'python3 worker.py' # 匹配完整命令行
pgrep -P 1234 # 某进程的子进程
pgrep -o nginx / -n nginx # 最老 / 最新的匹配进程
pstree 看父子关系,判断是谁拉起来的进程:
pstree
pstree -p # 显示 PID
pstree -p 1234 # 只看某个进程的子树
pstree -a -p | head -n 50 # 显示命令行参数
pstree -h # 高亮当前进程
判断一个进程是不是 systemd 服务,pstree -p 看它的祖先链里有没有 PID 1,或者直接:
systemctl status PID # systemd 能反查进程所属服务
ps -o pid,ppid,args -p PID
cat /proc/PID/cgroup # 容器或服务的 cgroup 路径会暴露归属
systemctl status 后面直接跟 PID 是个很实用的技巧,能立刻知道这个进程属于哪个 unit,从而用 systemctl stop 优雅停止,而不是去 kill。
top 与 htop:实时观察
top
top -p 1234
top -H -p 1234 # 线程视图
top -b -n 1 | head -n 20 # 批处理模式,适合脚本
top -u nginx # 只看某用户
交互键:P CPU 排序、M 内存排序、N PID 排序、T 累计时间排序、1 展开各逻辑 CPU、c 显示完整命令、H 线程视图、k 直接发信号(会提示输入 PID 和信号)、r 调整 nice 值、q 退出。
用 k 发信号时,默认信号是 15(TERM),这比手工 kill -9 安全得多。htop 的 F9 会列出所有信号供选择,更适合新手。
# 安装 htop
# RHEL、Rocky、CentOS
sudo dnf install -y epel-release && sudo dnf install -y htop
# Debian、Ubuntu
sudo apt update && sudo apt install -y htop
/proc/PID/:进程信息的宝库
/proc 是内核暴露的虚拟文件系统,每个进程一个目录,里面包含大量诊断信息。定位到 PID 之后,先查这些再决定怎么处理。
PID=1234
# 完整命令行(用 tr 把 NUL 换成空格)
sudo tr '\0' ' ' < /proc/$PID/cmdline; echo
# 可执行文件的真实路径
sudo readlink -f /proc/$PID/exe
# 工作目录
sudo readlink -f /proc/$PID/cwd
# 环境变量
sudo tr '\0' '\n' < /proc/$PID/environ | head -n 30
# 打开的文件(包括日志、socket、管道)
sudo ls -l /proc/$PID/fd | head -n 40
sudo ls /proc/$PID/fd | wc -l
# 内存映射与占用
sudo cat /proc/$PID/status | grep -E 'Name|State|Threads|VmRSS|VmSize'
sudo cat /proc/$PID/status
# 线程数
ls /proc/$PID/task | wc -l
# 资源限制
cat /proc/$PID/limits
# cgroup 归属(判断是否属于某个服务或容器)
cat /proc/$PID/cgroup
# I/O 统计
sudo cat /proc/$PID/io
# 进程启动以来的 CPU 时钟数
cat /proc/$PID/stat
cmdline、exe、cwd 三项组合起来能回答"这个进程到底是什么、从哪里启动的",对识别未知进程尤其重要。如果 exe 指向 /tmp 或某个奇怪路径,就要警惕是不是异常程序。
fd 目录里的句柄数量如果接近 limits 中的 open files 上限,说明连接或文件泄漏,进程很快会无法接受新请求。这是判断"服务突然不接受连接"的一个关键点。
信号:TERM 与 KILL 的本质区别
kill 命令的名字有误导性,它的本质是发送信号,进程收到信号后按自己的方式处理(或者忽略)。
| 信号 | 编号 | 默认行为 | 是否能被捕获 | 典型用途 |
|---|---|---|---|---|
SIGHUP |
1 | 终止 | 能 | 通知守护进程重读配置 |
SIGINT |
2 | 终止 | 能 | Ctrl+C 中断 |
SIGQUIT |
3 | 终止并 core dump | 能 | Java 线程转储常用 |
SIGTERM |
15 | 终止 | 能 | 优雅停止,首选 |
SIGUSR1 |
10 | 终止 | 能 | Nginx 重开日志文件 |
SIGUSR2 |
12 | 终止 | 能 | Nginx 平滑升级 |
SIGKILL |
9 | 立即终止 | 不能 | 最后手段 |
SIGSTOP |
19 | 暂停 | 不能 | 暂停进程,可用 CONT 恢复 |
SIGCONT |
18 | 继续 | 能 | 恢复被暂停的进程 |
为什么不能一上来就 kill -9? 因为 SIGKILL 由内核直接处理,进程完全收不到,也就没有机会执行清理:写缓冲区的数据可能丢失、打开的文件不会正常关闭、锁文件不会删除、子进程可能变成孤儿、数据库连接不会优雅断开(可能在服务端留下僵尸会话)、临时文件留在磁盘上。对数据库和消息队列这类有状态服务,kill -9 可能导致数据文件损坏或需要长时间恢复。
正确的停止流程是逐级升级:
# 第一优先:通过服务管理器停止,它会按 unit 文件定义的逻辑优雅处理
sudo systemctl stop nginx
# 第二:服务管理器不可用,或进程不属于任何服务时,先发 TERM
sudo kill -TERM 1234
# 第三:等待一段时间(比如 10 到 30 秒),观察是否已退出
sleep 15; ps -o pid,stat,etime -p 1234
# 第四:仍然存活才考虑 KILL,且必须确认无数据风险
sudo kill -KILL 1234
# 最后确认
ps -p 1234 > /dev/null && echo "仍存在" || echo "已退出"
如果进程是 systemd 服务,还可以在 unit 里设置 TimeoutStopSec 来控制 systemd 等待多久后强制杀掉,这比手工 kill -9 规范得多。
发送信号前的三项确认: 这个 PID 确实是要停的进程(核对 cmdline 和 exe);停掉它业务能否承接(有没有其他副本、要不要先摘流量);有没有未完成任务(数据库写入、队列消费、文件传输)。
killall 与 pkill:按名字批量操作
killall nginx # 按进程名发送 TERM
killall -TERM nginx
killall -u www-data php-fpm # 限定用户
pkill nginx # 默认 TERM
pkill -f 'python3 worker.py' # 匹配完整命令行
pkill -u deploy java # 限定用户
pkill -TERM -P 1234 # 结束某进程的子进程
pgrep -a -f 'worker.py' # 先确认会命中谁
风险提示: pkill -f 按完整命令行匹配,正则写得太宽松会误杀无关进程,比如 pkill -f python 可能把监控系统一起杀掉。批量操作前必须用 pgrep -a -f 先列出将命中的进程,确认清单后再执行。killall 在部分系统上按进程名精确匹配(限制在 15 个字符内),长名字可能匹配不到,这是它和 pkill -f 的重要区别。
killall 还有一个 -i 交互模式,会逐个询问是否终止,批量操作时建议加上:
killall -i -u www-data php-fpm
僵尸进程、孤儿进程与 D 状态进程
这三类是最容易被误解的"异常进程"。
僵尸进程(Z)。 进程已经退出,占用的资源基本释放,但内核保留了它的进程表项,等待父进程调用 wait() 读取退出状态。僵尸进程不占用内存和 CPU,只占一个 PID。
少量僵尸(几个)无需处理。数量持续增长,说明父进程有缺陷,没有正确回收子进程。
ps -eo pid,ppid,stat,comm | awk '$3 ~ /Z/'
ps -eo stat,ppid,pid,comm | grep -w Z
僵尸进程无法被 kill -9 杀死,因为它已经死了。处理方式有两种:
一是通知父进程回收,给它发 SIGCHLD:
sudo kill -CHLD 父进程PID
二是父进程确实有 bug 且不打算修复,那就结束父进程,僵尸会被 PID 1(systemd)收养并自动回收。结束父进程会影响它管理的所有子进程,必须先评估影响。
孤儿进程。 父进程先退出,子进程被 PID 1 收养。这不是错误,是正常机制。守护进程故意 fork 后让父进程退出,就是利用这个机制脱离终端。所以孤儿进程不需要处理,用 pstree 看到某个进程的父进程是 1,不代表异常。
D 状态进程(不可中断睡眠)。 这类进程在等待内核态操作返回,通常是磁盘 I/O、NFS 或者其他阻塞的系统调用。kill -9 对它无效,信号会被挂起,直到 I/O 完成。
ps -eo pid,ppid,stat,wchan:30,comm | awk '$3 ~ /D/'
cat /proc/PID/stack 2>/dev/null # 需要内核支持
sudo cat /proc/PID/wchan; echo # 显示等待的内核函数名
处理 D 状态进程的正确方向不是杀,而是解决它等待的资源:NFS 服务端是否挂了(用 mount、nfsstat 确认,必要时 umount -f);磁盘是否故障(dmesg 看 I/O error);存储是否拥塞(iostat -x 看 await)。资源恢复后进程会自己醒来。强行重启是最后的办法,且重启前要确认数据一致性。
顺带一提,wchan 列能显示进程正在等待哪个内核函数,是判断 D 状态成因的直接线索。配合 iostat 和 dmesg 基本能定位到具体设备或挂载点。
异常进程的标准处置流程
把前面的内容串成一个可复用的流程:
# 第一步:找到目标进程
ps -eo pid,ppid,user,stat,%cpu,%mem,etime,args --sort=-%cpu | head -n 20
# 第二步:确认身份,不要急着杀
PID=1234
sudo tr '\0' ' ' < /proc/$PID/cmdline; echo
sudo readlink -f /proc/$PID/exe
sudo readlink -f /proc/$PID/cwd
ps -o pid,ppid,stat,etime -p $PID
# 第三步:查归属,能走服务管理器就走管理器
systemctl status $PID --no-pager
sudo journalctl -u 服务名 --since '-30 min' --no-pager
# 第四步:优雅停止
sudo systemctl stop 服务名
# 或
sudo kill -TERM $PID
# 第五步:等待并观察
sleep 15
ps -o pid,stat -p $PID
# 第六步:仅在前一步无效且确认无数据风险时强制
sudo kill -KILL $PID
不熟悉的服务先查再动。 用 systemctl list-units --type=service 看有哪些服务,用 dpkg -S 或 rpm -qf 查命令属于哪个软件包。确认是恶意程序时,不要只删文件,要先断网、保留证据、查清入侵途径,否则删了还会再来。
常见问题
kill -9 之后进程还在,为什么?
三种可能:进程处于 D 状态等 I/O,kill -9 无法立即生效;进程是僵尸,已经死了只是没被回收;你 kill 的是父进程而实际工作的是子进程。用 ps -o pid,ppid,stat -p PID 确认状态,用 pstree -p PID 看子进程。
僵尸进程需要清理吗?
少量不用,它只占一个 PID。持续增长才说明父进程有问题。僵尸无法被杀死,正确做法是让父进程回收(kill -CHLD 父进程)或在评估影响后重启父进程。
kill 和 systemctl stop 有什么区别?
systemctl stop 会按 unit 文件执行完整的停止流程,包括发送停止命令、等待超时、清理 cgroup、处理依赖关系,并留下完整日志。kill 只是发一个信号。对 systemd 管理的服务,一律优先用 systemctl stop。
怎么防止误杀进程?
三个习惯:操作前用 /proc/PID/cmdline 和 exe 双重确认身份;批量操作前用 pgrep -a -f 先列出命中清单;生产环境的操作先在测试环境验证,并确认有回滚方案。
进程杀掉了,端口还被占着怎么办?
可能是 socket 处于 TIME-WAIT 状态,这是正常的 TCP 行为,通常几十秒内自动消失。用 ss -tan state time-wait | wc -l 确认。也可能是子进程还活着,用 pstree -p 查一下。
总结
- 先看懂
STAT列:R是运行、S是睡眠、D是等 I/O 且杀不掉、Z是僵尸、T是被暂停。 - 定位进程用
ps -eo自定义列加pgrep,看父子关系用pstree,看归属用systemctl status PID。 /proc/里的/ cmdline、exe、cwd、fd、status能确认进程身份和资源占用,动手前必查。- 停止顺序固定为
systemctl stop→kill -TERM→ 等待观察 →kill -KILL,kill -9只在确认无数据风险且前序手段无效时使用。 - 僵尸进程杀不死,要处理父进程;D 状态进程杀不动,要解决它等待的 I/O 资源;两者都不是靠
kill -9能解决的。
企业QQ咨询




