在`fork`、子进程和“子shell”上

kjo*_*kjo 5 process zsh fork subshell

这篇文章基本上是对我之前的一个问题的跟进。

从这个问题的答案中,我意识到不仅我不太了解“子外壳”的整个概念,而且更一般地说,我不了解fork-ing 和子进程之间的关系。

我曾经认为,当进程X执行 a 时fork,会创建一个进程Y,其父进程是X,但根据该问题的答案,

[a] subshel​​l 不是一个全新的进程,而是现有进程的一个分支。

这里的含义是“分叉”不是(或不会导致)“一个全新的过程”。

我现在很迷茫,实在是太迷糊了,无法提出一个连贯的问题来直接打消我的迷茫。

然而,我可以提出一个可能间接导致启蒙的问题。

因为,根据zshall(1),$ZDOTDIR/.zshenv每当一个新的实例zsh启动时就会被获取,那么任何$ZDOTDIR/.zshenv导致创建“一个全新的 [zsh] 进程”的命令都会导致无限回归。另一方面,在$ZDOTDIR/.zshenv文件中包含以下任一行不会导致无限回归:

echo $(date; printenv; echo $$) > /dev/null    #1
(date; printenv; echo $$)                      #2
Run Code Online (Sandbox Code Playgroud)

我发现通过上述机制引起无限回归的唯一方法是在文件中包含如下所示的行1$ZDOTDIR/.zshenv

$SHELL -c 'date; printenv; echo $$'            #3
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  1. 由于这种行为差异#1#2上面标记的命令和标记为#3帐户的命令之间有什么区别?

  2. 如果获得创建的壳#1#2被称为“子shell”,那些是什么样所产生的一个#3叫什么?

  3. 是否有可能根据 Unix 进程的“理论”(因为缺乏更好的词)来合理化(并且可能概括)上面描述的经验/轶事发现?

最后一个问题的动机是能够提前确定(即不诉诸实验)如果将它们包含在什么命令中会导致无限回归$ZDOTDIR/.zshenv


1date; printenv; echo $$我在上述各种示例中使用的特定命令序列并不是很重要。它们碰巧是其输出可能有助于解释我的“实验”结果的命令。(不过,我确实希望这些序列包含多个命令,原因在此处解释。)

Mic*_*mer 7

因为,根据 zshall(1),每当 zsh 的新实例启动时,就会获取 $ZDOTDIR/.zshenv

如果你在这里专注于“开始”这个词,你会有更好的时间。的效果fork()是创建另一个进程,该进程恰好从当前进程所在的位置开始。它克隆了一个现有的进程,唯一的区别是fork. 该文档使用“开始”表示从头开始进入程序。

您的示例 #3 运行$SHELL -c 'date; printenv; echo $$',从头开始一个全新的过程。它将经历普通的启动行为。例如,您可以通过在另一个 shell 中交换来说明这一点: runbash -c ' ... '而不是zsh -c ' ... '。在$SHELL这里使用没有什么特别的。

示例#1 和#2 运行子shell。shellfork本身并在该子进程中执行您的命令,然后在子进程完成后继续执行自己的命令。


您的问题 #1 的答案如上所述:示例 3 从一开始就运行一个全新的 shell,而其他两个运行子 shell。启动行为包括加载.zshenv.

他们特别指出这种行为的原因(这可能是导致您混淆的原因)是该文件(与其他文件不同)在交互式和非交互式 shell 中加载。


对于你的问题#2:

如果在#1 和#2 中创建的shell 称为“子shell”,那么与#3 生成的shell 一样的称为什么?

如果你想要一个名字,你可以称它为“子壳”,但实际上它什么都不是。它与您从 shell 启动的任何其他进程没有什么不同,无论是同一个 shell、不同的 shell 还是cat.


对于你的问题#3:

是否有可能根据 Unix 进程的“理论”(因为缺乏更好的词)来合理化(并且可能概括)上面描述的经验/轶事发现?

fork使用新的 PID 创建一个新进程,该进程从该进程停止的地方开始并行运行。exec用从某处加载的新程序替换当前正在执行的代码,从头开始运行。当您生成一个新程序时,您首先是您fork自己,然后exec是子程序中的该程序。这是适用于壳内和壳外任何地方的过程的基本理论。

子外壳是forks,您运行的每个非内置命令都会导致 afork和 an exec


请注意,在任何与 POSIX 兼容的 shell$$扩展到父 shell 的 PID ,因此无论如何您可能无法获得您期望的输出。另请注意,zsh 无论如何都会积极优化子shell 执行,并且通常exec是最后一个命令,或者如果没有它所有命令都是安全的,则根本不会产生子shell。

测试您的直觉的一个有用命令是:

strace -e trace=process -f $SHELL -c ' ... '
Run Code Online (Sandbox Code Playgroud)

这将为...您在新 shell 中运行的命令打印所有与进程相关的事件(而不是其他事件)的标准错误。您可以查看在新进程中运行和不运行的内容,以及execs 出现的位置。

另一个可能有用的命令是pstree -h,它将打印并突出显示当前进程的父进程树。您可以看到您在输出中的深度。