无效进程占用CPU
运行 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 个回答
有趣的是,甚至可能让人困惑的是,我现在有一个僵尸进程,它正在我的系统上占用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
这些是僵尸进程,统计信息中的 Z 就是标志,它们不会被清理,直到它们的父进程结束。我对 Python 了解不多,但你可能是在 Python 解释器中调用了 fork 或类似的命令来生成这些进程。结束解释器后,这些僵尸进程就会被清理掉。
如果你想要最新的 CPU 信息,可以试试 "top" 命令。
另外,我个人更喜欢 "ps -ef" 的输出,而不是 "ps aux",因为我觉得 aux 这个用法不太标准(所以没有用 '-' 来分隔命令和参数),而且在很多其他 Unix 系统上,比如 HPUX、AIX 等,aux 也不能正常工作。
"ps -ef" 会显示父进程 ID(ppid),这有助于你找到类似的问题。
僵尸进程(也就是“无效进程”)不会占用CPU资源:它只是被系统保留,以便父进程可以获取一些关于它的信息(比如返回状态、资源使用情况等等)。
通过ps
命令看到的CPU使用情况,是指这个进程在运行时的CPU使用情况:也就是说,是在它结束运行并变成僵尸进程之前的状态。