为什么后台控制运算符(&)创建的子shell在pstree下不显示

为什么后台控制运算符(&)创建的子shell在pstree下不显示

我知道当我运行时exit它会终止我当前的 shell,因为exit命令在同一个 shell 中运行。我还了解到,当我运行时exit &,原始 shell 不会终止,因为&确保命令在子 shell 中运行,从而exit终止该子 shell 并返回到原始 shell。但我不明白的是为什么命令 with 和 without&在 下看起来完全相同pstree,在本例中sleep 10为 and sleep 10 &。 4669是bash的PID,在该PID下发出firstsleep 10和then ,在此期间从另一个shell实例获得以下输出:sleep 10 &

# version without &
$ pstree 4669
bash(4669)───sleep(6345)

# version with &
$ pstree 4669
bash(4669)───sleep(6364)

版本是否应该&包含一个更多的衍生子 shell(例如,在本例中 PID 为 5555),就像这个一样?

bash(4669)───bash(5555)───sleep(6364)

pstreePS:为了更好的可读性,从开始的输出中省略了以下代码:

systemd(1)───slim(1009)───ck-launch-sessi(1370)───openbox(1551)───/usr/bin/termin(4510)───bash(4518)───screen(4667)───screen(4668)───

答案1

在我开始回答这个问题之前,我没有意识到使用& 控制操作员运行一个工作在后台启动一个子shell。当命令被括在括号中或形成管道的一部分(管道中的每个命令都在其自己的子 shell 中执行)时,就会创建子 shell。

命令列表Bash 手册的部分(谢谢吉米) 状态:

如果命令由控制运算符“&”终止,则 shell 会在子 shell 中异步执行该命令。这称为执行命令背景。 shell 不会等待命令完成,返回状态为 0 (true)。

据我了解,当你运行sleep 10 &shell时s 创建一个新的子进程(自身的副本),然后立即执行s 用外部命令 ( ) 中的代码替换此子进程sleep。这与正常运行命令(在前台)时发生的情况类似。请参阅Fork–exec 维基百科文章对此机制的简短概述。

我不明白为什么 Bash 会在子 shell 中运行后台命令,但如果您还希望能够运行 shell 内置命令,例如exitecho在后台运行(而不仅仅是外部命令),那么这是有意义的。

当它是在后台运行的 shell 内置命令时,就会发生这种fork情况(产生子 shell),而无需exec调用将其自身替换为外部命令。运行以下命令显示,当echo命令包含在大括号中并在后台运行(带有&)时,确实创建了一个子 shell:

$ { echo $BASH_SUBSHELL $BASHPID; }
0 21516
$ { echo $BASH_SUBSHELL $BASHPID; } &
[1] 22064
$ 1 22064

在上面的示例中,我将echo命令括在大括号中以避免BASH_SUBSHELL被当前 shell 扩展;大括号用于将命令组合在一起,而不使用子 shell。命令的第二个版本(以&控制运算符结尾)清楚地表明,使用 & 符号终止命令会导致创建一个子 shell(带有新的 PID)来执行echo内置命令。(我可能在这里简化了 shell 的行为。请参阅 mikeserv 的评论。)

我永远不会想到运行exit &,如果我没有阅读你的问题,我会期望当前的 shell 退出。现在知道此类命令是在子 shell 中运行的,您对退出的子 shell 的解释是有道理的。


“为什么后台控制运算符(&)创建的子shell在pstree下不显示”

如上所述,当您运行 时sleep 10 &,Bash 会自行创建子 shell,但由于sleep是外部命令,它会调用exec()系统调用,该系统调用会立即用程序的运行副本替换子进程中的 Bash 代码和数据sleep。当您运行时pstreeexec调用已经完成,子进程现在的名称为“睡觉”。


当离开我的计算机时,我试图想出一种方法来保持子 shell 运行足够长的时间,以便子 shell 可以通过pstree.我想我们可以通过内置运行命令time

$ time sleep 11 &
[2] 4502
$ pstree -p 26793
bash(26793)─┬─bash(4502)───sleep(4503)
            └─pstree(4504)

在这里,Bash shell (26793) 分叉创建一个子 shell (4502),以便在后台执行命令。该子 shell 运行其自己的time内置命令,该命令依次分叉(以创建 PID 4503 的新进程)并执行以运行外部sleep命令。


使用命名管道,吉米想出了一个聪明的方法来保持创建的子 shell 运行exit足够长的时间,以便可以通过以下方式显示它pstree

$ mkfifo file
$ exit <file &
[2] 6413
$ pstree -p 26793
bash(26793)─┬─bash(6413)
            └─pstree(6414)
$ echo > file
$ jobs
[2]-  Done    exit < file

从命名管道重定向stdin很聪明,因为它会导致子 shell 阻塞,直到它收到来自命名管道的输入。稍后,重定向(不带任何参数)的输出会将echo换行符写入命名管道,这会解锁子 shell 进程,而该进程又会运行exit内置命令。


同样,对于sleep命令:

$ mkfifo named_pipe
$ sleep 11 < named_pipe &
[1] 6600
$ pstree -p 26793
bash(26793)─┬─bash(6600)
            └─pstree(6603)

在这里,我们看到为在后台运行该命令而创建的子 shell 的 PID 为6600。接下来,我们通过向管道写入换行符来解锁进程:

$ echo > named_pipe

然后子 shellexec运行该sleep命令。

$ pstree -p 26793
bash(26793)─┬─pstree(6607)
            └─sleep(6600)

调用后exec(),我们可以看到子进程(6600)现在正在运行该sleep程序。

相关内容