我遇到了一个奇怪的问题,其中一个ps -o args -p <pid>
命令偶尔无法找到有问题的进程,即使它肯定在有问题的服务器上运行。有问题的进程是用于启动一些 Java 应用程序的长时间运行的包装器脚本。
该问题的“异常”发生似乎总是在清晨发生,因此有一些证据表明它与相关服务器上的磁盘负载有关,因为它们当时负载相当大,但是通过运行ps
in在一个紧密的循环中提出问题,我最终可以复制这个问题 - 每运行几百次左右我就会得到一个错误。
通过运行以下 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.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
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
考虑直接从/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 集,避免所有已知的竞争并直接从源中获取您需要的信息。
这有点难看,但是您描述的问题确实存在,它应该是获取该信息的可靠方法。