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

Jam*_*mes 10 linux ps procfs

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

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

通过运行以下 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>
Run Code Online (Sandbox Code Playgroud)

输出从比较fail.outgood.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
Run Code Online (Sandbox Code Playgroud)

...并且该较短的列表不包括相关的实际 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

fil*_*den 7

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

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

sed -e 's/\x00\?$/\n/' -e 's/\x00/ /g' "/proc/$pid/cmdline"
Run Code Online (Sandbox Code Playgroud)

此命令等效于:

ps -o args= -p "$pid"
Run Code Online (Sandbox Code Playgroud)

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

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


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

我一直认为,如果一个进程在ps启动之前存在并且在它完成之后仍然存在,那么它不会被它遗漏,我认为内核会保证这些总是被包含在内,即使有很多其他进程的流失被创造和毁灭。您所描述的情况表明情况并非如此。我仍然对此持怀疑态度,但考虑到已知的竞争条件是如何ps工作的,我想至少有可能列出 PID/proc可能会因为这些竞争条件而错过现有的PID 。

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


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

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


这将我们带到解决方法,直接转到/proc/$pid,而不列出系统的完整 PID 集,避免所有已知的竞争并直接从源中获取您需要的信息。

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

  • 感谢 Filipe,我投了赞成票,因为 sed 命令很有用(并且我已经将脚本更改为仅在 /proc 中查看),并且因为我没有意识到向 ps 添加“=”会删除标题. 我还没有接受答案,因为我仍然很好奇为什么它没有看到 /proc 的整个列表,我希望其他人知道:) (2认同)