为什么 sudo 不总是生成 shell 子进程?

mes*_*esr 7 bash process zsh sudo dash

我试图理解为什么某些 shell 在使用sudo调用时似乎会受到特殊处理调用时似乎会受到特殊处理。例如,似乎有两种可能的行为:

\n

“隐式”组(pstree是sudo的直接子级,中间没有 shell):

\n
$ sudo pstree -s $$\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80login\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sudo\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n$ sudo bash -c \'pstree -s $$\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80login\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sudo\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n$ sudo zsh -c \'pstree -s $$\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80login\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sudo\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n$ sudo dash -c \'pstree -s $$\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80login\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sudo\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n
Run Code Online (Sandbox Code Playgroud)\n

“显式”组(shell 是sudo的直接子级)的直接子级):

\n
$ sudo ksh -c \'pstree -s $$\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80login\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sudo\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80ksh\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n$ sudo tcsh -c \'pstree -s $$\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80login\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sudo\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80tcsh\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n$ sudo fish -c \'pstree -s $fish_pid\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80login\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sudo\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80fish\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n
Run Code Online (Sandbox Code Playgroud)\n

显然sudo和一些 shell 之间似乎发生了某种集成,但我找不到任何相关文档。我还 grep 了sudobash的源代码,但也找不到任何线索。

\n

这个另一个问题似乎相关:Why (...) does not spawn a new child process while run in back?

\n

我的sudobash版本是:

\n
$ sudo --version\nSudo version 1.8.29\n...\n$ bash --version\nGNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\n...\n
Run Code Online (Sandbox Code Playgroud)\n

Mar*_*ler 21

不,这不是 shell 和 sudo 之间的交互;这是负责执行任务的 shell 将其自身替换为您运行的命令!

\n

您可以从整个事物中删除 sudo 以获得相同的结果;例如,我在 alacritty 终端中运行 zsh:

\n
$> bash -c \'pstree -s $$\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80alacritty\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80zsh\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n
Run Code Online (Sandbox Code Playgroud)\n

这里没有bash!

\n

我们可以通过运行来验证会发生什么

\n
$> strace -o /tmp/bash-pstree.strace bash -c \'pstree -s $$\'\nsystemd\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80alacritty\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80zsh\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree\n$> bat /tmp/bash-pstree.strace # or just less /tmp/.... ; bat is just a nice code highlighter\n
Run Code Online (Sandbox Code Playgroud)\n

在那里我们看到

\n
$> grep execve /tmp/bash-pstree.strace\nexecve("/usr/bin/bash", ["bash", "-c", "pstree -s $$"], 0x7ffc02c52d30 /* 102 vars */) = 0\nexecve("/usr/bin/pstree", ["pstree", "-s", "35735"], 0x55b90ec78d00 /* 102 vars */) = \n
Run Code Online (Sandbox Code Playgroud)\n

所以,第一个 execve 是 bash 被调用,第二个是 bash 用 pstree \xe2\x80\x93 替换自身,之后实际上就没有 bash 存在了!其间不会发生分叉/克隆。

\n

当然,这只适用于命令链中的最后一个命令。如果我们将自己替换为之前运行的命令,则之后我们将无法执行任何操作。我们实际上可以很容易地验证这一点:

\n
$> bash -c \'pstree -p -s $$; pstree -p -s $$\'                                                                                                                                                                                                        \nsystemd(1)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80alacritty(34604)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80zsh(34609)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80bash(39257)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree(39258)\nsystemd(1)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80alacritty(34604)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80zsh(34609)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80pstree(39257)\n
Run Code Online (Sandbox Code Playgroud)\n

看,这里第一个 pstree 实际上是在 bash 创建的进程中运行的,第二个 pstree 由同一进程运行,替换了 bash。

\n

用它运行的程序替换 shell 当然是很好的,从资源角度来看:我们尽早生成所有文件句柄、内存、锁等。

\n

我不知道为什么有些 shell 会这样做,而其他 shell 则不会(它们可能在执行指定的命令之前使用forkclone复制自己的进程)。execve可能,这是开发人员从未想到的优化(如果您有一个像 一样丰富的 shell fish,为什么要早一点保护几 kB RAM?没有人会尝试从 shell 脚本生成 10000 条鱼来运行命令!),或者引入特殊情况在软件架构上很尴尬,或者是不可能的,因为 shell 保持对启动进程的一些控制,因此仍然需要存在才能例如接收信号或执行 IPC。当然,有些 shell 非常古老,因此可能有点简单(最后一次ksh发布是 10 年前!)。

\n

  • `strace -f -e clone,fork,execve bash -c 'pstree -s $$'` 将是验证它在 exec 之前不会分叉的另一种方法。不过,您的方法也是确凿的证据:您没有使用“strace -f”,因此它“不”跟踪子进程,但您仍然看到“execve”。所以它肯定已经取代了自己。您还可以从“strace -f -e execve”(不显示实现 fork 的“clone”系统调用)中看到这一点,只需查看 PID 号是否涉及 fork。跟踪“克隆”排除了您提到的分叉然后替换原始版本的可能性。 (2认同)