linux – 为什么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系统调用以某种方式返回比系统上的实际进程数少得多的数字(大约为~500与〜相比) 1100)

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,因此找不到它.

You can ignore this section, the ENOTTY errors are explained by dave_thompson’s comment below, and are unrelated

Additionally, the failed run gets some ENOTTY errors that don’t appear in the successful run. Near the beginning of the output I see

ioctl(1, TIOCGWINSZ, 0x7fffe19db310) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(1, TCGETS, 0x7fffe19db280) = -1 ENOTTY (Inappropriate ioctl for device)

And at the end I see a single

ioctl(1, TCGETS, 0x7fffe19db0d0) = -1 ENOTTY (Inappropriate ioctl for device)

The failed ioctl at the end happens right before the ps returns, but it occurs after the ps has already printed an empty results set, so I’m not sure if they’re related. I do know that they’re consistent in all of the failed strace outputs I have, but don’t appear in the successful ones.

我完全不知道为什么getdents偶尔会找不到完整的进程列表,而且我现在已经达到了这样的程度,即我将通过更改检查包装器的控制脚本来对整个事件进行创可贴有问题的脚本如果第一次失败,第二次调用ps,但我有兴趣知道是否有人有任何想法在这里发生了什么.

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

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

你可以使用这个sed one-liner获得进程$pid的参数:

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

此命令相当于:

ps -o args= -p "$pid"

(在ps中使用args =将省略标题.)

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,避免所有已知的比赛,只需直接从源获取所需的信息.

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

转载注明原文:linux – 为什么ps * very *偶尔会找不到有效的进程? - 代码日志