为什么在分组命令中执行简单命令不会fork子shell进程,而复合命令会做
Why does executing a simple command in a grouping command does not fork a subshell process, and the compound command will do it
我知道分组命令(command-list)
会创建一个子shell环境,每个列出的命令都在那个子shell中执行。但是如果我在分组命令中执行一个简单的命令,(使用ps
命令输出进程),那么没有输出任何子shell进程。但是如果我尝试在分组命令中执行一个命令列表(复合命令),那么一个子shell进程就会输出。为什么会产生这样的结果?
- 在分组命令中执行简单命令(仅
ps
命令)的测试:
[root@localhost ~]# (ps -f)
输出如下:
UID PID PPID C STIME TTY TIME CMD
root 1625 1623 0 13:49 pts/0 00:00:00 -bash
root 1670 1625 0 15:05 pts/0 00:00:00 ps -f
- 另一个在分组命令中执行复合命令(命令列表)的测试:
[root@localhost ~]# (ps -f;cd)
输出如下:
UID PID PPID C STIME TTY TIME CMD
root 1625 1623 0 13:49 pts/0 00:00:00 -bash
root 1671 1625 0 15:05 pts/0 00:00:00 -bash
root 1672 1671 0 15:05 pts/0 00:00:00 ps -f
我测试了很多其他命令(复合命令和简单命令),但结果都是一样的。我猜即使我在分组命令中执行一个简单的命令,bash
也应该fork一个子shell进程,否则它无法执行命令。但是为什么我看不到呢?
Bash 优化执行。它检测到只有一个命令在 (
)
组内并调用 fork
+ exec
而不是 fork
+ fork
+ exec
.这就是为什么您在进程列表中看到一个 bash
进程少了。使用花费更多时间 ( sleep 5 )
消除计时的命令时更容易检测。另外,您可能想阅读 unix.stackexchange 上的 this thread。
我认为优化是在 execute_in_subshell()
函数中 execute_cmd.c
的某个地方完成的(我添加的箭头 >
):
/* If this is a simple command, tell execute_disk_command that it
might be able to get away without forking and simply exec.
>>>> This means things like ( sleep 10 ) will only cause one fork
If we're timing the command or inverting its return value, however,
we cannot do this optimization. */
在execute_disk_command()
函数中我们还可以读到:
/* If we can get away without forking and there are no pipes to deal with,
don't bother to fork, just directly exec the command. */
它看起来像一个优化,dash 似乎也在做:
运行
bash -c '( sleep 3)' & sleep 0.2 && ps #or with dash
一样,更稳健:
strace -f -e trace=clone dash -c '(/bin/sleep)' 2>&1 |grep clone # 1 clone
显示子shell被跳过,但是如果在子shell之后的子shell中有post工作要做,则创建子shell :
strace -f -e trace=clone dash -c '(/bin/sleep; echo done)' 2>&1 |grep clone #2 clones
Zsh 和 ksh 甚至更进一步,因为(当他们看到它是脚本中的最后一个命令时):
strace -f -e trace=clone ksh -c '(/bin/sleep; echo done)' 2>&1 |grep clone # 0 clones
他们根本不分叉(=克隆),直接在 shell 进程中执行。
我知道分组命令(command-list)
会创建一个子shell环境,每个列出的命令都在那个子shell中执行。但是如果我在分组命令中执行一个简单的命令,(使用ps
命令输出进程),那么没有输出任何子shell进程。但是如果我尝试在分组命令中执行一个命令列表(复合命令),那么一个子shell进程就会输出。为什么会产生这样的结果?
- 在分组命令中执行简单命令(仅
ps
命令)的测试:
输出如下:[root@localhost ~]# (ps -f)
UID PID PPID C STIME TTY TIME CMD root 1625 1623 0 13:49 pts/0 00:00:00 -bash root 1670 1625 0 15:05 pts/0 00:00:00 ps -f
- 另一个在分组命令中执行复合命令(命令列表)的测试:
输出如下:[root@localhost ~]# (ps -f;cd)
UID PID PPID C STIME TTY TIME CMD root 1625 1623 0 13:49 pts/0 00:00:00 -bash root 1671 1625 0 15:05 pts/0 00:00:00 -bash root 1672 1671 0 15:05 pts/0 00:00:00 ps -f
我测试了很多其他命令(复合命令和简单命令),但结果都是一样的。我猜即使我在分组命令中执行一个简单的命令,bash
也应该fork一个子shell进程,否则它无法执行命令。但是为什么我看不到呢?
Bash 优化执行。它检测到只有一个命令在 (
)
组内并调用 fork
+ exec
而不是 fork
+ fork
+ exec
.这就是为什么您在进程列表中看到一个 bash
进程少了。使用花费更多时间 ( sleep 5 )
消除计时的命令时更容易检测。另外,您可能想阅读 unix.stackexchange 上的 this thread。
我认为优化是在 execute_in_subshell()
函数中 execute_cmd.c
的某个地方完成的(我添加的箭头 >
):
/* If this is a simple command, tell execute_disk_command that it
might be able to get away without forking and simply exec.
>>>> This means things like ( sleep 10 ) will only cause one fork
If we're timing the command or inverting its return value, however,
we cannot do this optimization. */
在execute_disk_command()
函数中我们还可以读到:
/* If we can get away without forking and there are no pipes to deal with,
don't bother to fork, just directly exec the command. */
它看起来像一个优化,dash 似乎也在做:
运行
bash -c '( sleep 3)' & sleep 0.2 && ps #or with dash
一样,更稳健:
strace -f -e trace=clone dash -c '(/bin/sleep)' 2>&1 |grep clone # 1 clone
显示子shell被跳过,但是如果在子shell之后的子shell中有post工作要做,则创建子shell :
strace -f -e trace=clone dash -c '(/bin/sleep; echo done)' 2>&1 |grep clone #2 clones
Zsh 和 ksh 甚至更进一步,因为(当他们看到它是脚本中的最后一个命令时):
strace -f -e trace=clone ksh -c '(/bin/sleep; echo done)' 2>&1 |grep clone # 0 clones
他们根本不分叉(=克隆),直接在 shell 进程中执行。