《后台跑 FFmpeg 产生僵尸进程?一文吃透 Linux 父子进程回收机制》
摘要: 僵尸进程(<defunct>)是父进程未调用 wait() 回收的子进程残留,虽不占资源但可能导致PID耗尽。常见于脚本中 & 或 nohup 启动的后台任务,因父进程退出后未回收子进程。解决方案包括:1) 显式 wait 等待子进程;2) 使用 systemd-run 自动回收;3) 超时兜底机制。生产环境应避免仅依赖 &/nohup,需结合进程管理工具(如s
·
一、现象:为什么 ps 里突然出现 <defunct>
?
很多运维同学写过这样的 shell 一行流:
ffmpeg -i a.mp4 -c copy output.mp4 &
# 或者
nohup ffmpeg -i a.mp4 -c copy output.mp4 >/dev/null 2>&1 &
第二天巡检却发现:
USER PID PPID ... COMMAND
work 1234 1 0 Z [ffmpeg] <defunct>
这就是“僵尸进程”(Zombie)。它只占一个 PID,不耗 CPU/内存,但如果批量任务不断产生,PID 耗尽=系统拒绝 fork=新任务全失败。理解成因才能根治。
二、僵尸进程的本质
Linux 里进程终止时,内核会保留一个“最小尸体”——仅含退出码、资源使用等统计信息,等待父进程 wait()/waitpid()
来“收尸”。
若父进程 永远不收,尸体就一直躺尸,状态标记为 Z
。
三、两种常见写法风险对比
写法 | 父进程是谁? | 是否会 wait | 僵尸风险 |
---|---|---|---|
cmd & |
当前 Shell | 交互式 Shell 会装作业控制,脚本 Shell 默认不等 | 脚本退出后高 |
nohup cmd & |
当前 Shell | 同上,仅忽略 SIGHUP,不解决 wait 问题 | 同上 |
一句话:&
和 nohup
都只解决“终端挂断”,不解决“收尸”。
四、典型翻车场景
- CI/CD 或 Crontab 调用脚本,脚本里 for 循环启动 100 个
ffmpeg &
后直接退出。 - 父 Shell 异常被杀(SSH 断、OOM),PPID 变成 1,但 systemd 也不会自动 wait 非 service 子进程。
结果:批量僵尸。
五、生产级 4 种安全方案
- 显式 wait(最简单)
for f in *.mp4; do
ffmpeg -i "$f" -c copy "out/${f%.mp4}.mp4" &
done
wait # 阻塞直到所有后台任务完成
- 进程替换+waitpid(bash ≥4.3)
#!/usr/bin/env bash
for f in *.mp4; do
(ffmpeg -i "$f" -c copy "out/${f%.mp4}.mp4" &)
pid=$!
echo "$pid" >>/tmp/ffmpeg.pids
done
while read -r pid; do
wait "$pid"
done </tmp/ffmpeg.pids
- systemd-run(推荐,自带日志与自重启)
systemd-run --user --scope -p MemoryMax=2G \
-p CPUQuota=50% \
--collect \
ffmpeg -i a.mp4 -c copy output.mp4
--collect
保证任务结束后立即回收,僵尸概率 0;还能用 journalctl -u run-r*.scope
看日志。
- 超时+清理兜底
timeout 300s ffmpeg -i a.mp4 -c copy output.mp4 &
PID=$!
(sleep 310 && kill -9 $PID 2>/dev/null) &
wait $PID
防止转码卡死,同时保证 wait。
六、一键巡检脚本
ps -eo pid,ppid,stat,comm | awk '$3~/^Z/ {print}'
输出不为空即存在僵尸;配合 pstree -p
可快速定位“不负责”的父进程。
七、结论
- 僵尸进程不是“病毒”,而是 父进程写法缺陷。
&
/nohup
只能让任务忽略挂端信号,不能替代 wait()。- 脚本场景务必
wait
;长期服务请交给 systemd 或 supervisor;批量转码用 作业调度器(SLURM、Airflow) 更佳。
做好“收尸”,生产环境才能夜夜安睡。
更多推荐
所有评论(0)