この記事は基本的に記事の後続の記事です。前の質問私のもの。
fork
この質問に答えて、私は「サブシェル」の全体的な概念をよく理解していなかっただけでなく、より一般的に-ingとサブプロセス間の関係を理解していないことに気づきました。
私はプロセスがX
aを実行するとfork
、新しいY
生成されたプロセスの親プロセスはですがX
、この質問に対する回答によると、
[a]サブシェルはまったく新しいプロセスではなく、既存のプロセスのフォークです。
ここで意味するのは、「ポーキング」が「完全に新しいプロセス」ではないか、発生しないことです。
私は今混乱しています。実際、私の混乱を直接解決する一貫した質問をするのはとても混乱しています。
しかし、間接的に実現できる質問を投げることはできます。
sourceによると、新しいインスタンスが起動するたびにインポートされるため、zshall(1)
「$ZDOTDIR/.zshenv
全くzsh
新しい[zsh]プロセス」を生成するコマンドは無限回帰を引き起こします。$ZDOTDIR/.zshenv
一方、ファイルに次の行のいずれかを含める$ZDOTDIR/.zshenv
といいえ無限回帰につながります。
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プロセスの「理論」(より良い言葉がない)の点で、上記の経験的/逸話的な結果を合理化(および一般化)することは可能ですか?
最後の質問の動機は、次のことを決定できることです。リードタイム(つまり、実験に頼らない)どのような命令が含まれると、無限回帰につながる可能性がありますか$ZDOTDIR/.zshenv
?
1位のさまざまな例で使用したコマンドの具体的な順序はdate; printenv; echo $$
それほど重要ではありません。それは私の「実験」の結果を説明するのに役立つ出力を提供するコマンドです。 (しかし、私は次の理由でこれらのシーケンスに複数のコマンドを含めたいと思います。ここ.)
答え1
zshall(1)によると、zshの新しいインスタンスが起動されるたびに$ ZDOTDIR / .zshenvが取得されます。
「スタート」という言葉に集中すれば、より良くなるでしょう。この機能はfork()
別のプロセスを作成することです現在プロセスがある場所から始める。既存のプロセスを複製し、唯一の違いは戻り値ですfork
。ドキュメントでは、「スタート」を使用して最初からプログラムを起動するという意味で使用します。
例#3が実行され、$SHELL -c 'date; printenv; echo $$'
まったく新しいプロセスが最初から始まります。通常の起動動作が発生します。たとえばbash -c ' ... '
、別のシェルを交換してこれを説明できますzsh -c ' ... '
。ここではシェルを使用することに特別なことはありません$SHELL
。
例#1と#2はサブシェルを実行します。自分でシェルを実行しfork
、その子プロセス内でコマンドを実行し、子プロセスが完了したら独自のコマンドを実行し続けます。
あなたの質問#1に対する答えは次のとおりです。例3は最初からまったく新しいシェルを実行し、他の2つはサブシェルを実行します。起動アクションにはロードが含まれます.zshenv
。
このファイルは(他のファイルとは異なり)インタラクティブシェルと非インタラクティブシェルの両方にロードされるため、この動作の理由は混乱の原因である可能性があると具体的に説明されています。
質問#2の場合:
#1と#2で生成されたシェルを「サブシェル」とすると、#3と同様に生成されたシェルは「サブシェル」と呼ばれますか?
名前が欲しいなら「サブシェル」と呼ぶことができますが、実際には何もありません。同じシェルでも、別のシェルでも、シェルで開始された他のプロセスと変わりませんcat
。
質問#3の場合:
Unixプロセスの「理論」(より良い言葉がない)の点で、上記の経験的/逸話的な結果を合理化(および一般化)することは可能ですか?
fork
新しいPIDを持つ新しいプロセスが作成され、プロセスが中断された時点から並列に実行されます。exec
現在実行中のコードをどこかから読み込まれた新しいプログラムに置き換えて、最初からやり直してください。新しいプログラムを作成するときは、最初に生成してからサブルーチンfork
からexec
プログラムを生成します。これは、シェルの内側または外側のどちらでもプロセスに適用される基本理論です。
サブシェルはfork
sで、実行されるすべての非組み込みコマンドはおよびfork
ですexec
。
拡張は$$
親シェルのPIDです。POSIX互換シェルからそのため、期待した結果が得られない場合があります。また、zshはとにかくサブシェルの実行を積極的に最適化し、通常は最後のコマンドになるか、またはサブシェルなしでexec
すべてのコマンドが安全である場合、サブシェルをまったく生成しません。
直観をテストするのに役立つコマンドは次のとおりです。
strace -e trace=process -f $SHELL -c ' ... '
...
これにより、新しいシェルで実行されるコマンドのすべてのプロセス関連イベント(他のイベントを除く)が標準エラーで印刷されます。新しいプロセスで何が実行されていないのか、exec
それが発生する場所を確認できます。
便利なもう1つのコマンドは、pstree -h
現在のプロセスの親プロセスツリーを印刷して強調表示することです。出力にレイヤ数を表示できます。
答え2
マニュアルでコマンドが.zshenv
「ソース化」されたと言うと、そのコマンドが実行されるシェルで実行されるという意味です。を呼び出さないので、fork()
サブシェルを生成しません。 3番目の例は、明示的にサブシェルを実行してcallを呼び出すfork()
ため、無限に繰り返されます。私はこれがあなたの最初の質問に(少なくとも部分的に)答えなければならないと思います。
コマンド1と2では何も「生成」されていないため、何も呼び出すことはできません。これらのコマンドは、ソーシングシェルのコンテキストで実行されます。
簡単に言えば、シェルルーチンまたはプログラムを「呼び出す」とシェルルーチンまたはプログラムを「ソース」することの違いは、通常、後者は外部プログラムではなくシェルコマンド/スクリプトにのみ適用されます。シェルスクリプト「インポート」は通常、
. <scriptname>
notまたはgetディレクティブの先頭の「ドットスペース」シーケンスを介して./<scriptname>
行われます。シェルの内部コマンドである/full/path/to/script
Sourcingを使用することもできます。source <scriptname>
source
答え3
fork
、すべてがうまくいくと仮定すると、2回返されます。 1つは元のプロセスIDを使用して親プロセスから返され、もう1つは新しい子プロセスから返されます(プロセスIDは異なりますが、親プロセスと多くの共通点を共有します)。この時点で、子プロセスは、exec(3)
一部の「新しい」バイナリがプロセスにロードされるようにする特定のタスクを実行できます。ただし、子プロセスはこれを行う必要はなく、親プロセスを介してロードされた他のコードを実行できます(zsh機能など)。したがって、fork
「完全に新しい」がシステムコールを介してロードされたことを意味する場合、exec(3)
「完全に新しい」プロセスが発生する可能性があります。
どの命令が無限回帰を引き起こすかを事前に推測するのは難しいです。フォークコールフォークケース(別名「forkbomb」)に加えて、別の簡単なケースは、いくつかのコマンドを介した単純な関数ラッパーです。
function ssh() {
ssh -o UseRoaming=no "$@"
}
代わりに、おそらく次のように書く必要があります。
function ssh() {
=ssh -o UseRoaming=no "$@"
}
または、関数を呼び出す関数への無限関数呼び出しをcommand ssh ...
避けてください。関数呼び出しはZSHプロセスの内部なので、これは決して関連しませんが、その単一の関数がZSHプロセスのいくつかの制限に達するまで、無限に無限に発生します。ssh
ssh
fork
strace
いつものように、コマンドに関連するシステムコールが何であるかを正確に明らかにするのが便利です(特にここfork
、そしておそらくいくつかexec
)。シェルは、または同様のものを使用してデバッグでき-x
、シェル内で内部的に実行される操作(関数呼び出しなど)を表示します。詳しくは、StevensのUnix環境での高度なプログラミングには、新しいプロセスの作成と処理に関するいくつかの章があります。