親が終了した後に子プロセスを停止させる方法は?


209

ちょうど1つの子プロセスを生成するプロセスがあるとします。親プロセスが何らかの理由(通常または異常、kill、^ C、assert failureなど)で終了した場合、子プロセスを終了させます。それを正しく行う方法は?


Stackoverflowに関するいくつかの同様の質問:


Windowsの stackoverflowに関する同様の質問:

回答:


189

次のようにsyscallでSIGHUPオプションPR_SET_PDEATHSIGを指定することで、子は親が死んだときにカーネルに配信(または他のシグナル)を要求できprctl()ます。

prctl(PR_SET_PDEATHSIG, SIGHUP);

詳細man 2 prctlについては、を参照してください。

編集:これはLinuxのみです


5
親がすでに亡くなっている可能性があるため、これは貧弱な解決策です。競合状態。正しい解決策:stackoverflow.com/a/17589555/412080
Maxim Egorushkin

16
貧弱な回答を呼ぶのはあまり良いことではありません-たとえそれが競合状態に対処していなくても。競合状態のない方法で使用する方法についての私の答えを参照しくださいprctl()。ところで、マキシムによってリンクされた答えは間違っています。
maxschlepzig

4
これは間違った回答です。親プロセスが死んだときではなく、forkを呼び出したスレッドが死んだときにシグナルを子プロセスに送信します。
Lothar

2
@Lotharなんらかの証明が見られるといいですね。man prctl言う:呼び出しプロセスの親プロセス終了シグナルをarg2に設定します(範囲1..maxsigのシグナル値、またはクリアする場合は0)。これは、親プロセスが終了したときに呼び出しプロセスが受け取るシグナルです。この値は、fork(2)の子および(Linux 2.4.36 / 2.6.23以降)set-user-IDまたはset-group-IDバイナリを実行するとクリアされます。
qrdl

1
@maxschlepzig新しいリンクをありがとう。前のリンクが無効のようです。ところで、何年も経った今でも、親側でオプションを設定するためのAPIはありません。お気の毒に。
rox

68

私は同じ問題を解決しようとしています。私のプログラムはOS Xで実行する必要があるため、Linuxのみのソリューションではうまくいきませんでした。

私はこのページの他の人々と同じ結論に達しました-親が死んだときに子供に通知するPOSIX互換の方法はありません。それで、私は次善の策、つまり子供の世論調査をまとめました。

親プロセスが(何らかの理由で)終了すると、子の親プロセスはプロセス1になります。子が定期的にポーリングするだけであれば、親が1かどうかを確認できます。そうである場合、子は終了する必要があります。

これはすばらしいことではありませんが、機能し、このページの他の場所で提案されているTCPソケット/ロックファイルポーリングソリューションよりも簡単です。


6
優れたソリューション。1を返すまでgetppid()を呼び出し続け、終了します。これは良かったし、今も使っています。しかし、非警察ソリューションは素晴らしいでしょう。Schofに感謝します。
neoneye 2010

10
参考までに、Solarisではゾーンにいる場合、gettpid()は1にならずpid、ゾーンスケジューラ(プロセスzsched)のを取得します。
PatrickSchlüter、

4
誰かが疑問に思っている場合、Androidシステムでは、親が死んだときにpidが1ではなく0(プロセスシステムpid)のように見えます。
Rui Marques

2
より堅牢でプラットフォームに依存しない方法で行うには、fork()を実行する前に、単にgetpid()を実行し、子のgetppid()が異なる場合は終了します。
セバスチャン2017

2
子プロセスを制御しないと、これは機能しません。たとえば、find(1)をラップするコマンドに取り組んでいて、何らかの理由でラッパーが停止した場合に、findが強制終了されるようにしたいと考えています。
Lucretiel、

34

私はこれまで、「子」で「元の」コードを実行し、「親」で「生成された」コードを実行することでこれを達成しました(つまり、のテストの通常の意味を逆にしますfork())。次に、「生成された」コードにSIGCHLDをトラップします...

あなたのケースでは不可能かもしれませんが、それが機能するときはかわいいです。


とても良い解決策、ありがとう!現在受け入れられている方がより一般的ですが、あなたの方がよりポータブルです。
パヴェルHajdan

1
親で作業を行う際の大きな問題は、親プロセスを変更することです。「永久に」実行しなければならないサーバーの場合、それはオプションではありません。
Alexis Wilke 2015年

29

子プロセスを変更できない場合は、次のようなことを試すことができます。

int pipes[2];
pipe(pipes)
if (fork() == 0) {
    close(pipes[1]); /* Close the writer end in the child*/
    dup2(0, pipes[0]); /* Use reader end as stdin */
    exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}

close(pipes[0]); /* Close the reader end in the parent */

これにより、ジョブ制御が有効になっているシェルプロセス内から子が実行されます。子プロセスはバックグラウンドで生成されます。シェルは改行(またはEOF)を待ってから子を殺します。

親が死んだとき-理由が何であれ-それはパイプの端を閉じます。子シェルは読み取りからEOFを取得し、バックグラウンドの子プロセスを強制終了します。


2
いいですが、5つのシステムコール、および10行のコードで生成されたshは、このコードパフォーマンスの一部について少し懐疑的です。
オレイアーデ2014年

+1。フラグをdup2使用read -uして特定のファイル記述子から読み取ることにより、stdinの引き継ぎを回避できます。またsetpgid(0, 0)、ターミナルで^ Cを押したときに終了しないように、子にa を追加しました。
グレッグ・ヒューギル2014年

dup2()呼び出しの引数の順序が間違っています。pipes[0]stdinとして使用する場合は、のdup2(pipes[0], 0)代わりに記述する必要がありdup2(0, pipes[0])ます。これはdup2(oldfd, newfd)、呼び出しが以前に開いたnewfdを閉じる場所です。
maxschlepzig

@Oleiade、私は同意します。特に、生成されたshは実際の子プロセスを実行するために別のフォークを実行するだけなので...
maxschlepzig

16

Linuxでは、次のように、子供に親の死亡信号をインストールできます。

#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h>  // perror()

// ...

pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() != ppid_before_fork)
        exit(1);
    // continue child execution ...

フォークの前に親プロセスIDを保存し、子プロセスでテストすると、子プロセスを呼び出したプロセスの終了とのprctl()競合状態が解消されることに注意してくださいprctl()

また、新しく作成された独自の子では、子の親の死亡信号がクリアされることにも注意してください。の影響を受けませんexecve()

すべての孤児の採用を担当するシステムプロセスがPID 1を持っていることが確実であれば、そのテストは簡略化できます。

pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() == 1)
        exit(1);
    // continue child execution ...

そのシステムプロセスに依存している initただし、 PID 1を持つことは移植性がありません。POSIX.1-2008は以下を指定します

呼び出し元プロセスの既存のすべての子プロセスとゾンビプロセスの親プロセスIDは、実装定義のシステムプロセスのプロセスIDに設定されます。つまり、これらのプロセスは、特別なシステムプロセスによって継承されます。

従来、すべての孤立を採用するシステムプロセスはPID 1、つまりinitです-これはすべてのプロセスの祖先です。

LinuxFreeBSDのような最新のシステムでは、別のプロセスがその役割を果たしている可能性があります。たとえば、Linuxでは、プロセスprctl(PR_SET_CHILD_SUBREAPER, 1)はその子孫のすべての孤立を継承するシステムプロセスとしてそれ自体を確立するために呼び出すことができます(Fedora 25のを参照)。


「祖父母が常にinitプロセスであることが確実であれば、そのテストは簡略化できる」とはわかりません。親プロセスが死ぬと、プロセスは祖父母の子ではなく、initプロセス(pid 1)の子になりますよね?したがって、テストは常に正しいようです。
ヨハネスシャウブ-litb


面白い、ありがとう。しかし、私はそれが祖父母とどう関係しているのか見当がつきません。
Johannes Schaub-litb

1
@ JohannesSchaub-litb、プロセスのgranparentがprocessであると常に想定できるわけではありませんinit(8)...想定できる唯一のことは、親プロセスが停止すると、その親IDが変更されるということです。これは実際には、プロセスの存続期間中に一度発生します。そして、プロセスの親が死ぬときです。そこへこの唯一の主な例外だ、とのためであるinit(8)子どもたちが、あなたのように、このから保護されているinit(8)ことはありませんexit(2)(その場合には、カーネルパニックを)
ルイス・コロラド

1
残念ながら、子がスレッドからforkし、その後スレッドが終了すると、子プロセスはSIGTERMを取得します。
rox

14

完全を期すために。macOSではkqueueを使用できます。

void noteProcDeath(
    CFFileDescriptorRef fdref, 
    CFOptionFlags callBackTypes, 
    void* info) 
{
    // LOG_DEBUG(@"noteProcDeath... ");

    struct kevent kev;
    int fd = CFFileDescriptorGetNativeDescriptor(fdref);
    kevent(fd, NULL, 0, &kev, 1, NULL);
    // take action on death of process here
    unsigned int dead_pid = (unsigned int)kev.ident;

    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example

    int our_pid = getpid();
    // when our parent dies we die as well.. 
    LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
    exit(EXIT_SUCCESS);
}


void suicide_if_we_become_a_zombie(int parent_pid) {
    // int parent_pid = getppid();
    // int our_pid = getpid();
    // LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);

    int fd = kqueue();
    struct kevent kev;
    EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
    kevent(fd, &kev, 1, NULL, 0, NULL);
    CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

DISPATCH_SOURCE_PROCおよびPROC_EXITでディスパッチソースを使用して、少し良いAPIでこれを行うことができます。
russbishop

何らかの理由で、これが私のMacをパニックにさせています。このコードを使用してプロセスを実行すると、50%程度の確率でフリーズし、ファンがこれまで聞いたことのない速度で回転します(超高速)。その後、Macがシャットダウンします。このコードには十分注意してください
Qix-モニカは

私のmacOSでは、親が終了した後に子プロセスが自動的に終了するようです。理由はわかりません。
Yi Lin Liu

私が使用した@YiLinLiu iirc NSTaskまたはposixのスポーン。参照してくださいstartTask:ここに私のコード内の関数github.com/neoneye/newton-commander-browse/blob/master/Classes/...
neoneye

11

子プロセスには、親プロセスとの間のパイプがありますか?その場合、書き込みの場合はSIGPIPEを受け取り、読み取りの場合はEOFを取得します-これらの状態が検出される可能性があります。


1
私は、これは、少なくともOS X上で、確実に実現しなかった
Schof

私はこの答えを追加するためにここに来ました。これは間違いなく私がこの場合に使用する解決策です。
Dolda2000 2015年

注意点:systemdは、管理するサービスでデフォルトでSIGPIPEを無効にしますが、パイプの閉鎖を確認することはできます。IgnoreSIGPIPE
jdizzle

11

ここで別の答えに触発されて、私は次のall-POSIXソリューションを思いつきました。一般的な考え方は、親と子の間に中間プロセスを作成することです。これには、1つの目的があります。親が死亡したときに通知し、明示的に子を殺します。

このタイプのソリューションは、子のコードを変更できない場合に役立ちます。

int p[2];
pipe(p);
pid_t child = fork();
if (child == 0) {
    close(p[1]); // close write end of pipe
    setpgid(0, 0); // prevent ^C in parent from stopping this process
    child = fork();
    if (child == 0) {
        close(p[0]); // close read end of pipe (don't need it here)
        exec(...child process here...);
        exit(1);
    }
    read(p[0], 1); // returns when parent exits for any reason
    kill(child, 9);
    exit(1);
}

この方法には2つの小さな警告があります。

  • 中間プロセスを故意に強制終了した場合、親が死亡しても子は強制終了されません。
  • 子が親の前に存在する場合、中間プロセスは元の子pidを強制終了しようとしますが、これは別のプロセスを参照する可能性があります。(これは、中間プロセスのコードを増やすことで修正できます。)

余談ですが、私が使用している実際のコードはPythonです。ここでは完全性のためです:

def run(*args):
    (r, w) = os.pipe()
    child = os.fork()
    if child == 0:
        os.close(w)
        os.setpgid(0, 0)
        child = os.fork()
        if child == 0:
            os.close(r)
            os.execl(args[0], *args)
            os._exit(1)
        os.read(r, 1)
        os.kill(child, 9)
        os._exit(1)
    os.close(r)

しばらく前に、IRIXの下で、私は両方の間にパイプがあり、どちらかが死亡した場合にパイプからの読み取りがSIGHUPを生成する親子スキームを使用したことに注意してください。これは、中間プロセスを必要とせずに、fork()を実行した子を殺すために使用した方法でした。
Alexis Wilke、2015年

2つ目の警告は間違っていると思います。子のpidは、その親に属するリソースであり、親(中間プロセス)がそれを待機する(または終了してinitに待機させる)まで解放または再利用できません。
R .. GitHub ICE HELPING ICEを停止する'28

7

標準のPOSIX呼び出しのみを使用することを保証できるとは思いません。実生活と同様に、子供が生まれると、それ自体に生命があります。

それはあります親プロセスがほとんどの可能な終了イベントをキャッチし、その時点で子プロセスを強制終了しようとする可能ですが、キャッチできないものは常に存在します。

たとえば、どのプロセスも SIGKILL。カーネルがこのシグナルを処理すると、指定されたプロセスは通知されずに強制終了されます。

類推を拡張する-それを行う他の唯一の標準的な方法は、子供が親をもういないことがわかったときに子供が自殺することです。

Linuxでのみそれを行う方法がありますprctl(2)-他の答えを見てください。


6

他の人が指摘しているように、親が存在するときに親pidが1になるのを頼ることは移植性がありません。特定の親プロセスIDを待つ代わりに、IDが変更されるのを待ちます。

pit_t pid = getpid();
switch (fork())
{
    case -1:
    {
        abort(); /* or whatever... */
    }
    default:
    {
        /* parent */
        exit(0);
    }
    case 0:
    {
        /* child */
        /* ... */
    }
}

/* Wait for parent to exit */
while (getppid() != pid)
    ;

フルスピードでポーリングしたくない場合は、必要に応じてマイクロスリープを追加します。

このオプションは、パイプを使用したり、信号に依存したりするよりも簡単に思えます。


残念ながら、そのソリューションは堅牢ではありません。初期値を取得する前に親プロセスが停止した場合はどうなりますか?子供は決して出ません。
dgatwood 2015年

@dgatwood、どういう意味?!?最初getpid()はを呼び出す前に親で行われfork()ます。その前に親が死亡した場合、その子は存在しません。起こりうることは、しばらくの間親が生きている子供です。
Alexis Wilke、2015年

このやや工夫された例では機能しますが、実際のコードでは、forkの後に必ずexecが続き、新しいプロセスはPPIDを要求することからやり直す必要があります。これらの2つのチェックの間の時間に、親がいなくなった場合、子は何もわかりません。また、親コードと子コードの両方を制御することはほとんどありません(または、PPIDを引数として渡すだけでもかまいません)。一般的な解決策として、そのアプローチはあまりうまく機能しません。そして現実的には、UNIXライクなOSが1でなくて出てきたとしたら、とにかく誰もがそれをやっているとは思えないほど多くのことが壊れるでしょう。
dgatwood 2015年

子のexecを実行する場合、pass parent pidはコマンドライン引数です。
ニッシュ

2
フルスピードでのポーリングは非常識です。
maxschlepzig 2016

4

トラップハンドラーをインストールして、SIGINTをキャッチします。これにより、子プロセスがまだ生きている場合は、子プロセスが強制終了されますが、他のポスターでは、SIGKILLをキャッチしません。

排他的アクセスで.lockfileを開き、子ポーリングでそれを開こうとします-オープンが成功した場合、子プロセスは終了します


または、子はロックモードを別のスレッドでブロックモードで開くことができます。その場合、これは非常に適切でクリーンなソリューションになる可能性があります。おそらくそれはいくつかの移植性の制限があります。
ジャン

4

この解決策は私にとってうまくいきました:

  • stdinパイプを子に渡します。ストリームにデータを書き込む必要はありません。
  • 子供は標準入力からEOFまで無期限に読み取ります。EOFは、親がいなくなったことを示します。
  • これは、親がいなくなったことを検出するための間違いのないポータブルな方法です。親がクラッシュしても、OSはパイプを閉じます。

これは、親が生きているときにのみ存在が意味を持つワーカータイプのプロセス用でした。


@SebastianJylanki試したかどうかは覚えていませんが、プリミティブ(POSIXストリーム)がOS全体でかなり標準的であるため、おそらく機能します。
joonas.fi 2018

3

早くて汚い方法は、子供と親の間にパイプを作成することだと思います。親が退出すると、子供はSIGPIPEを受け取ります。


SIGPIPEはパイプのクローズ時に送信されず、子がそれに書き込もうとしたときにのみ送信されます。
Alcaro 2017年

3

一部のポスターはすでにパイプとに言及していkqueueます。実際、呼び出しによって、接続されたUnixドメインソケットのペアを作成することもできsocketpair()ます。ソケットタイプはでなければなりませんSOCK_STREAM

2つのソケットファイル記述子fd1、fd2があるとします。次にfork()、fdsを継承する子プロセスを作成します。親ではfd2を閉じ、子ではfd1を閉じます。これで、各プロセスpoll()は残りのオープンfdをPOLLINイベント用に独自に終了できます。close()通常の存続期間中に両側が明示的にそのfdを示さない限り、POLLHUPフラグが相手の終了を示していることをかなり確信で​​きます(クリーンであるかどうかに関係なく)。このイベントが通知されると、子供は何をすべきか(例えば、死ぬか)を決定できます。

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    int sv[2];        /* sv[0] for parent, sv[1] for child */
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    pid_t pid = fork();

    if ( pid > 0 ) {  /* parent */
        close(sv[1]);
        fprintf(stderr, "parent: pid = %d\n", getpid());
        sleep(100);
        exit(0);

    } else {          /* child */
        close(sv[0]);
        fprintf(stderr, "child: pid = %d\n", getpid());

        struct pollfd mon;
        mon.fd = sv[1];
        mon.events = POLLIN;

        poll(&mon, 1, -1);
        if ( mon.revents & POLLHUP )
            fprintf(stderr, "child: parent hung up\n");
        exit(0);
    }
}

上記の概念実証コードをコンパイルして、などのターミナルで実行でき./a.out &ます。さまざまな信号による親PIDの強制終了を試すのに約100秒かかります。そうしないと、単純に終了します。どちらの場合も、「子:親が電話を切った」というメッセージが表示されます。

SIGPIPEハンドラーを使用するメソッドと比較して、このメソッドはwrite()呼び出しを試行する必要がありません。

この方法も対称的です。つまり、プロセスは同じチャネルを使用して互いの存在を監視できます。

このソリューションはPOSIX関数のみを呼び出します。私はこれをLinuxとFreeBSDで試しました。他のUnixでも動作するはずですが、実際にはテストしていません。

以下も参照してください。

  • unix(7)Linuxのmanページの、unix(4)FreeBSD用、poll(2)socketpair(2)socket(7)Linux上。

非常にクールですが、これには信頼性の問題があるのでしょうか。これを本番環境でテストしましたか?別のアプリをお持ちですか?
Aktau 2013

@Aktau、私はLinuxプログラムでこのトリックに相当するPythonを使用しています。子の作業ロジックは「親が終了した後にベストエフォートのクリーンアップを実行し、その後終了する」ためです。ただし、他のプラットフォームについては本当にわかりません。CスニペットはLinuxとFreeBSDで動作しますが、私が知っているのはこれだけです...また、親が再びforkしたり、親が本当に終了する前にfdを放棄したりする(このため、競合状態)。
Cong Ma

@Aktau-これは完全に信頼できます。
全知

1

下にPOSIXexit()_exit()および_Exit()機能はに定義されています:

  • プロセスが制御プロセスの場合、SIGHUPシグナルは、呼び出しプロセスに属する制御端末のフォアグラウンドプロセスグループの各プロセスに送信されます。

したがって、親プロセスがそのプロセスグループの制御プロセスになるように設定した場合、親が終了すると、子はSIGHUPシグナルを受け取るはずです。親が墜落したときにそれが起こるかどうかは確かではありませんが、そうなったと思います。確かに、クラッシュ以外のケースでは、問題なく動作するはずです。

完全な全体像を取得するには、基本定義(定義)セクション、exit()およびシステムサービス情報のほか、setsid()およびsetpgrp()-を含め、かなり多くの詳細な文書を読む必要がある場合があることに注意してください。(そうですね!)


3
うーん。ドキュメントはあいまいで矛盾していますが、親プロセスはプロセスグループだけでなく、セッションのリードプロセスである必要があります。セッションのリードプロセスは常にログインであり、新しいセッションのリードプロセスとして私のプロセスを引き継ぐことは、現時点では私の能力を超えていました。
Schof

2
SIGHUPは、存在するプロセスがログインシェルである場合にのみ、子プロセスに効果的に送信されます。 opengroup.org/onlinepubs/009695399/functions/exit.html "プロセスの終了はその子を直接終了しません。以下に説明するSIGHUPシグナルの送信は、/状況によっては間接的に子を終了します。"
Rob K

1
@Rob:正解です-それは私が与えた引用でも述べています:ある状況でのみ、子プロセスはSIGHUPを取得します。そして、SIGHUPを送信するのはログインシェルだけであると言うのは、非常に単純化していますが、それが最も一般的なケースです。複数の子を持つプロセスがそれ自体とその子の制御プロセスとしてそれ自体を設定する場合、SIGHUPは(便利なことに)マスターが死んだときにその子に送信されます。OTOH、プロセスはそれほど多くの問題に遭遇することはめったにないので、私は本当に重要な問題を提起するよりも、一瞬を選んでいます。
ジョナサンレフラー、2010

2
私は数時間それをいじりました、そしてそれを機能させることができませんでした。それは私が親が終了したときにすべて死ぬ必要があるいくつかの子を持つデーモンがある場合をうまく処理したでしょう。
Rob K

1

たとえば、pid 0にシグナルを送信すると、

kill(0, 2); /* SIGINT */

そのシグナルはプロセスグループ全体に送信されるため、子を効果的に殺すことができます。

次のような方法で簡単にテストできます。

(cat && kill 0) | python

次に^ Dを押すと、テキスト"Terminated"が表示され、標準入力が閉じられたために単に終了したのではなく、Pythonインタープリターが実際に強制終了されたことが示されます。


1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" 喜んでTerminatedの代わりに4を印刷します
Kamil Szot 2014

1

他の誰かに関係がある場合、C ++からフォークされた子プロセスでJVMインスタンスを生成するとき、親プロセスが完了した後でJVMインスタンスを正常に終了させる唯一の方法は、次のことです。これが最善の方法ではなかった場合、誰かがコメントでフィードバックを提供できれば幸いです。

1)prctl(PR_SET_PDEATHSIG, SIGHUP)Javaアプリを起動する前に、提案されたようにforkされた子プロセスを呼び出します。execv、および

2)親PIDが1になるまでポーリングするシャットダウンフックをJavaアプリケーションに追加し、ハードを実行しRuntime.getRuntime().halt(0)ます。ポーリングは、psコマンドを実行する別のシェルを起動することで実行されます(JavaまたはLinux上のJRubyでPIDを見つけるにはどうすればよいですか?)。

編集130118:

堅牢なソリューションではなかったようです。何が起こっているかのニュアンスを理解するのにまだ少し苦労していますが、画面/ SSHセッションでこれらのアプリケーションを実行しているときに、まだ孤立したJVMプロセスを取得していることがあります。

JavaアプリでPPIDをポーリングする代わりに、シャットダウンフックにクリーンアップを実行させた後、上記のようにハードストップを実行しました。次にwaitpid、すべてを終了するときがきたときに、生成された子プロセスのC ++親アプリを呼び出すようにしました。親プロセスが既存の参照を使用して子プロセスが終了することを確認しながら子プロセスが終了することを保証するため、これはより堅牢なソリューションのようです。これを、親プロセスがいつでも終了する前のソリューションと比較し、終了する前に孤立しているのかどうかを子供たちに調べさせました。


1
PID equals 1待機は有効ではありません。新しい親は他のPIDである可能性があります。元の親(fork()の前のgetpid())から新しい親(fork()の前に呼び出されたときに子のgetppid()がgetpid()と等しくない)に変更されるかどうかを確認する必要があります。
Alexis Wilke、2015年

1

Linux固有のもう1つの方法は、新しいPID名前空間に親を作成することです。その後、その名前空間ではPID 1になり、終了すると、その子はすべてですぐに削除されSIGKILLます。

残念ながら、新しいPID名前空間を作成するには、が必要CAP_SYS_ADMINです。ただし、この方法は非常に効果的であり、親の最初の起動以降に親または子に実際の変更を加える必要はありません。

参照クローン(2) pid_namespaces(7) 、および共有解除(2)


別の方法で編集する必要があります。それは...そのすべての子供や孫とひ孫などのためのinitプロセスとしてプロセスの行為を行うためにはprctlを使用することが可能です
Omnifarious

0

親が死亡した場合、孤児のPPIDは1に変わります。自分のPPIDを確認するだけで済みます。ある意味で、これは前述のポーリングです。これがそのためのシェルピースです:

check_parent () {
      parent=`ps -f|awk '$2=='$PID'{print $3 }'`
      echo "parent:$parent"
      let parent=$parent+0
      if [[ $parent -eq 1 ]]; then
        echo "parent is dead, exiting"
        exit;
      fi
}


PID=$$
cnt=0
while [[ 1 = 1 ]]; do
  check_parent
  ... something
done

0

2つの解決策が見つかりましたが、どちらも完璧ではありません。

1. SIGTERMシグナルを受信したときに、kill(-pid)によってすべての子を殺します。
明らかに、このソリューションは「kill -9」を処理できませんが、すべての子プロセスを覚えておく必要がないため、ほとんどの場合に機能し、非常に単純です。


    var childProc = require('child_process').spawn('tail', ['-f', '/dev/null'], {stdio:'ignore'});

    var counter=0;
    setInterval(function(){
      console.log('c  '+(++counter));
    },1000);

    if (process.platform.slice(0,3) != 'win') {
      function killMeAndChildren() {
        /*
        * On Linux/Unix(Include Mac OS X), kill (-pid) will kill process group, usually
        * the process itself and children.
        * On Windows, an JOB object has been applied to current process and children,
        * so all children will be terminated if current process dies by anyway.
        */
        console.log('kill process group');
        process.kill(-process.pid, 'SIGKILL');
      }

      /*
      * When you use "kill pid_of_this_process", this callback will be called
      */
      process.on('SIGTERM', function(err){
        console.log('SIGTERM');
        killMeAndChildren();
      });
    }

同様に、どこかでprocess.exitを呼び出すと、上記のように 'exit'ハンドラーをインストールできます。注:Ctrl + Cおよび突然のクラッシュは、プロセスグループを強制終了するためにOSによって自動的に処理されたため、ここではこれ以上ありません。

2.Use chjj / pty.js接続された端末を制御して、あなたのプロセスを生成します。
とにかく-9を殺しても現在のプロセスを殺すと、すべての子プロセスも(OSによって)自動的に殺されます。現在のプロセスは端末の別の側を保持しているため、現在のプロセスが停止すると、子プロセスはSIGPIPEを取得して停止するためと思います。


    var pty = require('pty.js');

    //var term =
    pty.spawn('any_child_process', [/*any arguments*/], {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: process.cwd(),
      env: process.env
    });
    /*optionally you can install data handler
    term.on('data', function(data) {
      process.stdout.write(data);
    });
    term.write(.....);
    */

0

端末制御とセッションを悪用することで、3つのプロセスを備えたポータブルな非ポーリングソリューションをなんとか実現しました。これはメンタルオナニーですが、うまくいきます。

トリックは:

  • プロセスAが開始されます
  • プロセスAがパイプPを作成します(パイプから読み取ることはありません)
  • プロセスAがプロセスBに分岐する
  • プロセスBは新しいセッションを作成します
  • プロセスBがその新しいセッションに仮想端末を割り当てる
  • プロセスBはSIGCHLDハンドラーをインストールして、子が終了したときに終了する
  • プロセスBはSIGPIPEハンドラーを設定します
  • プロセスBがプロセスCに分岐
  • プロセスCは必要なことをすべて実行します(たとえば、exec()は変更されていないバイナリを実行するか、またはロジックを実行します)。
  • プロセスBがパイプPに書き込む(およびそのようにブロックする)
  • プロセスAはプロセスBでwait()し、終了すると終了します。

そのように:

  • プロセスAが停止した場合:プロセスBはSIGPIPEを取得して停止します
  • プロセスBが終了した場合:プロセスAのwait()が返って終了すると、プロセスCはSIGHUPを取得します(端末が接続されているセッションのセッションリーダーが終了すると、フォアグラウンドプロセスグループのすべてのプロセスがSIGHUPを取得するためです)。
  • プロセスCが死んだ場合:プロセスBがSIGCHLDを取得して死ぬので、プロセスAが死ぬ

欠点:

  • プロセスCはSIGHUPを処理できません
  • プロセスCは別のセッションで実行されます
  • プロセスCは、もろい設定を壊すため、セッション/プロセスグループAPIを使用できません。
  • そのようなすべての操作のために端末を作成することは、これまでで最高のアイデアではありません

0

7年が経過しても、開発中にwebpack-dev-serverを起動し、バックエンドプロセスの停止時にアプリケーションを強制終了する必要があるSpringBootアプリケーションを実行しているため、この問題が発生しました。

使用しようとしRuntime.getRuntime().addShutdownHookましたが、Windows 10では機能しましたが、Windows 7では機能しませんでした。

プロセスが終了するまで待機するか、InterruptedExceptionWindowsの両方のバージョンで正しく動作するように見える専用スレッドを使用するように変更しました。

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}

0

歴史的に、UNIX v7から、プロセスシステムはプロセスの親IDをチェックすることによってプロセスの孤立を検出しました。私が言うように、歴史的に、init(8)システムプロセスはただ1つの理由で特別なプロセスです。新しい親プロセスIDの割り当てを処理するカーネルアルゴリズムはこの事実に依存しているため、死ぬことはありません。プロセスがプロセスを実行すると、システムコールの前にプロセスが孤立します。exit(2)呼び出しをと(プロセスシステムコールまたは外部タスクがシグナルを送信するなど)、カーネルはこのプロセスのすべての子にinitプロセスのIDを親プロセスIDとして再割り当てします。これにより、テストが最も簡単になり、プロセスが孤立しているかどうかを知るための最もポータブルな方法になります。getppid(2)システムコールの結果を確認し、それがinit(2)

このアプローチから、問題につながる可能性のある2つの問題が発生します。

  • 最初に、initプロセスを任意のユーザープロセスに変更する可能性があるため、initプロセスが常にすべての孤立プロセスの親になることをどのように保証できますか?まあ、exitシステムコールコードでは、呼び出しを実行しているプロセスがinitプロセス(pidが1に等しいプロセス)であるかどうかを確認する明示的なチェックがあり、そうである場合、カーネルパニック(これ以上維持できないはずです)プロセス階層)なので、initプロセスがexit(2)呼び出しを行うことはできません。
  • 第二に、上記の基本テストには競合状態があります。初期プロセスのIDは歴史的にであると想定1されていますが、POSIXアプローチでは保証されていません。(他の応答で公開されているように)システムのプロセスIDのみがその目的のために予約されていることを示します。posixの実装ではほとんどこれが行われず、元のUNIX派生システムでは1getppid(2)システムコールの応答がプロセスが孤立していると想定するのに十分であると想定できます。チェックするもう1つの方法はgetppid(2)、フォークの直後を作成し、その値を新しい呼び出しの結果と比較することです。両方の呼び出しが一緒にアトミックではないため、これは単純にすべてのケースで機能するわけではなく、親プロセスfork(2)は最初のgetppid(2)システムコールの後および最初に停止する可能性があります。プロセスparent id only changes once, when its parent does an出口(2)call, so this should be enough to check if the getppid(2)result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children ofinit(8) `、ただし、これらのプロセスは親を持たないものとして安全に想定できます(システムでinitプロセスを置き換える場合を除く)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.