부모가 종료 된 후 자식 프로세스를 죽이는 방법은 무엇입니까?


209

정확히 하나의 자식 프로세스를 생성하는 프로세스가 있다고 가정하십시오. 이제 부모 프로세스가 어떤 이유로 든 (일반적으로 또는 비정상적으로 kill, ^ C, assert failure 등) 종료 될 때 자식 프로세스가 죽기를 원합니다. 올바르게하는 방법?


stackoverflow에 대한 비슷한 질문 :


Windows의 stackoverflow에 대한 비슷한 질문 :

답변:


189

아이가 제공하는 커널을 요청할 수 있습니다 SIGHUP(또는 다른 신호) 때 옵션을 지정하여 부모 다이 PR_SET_PDEATHSIGprctl()이 같은 콜 :

prctl(PR_SET_PDEATHSIG, SIGHUP);

보다 man 2 prctl 을 참조하십시오.

편집 : 이것은 Linux 전용입니다


5
부모가 이미 죽었 기 때문에 이것은 좋지 않은 해결책입니다. 경쟁 조건. 올바른 해결책 : stackoverflow.com/a/17589555/412080
Maxim Egorushkin

16
경쟁 조건을 해결하지 않더라도 좋지 않은 답변을 부르는 것은 그리 좋지 않습니다. 경쟁 조건없이 자유롭게 사용하는 방법에 대한 내 답변 을 참조하십시오 prctl(). Btw, Maxim의 답변이 잘못되었습니다.
maxschlepzig

4
이것은 단지 잘못된 anser입니다. 부모 프로세스가 죽을 때가 아니라 포크를 호출 한 스레드가 죽을 때 신호를 자식 프로세스에 보냅니다.
Lothar

2
@Lothar 어떤 종류의 증거를 보는 것이 좋을 것입니다. man prctl: 호출 프로세스의 상위 프로세스 종료 신호를 arg2 (1..maxsig 범위의 신호 값 또는 지우려면 0)로 설정하십시오. 이것은 부모가 죽을 때 호출 프로세스가 얻을 것이라는 신호입니다. set-user-ID 또는 set-group-ID 바이너리를 실행할 때 fork (2) 및 (Linux 2.4.36 / 2.6.23부터) 자식에 대해이 값이 지워집니다.
qrdl

1
@maxschlepzig 새로운 링크에 감사드립니다. 이전 링크가 유효하지 않은 것 같습니다. 그건 그렇고, 몇 년이 지난 후에도 여전히 부모 측에서 옵션을 설정하기위한 API는 없습니다. 아쉽습니다.
ROX

68

같은 문제를 해결하려고하는데 프로그램이 OS X에서 실행되어야하기 때문에 Linux 전용 솔루션이 작동하지 않았습니다.

이 페이지의 다른 사람들과 같은 결론에 도달했습니다. 부모가 죽을 때 아이에게 알리는 POSIX 호환 방법은 없습니다. 그래서 나는 다음으로 가장 좋은 일을 멈췄습니다.

어떤 이유로 든 부모 프로세스가 종료되면 (어떤 이유로 든) 자식의 부모 프로세스가 프로세스 1이됩니다. 자식이 단순히 정기적으로 폴링하는 경우 부모가 1인지 여부를 확인할 수 있습니다.

이것은 좋지는 않지만 작동 하며이 페이지의 다른 곳에서 제안 된 TCP 소켓 / 잠금 파일 폴링 솔루션보다 쉽습니다.


6
탁월한 솔루션. 1을 리턴 할 때까지 getppid ()를 계속 호출 한 다음 종료하십시오. 이것은 좋으며 이제도 사용합니다. 비정치 솔루션은 좋을 것입니다. Schof 감사합니다.
neoneye

10
정보 제공을 위해 Solaris에서 영역에있는 경우 gettpid()1이되지 않지만 pid영역 스케줄러 (process zsched)를 가져옵니다 .
Patrick Schlüter

4
누군가 궁금해하는 경우 Android 시스템에서 pid는 부모가 사망 할 때 1 대신 0 (프로세스 시스템 pid) 인 것처럼 보입니다.
Rui Marques

2
fork ()-ing 전에 getpid ()를 사용하고 자식의 getppid ()가 다른 경우 종료하기 전에보다 강력하고 플랫폼에 독립적 인 방식으로 수행하십시오.
Sebastien

2
자식 프로세스를 제어하지 않으면 작동하지 않습니다. 예를 들어, find (1)를 래핑하는 명령을 작성 중이며 래퍼가 어떤 이유로 죽으면 찾기가 종료되도록하고 싶습니다.
Lucretiel

34

과거에는 "자식"에서 "원본"코드를 실행하고 "부모"에서 "스폰 된"코드를 실행하여이 작업을 수행했습니다. fork() )이를 달성했습니다. 그런 다음 "spawned"코드에서 SIGCHLD를 트랩하십시오.

귀하의 경우에는 가능하지 않지만 작동하면 귀엽습니다.


아주 좋은 해결책, 감사합니다! 현재 받아 들여지는 것이 더 일반적이지만 더 이식성이 뛰어납니다.
Paweł Hajdan

1
부모에서 작업을 수행 할 때 가장 큰 문제는 부모 프로세스를 변경한다는 것입니다. "영원히"실행해야하는 서버의 경우 이는 옵션이 아닙니다.
Alexis Wilke

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
멋지지만 다섯 번의 시스템 호출과 10 줄의 코드로 생성 된이 코드 성능에 대해 약간 회의적입니다.
Oleiade

+1. 플래그를 dup2사용하여 read -u특정 파일 디스크립터에서 읽음 으로써 및 stdin 인수를 피할 수 있습니다 . 또한 setpgid(0, 0)터미널에서 ^ C를 누를 때 종료되지 않도록 자식을 추가했습니다 .
Greg Hewgill

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 ...

그러나 시스템 프로세스에 의존하고 initPID 1을 갖는 것은 이식성이 없습니다. POSIX.1-2008은 다음을 지정합니다 .

기존의 모든 자식 프로세스와 호출 프로세스의 좀비 프로세스의 부모 프로세스 ID는 구현 정의 시스템 프로세스의 프로세스 ID로 설정해야합니다. 즉, 이러한 프로세스는 특수 시스템 프로세스에 의해 상속됩니다.

전통적으로 모든 고아를 채택하는 시스템 프로세스는 PID 1, 즉 init-모든 프로세스의 조상입니다.

Linux 또는 FreeBSD 와 같은 최신 시스템에서는 다른 프로세스가 그 역할을 수행 할 수 있습니다. 예를 들어, Linux에서는 프로세스가 prctl(PR_SET_CHILD_SUBREAPER, 1)자신의 자손 중 모든 고아를 상속하는 시스템 프로세스로 설정하기 위해 호출 할 수 있습니다 (참조, Fedora 25 의 ).


"조부모가 항상 초기화 과정이라고 확신하면 테스트를 단순화 할 수 있습니다." 부모 프로세스가 죽으면 프로세스는 조부모의 자식이 아닌 init 프로세스의 자식 (pid 1)이됩니다. 따라서 테스트는 항상 올바른 것 같습니다.
Johannes Schaub-litb


흥미 롭군 비록 조부모와 어떤 관계가 있는지는 알 수 없습니다.
Johannes Schaub-litb

1
@ JohannesSchaub-litb, 프로세스의 granparent가 프로세스라고 항상 가정 할 수는 없습니다 init(8).... 부모 프로세스가 죽을 때 부모 ID가 변경된다는 것만 가정 할 수 있습니다. 이것은 실제로 프로세스 수명 동안 한 번만 발생합니다. 프로세스의 부모가 죽을 때입니다. 여기에는 단 하나의 주요 예외가 있으며 init(8)어린이 에게는 예외입니다 . 그러나 init(8)결코 exit(2)그런 경우 로부터 보호받습니다 (이 경우 커널 패닉)
Luis Colorado

1
불행하게도, 자식이 스레드에서 분기 된 다음 스레드가 종료되면 자식 프로세스는 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-MONICA가 MISTREATED했습니다. 19

내 macOS에서 부모 프로세스가 종료되면 자식 프로세스가 자동으로 종료됩니다. 이유를 모르겠습니다.
이 린 리우

@YiLinLiu iirc 내가 사용 NSTask했거나 posix 스폰. startTask여기 내 코드에서 함수를 참조 하십시오 : github.com/neoneye/newton-commander-browse/blob/master/Classes/…
neoneye

11

하위 프로세스에 상위 프로세스와의 파이프가 있습니까? 그렇다면 글을 쓰면 SIGPIPE를 받거나 읽을 때 EOF를 받게됩니다. 이러한 조건이 감지 될 수 있습니다.


1
나는 적어도 OS X에서 이것이 안정적으로 일어나지 않았다는 것을 알았습니다.
Schof

이 답변을 추가하기 위해 여기에 왔습니다. 이것은 분명히이 경우에 사용할 솔루션입니다.
Dolda2000

주의 사항 : systemd는 관리하는 서비스에서 SIGPIPE를 기본적으로 비활성화하지만 파이프 폐쇄를 여전히 확인할 수 있습니다. IgnoreSIGPIPE
jdizzle

11

여기에 또 다른 대답에서 영감을 얻은 다음과 같은 모든 POSIX 솔루션을 생각해 냈습니다. 일반적인 아이디어는 부모와 자식 사이에 중간 프로세스를 생성하는 것입니다. 부모 프로세스는 부모가 죽을 때를 통지하고 자식을 명시 적으로 죽이는 것입니다.

이 유형의 솔루션은 자식의 코드를 수정할 수 없을 때 유용합니다.

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);
}

이 방법에는 두 가지 작은 경고가 있습니다.

  • 중간 과정을 일부러 죽이면 부모가 죽을 때 자식이 죽지 않습니다.
  • 자식이 부모보다 먼저 나가면 중간 프로세스가 원래 자식 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이 발생하면 SIGHUP을 생성했습니다. 이것이 중간 프로세스없이 포크 ()를 가진 아이들을 죽이는 방식이었습니다.
Alexis Wilke

두 번째 경고는 틀렸다고 생각합니다. 자식의 pid는 부모에 속하는 리소스이며 부모 (중간 프로세스)가 기다릴 때까지 (또는 종료하고 기다릴 때까지) 해제 / 재사용 할 수 없습니다.
R .. GitHub 중지 지원 얼음

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

@dgatwood, 무슨 뜻인가요?!? 첫 번째 getpid()는 호출하기 전에 부모에서 수행됩니다 fork(). 자녀가 존재하기 전에 부모가 사망하는 경우. 일어날 수있는 일은 아이가 부모를 잠시 살면서 나가는 것입니다.
Alexis Wilke

이 다소 고안된 예에서는 작동하지만 실제 코드에서는 포크 뒤에 거의 exec가 붙어 있으며 PPID를 요청하여 새 프로세스를 다시 시작해야합니다. 두 검사 사이에 부모가 떠나면 아이는 전혀 모른다. 또한 부모 코드와 자식 코드를 모두 제어하지 못할 수도 있습니다 (또는 PPID를 인수로 전달할 수도 있습니다). 따라서 일반적인 솔루션으로는 그 방법이 잘 작동하지 않습니다. 현실적으로, 유닉스 계열 OS가 1이 아닌 상태로 나온다면, 많은 일들이 망가 져서 아무도 그것을 상상하지 못할 것입니다.
dgatwood 2016 년

자식에 대한 exec를 수행 할 때 부모 pid는 명령 줄 인수입니다.
Nish

2
최고 속도로 폴링하는 것은 미친 짓입니다.
maxschlepzig

4

트랩 핸들러 설치SIGINT를 잡기 위해 를 SIGINT를 잡을 수없는 다른 포스터가 올바른데도 불구하고 자식 프로세스가 아직 살아 있으면 죽입니다.

독점 액세스 권한으로 .lockfile을 열고 하위 파일을 열려고 시도하십시오. 열림에 성공하면 하위 프로세스가 종료되어야합니다.


또는 자식은 차단 모드에서 별도의 스레드에서 잠금 파일을 열 수 있습니다.이 경우 꽤 좋고 깨끗한 솔루션 일 수 있습니다. 아마도 그것은 약간의 이식성 제한이 있습니다.
Jean

4

이 솔루션은 저에게 효과적이었습니다.

  • stdin 파이프를 자식에게 전달하십시오-스트림에 데이터를 쓸 필요가 없습니다.
  • 아동은 stdin에서 EOF까지 무한정 읽습니다. EOF는 부모가 갔다는 신호입니다.
  • 이것은 부모가 언제 갔는지 감지 할 수있는 완벽한 방법입니다. 부모가 충돌하더라도 OS는 파이프를 닫습니다.

이것은 부모가 살아있을 때만 존재하는 작업자 유형 프로세스를위한 것입니다.


@SebastianJylanki 내가 시도했는지 기억하지 못하지만 원시 (POSIX 스트림)가 OS에서 상당히 표준이기 때문에 아마도 작동합니다.
joonas.fi

3

빠르고 더러운 방법은 자녀와 부모 사이에 파이프를 만드는 것입니다. 부모가 퇴장하면 자녀에게 SIGPIPE가 제공됩니다.


SIGPIPE는 파이프 가까이에서 보내지 않고 자식이 쓰려고 할 때만 보내집니다.
Alcaro

3

일부 포스터는 이미 파이프와을 언급했습니다 kqueue. 사실 당신은 또한 연결된 한 쌍의 생성 할 수 있습니다 유닉스 도메인 소켓 바이 socketpair()전화를. 소켓 유형은이어야합니다 SOCK_STREAM.

두 개의 소켓 파일 디스크립터 fd1, fd2가 있다고 가정 해 봅시다. 이제 fork()자식 프로세스를 생성하여 fd를 상속합니다. 부모에서 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 초가 걸리거나 종료됩니다. 두 경우 모두 "child : parent hung up"이라는 메시지가 표시됩니다.

SIGPIPE핸들러를 사용하는 메소드와 비교 하여이 메소드는 write()호출을 시도 할 필요가 없습니다 .

이 방법은 대칭 적입니다 . 즉, 프로세스는 동일한 채널을 사용하여 서로의 존재를 모니터링 할 수 있습니다.

이 솔루션은 POSIX 함수 만 호출합니다. 나는 리눅스와 FreeBSD에서 이것을 시도했다. 다른 유닉스에서도 작동해야한다고 생각하지만 실제로 테스트하지는 않았습니다.

또한보십시오:

  • unix(7)리눅스 매뉴얼 페이지, unix(4)FreeBSD를위한, poll(2), socketpair(2), socket(7)리눅스에.

매우 시원하지만, 이것이 신뢰성 문제가 있는지 정말로 궁금합니다. 프로덕션 환경에서 이것을 테스트 했습니까? 다른 앱으로?
Aktau

@ Aktau, 나는 Linux 프로그램 에서이 트릭과 동등한 Python을 사용하고 있습니다. 자녀의 작업 논리는 "부모가 종료 한 후에도 최선을 다해 정리하고 종료하는 것"이기 때문에 필요했습니다. 그러나 다른 플랫폼에 대해서는 잘 모르겠습니다. C 스 니펫은 Linux 및 FreeBSD에서 작동하지만 그게 내가 아는 전부입니다 ... 또한 부모가 다시 포크하거나 부모가 실제로 종료하기 전에 fd를 포기하는 등 조심해야 할 경우가 있습니다 (따라서 시간 창을 만듭니다) 경쟁 조건).
Cong Ma

@ Aktau-이것은 완전히 신뢰할 수 있습니다.
Omnifarious

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, 프로세스는 그다지 많은 문제를 겪지 않습니다. 그래서 나는 정말로 중요한 퀴즈를 제기하는 것보다 더 낫습니다.
Jonathan Leffler

2
나는 몇 시간 동안 그것을 가지고 장난했고 그것을 작동시키지 못했습니다. 부모가 종료 될 때 모두 죽어야 할 자식이있는 데몬이있는 경우를 잘 처리했을 것입니다.
Rob K

1

예를 들어 pid 0에 신호를 보내면

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

그 신호는 전체 프로세스 그룹으로 보내져 효과적으로 자식을 죽입니다.

다음과 같은 방법으로 쉽게 테스트 할 수 있습니다.

(cat && kill 0) | python

그런 다음 ^ D를 누르면 텍스트 "Terminated"가 표시되어 stdin이 닫혀서 종료 된 대신 파이썬 인터프리터가 실제로 종료되었음을 나타냅니다.


1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" 행복 대신 4 종료의 인쇄
카밀 Szot

1

다른 사람과 관련이있는 경우 C ++의 분기 하위 프로세스에서 JVM 인스턴스를 생성 할 때 부모 프로세스가 완료된 후 JVM 인스턴스를 올바르게 종료 할 수있는 유일한 방법은 다음을 수행하는 것입니다. 이것이 최선의 방법이 아닌 경우 누군가 의견에 피드백을 제공 할 수 있기를 바랍니다.

1) 다음을 prctl(PR_SET_PDEATHSIG, SIGHUP)통해 Java 앱을 시작하기 전에 제안 된 분기 된 자식 프로세스를 호출하십시오 .execv 하고,

2) 상위 PID가 1이 될 때까지 폴링하는 종료 후크를 Java 애플리케이션에 추가 한 후 열심히 수행하십시오 Runtime.getRuntime().halt(0). 폴링은 ps명령 을 실행하는 별도의 쉘을 시작하여 수행됩니다 ( Linux에서 Java 또는 JRuby에서 PID를 찾으려면 어떻게합니까? 참조). ).

편집 130118 :

강력한 솔루션이 아닌 것 같습니다. 나는 아직도 무슨 일이 일어나고 있는지의 뉘앙스를 이해하기 위해 조금 고심하고 있지만 화면 / SSH 세션에서 이러한 응용 프로그램을 실행할 때 여전히 고아 JVM 프로세스를 얻었습니다.

Java 앱에서 PPID를 폴링하는 대신, 종료 후크가 정리를 수행 한 다음 위와 같이 하드 정지를 수행했습니다. 그런 다음 waitpid모든 것을 종료 할 때가되었을 때 생성 된 자식 프로세스에서 C ++ 부모 앱 을 호출해야했습니다 . 자식 프로세스는 프로세스가 종료되도록 보장하고 부모는 기존 참조를 사용하여 자식이 종료되도록보다 강력한 솔루션 인 것 같습니다. 이것을 부모 프로세스가 원할 때마다 종료하고 종료하기 전에 고아인지 확인하려고 시도했던 이전 솔루션과 비교하십시오.


1
PID equals 1대기가 유효하지 않습니다. 새로운 부모는 다른 PID 일 수 있습니다. 원래 부모 (fork () 앞의 getpid ())에서 새 부모 (fork () 전에 호출 될 때 getpid ()와 같지 않은 자식의 getppid ())로 변경되는지 확인해야합니다.
Alexis Wilke

1

Linux 고유의 다른 방법은 부모를 새로운 PID 네임 스페이스에서 생성하는 것입니다. 그런 다음 해당 네임 스페이스에서 PID 1이되고 종료되면 모든 하위 항목이 즉시 종료됩니다 SIGKILL.

불행히도, 새로운 PID 네임 스페이스를 만들려면가 있어야 CAP_SYS_ADMIN합니다. 그러나이 방법은 매우 효과적이며 부모가 처음 시작한 이후에는 부모 나 자녀를 실질적으로 변경할 필요가 없습니다.

참조 클론 (2) , pid_namespaces (7) , 및 공유 해제 (2) .


다른 방법으로 편집해야합니다. prctl을 사용하여 프로세스가 자식 및 손자, 증손자 등을위한
초기

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

완벽하지 않은 두 가지 솔루션을 찾았습니다.

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 를 사용하여 제어 터미널이 연결된 상태에서 프로세스를 생성하십시오.
어쨌든 현재 프로세스를 죽일 때조차도 -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에서는 작동하지 않았습니다.

프로세스가 종료 될 때까지 기다리거나 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();
}

0

역사적으로 UNIX v7부터 프로세스 시스템은 프로세스의 상위 ID를 확인하여 프로세스의 고아를 감지했습니다. 내가 말했듯이 역사적으로 init(8)시스템 프로세스는 한 가지 이유에 의해서만 특별한 프로세스입니다. 죽을 수 없습니다. 새로운 부모 프로세스 ID 할당을 처리하는 커널 알고리즘은이 사실에 의존하기 때문에 죽을 수 없습니다. 프로세스가 프로세스를 실행 하면 시스템 호출 전에 프로세스가 고아가됩니다.exit(2) 호출을 (프로세스 시스템 호출 또는 신호를 보내는 등의 외부 작업을 통해) 커널은이 프로세스의 모든 자식을 init 프로세스의 id를 상위 프로세스 ID로 다시 할당합니다. 이는 프로세스가 고아인지 여부를 확인하는 가장 쉬운 테스트 및 가장 이식 가능한 방법으로 이어집니다. getppid(2)시스템 호출 의 결과를 확인하고 시스템 호출의 프로세스 ID인지 확인하십시오.init(2)

이 접근법에서 문제를 일으킬 수있는 두 가지 문제가 있습니다.

  • 먼저 init프로세스를 사용자 프로세스 로 변경할 가능성이 있으므로 init 프로세스가 항상 모든 고아 프로세스의 부모가되도록하려면 어떻게해야합니까? 음,에 exit시스템 호출 코드 호출을 실행하는 프로세스는 init 프로세스 (PID의 발행 주식 등을 1로 처리) 인 경우 볼 수있는 명시 적 검사가있다 그 사건을의 경우, 커널 패닉은 (는 유지 관리가 더 이상 할 수 없게한다 init 프로세스가 exit(2)호출 을 수행하는 것이 허용되지 않습니다 .
  • 둘째, 위에 공개 된 기본 테스트에는 경쟁 조건이 있습니다. Init 프로세스의 id는 역사적으로로 간주 1되지만 POSIX 접근 방식에서는 보증하지 않지만 다른 응답에 노출 된 것처럼 시스템 프로세스 ID 만 해당 용도로 예약되어 있습니다. posix 구현은 거의 수행하지 않으며 원래 유닉스 파생 시스템 1에서는 getppid(2)시스템 호출의 응답으로 프로세스가 고아라고 가정하기에 충분하다고 가정 할 수 있습니다. 확인하는 또 다른 방법 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 thegetppid (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 프로세스를 대체하는 경우 제외)

-1

환경을 사용하여 부모 pid를 자식에게 전달한 다음 자식에서 / proc / $ ppid가 있는지 정기적으로 확인했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.