web-dev-qa-db-ja.com

デーモンを作成するときにダブルフォークを実行する理由は何ですか?

私はPythonでデーモンを作成しようとしています。 次の質問 が見つかりました。これには現在フォローしている優れたリソースがいくつかありますが、なぜダブルフォークが必要なのか興味があります。私はグーグルの周りをひっかいてみましたが、たくさんのリソースが必要だと宣言していますが、それは理由ではありません。

デーモンが制御端末を獲得するのを防ぐためだという人もいます。 2番目のフォークなしでこれをどのように行うのでしょうか?影響は何ですか?

154
Shabbyrobe

質問で参照されているコードを見ると、正当化は次のとおりです。

# Fork a second child and exit immediately to prevent zombies.  This
# causes the second child process to be orphaned, making the init
# process responsible for its cleanup.  And, since the first child is
# a session leader without a controlling terminal, it's possible for
# it to acquire one by opening a terminal in the future (System V-
# based systems).  This second fork guarantees that the child is no
# longer a session leader, preventing the daemon from ever acquiring
# a controlling terminal.

そのため、デーモンがinitに再度親になるようにし(デーモンを開始するプロセスが長く続く場合にのみ)、デーモンが制御ttyを再取得する可能性を削除します。したがって、これらのどちらも当てはまらない場合は、1つのフォークで十分です。 " nix Network Programming-Stevens "にはこれに関する良いセクションがあります。

102
Beano

私はダブルフォークを理解しようとしていたので、ここでこの質問に出くわしました。多くの研究の後、これが私が理解したことです。同じ質問を持っている人にとって、物事をより明確にするのに役立つことを願っています。

Unixでは、すべてのプロセスはグループに属し、グループはセッションに属します。これが階層です…

セッション(SID)→プロセスグループ(PGID)→プロセス(PID)

プロセスグループの最初のプロセスがプロセスグループリーダーになり、セッションの最初のプロセスがセッションリーダーになります。すべてのセッションには、1つのTTYを関連付けることができます。セッションリーダーのみがTTYを制御できます。プロセスが真にデーモン化される(バックグラウンドで実行される)には、セッションリーダーが強制終了され、セッションがTTYを制御する可能性がなくなるようにする必要があります。

UbuntuでSander Marechalのpythonデーモンプログラムの例 このサイト を実行しました。ここにコメント付きの結果があります。

1. `Parent`    = PID: 28084, PGID: 28084, SID: 28046
2. `Fork#1`    = PID: 28085, PGID: 28084, SID: 28046
3. `Decouple#1`= PID: 28085, PGID: 28085, SID: 28085
4. `Fork#2`    = PID: 28086, PGID: 28085, SID: 28085

プロセスはDecouple#1であるため、PID = SIDの後のセッションリーダーであることに注意してください。 TTYを引き続き制御できます。

Fork#2はセッションリーダーPID != SIDではなくなったことに注意してください。このプロセスがTTYを制御することはありません。 完全にデーモン化されました。

私は個人的に用語を二度分からないようにしています。より良いイディオムはfork-decouple-forkかもしれません。

興味のある追加リンク:

158

厳密に言えば、ダブルフォークは、デーモンをinitの子として再ペアレント化することとは関係ありません。子の親を再設定するために必要なのは、親が終了する必要があることだけです。これは、単一のフォークでのみ実行できます。また、ダブルフォークを単独で実行しても、デーモンプロセスの親がinitに変更されることはありません。デーモンの親must exit。言い換えると、適切なデーモンをフォークすると親は常に終了し、デーモンプロセスはinitに再度親になります。

なぜダブルフォークなのか? POSIX.1-2008 セクション11.1.3、「 制御端末 」には回答があります(強調を追加)。

セッションの制御端末は、セッションリーダーによって割り当てられます、実装定義の方法で。セッションリーダーに制御端末がなく、_O_NOCTTY_オプションを使用せずにセッションにまだ関連付けられていない端末デバイスファイルを開いた場合(open()を参照)、端末は、セッションリーダーの制御端末になります。セッションリーダーではないのプロセスが端末ファイルを開くか、open()で_O_NOCTTY_オプションが使用されている場合、その端末は、呼び出しプロセスの制御端末になりません

これは、デーモンプロセスが次のような処理を行うと...

_int fd = open("/dev/console", O_RDWR);
_

...次に、デーモンプロセスmightデーモンプロセスがセッションリーダーであるかどうか、およびシステムの実装に応じて、制御端末として_/dev/console_を取得します。プログラムはguaranteeで、プログラムがセッションリーダーではないことを最初に確認した場合、上記の呼び出しは制御端末を取得しません。

通常、デーモンを起動すると、setsidが(forkを呼び出した後の子プロセスから)呼び出されて、制御端末からデーモンを分離します。ただし、setsidを呼び出すことは、呼び出しプロセスが新しいセッションのセッションリーダーになることも意味します。これにより、デーモンが制御端末を再取得できる可能性が残ります。ダブルフォーク技術は、デーモンプロセスがセッションリーダーではないことを保証します。これにより、上記の例のように、openへの呼び出しがデーモンプロセスで制御端末を再取得しないことが保証されます。

ダブルフォークのテクニックは少し妄想的です。 knowの場合、デーモンは端末デバイスファイルを開かないことが必要な場合があります。また、一部のシステムでは、デーモンが端末デバイスファイルを開いても、その動作は実装定義であるため、必要ない場合があります。ただし、実装定義ではないことの1つは、セッションリーダーのみが制御端末を割り当てることができるということです。 プロセスがセッションリーダーでない場合、制御端末を割り当てることができません。したがって、偏執的になりたい場合、デーモンプロセスが誤って実装定義の詳細に関係なく、制御端末、ダブルフォーク技術が不可欠です。

104
Dan Moulding

Bad CTK から取得:

「Unixのいくつかのフレーバーでは、デーモンモードに入るために、起動時にダブルフォークを行う必要があります。これは、シングルフォークが制御端末から切り離されることが保証されていないためです。」

11
Stefan Thyberg

StephensとRagoによる「Unix環境での高度なプログラミング」によると、2番目の分岐はより推奨事項であり、デーモンがSystem Vベースのシステムで制御端末を獲得しないことを保証するために行われます。

7
Paolo Tedesco

1つの理由は、親プロセスが子のためにすぐにwait_pid()し、それを忘れることができるからです。その後、孫が死亡すると、その親はinitになり、それを待機します(ゾンビ状態から外します)。

その結果、親プロセスはフォークされた子を認識する必要がなくなり、libsなどから長時間実行されているプロセスをフォークすることも可能になります。

3
KarlP

Daemon()呼び出しは、成功すると親呼び出し_exit()を持ちます。元々の動機は、子がデーモン化する間に親が追加の作業を行えるようにすることであった可能性があります。

また、デーモンに親プロセスがなく、initの親になることを保証するために必要であるという誤った信念に基づいている可能性があります-しかし、これは親がシングルフォークのケースで死ぬと発生します。

だから、結局はすべてが伝統に要約されると思います-親がとにかく短命で死ぬ限り、単一のフォークで十分です。

2
bdonlan

適切な議論は http://www.developerweb.net/forum/showthread.php?t=3025 にあるようです。

そこからmlampkinを引用する:

... setsid()呼び出しを(端末との関連付けを解除する)「新しい」方法として考え、その後の[2番目] fork()呼び出しをSVr4に対処するための冗長性として考えてください...

2
Stobor

この方法で理解する方が簡単かもしれません:

  • 最初のforkとsetsidは新しいセッションを作成します(ただし、プロセスID ==セッションID)。
  • 2番目の分岐は、プロセスID!=セッションIDを確認します。
0
pandy.song