无效进程占用CPU

4 投票
3 回答
5235 浏览
提问于 2025-04-17 21:11

运行 ps aux 命令后,你会看到下面这些内容:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
ubuntu    1496  9.1  0.0      0     0 pts/0    Z+   19:47   0:30 [python] <defunct>
ubuntu    1501 14.6  0.0      0     0 pts/0    Z+   19:47   0:48 [python] <defunct>
ubuntu    1502 14.8  0.0      0     0 pts/0    Z+   19:47   0:48 [python] <defunct>
ubuntu    1503 15.1  0.0      0     0 pts/0    Z+   19:47   0:49 [python] <defunct>
ubuntu    1504 15.4  0.0      0     0 pts/0    Z+   19:47   0:50 [python] <defunct>
ubuntu    1505 15.8  0.0      0     0 pts/0    Z+   19:47   0:52 [python] <defunct>
ubuntu    1506 16.0  0.0      0     0 pts/0    Z+   19:47   0:53 [python] <defunct>
ubuntu    1507 14.1  0.0      0     0 pts/0    Z+   19:47   0:46 [python] <defunct>
ubuntu    1508 14.3  0.0      0     0 pts/0    Z+   19:47   0:47 [python] <defunct>
ubuntu    1509 14.4  0.0      0     0 pts/0    Z+   19:47   0:47 [python] <defunct>
ubuntu    1510 14.6  0.0      0     0 pts/0    Z+   19:47   0:48 [python] <defunct>
ubuntu    1511 14.9  0.0      0     0 pts/0    Z+   19:47   0:49 [python] <defunct>
ubuntu    1512 10.7  0.0      0     0 pts/0    Z+   19:47   0:35 [python] <defunct>
ubuntu    1513 71.3  0.0      0     0 pts/0    Z+   19:47   3:55 [python] <defunct>

这些是通过多进程创建的一些进程,它们已经完成了任务,现在在等着被父进程合并。那它们为什么还占用 CPU 呢?

如果这只是 ps 命令的一个显示问题,那我该怎么才能准确知道 CPU 的使用情况呢?

3 个回答

1

有趣的是,甚至可能让人困惑的是,我现在有一个僵尸进程,它正在我的系统上占用CPU时间。那么问题是,为什么会这样呢?一般来说,任何显示僵尸进程的ps命令输出意味着唯一在使用的就是进程表中的条目;根据维基百科的说法:“……僵尸进程或无效进程是指已经完成执行(通过exit系统调用)但仍在进程表中有条目的进程:它处于‘终止状态’。”而在unix.stackexchange上也提到:“僵尸进程几乎不占用资源,所以让它们存在不会影响性能。”

所以我有一个僵尸进程:

# ps -e -o pid,ppid,stat,comm| grep Z
 7296     1 Zl   myproc <defunct>

看起来它正在使用CPU时间:

# ps -e -o pid,ppid,bsdtime,stat,comm| grep Z; sleep 10; ps -e -o pid,ppid,bsdtime,stat,comm | grep Z
 7296     1  56:00 Zl   myproc <defunct>
 7296     1  56:04 Zl   myproc <defunct>

那么,僵尸进程是怎么积累CPU时间的呢?

我改变了我的搜索:

# ps -eT -o pid,lwp,ppid,bsdtime,stat,comm| grep 7296 
 7296  7296     1   1:29 Zl   myproc <defunct>
 7296  8009     1  56:11 Dl   myproc

我发现有一个线程在运行,并且正在使用系统I/O。确实,如果我这样做,我可以看到第15个字段(stime)在变化:

# watch -d -n 1 cat /proc/8009/stat
Every 1.0s: cat /proc/8009/stat                  Fri Jun  4 11:19:55 2021

8009 (myproc) D 1 7295 7295 0 -1 516 18156428 12281 37 0 11609 344755

(第15个字段被截断)

所以我尝试用TERM命令杀掉进程8009……没成功。用KILL命令杀掉它也没用。

我觉得这听起来像是内核的一个bug。我确实尝试过用strace来跟踪它,这有点傻,因为现在我的strace无法退出。

这是在RHEL 7.7上,内核版本是3.10.0-1062。虽然这个版本有点旧,但我认为可以得出结论(在我心里)僵尸进程可能由于某个地方的bug而积累系统资源。

顺便提一下,根据iotop的显示,我们的I/O峰值达到了4 GBps,这可真不少。我觉得这个东西肯定对我们的系统产生了影响,我想重启一下。

查看/proc/8009的ls输出返回了这个:

# ls -l /proc/8009
ls: cannot read symbolic link /proc/8009/cwd: No such file or directory
ls: cannot read symbolic link /proc/8009/root: No such file or directory
ls: cannot read symbolic link /proc/8009/exe: No such file or directory

(正常的/proc/pid输出后面跟着……但我把它截断了)

/proc/8009/fd是空的。所以即使我有大量的I/O发生,它也没有写入任何文件。我没有看到文件系统空间被使用,df -h的输出显示也是如此。

最后:尝试重启变得不可能。shutdown -r now没有效果。有几个systemd进程卡在I/O等待中:

  PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
22725 root       20   0  129M  2512  1548 R  0.0  0.0  0:00.19 htop
22227 root       20   0  195M  4776  2652 D  0.0  0.0  0:00.00 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
    1 root       20   0  195M  4776  2652 D  0.0  0.0  0:58.41 /usr/lib/systemd/systemd --switched-root --system --deserialize 22

这是关机的输出。我觉得此时init非常困惑:

# shutdown -r now
Failed to open /dev/initctl: No such device or address
Failed to talk to init daemon.

reboot也显示同样的情况。我可能得拔掉这台机器的电源。

……更新:就在我登录控制台的时候,系统重启了!大概花了10分钟。所以我不知道systemd在干什么,但它确实在做某些事情。

……另一个更新:今天我有3台机器发生了这种情况,都是共享相同的特征:相同的二进制文件,某种行为(没有打开的文件描述符,但有I/O发生,有两个线程,子线程在积累CPU时间)。正如@Stephane Chazelas提到的,我进行了堆栈跟踪。这是一个典型的输出;我对内核不太了解,但也许对未来的某些人会有兴趣……注意242603是父线程,242919是忙碌的子线程:

# grep -H . /proc/242919/task/*/stack
/proc/242919/task/242603/stack:[<ffffffff898a131e>] do_exit+0x6ce/0xa50
/proc/242919/task/242603/stack:[<ffffffff898a171f>] do_group_exit+0x3f/0xa0
/proc/242919/task/242603/stack:[<ffffffff898b252e>] get_signal_to_deliver+0x1ce/0x5e0
/proc/242919/task/242603/stack:[<ffffffff8982c527>] do_signal+0x57/0x6f0
/proc/242919/task/242603/stack:[<ffffffff8982cc32>] do_notify_resume+0x72/0xc0
/proc/242919/task/242603/stack:[<ffffffff89f8c23b>] int_signal+0x12/0x17
/proc/242919/task/242603/stack:[<ffffffffffffffff>] 0xffffffffffffffff
/proc/242919/task/242919/stack:[<ffffffffc09cbb03>] ext4_mb_new_blocks+0x653/0xa20 [ext4]
/proc/242919/task/242919/stack:[<ffffffffc09c0a36>] ext4_ext_map_blocks+0x4a6/0xf60 [ext4]
/proc/242919/task/242919/stack:[<ffffffffc098fcf5>] ext4_map_blocks+0x155/0x6e0 [ext4]
/proc/242919/task/242919/stack:[<ffffffffc0993cfa>] ext4_writepages+0x6da/0xcf0 [ext4]
/proc/242919/task/242919/stack:[<ffffffff899c8d31>] do_writepages+0x21/0x50
/proc/242919/task/242919/stack:[<ffffffff899bd4b5>] __filemap_fdatawrite_range+0x65/0x80
/proc/242919/task/242919/stack:[<ffffffff899bd59c>] filemap_flush+0x1c/0x20
/proc/242919/task/242919/stack:[<ffffffffc099116c>] ext4_alloc_da_blocks+0x2c/0x70 [ext4]
/proc/242919/task/242919/stack:[<ffffffffc098a4d9>] ext4_release_file+0x79/0xc0 [ext4]
/proc/242919/task/242919/stack:[<ffffffff89a4a9cc>] __fput+0xec/0x260
/proc/242919/task/242919/stack:[<ffffffff89a4ac2e>] ____fput+0xe/0x10
/proc/242919/task/242919/stack:[<ffffffff898c1c0b>] task_work_run+0xbb/0xe0
/proc/242919/task/242919/stack:[<ffffffff898a0f24>] do_exit+0x2d4/0xa50
/proc/242919/task/242919/stack:[<ffffffff898a171f>] do_group_exit+0x3f/0xa0
/proc/242919/task/242919/stack:[<ffffffff898b252e>] get_signal_to_deliver+0x1ce/0x5e0
/proc/242919/task/242919/stack:[<ffffffff8982c527>] do_signal+0x57/0x6f0
/proc/242919/task/242919/stack:[<ffffffff8982cc32>] do_notify_resume+0x72/0xc0
/proc/242919/task/242919/stack:[<ffffffff89f8256c>] retint_signal+0x48/0x8c
/proc/242919/task/242919/stack:[<ffffffffffffffff>] 0xffffffffffffffff
1

这些是僵尸进程,统计信息中的 Z 就是标志,它们不会被清理,直到它们的父进程结束。我对 Python 了解不多,但你可能是在 Python 解释器中调用了 fork 或类似的命令来生成这些进程。结束解释器后,这些僵尸进程就会被清理掉。

如果你想要最新的 CPU 信息,可以试试 "top" 命令。

另外,我个人更喜欢 "ps -ef" 的输出,而不是 "ps aux",因为我觉得 aux 这个用法不太标准(所以没有用 '-' 来分隔命令和参数),而且在很多其他 Unix 系统上,比如 HPUX、AIX 等,aux 也不能正常工作。

"ps -ef" 会显示父进程 ID(ppid),这有助于你找到类似的问题。

4

僵尸进程(也就是“无效进程”)不会占用CPU资源:它只是被系统保留,以便父进程可以获取一些关于它的信息(比如返回状态、资源使用情况等等)。

通过ps命令看到的CPU使用情况,是指这个进程在运行时的CPU使用情况:也就是说,是在它结束运行并变成僵尸进程之前的状态。

撰写回答