为什么在分组命令中执行简单命令不会分叉子外壳进程,而复合命令会这样做



我知道分组命令(command-list)创建一个子外壳环境,并且每个列出的命令都在该子shell中执行。但是如果我在分组命令中执行一个简单的命令(使用ps命令输出进程),那么不会输出子壳进程。但是,如果我尝试在分组命令中执行命令列表(复合命令),则会输出一个子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也应该分叉一个子壳进程,否则它无法执行该命令。但是为什么我看不到它呢?

Bash 优化了执行。它检测到()组中只有一个命令,并调用fork+exec而不是fork+fork+exec。这就是为什么您在进程列表中看到一个进程bash较少。使用需要更多时间( sleep 5 )消除计时的命令时,更容易检测。另外,你可能想在unix.stackexchange上阅读这个线程。

我认为优化是在execute_cmd.c函数execute_in_subshell()的某个地方完成的(箭头>我添加的):

/* 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. */

它看起来像一个优化,破折号似乎也在这样做:

运行

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

显示子外壳被跳过,但如果子外壳在子外壳之后要完成后期工作,则会创建子外壳:

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 进程中执行。

最新更新