ちょうど1つの子プロセスを生成するプロセスがあるとします。親プロセスが何らかの理由(通常または異常、kill、^ C、assert failureなど)で終了した場合、子プロセスを終了させます。それを正しく行う方法は?
Stackoverflowに関するいくつかの同様の質問:
Windowsの stackoverflowに関する同様の質問:
ちょうど1つの子プロセスを生成するプロセスがあるとします。親プロセスが何らかの理由(通常または異常、kill、^ C、assert failureなど)で終了した場合、子プロセスを終了させます。それを正しく行う方法は?
Stackoverflowに関するいくつかの同様の質問:
Windowsの stackoverflowに関する同様の質問:
回答:
次のようにsyscallでSIGHUP
オプションPR_SET_PDEATHSIG
を指定することで、子は親が死んだときにカーネルに配信(または他のシグナル)を要求できprctl()
ます。
prctl(PR_SET_PDEATHSIG, SIGHUP);
詳細man 2 prctl
については、を参照してください。
編集:これはLinuxのみです
prctl()
。ところで、マキシムによってリンクされた答えは間違っています。
man prctl
言う:呼び出しプロセスの親プロセス終了シグナルをarg2に設定します(範囲1..maxsigのシグナル値、またはクリアする場合は0)。これは、親プロセスが終了したときに呼び出しプロセスが受け取るシグナルです。この値は、fork(2)の子および(Linux 2.4.36 / 2.6.23以降)set-user-IDまたはset-group-IDバイナリを実行するとクリアされます。
私は同じ問題を解決しようとしています。私のプログラムはOS Xで実行する必要があるため、Linuxのみのソリューションではうまくいきませんでした。
私はこのページの他の人々と同じ結論に達しました-親が死んだときに子供に通知するPOSIX互換の方法はありません。それで、私は次善の策、つまり子供の世論調査をまとめました。
親プロセスが(何らかの理由で)終了すると、子の親プロセスはプロセス1になります。子が定期的にポーリングするだけであれば、親が1かどうかを確認できます。そうである場合、子は終了する必要があります。
これはすばらしいことではありませんが、機能し、このページの他の場所で提案されているTCPソケット/ロックファイルポーリングソリューションよりも簡単です。
gettpid()
は1にならずpid
、ゾーンスケジューラ(プロセスzsched
)のを取得します。
私はこれまで、「子」で「元の」コードを実行し、「親」で「生成された」コードを実行することでこれを達成しました(つまり、のテストの通常の意味を逆にします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を取得し、バックグラウンドの子プロセスを強制終了します。
dup2
使用read -u
して特定のファイル記述子から読み取ることにより、stdinの引き継ぎを回避できます。またsetpgid(0, 0)
、ターミナルで^ Cを押したときに終了しないように、子にa を追加しました。
dup2()
呼び出しの引数の順序が間違っています。pipes[0]
stdinとして使用する場合は、のdup2(pipes[0], 0)
代わりに記述する必要がありdup2(0, pipes[0])
ます。これはdup2(oldfd, newfd)
、呼び出しが以前に開いたnewfdを閉じる場所です。
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です-これはすべてのプロセスの祖先です。
LinuxやFreeBSDのような最新のシステムでは、別のプロセスがその役割を果たしている可能性があります。たとえば、Linuxでは、プロセスprctl(PR_SET_CHILD_SUBREAPER, 1)
はその子孫のすべての孤立を継承するシステムプロセスとしてそれ自体を確立するために呼び出すことができます(Fedora 25の例を参照)。
init(8)
...想定できる唯一のことは、親プロセスが停止すると、その親IDが変更されるということです。これは実際には、プロセスの存続期間中に一度発生します。そして、プロセスの親が死ぬときです。そこへこの唯一の主な例外だ、とのためであるinit(8)
子どもたちが、あなたのように、このから保護されているinit(8)
ことはありませんexit(2)
(その場合には、カーネルパニックを)
完全を期すために。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);
}
NSTask
またはposixのスポーン。参照してくださいstartTask
:ここに私のコード内の関数github.com/neoneye/newton-commander-browse/blob/master/Classes/...
子プロセスには、親プロセスとの間のパイプがありますか?その場合、書き込みの場合はSIGPIPEを受け取り、読み取りの場合はEOFを取得します-これらの状態が検出される可能性があります。
ここで別の答えに触発されて、私は次の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つの小さな警告があります。
余談ですが、私が使用している実際のコードは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呼び出しのみを使用することを保証できるとは思いません。実生活と同様に、子供が生まれると、それ自体に生命があります。
それはあります親プロセスがほとんどの可能な終了イベントをキャッチし、その時点で子プロセスを強制終了しようとする可能ですが、キャッチできないものは常に存在します。
たとえば、どのプロセスも SIGKILL
。カーネルがこのシグナルを処理すると、指定されたプロセスは通知されずに強制終了されます。
類推を拡張する-それを行う他の唯一の標準的な方法は、子供が親をもういないことがわかったときに子供が自殺することです。
Linuxでのみそれを行う方法がありますprctl(2)
-他の答えを見てください。
他の人が指摘しているように、親が存在するときに親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)
;
フルスピードでポーリングしたくない場合は、必要に応じてマイクロスリープを追加します。
このオプションは、パイプを使用したり、信号に依存したりするよりも簡単に思えます。
getpid()
はを呼び出す前に親で行われfork()
ます。その前に親が死亡した場合、その子は存在しません。起こりうることは、しばらくの間親が生きている子供です。
この解決策は私にとってうまくいきました:
これは、親が生きているときにのみ存在が意味を持つワーカータイプのプロセス用でした。
早くて汚い方法は、子供と親の間にパイプを作成することだと思います。親が退出すると、子供はSIGPIPEを受け取ります。
一部のポスターはすでにパイプとに言及してい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上。下にPOSIX、exit()
、_exit()
および_Exit()
機能はに定義されています:
したがって、親プロセスがそのプロセスグループの制御プロセスになるように設定した場合、親が終了すると、子はSIGHUPシグナルを受け取るはずです。親が墜落したときにそれが起こるかどうかは確かではありませんが、そうなったと思います。確かに、クラッシュ以外のケースでは、問題なく動作するはずです。
完全な全体像を取得するには、基本定義(定義)セクション、exit()
およびシステムサービス情報のほか、setsid()
およびsetpgrp()
-を含め、かなり多くの詳細な文書を読む必要がある場合があることに注意してください。(そうですね!)
たとえば、pid 0にシグナルを送信すると、
kill(0, 2); /* SIGINT */
そのシグナルはプロセスグループ全体に送信されるため、子を効果的に殺すことができます。
次のような方法で簡単にテストできます。
(cat && kill 0) | python
次に^ Dを押すと、テキスト"Terminated"
が表示され、標準入力が閉じられたために単に終了したのではなく、Pythonインタープリターが実際に強制終了されたことが示されます。
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -"
喜んでTerminatedの代わりに4を印刷します
他の誰かに関係がある場合、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 ++親アプリを呼び出すようにしました。親プロセスが既存の参照を使用して子プロセスが終了することを確認しながら子プロセスが終了することを保証するため、これはより堅牢なソリューションのようです。これを、親プロセスがいつでも終了する前のソリューションと比較し、終了する前に孤立しているのかどうかを子供たちに調べさせました。
PID equals 1
待機は有効ではありません。新しい親は他のPIDである可能性があります。元の親(fork()の前のgetpid())から新しい親(fork()の前に呼び出されたときに子のgetppid()がgetpid()と等しくない)に変更されるかどうかを確認する必要があります。
Linux固有のもう1つの方法は、新しいPID名前空間に親を作成することです。その後、その名前空間ではPID 1になり、終了すると、その子はすべてですぐに削除されSIGKILL
ます。
残念ながら、新しいPID名前空間を作成するには、が必要CAP_SYS_ADMIN
です。ただし、この方法は非常に効果的であり、親の最初の起動以降に親または子に実際の変更を加える必要はありません。
参照クローン(2) 、pid_namespaces(7) 、および共有解除(2) 。
親が死亡した場合、孤児の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
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(.....);
*/
端末制御とセッションを悪用することで、3つのプロセスを備えたポータブルな非ポーリングソリューションをなんとか実現しました。これはメンタルオナニーですが、うまくいきます。
トリックは:
そのように:
欠点:
7年が経過しても、開発中にwebpack-dev-serverを起動し、バックエンドプロセスの停止時にアプリケーションを強制終了する必要があるSpringBootアプリケーションを実行しているため、この問題が発生しました。
使用しようとしRuntime.getRuntime().addShutdownHook
ましたが、Windows 10では機能しましたが、Windows 7では機能しませんでした。
プロセスが終了するまで待機するか、InterruptedException
Windowsの両方のバージョンで正しく動作するように見える専用スレッドを使用するように変更しました。
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();
}
歴史的に、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)
呼び出しを行うことはできません。1
されていますが、POSIXアプローチでは保証されていません。(他の応答で公開されているように)システムのプロセスIDのみがその目的のために予約されていることを示します。posixの実装ではほとんどこれが行われず、元のUNIX派生システムでは1
、getppid(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 of
init(8) `、ただし、これらのプロセスは親を持たないものとして安全に想定できます(システムでinitプロセスを置き換える場合を除く)私は環境に子pidを使用して親pidを渡し、/ proc / $ ppidが子から存在するかどうかを定期的にチェックしました。