为什么ps *非常*偶尔无法找到有效的进程?


9

我遇到了一个奇怪的问题,即使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>

输出从比较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

...并且较短的列表未包含实际的pid,因此未找到。

您可以忽略本节,下面的dave_thompson的注释解释了ENOTTY错误,并且它们无关

此外,失败的运行会出现一些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输出中都是一致的,但不会出现在成功的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
仅供参考,ioctl与获取终端设置有关(例如,第一个是查找行数和列数),因此奇怪的是它们失败了,但可能不是直接原因。这听起来像是一个内核错误……
derobert


2
你必须>/dev/null在“失败”的调用(中环),但不是“好”调用,因此对FD 1. ENOTTY
dave_thompson_085

哦,该死。感谢您抓住那个戴夫,这肯定解释了ENOTTY。
詹姆斯,

很高兴看到我不是唯一遇到此问题的人。我解决这个问题的方法是有一个try-catch,如果命令失败,它会重试,但仍然很烦人:/
Josh Correia

Answers:


7

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

您可以使用此sed单行代码获取process的args $pid

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

此命令等效于:

ps -o args= -p "$pid"

(使用args=ps将省略报头)。

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


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

我一直认为,如果某个进程在ps启动之前就存在,而在完成之后仍然存在,那么它就不会被它遗漏,我假设即使有很多其他进程搅动,内核也将保证始终包含这些进程。被创造和摧毁。您所描述的暗示事实并非如此。我对此仍然持怀疑态度,但是鉴于已知如何ps运行的竞赛条件,我认为/proc由于这些竞赛条件列出PID 可能会错过现有的PID至少是合理的。

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


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

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


这将我们带到变通方法,直接进行操作/proc/$pid,而没有列出系统的完整PID集合,从而避免了所有已知的竞争,而直接从源头直接获取您需要的信息。

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


2
感谢Filipe,我已经投票赞成,因为sed命令很有用(并且我已经更改了脚本以仅查看/ proc),并且因为我没有意识到在ps上添加“ =”会删除标头。我没有接受答案,因为我仍然非常好奇为什么它看不到/ proc的整个列表,并且我希望其他人也知道:)
James
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.