Please enable Javascript to view the contents

如何清理僵尸进程

 ·  ☕ 2 分钟

1. 什么是僵尸进程

进程的创建过程:

  1. 父进程调用 fork() 创建子进程
  2. 子进程执行 exec() 加载新程序
  3. 子进程结束执行,调用 exit() 或返回
  4. 父进程调用 wait()waitpid()

如果父进程没有调用 wait()waitpid(),子进程结束后仍然保留在系统中,成为僵尸进程。

2. 怎么查看僵尸进程

可以使用 ps 命令查看僵尸进程:

1
ps -A -ostat,ppid,pid,cmd | grep -e'^[Zz]'
1
2
3
4
Zl   2062969 2068159 [python] <defunct>
Zl   2062969 2074157 [python] <defunct>
ZNl  2062969 2081477 [ray::WorkerDict] <defunct>
ZNl  2062969 2081478 [ray::WorkerDict] <defunct>

第一列是进程的状态,第二列是父进程 ID (PPID),第三列是僵尸进程的 ID (PID),第四列是命令。

STAT含义
Z僵尸进程
l多线程进程
N低优先级进程

3. 怎么清理僵尸进程

3.1 直接杀死父进程

僵尸进程本身无法被清理,只能清理其父进程来清理。

简单点可以直接 kill 父进程:

1
kill -9 <parent_pid>

也可以批量清理所有僵尸进程的父进程:

1
ps -A -ostat,ppid,pid,cmd | grep -e'^[Zz]' |awk '{print $2}' | xargs kill -9

3.2 恢复父进程回收僵尸进程

给父进程发送 SIGCONT (kill -18) 可以将父进程从暂停中恢复,然后继续 wait() 子进程正常结束。

  • 判断父进程是否挂起
1
ps -p <parent_pid> -o stat

如果状态是 T,表示进程被暂停,可以使用 SIGCONT 恢复。

  • 发送 SIGCONT 信号
1
kill -18 <parent_pid>

3.3 回收进程所在容器

  • 查找父进程所在的容器
1
export parent_pid=2062969
1
cat /proc/${parent_pid}/cgroup
1
0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod28df2390_283c_4962_b6f6_4efaafe529ec.slice/cri-containerd-c94382533ede3ae949b06562b8f347a0656a7446403722d4b026256c19e6aea1.scope
  • 查看容器信息

截取 containerd 后面的部分字符串用于查找容器。

1
nerdctl -n k8s.io ps -a | grep c94382533
1
c94382533ede    docker.io/3257a037b275eb6ca6f75d86f2bc63b9:v1.0                          "/bin/bash -c bash r…"    14 hours ago    Created             k8s://default/elastic-job-261548-edljob-worker-0/main
  • 删除容器
1
nerdctl -n k8s.io rm -f c94382533ede
  • 清理全部异常容器
1
nerdctl -n k8s.io ps -a | grep -E 'Exited|Created|Dead|Paused' | awk '{print $1}' | xargs -r nerdctl -n k8s.io rm -f

3.4 进程卡存储存储上

  • 查看进程的状态
1
export parent_pid=1203391
1
cat /proc/${parent_pid}/status
1
2
3
4
5
6
Name:   python3.10
State:  D (disk sleep)
Tgid:   1203391
Ngid:   1203391
Pid:    1203391
PPid:   1197458
  • 修复存储问题

这种情况下,可以尝试查看一下 df -h 是不是卡死。如果是,那么清理挂载点即可。

1
opscli task -f ~/.ops/tasks/clear-mount.yaml

3.5 进程卡在设备锁上

  • 查看进程的状态
1
export parent_pid=2062969
1
cat /proc/${parent_pid}/status
1
2
3
4
5
6
Name:   bash
State:  S (sleeping)
Tgid:   2062969
Ngid:   0
Pid:    2062969
PPid:   2062945
  • 查看进程的堆栈
1
cat /proc/${parent_pid}/stack
1
2
3
4
5
6
7
8
[<0>] do_wait+0x1c7/0x230
[<0>] kernel_wait4+0xaf/0x150
[<0>] zap_pid_ns_processes+0x111/0x1b0
[<0>] do_exit+0xa7c/0xac0
[<0>] do_group_exit+0x47/0xb0
[<0>] __x64_sys_exit_group+0x18/0x20
[<0>] do_syscall_64+0x57/0x190
[<0>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
  • 查看子进程的堆栈
1
ps -ef --forest | grep ${parent_pid}
1
2
3
4
5
root     2062969       1  0 02:02 ?        00:00:01 [bash]
root     2068159 2062969 12 02:03 ?        01:43:37  \_ [python] <defunct>
root     2074157 2062969  0 02:03 ?        00:00:29  \_ [python] <defunct>
root     2081477 2062969 39 02:04 ?        05:32:22  \_ [ray::WorkerDict] <defunct>
root     2081478 2062969 36 02:04 ?        05:11:06  \_ [ray::WorkerDict] <defunct>
1
export child_pid=2068159
1
cat /proc/${child_pid}/stack
1
2
3
[<0>] rwsem_down_write_slowpath+0x244/0x4d0
[<0>] os_acquire_rwlock_write+0x35/0x40 [nvidia]
[<0>] _nv042313rm+0x10/0x40 [nvidia]

进程正在尝试获取 nvidia 提供的一个写锁,但是阻塞了。

  • 查看哪些进程在使用 nvidia 设备
1
fuser -v /dev/nvidia*
  • 批量杀死这些进程
1
fuser -v /dev/nvidia* |awk '{for(i=1;i<=NF;i++)print "kill -9 " $i;}' | sh
1
2
3
                     USER        PID ACCESS COMMAND
/dev/nvidiactl:      root      F.... nvidia-smi
                     root      F.... nvidia-smi

D 状态(Uninterruptible sleep) 意味着进程处于内核阻塞状态,在等待某些资源不能被中断,不能 kill ,可以升级一下驱动版本之后,进行重启。


微信公众号
作者
微信公众号