Linux进程分析与死锁排查
一、初步侦察
首先确认进程的基本信息和来源:
sudo witr -p <PID> # 查看进程树、来源、运行时长
ps aux | grep <PID> # 确认进程存在
cat /proc/<PID>/status # 查看详细状态(State字段最关键)
State 字段含义:
R运行中,S可中断睡眠(正常),D不可中断睡眠(IO等待,危险),Z僵尸进程
二、判断进程在等什么
sudo strace -p <PID>
通过系统调用判断阻塞原因:
| 系统调用 | 说明 |
|---|---|
pselect6 / select / poll + 反复 Timeout | 等待某个 fd 有数据,但一直没有 |
read 无返回 | 阻塞读,数据来源卡住 |
futex | 等待锁或线程同步 |
wait4 | 等待子进程结束 |
recvfrom / recv | 等待网络数据 |
本案例中 pselect6 反复超时,说明在轮询等待某些 fd,是排查的起点。
三、锁文件排查
如果怀疑是资源锁冲突:
lsof /var/lib/dpkg/lock
lsof /var/lib/apt/lists/lock
lsof /var/cache/apt/archives/lock
无输出则排除锁冲突,继续深入 fd 分析。
四、fd 深度分析(核心)
# 查看进程所有打开的文件描述符
sudo ls -la /proc/<PID>/fd/
# 判断每个fd的类型
# socket:[xxx] → 网络连接
# pipe:[xxx] → 管道
# /path/to/file → 普通文件
如果是 socket,检查网络状态:
sudo ss -tp | grep <PID>
# 看连接是 ESTABLISHED / CLOSE_WAIT / TIME_WAIT
如果是 pipe,找管道两端的持有者:
sudo lsof 2>/dev/null | grep <pipe_inode_id>
# 找到读端(r)和写端(w)各是哪个进程
五、死锁识别
拿到管道两端信息后,画出依赖关系图:
进程A ──写──→ pipe1 ──→ 进程B(进程B在等pipe1有数据)
进程A ←──读←── pipe2 ←── 进程B(进程A在等pipe2有数据)
判定条件:
- A 等 B 写入才能继续
- B 等 A 写入才能继续
- 双方都在等,形成闭环 → 死锁确认
本案例的死锁闭环:
apt-get ──[fd8写]──→ pipe:217112098 ──→ [fd0读] http
apt-get ←──[fd5读]←── pipe:217112097 ←── [fd1写] http
六、处理死锁
死锁一旦形成无法自行恢复,必须手动介入:
# 1. 终止整条进程链(从子进程到父进程)
sudo kill -9 <child_pid1> <child_pid2> <parent_pid>
# 2. 清理残留锁文件
sudo rm -f /var/lib/apt/lists/lock
sudo rm -f /var/lib/dpkg/lock
sudo rm -f /var/cache/apt/archives/lock
# 3. 修复可能损坏的状态
sudo dpkg --configure -a
# 4. 验证恢复正常
sudo apt-get update
七、预防措施
针对本案例(apt 无限等待),配置超时:
sudo tee /etc/apt/apt.conf.d/99timeout << EOF
Acquire::http::Timeout "60";
Acquire::https::Timeout "60";
Acquire::Retries "3";
EOF
通用建议:
- 对长时间运行的关键进程设置监控告警(运行超过阈值自动通知)
- 定期检查
ps aux中异常高龄进程 - 进程间通信设计时加入超时机制,避免无限阻塞
完整排查流程总结
witr / ps 发现异常进程
↓
cat /proc/PID/status 确认进程状态
↓
strace -p PID 观察系统调用,判断在等什么
↓
ls /proc/PID/fd 查看所有文件描述符类型
↓
┌───────────┬───────────┐
socket pipe 文件锁
↓ ↓ ↓
ss -tp lsof|grep lsof /path
查网络 找两端 查持有者
↓ ↓
画依赖图
判断是否闭环
↓
kill 整条链
清理锁文件
修复状态
验证恢复
这套方法论适用于所有 Linux 进程间通信死锁场景,不限于 apt,同样适用于数据库连接池、消息队列、任何使用 pipe/socket 通信的服务。