为什么一个进程 ps 命令在目录周围显示空格而不是斜杠?

为什么一个进程 ps 命令在目录周围显示空格而不是斜杠?

我正在尝试调试一个失败的旧进程监视器,该监视器搜索正在运行的进程。我确定这是由一些神秘的 Linux 行为引起的。正在运行的进程是 /opt/my/path/directoryname/daemonname 但由于某些奇怪的原因,它在目录名周围显示空格,而不是 /。许多守护进程都从同一目录运行,并以正确的路径显示。它只是每台服务器上的一个特定守护进程。

ps -ef显示此守护程序的以下内容:

/opt/my/path directoryname daemonname --arg1 VALUE1 --arg-two VALUE.TWO --arg-three ARG.UMENT.THREE.ERR --worker daemonname --pid-role daemonname

该守护进程由 perl 脚本启动。与其他脚本相比,看起来目录路径是使用与所有其他脚本完全相同的环境变量提供的,但这些其他脚本会产生正确的路径,例如/opt/my/path/directoryname/daemonname.相关的 perl 行看起来像这样exec => catfile( $settings->config("/directories/executables"), $daemon_name ),,我已经确认 config("/directories/executables") 的值是正确的值/opt/my/path/directoryname

由于所有有关目录名称包含空格的问题,搜索这个问题一直是一场噩梦。这不是那个。任何路径都不应包含空格。 ps 显示我的 mkdir 命令,其中有一个斜杠的空格是我能找到的最接近的,但没有得到答复。

答案1

返回的字段之一ps -f是传递给进程(或其祖先之一)最后执行的命令执行的参数列表。您可以通过ps -o args.

当进程执行文件时,这是通过execve()系统调用:

execve("/path/to/executable", [arg0, arg1..., 0], [env1, env2... 0])

arg0按照惯例,是可执行文件的名称。当 shell 解释:

cmd arg

命令行,它调用execve("/path/to/cmd", ["cmd", "arg", 0], ...).当那是:

/path/to/cmd arg

它调用execve("/path/to/cmd", ["/path/to/cmd", "arg", 0], ...).

这些字符串 ( cmd, arg) 最终位于进程堆栈的底部,以 NUL 分隔。在这个过程中,这就是argv[0], argv[1]... 所指的。

在 Linux 上,该区域在 中公开/proc/<pid>/cmdline,这就是ps从现场获取它的地方args

从技术上讲,ps获取该字符串列表并打印它并用空格连接,以便最终字段args看起来像用于执行命令的 shell 命令行。

在这里,如果ps显示:

/opt/my/path directoryname daemonname --arg1...

这些空格可能表示这些字符串中的真实空格,或者单独的字符串,或者这些字符串中的空格已被 NUL 替换。

如果该过程碰巧使用了标准strtok()功能或类似的方法argv[0]将其拆分为/s ,可能会找出它所运行的可执行文件的基本名称或其路径的不同目录组件。

例如,如果我创建:

#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
  strtok(argv[0], "/");
  while (strtok(NULL, "/"));
  pause();
}

并编译并运行它:

$ cc a.c
$ "$PWD/a.out" &
[1] 67758
$ ps -fp "$!"
UID          PID    PPID  C STIME TTY          TIME CMD
chazelas   67758   60043  0 19:19 pts/4    00:00:00 /home chazelas a.out

您会看到/s 已被空格替换。

实际上,它们已被 NUL 取代,可以通过以下方式验证:

$ sed -n l "/proc/$!/cmdline"
/home\000chazelas\000a.out\000$

ps看到这些 NUL 并将它们解释为 3 个不同参数的分隔符,并用空格连接起来。

正如您后来在评论中指出的那样,dirname()是另一个标准函数,可以/在其参数中将 s 替换为 0。

$ cat b.c
#include <libgen.h>
#include <unistd.h>
int main(int argc, char *argv[]) {dirname(dirname(argv[0])); pause();}
$ cc b.c
$ "$PWD/a.out" &
[1] 42128
$ ps -fp "$!"
UID          PID    PPID  C STIME TTY          TIME CMD
chazelas   42128   39744  0 20:18 pts/6    00:00:00 /home chazelas a.out

然后假设这是正确的解释,它不是奇怪的 Linux 行为这只是应用程序本身argv[0]在启动后对其进行修改(可能通过将/s 替换为其中的 NUL,可能是为了提取一些路径组件)。

在 Linux 上,查找进程当前正在运行的可执行文件的更可靠方法是执行readlink()on /proc/<pid>/exe,例如使用realpathorreadlink实用程序或stat内置的zsh

$ zmodload zsh/stat
$ stat +link /proc/$!/exe
/home/chazelas/a.out
$ readlink "/proc/$!/exe"
/home/chazelas/a.out
$ realpath "/proc/$!/exe"
/home/chazelas/a.out

Linux 上的 procps 实现ps将其返回为ps -o exe.

没有万无一失的方法可以确定传递给命令的参数是什么,因为/proc/<pid>/cmdline进程可以修改显示的内容。

不过,审核日志可能包含这些信息。

相关内容