Pythonでデーモンを作成しようとしています。次の質問を見つけました。これには、現在フォローしているいくつかの優れたリソースが含まれていますが、ダブルフォークが必要な理由について興味があります。私はグーグルを引っ掻き回して、それが必要であると宣言している多くのリソースを見つけましたが、その理由はありません.
デーモンが制御端末を取得するのを防ぐためだと言う人もいます。2番目のフォークなしでこれを行うにはどうすればよいでしょうか? 影響は何ですか?
ダブルフォークを理解しようとしていたところ、ここでこの質問に出くわしました。多くの研究の後、これは私が理解したものです。同じ疑問を持っている人にとって、物事をより明確にするのに役立つことを願っています。
Unix では、すべてのプロセスはグループに属し、グループはセッションに属します。これが階層です…</p>
セッション (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-twes という用語は混乱を招くと思います。より良いイディオムは fork-decouple-fork かもしれません。
興味のある追加のリンク:
厳密に言えば、ダブルフォークは、デーモンを の子として再ペアレント化することとは何の関係もありませんinit
。子を再親化するために必要なのは、親を終了することだけです。これは、単一のフォークのみで実行できます。また、それ自体でダブル フォークを実行しても、デーモン プロセスの親は変更されませんinit
。デーモンの親は終了する必要があります。つまり、適切なデーモンを fork すると、親は常に終了し、デーモン プロセスの親は に変更されinit
ます。
では、なぜダブルフォークなのですか?POSIX.1-2008セクション 11.1.3、「制御端末」に答えがあります (強調を追加):
セッションの制御端末は、実装定義の方法でセッション リーダーによって割り当てられます。
O_NOCTTY
セッション リーダーに制御端末がなく、オプションを使用せずにセッションにまだ関連付けられていない端末デバイス ファイルを開く場合 ( を参照open()
)、端末がセッション リーダーの制御端末になるかどうかは実装定義です。セッション リーダーではないプロセスが端末ファイルを開くか、 でO_NOCTTY
オプションが使用されているopen()
場合、その端末は呼び出しプロセスの制御端末にはなりません。
これは、デーモンプロセスがこのようなことを行うと...
int fd = open("/dev/console", O_RDWR);
...その後、デーモンプロセスが/dev/console
セッションリーダーであるかどうか、およびシステムの実装に応じて、デーモンプロセスが制御端末として取得する場合があります。プログラムは、プログラムが最初にそれがセッションリーダーでないことを確認した場合、上記の呼び出しが制御端末を取得しないことを保証できます。
通常、デーモンを起動するとき、setsid
( を呼び出した後に子プロセスからfork
) が呼び出され、制御端末からデーモンの関連付けが解除されます。ただし、呼び出しsetsid
は、呼び出しプロセスが新しいセッションのセッションリーダーになることも意味します。これにより、デーモンが制御端末を再取得する可能性が残されます。ダブル フォーク手法により、デーモン プロセスがセッション リーダーではないことが保証されopen
ます。これにより、上記の例のように への呼び出しによって、デーモン プロセスが制御端末を再取得することはありません。
ダブルフォークのテクニックは少し妄想的です。デーモンが端末デバイス ファイルを開かないことがわかっている場合は、必要ないかもしれません。また、一部のシステムでは、デーモンが端末デバイス ファイルを開く場合でも、その動作は実装定義であるため、必要ない場合があります。ただし、実装で定義されていないことの 1 つは、セッション リーダーだけが制御端末を割り当てることができるということです。プロセスがセッション リーダーでない場合、制御端末を割り当てることはできません。したがって、実装で定義された詳細に関係なく、妄想的になり、デーモンプロセスが制御端末を誤って取得できないことを確認したい場合は、ダブルフォーク手法が不可欠です。
質問で参照されているコードを見ると、正当な理由は次のとおりです。
ゾンビを防ぐために、2 番目の子をフォークしてすぐに終了します。これにより、2 番目の子プロセスが孤立し、init プロセスがそのクリーンアップを担当します。また、最初の子は制御端末を持たないセッション リーダーであるため、将来的に端末を開くことで端末を取得することができます (System V ベースのシステム)。この 2 番目のフォークは、子がセッション リーダーではなくなることを保証し、デーモンが制御端末を取得することを防ぎます。
そのため、デーモンの親が init に変更されていることを確認し (デーモンを開始するプロセスが長く存続する場合に備えて)、デーモンが制御 tty を再取得する可能性を排除します。したがって、これらのケースのどちらにも当てはまらない場合は、1 つのフォークで十分です。「Unix Network Programming - Stevens」には、これに関する適切なセクションがあります。
Bad CTKから取得:
「Unix の一部のフレーバーでは、デーモン モードに入るために、起動時にダブル フォークを実行する必要があります。これは、単一のフォークが制御端末から切り離されることが保証されていないためです。」
Stephens と Rago による "Advanced Programming in the Unix Environment" によると、2 番目のフォークはより推奨されており、デーモンが System V ベースのシステムで制御端末を取得しないことを保証するために行われます。
理由の 1 つは、親プロセスがすぐに子の wait_pid() を実行し、それを忘れることができることです。孫が死ぬと、その親は初期化され、それを待って () ゾンビ状態から抜け出します。
その結果、親プロセスはフォークされた子プロセスを認識する必要がなくなり、長時間実行されるプロセスをライブラリなどからフォークすることも可能になります。
それについてのまともな議論はhttp://www.developerweb.net/forum/showthread.php?t=3025にあるようです
そこからmlampkinを引用:
... setsid( ) 呼び出しを「新しい」方法 (端末との関連付けを解除する) と考え、その後の [2 回目の] fork( ) 呼び出しを SVr4 を処理するための冗長性と考えてください...
daemon() 呼び出しは、成功した場合、親呼び出し _exit() を持ちます。元々の動機は、子がデーモン化されている間に親が追加の作業を行えるようにすることだったのかもしれません。
また、デーモンに親プロセスがなく、init に再親化されることを保証するために必要であるという誤った信念に基づいている可能性もありますが、これは、単一のフォークの場合に親プロセスが終了すると、とにかく発生します。
ですから、結局のところ、すべてが伝統に帰着するだけだと思います-とにかく親が短期間で死ぬ限り、単一のフォークで十分です。
次のようにすると理解しやすいかもしれません。