考虑以下两个 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 子进程。但该进程不会创建另一个子进程。因为运行sleep
shell后无需再执行任何操作,因此无需先分叉即可直接execve()
进行操作。sleep
execve
因为速度太快了,您只能看到in之后的结果pstree
。
但在
bash -c "sleep 10000; sleep 99999 "
case 新的 bash 分叉两次,每个命令一次。它也可以执行execve
最后一个命令,而不是首先分叉。我不知道为什么没有。
这种情况和重定向情况可能只是需要分叉时的检测问题。