为什么 bash 不为简单命令生成子 shell?

为什么 bash 不为简单命令生成子 shell?

考虑以下两个 bash 命令。一个创建子 shell,另一个则不创建。

无子壳

$ bash -c "sleep 10000 "

pstree 输出:

bash,100648
  └─sleep,103509 10000

带子壳

$ bash -c "sleep 10000; sleep 99999 "


bash,100648
  └─bash,103577 -c sleep 10000; sleep 99999
      └─sleep,103578 10000

这是某种优化吗? Bash 会查看命令并跳过简单命令的进程创建。看起来这个简单的命令是由父终端的 bash 生成的。


我使用的 Bash 版本是

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

不过,我认为在 bash 3.X 中也可以观察到同样的情况。


更多例子。使用内置命令会生成子 shell。内置函数不会在父 bash 中执行。

$ bash -c "read"

bash,100648
  └─bash,104336 -c read

sleep可以使用top和 输出重定向来重现相同的行为。

$ bash -c "top -b "
bash,100648
  └─top,104392 -b

$ bash -c "top -b > /dev/null "
bash,100648
  └─bash,104420 -c top -b > /dev/null
      └─top,104421 -b

答案1

在第一个例子中,PID 为 100648 的 shell 是你的交互式 shell,而sleep实际上是替换bash -c ''进程的进程。execve()系统调用生成一个新程序,该程序保留调用它的原始进程的 PID,因此您bash -c ''在那里看不到原始进程。简单命令取代 shell 进程的简单原因是它节省资源,如中所述斯蒂芬的回答为什么简单的 bash 命令中没有明显的克隆或分叉以及它是如何完成的?

从一个终端的简单测试也可以看出这一点:

$ echo $$
10250
$ bash -c 'sleep 60'

在另一篇文章中:

$ pstree -p 10250
bash(10250)───sleep(21031)

至于为什么会这样,是因为只有一个简单的命令,没有管道,并且bash 对简单命令执行直接 execve

正是出于这个原因,bash在第二个示例中认识到您有多个命令语句,因此sleep对于"sleep 10000; sleep 99999 ".在第一种情况下,execve()可以只替换父进程,当新进程退出时 - 没问题。但这里 shell 在第一个命令完成后无法退出。因此,将会出现 fork 和 execve,sleep 10000这就是您在输出中看到的内容pstree,稍后会出现一个新的 fork 和 execvesleep 99999


您可以执行的另一个测试是相信简单的命令bash -c ''替换了 shell 进程:

$ bash -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  23946
/proc/23946/status:Pid: 23946

请注意,这种行为是 bash 特定的。对于/bin/dash基于 Debian 的发行版(在 中也很明显strace):

$ sh -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  24188
/proc/24187/status:Pid: 24187

答案2

的输出pstree具有误导性。

bash -c "sleep 10000 "

确实创建了一个 bash 子进程。但该进程不会创建另一个子进程。因为运行sleepshell后无需再执行任何操作,因此无需先分叉即可直接execve()进行操作。sleep

execve因为速度太快了,您只能看到in之后的结果pstree

但在

bash -c "sleep 10000; sleep 99999 "

case 新的 bash 分叉两次,每个命令一次。它也可以执行execve最后一个命令,而不是首先分叉。我不知道为什么没有。

这种情况和重定向情况可能只是需要分叉时的检测问题。

相关内容