この投稿は基本的に私の 以前の質問 のフォローアップです。
その質問への回答から、「サブシェル」の概念全体を完全に理解していないだけでなく、より一般的には、fork
- ingと子プロセスの関係も理解していないことに気付きました。
私は、プロセスX
がfork
を実行すると、newプロセスY
は親がX
であるが、その質問への回答によれば、
[a]サブシェルは完全に新しいプロセスではなく、既存のプロセスのフォークです。
ここでの意味は、「フォーク」は「完全に新しいプロセス」ではない(または結果として生じない)ということです。
私は今、非常に混乱していて、実際、私の混乱を直接払拭するための首尾一貫した質問を作成することもできません。
しかし、私は間接的に啓蒙主義につながるかもしれない質問を定式化することができます。
zshall(1)
によると、zsh
の新しいインスタンスが開始されるたびに$ZDOTDIR/.zshenv
が取得されるため、$ZDOTDIR/.zshenv
内のコマンドは、「a完全に新しい[zsh]プロセス」は、無限後退になります。一方、$ZDOTDIR/.zshenv
ファイルに次の行のいずれかを含めると、notでは無限後退になります。
echo $(date; printenv; echo $$) > /dev/null #1
(date; printenv; echo $$) #2
上記のメカニズムによって無限後退を誘発することがわかった唯一の方法は、次のような行を含めることでした。1$ZDOTDIR/.zshenv
ファイル内:
$Shell -c 'date; printenv; echo $$' #3
私の質問は次のとおりです。
上記の#1
、#2
とマークされたコマンドと、#3
とマークされたコマンドの違いは、この動作の違いによるものですか?
#1
と#2
で作成されるシェルが「サブシェル」と呼ばれる場合、#3
によって生成されるシェルのようなものは何と呼ばれますか?
unixプロセスの「理論」(より良いWordがないため)の観点から、上記の経験的/事例的発見を合理化(そしておそらく一般化)することは可能ですか?
最後の質問の動機は、事前に(つまり、実験に頼らずに)どのコマンドが無限後退につながるかを判断できるようにすることです。 $ZDOTDIR/.zshenv
に含まれていましたか?
1 上記のさまざまな例で使用したコマンドdate; printenv; echo $$
の特定のシーケンスはそれほど重要ではありません。それらはたまたま、私の「実験」の結果を解釈するのに役立つ可能性のある出力を持つコマンドです。 (ただし、説明した理由により、これらのシーケンスを複数のコマンドで構成する必要がありました ここ 。)
Zshall(1)によると、$ ZDOTDIR/.zshenvは、zshの新しいインスタンスが開始されるたびにソースされるためです。
ここで「始まり」という言葉に焦点を当てると、より良い時間を過ごすことができます。 fork()
の効果は、別のプロセスを作成することです 現在のプロセスがすでにある場所から始まります 。これは既存のプロセスのクローンを作成しますが、唯一の違いはfork
の戻り値です。ドキュメントでは、「開始」を使用して、プログラムを最初から入力することを意味しています。
例3は$Shell -c 'date; printenv; echo $$'
を実行し、まったく新しいプロセスを最初から開始します。通常の起動動作を実行します。たとえば、別のシェルでスワップすることで、それを説明できます。bash -c ' ... '
の代わりにzsh -c ' ... '
を実行します。ここで$Shell
を使用することに特別なことは何もありません。
例1と2はサブシェルを実行します。シェルはそれ自体をfork
sし、その子プロセス内でコマンドを実行し、子が完了すると独自の実行を続行します。
質問1の答えは上記です。例3は最初からまったく新しいシェルを実行し、他の2つはサブシェルを実行します。起動動作には、.zshenv
のロードが含まれます。
彼らがこの振る舞いを具体的に呼ぶ理由は、おそらくあなたの混乱につながるものですが、このファイルは(他のいくつかとは異なり)インタラクティブシェルと非インタラクティブシェルの両方にロードされるためです。
あなたの質問#2へ:
#1と#2で作成されたシェルが「サブシェル」と呼ばれる場合、#3で生成されたもののようなものは何と呼ばれますか?
名前が必要な場合は「子シェル」と呼ぶことができますが、実際には何もありません。同じシェル、別のシェル、またはcat
のいずれであっても、シェルから開始する他のプロセスと同じです。
あなたの質問#3へ:
unixプロセスの「理論」(より良いWordがないため)の観点から、上記の経験的/事例的発見を合理化(そしておそらく一般化)することは可能ですか?
fork
は、新しいPIDを使用して新しいプロセスを作成します。このプロセスは、中断したところから並行して実行を開始します。 exec
現在実行中のコードを、どこかからロードされ、最初から実行されている新しいプログラムに置き換えます。新しいプログラムを生成するときは、最初に自分でfork
し、次に子でそのプログラムをexec
します。これが、シェルの内外を問わず、あらゆる場所に適用されるプロセスの基本理論です。
サブシェルはfork
sであり、実行するすべての非組み込みコマンドはfork
とexec
の両方につながります。
$$
は親シェルのPIDに展開されることに注意してください POSIX互換シェルの場合 したがって、期待どおりの出力が得られない可能性があります。 zshはとにかくサブシェルの実行を積極的に最適化し、通常は最後のコマンドをexec
sするか、すべてのコマンドがサブシェルなしで安全である場合はサブシェルをまったく生成しないことにも注意してください。
直感をテストするための便利なコマンドの1つは次のとおりです。
strace -e trace=process -f $Shell -c ' ... '
これにより、新しいシェルで実行するコマンド...
のすべてのプロセス関連イベント(およびその他のイベントはなし)が標準エラーに出力されます。新しいプロセスで何が実行され、何が実行されないか、およびexec
sがどこで発生するかを確認できます。
もう1つの便利なコマンドはpstree -h
です。これは、現在のプロセスの親プロセスのツリーを出力して強調表示します。出力の深さのレイヤー数を確認できます。
マニュアルに_.zshenv
_のコマンドが「ソース」であると記載されている場合、それはそれらを実行しているシェル内で実行されることを意味します。これらはfork()
の呼び出しを引き起こさないため、サブシェルを生成しません。 3番目の例では、サブシェルを明示的に実行し、fork()
の呼び出しを呼び出して呼び出すため、無限に再帰します。それは、(少なくとも部分的に)あなたの最初の質問に答えるべきだと私は信じています。
コマンド1と2には「作成」されたものはないため、何も呼ばれることはありません。これらのコマンドは、ソーシングシェルのコンテキスト内で実行されます。
一般化は、シェルルーチンまたはプログラムの「呼び出し」とシェルルーチンまたはプログラムの「ソーシング」の違いです。後者は通常、外部プログラムではなく、シェルコマンド/スクリプトにのみ適用できます。シェルスクリプトの「ソーシング」は、通常、_. <scriptname>
_または_./<scriptname>
_ではなく_/full/path/to/script
_を介して行われます。ソーシングディレクティブの先頭にある「ドットスペース」シーケンスに注意してください。ソーシングは、_source <scriptname>
_を使用して呼び出すこともできます。source
コマンドはシェル内部です。
fork
は、すべてがうまくいくと仮定して、2回戻ります。 1つの戻り値は親プロセス(元のプロセスIDを持つ)にあり、もう1つは新しい子プロセス(異なるプロセスIDですが、それ以外は親プロセスと多くの共通点を共有しています)にあります。この時点で、子は何かをexec(3)
する可能性があります。これにより、「新しい」バイナリがそのプロセスにロードされますが、子はそれを行う必要はなく、親プロセスを介してすでにロードされている他のコードを実行できます。 (たとえば、zsh関数)。したがって、「完全に新しい」がexec(3)
システムコールを介してロードされたものを意味すると解釈される場合、fork
は「完全に新しい」プロセスをもたらす場合ともたらさない場合があります。
どのコマンドが無限後退を引き起こすかを事前に推測するのは難しいです。 fork-calling-forkの場合(別名「forkbomb」)に加えて、別の簡単なケースは、いくつかのコマンドの単純な関数ラッパーを使用することです。
function ssh() {
ssh -o UseRoaming=no "$@"
}
代わりに、おそらく次のように書く必要があります
function ssh() {
=ssh -o UseRoaming=no "$@"
}
またはcommand ssh ...
ssh
関数の無限の関数呼び出しを回避するには、ssh
関数を呼び出します...関数呼び出しはZSHプロセスの内部にあるため、これにはfork
は含まれませんが、その単一のZSHプロセスによって、いくつかの制限が発生します。
strace
は、いつものように、コマンドに関係するシステムコールを正確に明らかにするのに便利です(特にここではfork
とおそらくいくつかのexec
呼び出し)。シェルは、シェルが内部で実行していること(関数呼び出しなど)を示す-x
などでデバッグできます。詳細については、「Unix環境での高度なプログラミング」のStevensに、新しいプロセスの作成と処理に関連する章がいくつかあります。