web-dev-qa-db-ja.com

Bash:PIDが正確に解放されるのはいつですか?

免責事項:この質問は予想よりもはるかに長く出てきました。私はそれを5つのサブ質問に分けました。開封する前に心をはっきりさせようとしたのですが、今のところあまりにも多くの面で混乱しています。


正しく Bashのプロセスを確実な方法で処理する方法について私の心を明確にしようとして、私は このグレッグのWiki記事 。そこに、むしろ最初に、この声明があります

何かをしたい子プロセスを開始した親プロセスにまだいる場合は、それは完璧です。以下に説明する理由により、PIDが子プロセス(デッドまたはアライブ)であることが保証されます。 killを使用して、シグナルを送信したり、終了したり、実行中であるかどうかを確認したりできます。 waitを使用して、終了するのを待つか、終了した場合は終了コードを取得できます。

ページの終わりに向かって、上記の以下に説明する理由が見つかります。

各UNIXプロセスには、親プロセスもあります。この親プロセスはそれを開始したプロセスですが、新しいプロセスが終了する前に親プロセスが終了した場合、initプロセスに変更できます。 (つまり、initは孤立したプロセスを取得します。)この親子関係を理解することは、UNIXでの信頼性の高いプロセス管理の鍵であるため、非常に重要です。プロセスのPIDは、プロセスが終了した後、PIDの親プロセスwaitsが終了したかどうかを確認し、終了コードを取得するまで、使用できるように解放されることはありません。親が終了すると、プロセスはinitに返され、これが自動的に行われます。

これは1つの主な理由で重要です。親プロセスが子プロセスを管理している場合、子プロセスが停止したとしても、親プロセスがwaitedし、子供が死亡したことに気づきました。これにより、親プロセスは、子プロセスに対して持っているPIDが、それが生きているか「ゾンビ」であるかに関係なく、常にその子プロセスを指すことが保証されます。他の誰もその保証を持っていません。

残念ながら、この保証はシェルスクリプトには適用されません。シェルは子プロセスを積極的に取得し、終了ステータスをメモリに保存します。メモリでは、waitを呼び出すとスクリプトで使用できるようになります。しかし、子はすでに刈り取られているのでbeforewaitを呼び出すので、PIDを保持するゾンビはありません。カーネルはそのPIDを自由に再利用でき、保証に違反しています。


私は今までに上記の段落を数回読みましたが、その背後にあるメッセージを正しく把握しているかどうかはまだわかりません。

質問1:2番目の長い引用から、特にその最後の段落から、シェル(私はBashにのみ興味があります)スクリプトであると結論付けます変数に格納したPIDが、開始したバックグラウンドプロセスを参照していることを100%確信することはできません。これは、カーネルによって他のプロセス(子でなくても)に再利用される可能性があるためです。これは正しいです?上記の保証はシステムのどこに適用されますか?

質問2:2番目の引用の最後の段落が最初の引用と矛盾しているようです。一般的に、それは本当ですかシェルスクリプトでそれ"子プロセスを開始した親プロセスにまだいる場合[...] PIDがあなたの子であることが保証されますプロセス(デッドまたはアライブ) "

質問3:このトピックについて、ウェブ上で他の情報源を見つけようとしましたが、いつものように、真実と不正確な記述を区別するのは困難です。私はいくつかの確認を得ましたが、さらにいくつかの疑問もありました。 this および this の質問を参照すると、スクリプトでプロセスをバックグラウンドで開始し、そのPIDを変数に格納し、いくつかの処理を行ってから使用するという単純な方法のようです。 PIDをwaitと組み合わせて終了コードを取得するか、killと組み合わせて信号を送信すると、PIDのカーネルによる再利用が原因で失敗する可能性があります。一般的なレシピはありますか?

質問4:私も見つけました このコメント 「バックグラウンド(プロセス)にリターンコードをファイルに保存させ、親にファイルからフェッチしてもらいます」。これは信頼できる方法ですか?

質問5:wait -nの使用に関する警告はありますか? waitに(再利用される可能性のある)PIDを明示的に指定しなければ、何も問題は発生しないと思います。ただし、 そうです Bash v4.4では、wait-nオプションは、ジョブ制御が有効になっているset -mで役立ちます。それはBashv5.0にも当てはまりますか?

ボーナス質問:この回答 はグレッグのウィキに似た何かを言います。

Pidを使用してシグナルを安全に送信できるケースは1つだけです。ターゲットプロセスがシグナルを送信するプロセスの直接の子であり、親がまだそれを待機していない場合です。

direct子とは何ですか?子供とは違うの?

2
Axel Krypton

...シェル(私はBashにのみ興味があります)スクリプトでは、変数に格納したPIDが、カーネルによって他のプロセスに再利用される可能性があるため、開始したバックグラウンドプロセスを参照していることを100%確信できません。 ..

正しい。

シェルがプログラムされる方法では、子プロセスが終了するとすぐに、シェルはすぐにwait()を呼び出し(内部状態の一部として終了ステータスを保存します)、PIDを解放して再利用できるようにします。別のプロセス。

シェルスクリプトでは、"子プロセスを開始した親プロセスにまだいる場合[...] PIDが子プロセス(デッドまたはアライブ)であることが保証されます"

いいえ、それは真実ではありません。

なぜなら、前述のように(そして引用で)、シェル自体が子プロセスをすぐに取得し、基本的にその保証を破壊するからです。

スクリプト内のプロセスをバックグラウンドで開始し、そのPIDを変数に格納し、いくつかの処理を行ってから、PIDを使用して終了コードを取得するのを待つか、killを使用してシグナルを送信するという単純な方法は、 PIDのカーネル。

シェルを使用すると、それが最善の方法です。

waitの使用は実際には問題ではなく、killのみを使用することに注意してください。子プロセスがすでに終了し、PIDが再利用され、別のプロセスを強制終了している可能性があるためです。

wait自体はシェルに実装されています。子プロセスを取得すると、その終了ステータスがメモリに保存されるため、その情報を使用してwaitビルトインを実装できます(また、まだ実行中の子プロセスを待機します)。

また、カーネルは通常、PIDの再利用を回避しようと努力しますが、少なくともPIDの再利用を遅らせようとします。これは、PIDが再利用されていないという保証がない場合があるため、カーネルはこの状況を最小限に抑えようとします。信号が間違ったプロセスに配信されます。

一般的なレシピはありますか?

信頼性のために?

はい、Cでバックグラウンドプロセスを起動するコードを実装します。またはPython、Perl、Rubyなど。シェルではありません。

シェルのようにデフォルトでは子を刈り取らないので、この問題は発生しません。そこで明示的に行う必要があります。

または、システムマネージャー(systemdなど)を使用してバックグラウンドプロセスを起動することを検討してください。

「バックグラウンド(プロセス)でリターンコードをファイルに保存し、親にファイルからフェッチさせる」。これは信頼できる方法ですか?

多分。

そこに干渉がなかったという保証はほとんどありません。その単一のプロセスだけが書き込むことができ、他のプロセスが書き込むことができない場所を持つことは困難です。

同じことはwait呼び出しには当てはまりません。カーネルは、別のプロセスによって偽造されないようにします。

さらに、wait呼び出しは、プロセスが強制終了されたり、クラッシュしたりすることも通知します。その場合、プロセス自体に依存してその戻りステータスをファイルに記録すると、不完全な情報が得られる可能性があります。

また、PIDの再利用に関する主な問題は、そのPIDを強制終了することであり、waitを介してリターンコードを取得することに問題はありません。また、killの問題は、ファイルを使用して実際に対処されていません。戻りコードを保管します。

wait -nの使用に関する注意事項はありますか?

あんまり。 waitは信頼性が高く、AFAICTはPIDの再利用の影響を受けません。シェルが子を取得すると、使用されていたPIDや戻りコードなどの情報が内部状態の一部として保持されるためです。

waitを呼び出すと、そのテーブルから情報を取得します。

waitが最初のインスタンスで呼び出される前に、同じシェルの新しいバックグラウンドの子によってPIDが再利用される場合、1つの潜在的な問題があると思います。それ以降、そのテーブルとあなたは衝突します。最終的に、同じPIDを持つ2つの別々のバックグラウンドプロセスになります。これはコーナーケースであり、非常にまれですが、潜在的に現実的だと思います。そのような場合にシェルが何をするかはよくわかりません...シェルの実装にも依存し、バージョンによって異なる場合があります。

ただし、前述のように、この問題の実際の解決策は、PIDの保証が重要な場合にシェル以外のものを使用して、PIDが存在することについての保証を維持することです。

直接子とは何ですか?子供とは違うの?

それは子供と同じです。

それはあなたが自分でフォークした子供です。

たとえば、子プロセスがプロセスをフォークし、そのプロセスのPIDを渡した場合、そのプロセスが存続するという保証はありません。

そのプロセスを刈り取るのはあなたの子供の仕事なので、彼らがそれを刈り取るまでPIDが再利用されないという保証を持つことができるのはあなたの子供です。あなたのものではありません。

もちろん、親プロセスは子と調整してその保証を拡張することができます。たとえば、PIDがまだ期待どおりであるかどうかを子に問い合わせるときに子を取得しないようにするか、シグナルを送信するか、おそらく親に代わって信号を送信する子供(保証があります)。

うまくいけば、これはそれをクリアするのに役立ちます。

4
filbranden