为什么 ps *very* 有时会找不到有效的进程?

为什么 ps *very* 有时会找不到有效的进程?

我遇到了一个奇怪的问题,其中一个ps -o args -p <pid>命令非常偶尔无法找到有问题的进程,即使它肯定在有问题的服务器上运行。这些进程是用于启动某些 Java 应用程序的长时间运行的包装脚本。

该问题的“野外”发生似乎总是发生在清晨,因此有一些证据表明它与相关服务器上的磁盘负载有关,因为当时它们的负载相当重,但是通过ps运行问题在一个紧密的循环中,我最终可以复制这个问题 - 每运行几百次左右我就会收到一个错误。

通过运行以下 bash 脚本,我成功地为失败和成功的运行生成 strace 输出:

while [ $? == 0 ] ; do strace -o fail.out ps -o args -p <pid> >/dev/null ; done ; strace -o good.out ps -o args -p <pid>

fail.out比较和的输出good.out,我可以看到getdents运行时失败的系统调用以某种方式返回的数字比系统上的实际进程数小得多(与 ~1100 相比,大约为 ~500)

grep getdents good.out
  getdents(5, /* 1174 entries */, 32768)  = 32760
  getdents(5, /* 31 entries */, 32768)    = 992
  getdents(5, /* 0 entries */, 32768)     = 0

grep getdents fail.out
  getdents(5, /* 673 entries */, 32768)   = 16728
  getdents(5, /* 0 entries */, 32768)     = 0

...并且那个较短的列表不包括有问题的实际 pid,因此找不到它。

您可以忽略此部分,ENOTTY 错误由下面 dave_thompson 的评论解释,并且不相关

此外,失败的运行会出现一些ENOTTY成功运行中不会出现的错误。我看到输出的开头附近

ioctl(1, TIOCGWINSZ, 0x7fffe19db310) = -1 ENOTTY(不适合设备的 ioctl) ioctl(1, TCGETS, 0x7fffe19db280) = -1 ENOTTY(不适合设备的 ioctl)

最后我看到一个

ioctl(1, TCGETS, 0x7fffe19db0d0) = -1 ENOTTY(不适合设备的 ioctl)

ioctl最后的失败发生在ps返回之前,但它发生在ps已经打印了空结果集之后,所以我不确定它们是否相关。我确实知道它们在我所有失败的 strace 输出中都是一致的,但不会出现在成功的输出中。

我完全不知道为什么getdents有时找不到完整的进程列表,现在我已经到了这样的地步:我要通过更改检查包装器脚本的控制脚本来给整个事情贴上创可贴如果第一次失败,我有疑问要第二次打电话ps,但我很想知道是否有人对这里发生的事情有任何想法。

有问题的系统在 CentOS 7 和 procps-ng 版本 3.3.10-17.el7_5.2.x86_64 上运行内核 4.16.13-1.el7.elrepo.x86_64

答案1

考虑直接从文件系统读取所需的信息,/proc而不是通过ps.您将在 file 中找到您要查找的信息(“args”)/proc/$pid/cmdline,仅用 NUL 字节而不是空格分隔。

您可以使用这一sed行来获取 process 的参数$pid

sed -e 's/\x00\?$/\n/' -e 's/\x00/ /g' "/proc/$pid/cmdline"

该命令相当于:

ps -o args= -p "$pid"

(使用args=inps将省略标头。)

sed命令将首先查找最后一个尾随 NUL 字节并将其替换为换行符,然后用空格替换所有其他 NUL 字节(分隔各个参数),最终生成与您在ps.


关于列出系统中的进程,ps通过列出 中的目录来实现/proc,但该过程存在固有的竞争条件,因为进程在ps运行时启动和退出,所以您得到的并不是真正的快照,而是近似值。特别是,它可能ps会显示在显示其结果时已经终止的进程,或者忽略在运行时已启动的进程(但在列出 的内容时内核未返回/proc。)

我总是假定如果一个进程在启动之前就存在ps并且在完成之后仍然存在,那么它会不是被它想念,我假定内核将保证始终包含这些进程,即使有大量其他进程被创建和销毁。你所描述的情况表明事实并非如此。我对此仍然持怀疑态度,但考虑到工作原理中存在已知的竞争条件,我认为列出 PID可能会由于这些竞争条件而错过现有的 PID,ps这至少是合理的。/proc

可以通过检查 Linux 内核的源代码来验证这一点,但我还没有这样做,所以无法真正确定是否存在这种会错过长时间运行的进程的竞争条件,因为你描述。


另一部分是ps工作方式。即使您将单个 PID 与参数一起传递给它-p,它仍然会列出所有现有的 PID,即使您只对单个 PID 感兴趣。在这种情况下,它肯定可以走捷径,跳过列出条目/proc并直接进入/proc/$pid.

我不能说为什么要这样实施。也许因为大多数ps选项都是进程上的“过滤器”,所以以-p相同的方式实现更容易,采取捷径直接进入/proc/$pid可能会涉及单独的代码路径或代码重复......另一个假设是,某些情况下包括-p加上附加选项将最终需要列出,因此确定哪些具体情况允许走捷径、哪些情况不允许走捷径可能很复杂。


这让我们找到了解决方法,直接进入/proc/$pid,无需列出系统的完整 PID 集,避免所有已知的竞争,只需直接从源获取所需的信息。

这有点丑陋,但你描述的问题确实存在,它应该是获取该信息的可靠方法。

相关内容