230

ちょうど 1 つの子プロセスを生成するプロセスがあるとします。親プロセスが何らかの理由で終了した場合 (通常または異常、強制終了、^C、失敗のアサートなど)、子プロセスを終了させたいと考えています。それを正しく行う方法は?


スタックオーバーフローに関するいくつかの同様の質問:


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

4

24 に答える 24

198

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

prctl(PR_SET_PDEATHSIG, SIGHUP);

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

編集:これはLinux専用です

于 2008-11-12T16:12:14.440 に答える
72

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

私はこのページの他の人たちと同じ結論に達しました。親が亡くなったときに子供に通知する POSIX 互換の方法はありません。そこで私は次善の策を考え出しました - 子に世論調査をさせることです。

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

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

于 2010-01-10T01:18:08.007 に答える
33

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

あなたの場合は不可能かもしれませんが、うまくいくとかわいいです。

于 2008-11-12T19:58:48.147 に答える
30

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

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

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

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

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

于 2010-10-29T17:38:01.133 に答える
15

完全を期すために。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);
}
于 2011-06-26T15:38:14.707 に答える
11

ここでの別の回答に触発されて、次のすべての 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)
于 2014-05-01T02:25:57.170 に答える
11

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

于 2008-11-12T16:15:49.597 に答える
9

標準の POSIX 呼び出しのみを使用することを保証できるとは思いません。実生活と同じように、子が産み出されると、独自の生活が始まります。

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

たとえば、どのプロセスも をキャッチできませんSIGKILL。カーネルがこのシグナルを処理すると、指定されたプロセスを強制終了し、そのプロセスには通知しません。

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

Linux でのみ行う方法がありますprctl(2)- 他の回答を参照してください。

于 2008-11-12T15:45:14.133 に答える
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)
    ;

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

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

于 2013-05-21T00:49:22.470 に答える
5

一部のポスターでは、パイプと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 秒かかります。そうしないと、単に終了します。いずれの場合も、「子: 親が電話を切りました」というメッセージが表示されます。

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

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

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

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

  • unix(7)unix(4)Linuxの FreeBSD 用の Linux man ページのpoll(2), socketpair(2), socket(7).
于 2013-03-13T04:55:00.473 に答える
5

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

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

于 2008-11-12T16:42:06.813 に答える
3

手っ取り早い方法は、子と親の間にパイプを作成することだと思います。親が終了すると、子は SIGPIPE を受け取ります。

于 2010-12-09T08:25:14.643 に答える
1

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

1) を介して Java アプリを起動する前に、フォークされた子プロセスを提案どおりに呼び出しますprctl(PR_SET_PDEATHSIG, SIGHUP)execv

2) 親の PID が 1 になるまでポーリングするシャットダウン フックを Java アプリケーションに追加し、次に hard を実行しRuntime.getRuntime().halt(0)ます。ポーリングは、コマンドを実行する別のシェルを起動することによって行われますps(参照: How do I find my PID in Java or JRuby on Linux? を参照)。

編集 130118:

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

Java アプリで PPID をポーリングする代わりに、単純にシャットダウン フックにクリーンアップを実行させ、その後に上記のようにハード停止させました。waitpid次に、すべてを終了するときに、生成された子プロセスで C++ 親アプリを呼び出すようにしました。これは、子プロセスが確実に終了するのに対し、親プロセスは既存の参照を使用して子プロセスが確実に終了するため、より堅牢なソリューションのようです。これを、必要に応じて親プロセスを終了させ、終了する前に親プロセスが孤立しているかどうかを子プロセスに判断させた以前のソリューションと比較してください。

于 2013-01-08T08:22:13.193 に答える
1

POSIXではexit()_exit()および関数は次の_Exit()ように定義されています。

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

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

全体像を把握するには、基本定義 (定義) セクションや、 およびexit()のシステム サービス情報など、かなり多くの細字を読む必要がある場合があることに注意してください。(私もそうです!)setsid()setpgrp()

于 2008-11-12T20:54:28.270 に答える
1

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

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

そのシグナルはプロセスグループ全体に送信されるため、事実上子プロセスが強制終了されます。

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

(cat && kill 0) | python

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

于 2011-12-05T21:31:47.627 に答える
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. chjj /pty.jsを使用して、制御端末が接続されたプロセスを生成します。
とにかく kill -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(.....);
    */

于 2013-11-26T09:18:11.983 に答える
-1

親が死亡した場合、孤児の 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
于 2011-12-06T23:17:22.293 に答える