子プロセスを1つだけ生成するプロセスがあるとします。親プロセスが何らかの理由で(通常または異常に、kill、^ C、失敗などをアサートして)終了した場合、子プロセスを停止させます。それを正しく行う方法は?
Stackoverflowに関するいくつかの同様の質問:
Windowsのstackoverflowに関するいくつかの同様の質問:
子は、次のようにprctl()
syscallでオプションPR_SET_PDEATHSIG
を指定することにより、親が死んだときにSIGHUP
(または他の信号)を配信するようカーネルに要求できます。
prctl(PR_SET_PDEATHSIG, SIGHUP);
詳細については、man 2 prctl
を参照してください。
編集:これはLinux専用です
私は同じ問題を解決しようとしていますが、私のプログラムはOS X上で実行する必要があるため、Linuxのみのソリューションはうまくいきませんでした。
私はこのページの他の人々と同じ結論に達しました-親が亡くなったときに子供に通知するPOSIX互換の方法はありません。だから私は次善の策を練り上げた-子投票をした。
親プロセスが(何らかの理由で)終了すると、子の親プロセスはプロセス1になります。子が定期的に単純にポーリングする場合、親が1であるかどうかを確認できます。
これは素晴らしいことではありませんが、機能し、このページの他の場所で提案されているTCPソケット/ロックファイルポーリングソリューションよりも簡単です。
「子」で「元の」コードを実行し、「親」で「生成された」コードを実行することでこれを達成しました(つまり、fork()
の後にテストの通常の意味を逆にします)。次に、「生成された」コードでSIGCHLDをトラップします...
あなたのケースでは不可能かもしれませんが、うまくいくとかわいいかもしれません。
子プロセスを変更できない場合は、次のようなものを試すことができます。
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を取得し、バックグラウンドの子プロセスを強制終了します。
完全を期すために。 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);
}
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()
の影響を受けません。
すべての orphans の採用を担当するシステムプロセスが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です。これはすべてのプロセスの祖先です。
Linux や FreeBSD などの最新のシステムでは、別のプロセスがその役割を持っている可能性があります。たとえば、Linuxでは、プロセスは prctl(PR_SET_CHILD_SUBREAPER, 1)
を呼び出して、その子孫のすべてのオーファンを継承するシステムプロセスとして確立します(Fedora 25の example を参照)。
子プロセスには、親プロセスとの間のパイプがありますか?その場合、書き込み中にSIGPIPEを受け取るか、読み取り中にEOFを取得します-これらの状態を検出できます。
ここでの別の答えに触発されて、次のすべての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つの小さな注意事項があります。
余談ですが、私が使用している実際のコードは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)
標準のPOSIX呼び出しのみを使用することを保証することは不可能だと思います。実生活のように、子供が生まれると、それ自身の生活があります。
isは、親プロセスがほとんどの終了イベントをキャッチし、その時点で子プロセスを強制終了しようとする可能性がありますが、キャッチできないものが常にいくつかあります。
たとえば、SIGKILL
をキャッチできるプロセスはありません。カーネルがこのシグナルを処理すると、指定されたプロセスは何も通知せずに終了します。
類推を拡張するために-それを行う唯一の他の標準的な方法は、親がいなくなったことがわかったときに子供が自殺することです。
prctl(2)
を使用してLinux専用の方法があります-他の回答を参照してください。
他の人が指摘したように、親が終了するときに親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)
;
フルスピードでポーリングしたくない場合は、必要に応じてマイクロスリープを追加します。
このオプションは、パイプを使用したり、信号に依存するよりも簡単に思えます。
トラップハンドラをインストールして、SIGINTをキャッチします。これにより、子プロセスがまだ生きている場合にそれを強制終了しますが、SIGKILLをキャッチしない他のポスターは正しいです。
排他的アクセスで.lockfileを開き、子がそれを開こうとしてポーリングするようにします-開くことに成功した場合、子プロセスは終了するはずです
このソリューションは私のために働いた:
これは、親が生存している場合にのみその存在が意味をなすワーカー型プロセス用でした。
迅速で汚い方法は、子と親の間にパイプを作成することだと思います。親が終了すると、子はSIGPIPEを受け取ります。
一部のポスターでは、すでにパイプとkqueue
に言及しています。実際には、socketpair()
呼び出しによって、接続されたnixドメインソケットのペアを作成することもできます。ソケットタイプはSOCK_STREAM
である必要があります。
2つのソケットファイル記述子fd1、fd2があるとします。 fork()
では、fdsを継承する子プロセスを作成します。親ではfd2を閉じ、子ではfd1を閉じます。これで、各プロセスは、POLLIN
イベントに対して、poll()
残りの開いているfdを独自に終了できます。通常のライフタイム中に各サイドが明示的に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()
呼び出しを試行する必要がありません。
このメソッドもsymmetricです。つまり、プロセスは同じチャネルを使用して互いの存在を監視できます。
このソリューションは、POSIX関数のみを呼び出します。これをLinuxとFreeBSDで試しました。他のUnixでも動作するはずですが、実際にはテストしていません。
こちらもご覧ください:
unix(7)
、FreeBSDのunix(4)
、Linuxのpoll(2)
、socketpair(2)
、socket(7)
。他の人に関連する場合、C++からフォークされた子プロセスでJVMインスタンスを生成するとき、親プロセスが完了した後にJVMインスタンスを適切に終了させる唯一の方法は、次のことを行うことでした。これが最善の方法でなかった場合、誰かがコメントでフィードバックを提供できることを願っています。
1)execv
を介してJavaアプリを起動する前に、推奨されるように分岐した子プロセスでprctl(PR_SET_PDEATHSIG, SIGHUP)
を呼び出します。
2)親PIDが1になるまでポーリングするJavaアプリケーションにシャットダウンフックを追加してから、ハードRuntime.getRuntime().halt(0)
を実行します。ポーリングは、ps
コマンドを実行する別のシェルを起動することで実行されます( LinuxでJavaまたはJRubyでPIDを見つけるにはどうすればよいですか? )。
130118を編集:
堅牢なソリューションではなかったようです。私はまだ何が起こっているのかという微妙な意味を理解するのに少し苦労していますが、これらのアプリケーションを画面/ SSHセッションで実行しているときに時々Orphan JVMプロセスを取得していました。
JavaアプリでPPIDをポーリングする代わりに、シャットダウンフックにクリーンアップを実行させた後、上記のように強制停止しました。その後、すべてを終了するときに、生成された子プロセスのC++親アプリでwaitpid
を呼び出すようにしました。子プロセスは確実に終了し、親は既存の参照を使用して子プロセスが終了するようにするため、これはより堅牢なソリューションのようです。これを、親プロセスが満足するたびに終了し、終了する前に親が孤立していたかどうかを子供に理解させようとした以前のソリューションと比較してください。
POSIX では、exit()
、_exit()
、および_Exit()
関数は次のように定義されます。
そのため、親プロセスをそのプロセスグループの制御プロセスとする場合、親が終了すると、子はSIGHUPシグナルを受け取る必要があります。親がクラッシュしたときにそれが起こるかどうかは確かではありませんが、そうなると思います。確かに、非クラッシュの場合、それはうまく動作するはずです。
基本定義(定義)セクション、およびexit()
およびsetsid()
およびsetpgrp()
のシステムサービス情報を含む、かなり多くの細かい印刷物を読む必要があることに注意してください。完全な画像を取得します。 (そうだ!)
たとえば、pid 0にシグナルを送信する場合
kill(0, 2); /* SIGINT */
そのシグナルはプロセスグループ全体に送信されるため、子を効果的に殺します。
次のようなもので簡単にテストできます。
(cat && kill 0) | python
^ Dを押すと、"Terminated"
というテキストが表示されます。これは、Pythonインタープリターが、stdinが閉じられたために終了しただけでなく、実際に強制終了されたことを示します。
歴史的に、UNIX v7から、プロセスシステムはプロセスの親IDをチェックすることによりプロセスの孤立を検出しました。私が言うように、歴史的に、init(8)
システムプロセスは、ただ一つの理由で特別なプロセスです:それは死ぬことができません。新しい親プロセスIDの割り当てを処理するカーネルアルゴリズムはこの事実に依存しているため、死ぬことはありません。プロセスがそのexit(2)
呼び出しを実行すると(プロセスシステムコールまたは信号などを送信する外部タスクによる)、カーネルはこのプロセスのすべての子にinitプロセスのidを親プロセスとして再割り当てします。 id。これにより、テストが最も簡単になり、プロセスが孤立したかどうかを最もポータブルに知ることができます。 getppid(2)
システムコールの結果を確認し、それがinit(2)
プロセスのプロセスIDである場合、システムコールの前にプロセスは孤立しました。
このアプローチから、問題につながる可能性のある2つの問題が発生します。
init
プロセスを任意のユーザープロセスに変更する可能性があります。そのため、initプロセスが常にすべての孤立プロセスの親になることを保証するにはどうすればよいでしょうか。さて、exit
システムコールコードでは、呼び出しを実行しているプロセスがinitプロセス(pidが1に等しいプロセス)であるかどうかを確認する明示的なチェックがあります。プロセス階層を維持できるようになります)。そのため、initプロセスがexit(2)
呼び出しを行うことは許可されません。1
であると想定されていますが、POSIXのアプローチでは保証されていません。これを行うPOSIX実装はほとんどありません。元のUNIX派生システムでは、getppid(2)
システムコールの応答として1
があれば、プロセスが孤立していると見なすことができます。チェックする別の方法は、フォークの直後にgetppid(2)
を作成し、その値を新しい呼び出しの結果と比較することです。両方の呼び出しがアトミックではなく、親プロセスはfork(2)
の後、最初のgetppid(2)
システムコールの前に終了する可能性があるため、これは単にすべての場合に機能しません。 processparent id only changes once, when its parent does an
exit(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 of
init(8) `。ただし、これらのプロセスは親を持たないと安全に仮定できます(システムでinitプロセスを代用する場合を除く) )7年が過ぎましたが、開発中にwebpack-dev-serverを起動し、バックエンドプロセスが停止したときにそれを強制終了する必要があるSpringBootアプリケーションを実行しているため、この問題に遭遇しました。
Runtime.getRuntime().addShutdownHook
を使用しようとしましたが、Windows 10では機能しましたが、Windows 7では機能しませんでした。
プロセスが終了するか、両方のWindowsバージョンで正常に動作するInterruptedException
を待つ専用スレッドを使用するように変更しました。
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();
}
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(.....);
*/
これを行うLinux固有の別の方法は、新しいPID名前空間に親を作成することです。その後、そのネームスペースでPID 1になり、終了すると、そのすべての子がSIGKILL
で即座に削除されます。
残念ながら、新しいPID名前空間を作成するには、CAP_SYS_ADMIN
が必要です。ただし、この方法は非常に効果的であり、親の最初の起動以降、親または子に実際の変更を加える必要はありません。
clone(2) 、 pid_namespaces(7) 、および nshare(2) を参照してください。
私は、端末制御とセッションを悪用することにより、3つのプロセスでポータブルな非ポーリングソリューションを実行することができました。これはメンタルオナニーですが、動作します。
トリックは次のとおりです。
その方法:
欠点:
親が死亡すると、孤児の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