web-dev-qa-db-ja.com

バックグラウンドコントロールオペレーター(&)によって作成されたサブシェルがpstreeの下に表示されないのはなぜですか

exitコマンドが同じシェルで実行されるため、exitを実行すると現在のシェルが終了することを理解しています。また、exit &を実行すると、元のシェルが終了しないことも理解しています。これは、&によってコマンドがサブシェルで実行されるため、exitがこのサブシェルを終了して戻るためです。元のシェルに戻ります。しかし、私が理解していないのは、&があるコマンドとないコマンドが、pstree、この場合はsleep 10sleep 10 &でまったく同じに見える理由です。 4669は、最初にsleep 10、次にsleep 10 &が発行され、この間に別のシェルインスタンスから次の出力が取得されたbashのPIDです。

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

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

&のバージョンには、このように、スポーンされたサブシェルがもう1つ含まれているべきではありませんか(この場合、PID 5555の場合など)?

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

PS:読みやすくするために、pstreeの出力から次のコードが省略されました:

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

この質問に答え始めるまで、_&_制御演算子を使用してジョブを実行することに気づいていませんでしたバックグラウンドでサブシェルを開始します。サブシェルは、コマンドが括弧で囲まれている場合、またはパイプラインの一部を形成している場合に作成されます(パイプライン内の各コマンドは独自のサブシェルで実行されます)。

コマンドのリスト Bashマニュアルのセクション(thanks jimmij)は次のように述べています。

コマンドが制御演算子「&」によって終了した場合、シェルはサブシェルでコマンドを非同期的に実行します。これは、backgroundでコマンドを実行することとして知られています。シェルはコマンドが終了するのを待たず、戻りステータスは0(true)です。

私が理解しているように、_sleep 10 &_を実行すると、シェル fork sで新しい子プロセス(それ自体のコピー)が作成され、すぐに exec sで置き換えられます。この子プロセスは、外部コマンド(sleep)からのコードを使用します。これは、コマンドが通常どおり(フォアグラウンドで)実行されたときに発生することと似ています。このメカニズムの概要については、 Fork–exec Wikipediaの記事 を参照してください。

Bashがサブシェルでバックグラウンドコマンドを実行する理由を理解できませんでしたが、exitechoなどのシェルビルトインをバックグラウンドで実行できるようにしたい場合は理にかなっています(外部コマンドだけではありません)。

バックグラウンドで実行されているのがシェルビルトインの場合、外部コマンドに置き換えるためのfork呼び出しなしで、execが発生します(サブシェルになります)。次のコマンドを実行すると、echoコマンドが中括弧で囲まれ、バックグラウンドで(_&_を使用して)実行されると、サブシェルが実際に作成されることがわかります。

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

上記の例では、現在のシェルによって_BASH_SUBSHELL_が展開されないように、echoコマンドを中括弧で囲んでいます。中括弧は、サブシェルを使用せずにコマンドをグループ化するために使用されます。コマンドの2番目のバージョン(_&_制御演算子で終わる)は、コマンドをアンパサンドで終了すると、echoビルトインを実行するためのサブシェル(新しいPID)が作成されたことを明確に示しています。 。 (ここではおそらくシェルの動作を単純化しています。mikeservのコメントを参照してください。)

_exit &_を実行することを考えたことはなかったでしょうし、あなたの質問を読まなかったら、現在のシェルが終了することを期待していました。このようなコマンドがサブシェルで実行されることがわかったので、終了するのはサブシェルであるという説明は理にかなっています。


「バックグラウンドコントロールオペレーター(&)によって作成されたサブシェルがpstreeの下に表示されないのはなぜですか」

上記のように、_sleep 10 &_を実行すると、Bashはそれ自体をフォークしてサブシェルを作成しますが、sleepは外部コマンドであるため、exec()システムコールを呼び出してすぐにBashを置き換えます。 sleepプログラムの実行中のコピーを使用した子プロセスのコードとデータ。 pstreeを実行するまでに、exec呼び出しはすでに完了しており、子プロセスの名前は「sleep」になります。


コンピューターから離れている間、サブシェルがpstreeで表示されるのに十分な時間サブシェルを実行し続ける方法を考えようとしました。 timeビルトインを介してコマンドを実行できると思いました。

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

ここで、Bashシェル(26793)は、コマンドをバックグラウンドで実行するためにサブシェル(4502)を作成するためにフォークします。このサブシェルは、独自のtime組み込みコマンドを実行します。このコマンドは、(PID 4503で新しいプロセスを作成するために)フォークし、外部のsleepコマンドを実行するために実行します。


名前付きパイプ を使用して、jimmijは、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をリダイレクトすると、名前付きパイプから入力を受け取るまでサブシェルがブロックされるため、賢い方法です。後で、echoの出力を(引数なしで)リダイレクトすると、名前付きパイプに改行文字が書き込まれ、サブシェルプロセスのブロックが解除され、サブシェルプロセスがexit組み込みコマンドを実行します。


同様に、sleepコマンドの場合:

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

ここでは、コマンドをバックグラウンドで実行するために作成されたサブシェルのPIDが_6600_であることがわかります。次に、パイプに改行文字を書き込んでプロセスのブロックを解除します。

_$ echo > named_pipe
_

次に、サブシェルはexecsを実行して、sleepコマンドを実行します。

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

exec()呼び出しの後、子プロセス(_6600_)がsleepプログラムを実行していることがわかります。

7