Pergunta

Suponha que eu tenho um processo que gera exatamente um processo filho. Agora, quando o processo é encerrado pai por qualquer motivo (normal ou anormal, por matar, ^ C, insuficiência assert ou qualquer outra coisa) Eu quero o processo filho para morrer. Como fazer isso corretamente?


Alguns pergunta semelhante sobre stackoverflow:


Alguns pergunta semelhante sobre stackoverflow para Windows :

Foi útil?

Solução

Criança pode pedir kernel para entregar SIGHUP (ou outro sinal), quando morre pais especificando opção PR_SET_PDEATHSIG em syscall prctl() assim:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Veja man 2 prctl para mais detalhes.

Edit: Este é o Linux-only

Outras dicas

Eu estou tentando resolver o mesmo problema, e uma vez que o meu programa deve ser executado em OS X, o Linux-única solução não funcionou para mim.

cheguei à mesma conclusão que as outras pessoas nesta página - não há uma maneira compatível com POSIX de notificar uma criança quando um pai morre. Então eu kludged até a próxima melhor coisa -. Ter a sondagem criança

Quando um processo pai morre (por qualquer motivo) processo pai da criança torna-se processo 1. Se a criança simplesmente enquetes periodicamente, pode verificar se o seu pai é 1. Se for, a criança deve sair.

Este não é grande, mas funciona, e é mais fácil do que o soquete TCP soluções de votação / lockfile sugeriu em outro lugar nesta página.

eu tenho conseguido isso no passado, executando o código "original" na "criança" e o código "desovado" no "pai" (ou seja: você inverter o sentido usual do teste após fork()). Então SIGCHLD armadilha no código "gerou" ...

pode não ser possível no seu caso, mas bonito quando ele funciona.

Se você é incapaz de modificar o processo de criança, você pode tentar algo como o seguinte:

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 */

Este é executado a criança de dentro de um processo shell com controle de trabalho habilitado. O processo filho é gerado em segundo plano. O aguarda shell para uma nova linha (ou um EOF) depois mata a criança.

Quando as matrizes pais - não importa o que a razão - que vai fechar a sua extremidade do tubo. O shell criança receberá um EOF da leitura e prossiga para matar o processo filho em segundo plano.

Para sermos mais completos. No MacOS você pode usar 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);
}

O processo filho tem um tubo de / para o processo pai? Se assim for, você receberia um SIGPIPE se a escrita, ou get EOF ao ler -. Estas condições podia ser detectado

No Linux, você pode instalar um sinal de morte dos pais na criança, por exemplo:.

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

Note que armazenar o ID do processo pai antes do garfo e testá-lo na criança após o prctl() elimina uma condição de corrida entre prctl() e a saída do processo que chamou a criança.

Observe também que o sinal de morte pai da criança é eliminado em crianças recém-criados de seu próprio. Não é afetado por uma execve().

Esse teste pode ser simplificado se estamos certos de que o processo do sistema que está encarregado de adoptar todas as órfãos tem 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 ...

Baseando-se nesse processo sistema que está sendo init e ter PID 1 não é portátil, no entanto. POSIX.1-2008 especifica :

O ID do processo pai de todos os processos filhos existentes e processos zombie do processo de chamada deve ser configurado para o ID do processo de um processo do sistema definido pela implementação. Ou seja, estes processos devem ser herdado por um processo de sistema especial.

Tradicionalmente, o processo do sistema adopta todas órfãos é PID 1, isto é, o init -. Que é o antepassado de todos os processos

Em sistemas modernos como Linux ou FreeBSD outro processo pode ter esse papel. Por exemplo, no Linux, um processo pode chamar prctl(PR_SET_CHILD_SUBREAPER, 1) para estabelecer-se como processo de sistema que herda todos os órfãos de qualquer dos seus descendentes (cf. uma exemplo sobre Fedora 25).

Inspirado em outra resposta aqui, eu vim com a seguinte solução all-POSIX. A idéia geral é criar um processo intermediário entre o pai ea criança, que tem uma finalidade:. Notar quando as matrizes pai, e matar explicitamente a criança

Este tipo de solução é útil quando o código em que a criança não pode ser modificado.

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

Existem duas pequenas ressalvas com este método:

  • Se você deliberadamente matar o processo intermediário, então a criança não será morto quando as matrizes pais.
  • Se a criança sai antes do pai, em seguida, o processo intermediário vai tentar matar o pid criança original, que poderia agora se referem a um processo diferente. (Isto poderia ser fixado com mais de código no processo intermediário.)

Como um aparte, o código real que estou usando é em Python. Aqui é para ser completo:

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)

Eu não acredito que é possível para garantir que o uso de apenas chamadas POSIX padrão. Como na vida real, uma vez que uma criança é gerado, ele tem uma vida própria.

é possível para o processo pai para pegar mais possíveis eventos de terminação, e tentar matar o processo filho nesse ponto, mas há sempre alguns que não pode ser capturado.

Por exemplo, nenhum processo pode pegar um SIGKILL. Quando as alças do kernel este sinal que vai matar o processo especificado sem notificação nesse processo qualquer.

Para estender a analogia -. A única outra maneira padrão de fazer isso é para a criança a cometer suicídio quando ele descobre que ele já não tem um pai

Há um Linux-única maneira de fazê-lo com prctl(2) - ver outras respostas

.

Como outras pessoas têm apontado, contando com o PID pai para se tornar um quando sai do pais é não-portáteis. Em vez de esperar por um determinado ID do processo pai, é só esperar para o ID de mudança:

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

Adicionar um micro-sono como desejado se você não quer poll a toda a velocidade.

Esta opção parece mais simples para mim do que usar um tubo ou depender de sinais.

Instale um manipulador armadilha para SIGINT captura, que mata fora de seu processo filho se ele ainda está vivo, embora outros cartazes estão correctas que não vai pegar SIGKILL.

Abra um .lockfile com acesso exclusivo e ter a sondagem criança nele tentando abri-la - se a abertura ocorre, o processo filho deve sair

Esta solução funcionou para mim:

  • Passe stdin tubo para criança -. Você não tem que escrever todos os dados no fluxo
  • A criança lê indefinidamente a partir de stdin até EOF. Um sinais EOF que o pai se foi.
  • Este é infalível e forma portátil para detectar quando o pai passou. Mesmo se acidentes pais, OS irá fechar o tubo.

Este foi para um processo do tipo trabalhador cuja existência só faz sentido quando o pai estava vivo.

Eu acho que uma maneira rápida e suja é a criação de um tubo entre a criança eo pai. Quando sai de pais, as crianças receberão um SIGPIPE.

Alguns cartazes já tubos e kqueue mencionado. Na verdade, você também pode criar um par de sockets domínio Unix conectados pela chamada socketpair(). O tipo de socket deve ser SOCK_STREAM.

Vamos supor que você tem o arquivo dois soquetes descritores fd1, fd2. Agora fork() para criar o processo filho, que vai herdar as fds. No pai você perto fd2 e na criança que feche fd1. Agora, cada processo pode poll() o fd aberto restante no seu próprio fim para o evento POLLIN. Enquanto cada lado não close() explicitamente a sua fd durante a vida normal, você pode ter certeza de que uma bandeira POLLHUP deve indicar rescisão do outro (não importa limpo ou não). Após a notificação deste evento, a criança pode decidir o que fazer (por exemplo, para morrer).

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

Você pode tentar compilar o código de prova de conceito acima, e executá-lo em um terminal como ./a.out &. Você tem cerca de 100 segundos para experimentar matar o PID pai por vários sinais, ou ele vai simplesmente sair. Em ambos os casos, você deve ver o "filho: pai desligou" mensagem

.

Em comparação com o método usando manipulador SIGPIPE, este método não requer tentando a chamada write().

Este método é também simétrica , isto é, os processos podem utilizar o mesmo canal para controlar a existência de uns aos outros.

Esta solução exige apenas as funções POSIX. Eu tentei isso em Linux e FreeBSD. Eu acho que deve funcionar em outros Unixes, mas eu realmente não tenho testado.

Veja também:

  • unix(7) de páginas man Linux, unix(4) para FreeBSD, poll(2), socketpair(2), socket(7) no Linux.

POSIX , os exit(), funções _exit() e _Exit() são definidos para :

  • Se o processo é um processo de controle, o sinal SIGHUP será enviado para cada processo no grupo de processos de primeiro plano do controle do terminal pertencente ao processo de chamada.

Então, se você mandar para o processo pai para ser um processo de controle para o seu grupo processo, a criança deve receber um sinal SIGHUP quando as saídas principais. Eu não estou absolutamente certo de que acontece quando o pai deixa de funcionar, mas eu acho que ele faz. Certamente, para os casos não-acidente, ele deve funcionar bem.

Note que você pode ter que ler um monte de letras miúdas - incluindo as definições Base (Definições) seção, bem como as informações Serviços do Sistema para exit() e setsid() e setpgrp() - para obter a imagem completa. (Então, seria eu!)

Se você enviar um sinal para o pid 0, usando, por exemplo

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

que sinal é enviado para todo o grupo de processos, assim, matando efetivamente a criança.

Você pode testá-lo facilmente com algo como:

(cat && kill 0) | python

Se você pressione ^ D, você verá o "Terminated" texto como uma indicação de que o Python intérprete foram de fato mortos, em vez de apenas saiu por causa de STDIN sendo fechado.

No caso, é relevante para qualquer outra pessoa, quando eu desova instâncias JVM em processos filhos bifurcados de C ++, a única maneira que eu poderia obter as instâncias JVM para terminar corretamente após o processo pai completou era fazer o seguinte. Esperemos que alguém pode fornecer feedback nos comentários se esta não era a melhor maneira de fazer isso.

1) prctl(PR_SET_PDEATHSIG, SIGHUP) Chamar no processo filho bifurcada como sugerido antes de lançar o Java aplicativo via execv, e

2) Adicionar um gancho de desligamento para a aplicação Java que as pesquisas até que seu pai PID igual a 1, em seguida, fazer uma Runtime.getRuntime().halt(0) duro. A votação é feita por lançar um shell separado que executa o comando ps (Veja: Como faço para encontrar o meu PID em Java ou JRuby no Linux? ).

EDIT 130118:

Parece que não era uma solução robusta. Eu ainda estou lutando um pouco para entender as nuances do que está acontecendo, mas eu ainda estava por vezes ficando processos órfão JVM ao executar esses aplicativos em sessões de tela / SSH.

Em vez da votação para o PPID no aplicativo Java, eu simplesmente tinha o gancho de desligamento executar a limpeza seguido por uma parada dura como acima. Em seguida, tenho a certeza de invocar waitpid no C ++ aplicativo pai no processo filho gerado quando era hora de terminar tudo. Esta parece ser uma solução mais robusta, como é assegurado processo filho que ele termina, enquanto os usos pai existente referências para se certificar de que seus filhos terminar. Compare isso com a solução anterior, que teve o processo pai terminar sempre que quisesse, e tinha as crianças tentar descobrir se tivessem sido órfão antes de terminar.

Se morre pai, PPID de órfãos mudar para 1 - você só precisa verificar o seu próprio PPID. De certa forma, este é polling, mencionado acima. aqui é peça shell para isso:

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

Eu encontrei 2 soluções, ambos não aperfeiçoar.

1.Kill todas as crianças por kill (-pid) quando recebeu sinal SIGTERM.
Obviamente, esta solução não pode lidar com "kill -9", mas fazer o trabalho para a maioria de caso e muito simples porque não precisa se lembrar de todos os processos filho.


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

Ao mesmo modo, você pode instalar 'exit' manipulador como maneira acima se você chamar process.exit em algum lugar. Nota:. Ctrl + C e queda súbita foram processados ??automaticamente pelo sistema operacional para matar grupo de processo, por isso não mais aqui

chjj / pty.js para desovar seu processo com o controle terminal conectado.
Quando você matar processo atual de qualquer maneira, mesmo kill -9, todos os processos filhos serão automaticamente matou também (por OS?). Eu acho que por causa do processo atual realizar outro lado do terminal, assim que se morre processo atual, o processo filho vai ter SIGPIPE assim morre.


    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(.....);
    */

Eu consegui fazer uma solução portátil, não-polling com 3 processos por abusar de controle terminal e sessões. Esta é masturbação mental, mas obras.

O truque é:

  • processo A é iniciado
  • Um processo cria um tubo P (e nunca lê a partir dele)
  • processar um garfos em processo B
  • processo B cria uma nova sessão
  • processo B aloca um terminal virtual para essa nova sessão
  • processo B instala manipulador SIGCHLD a morrer quando a criança sai
  • processo B define um manipulador SIGPIPE
  • garfos processo B em processo C
  • processo C faz tudo o que ele precisa (por exemplo exec () é o binário não modificada ou executa qualquer lógica)
  • processo B escreve para tubo P (e blocos que forma)
  • processo Uma espera () s em processo B e sai quando morre

Essa maneira:

  • se morre processo A: Processo B recebe um SIGPIPE e matrizes
  • se morre processo B: processo de espera de A () retorna e morre, processo C recebe um SIGHUP (porque, quando o líder da sessão de uma sessão com um terminal morre em anexo, todos os processos no grupo de processos de primeiro plano obter um SIGHUP)
  • se morre processo C: Processo B recebe um SIGCHLD e matrizes, assim morre processar um

Deficiências:

  • processo C não pode lidar com SIGHUP
  • processo C será executado em uma sessão diferente
  • processo C não pode usar sessão / processo do grupo API porque ele vai quebrar a configuração frágil
  • criação de um terminal para cada tal operação não é a melhor ideia nunca

Apesar de 7 anos se passaram Acabei de passar por essa situação que eu estou correndo aplicação SpringBoot que as necessidades para começar Webpack-dev-servidor durante o desenvolvimento e as necessidades para matá-lo quando o processo servidor pára.

Eu tento usar Runtime.getRuntime().addShutdownHook mas funcionou no Windows 10, mas não no Windows 7.

Eu tenho alterá-lo para usar um segmento dedicado que aguarda o processo de parar de fumar ou para InterruptedException que parece funcionar corretamente em ambas as versões do 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();
}

orphanity Historicamente, a partir UNIX v7, o sistema de processo detectou dos processos, verificando um processo de id pai. Como eu digo, historicamente, o processo do sistema init(8) é um processo especial por uma única razão: Ele não pode morrer. Ela não pode morrer porque o algoritmo kernel para lidar com a atribuição de um novo ID de processo pai, depende deste fato. quando um processo executa o seu apelo exit(2) (por meio de uma chamada de sistema de processo ou por tarefa externo como enviá-lo um sinal ou similar) o kernel atribui novamente todos os filhos deste processo o id do processo init como seu ID de processo pai. Isto leva a que o teste mais fácil, e mais maneira portátil de saber se um processo tem órfão. Basta verificar o resultado da chamada de sistema getppid(2) e se é o ID do processo do processo init(2) então o processo tem órfão antes da chamada do sistema.

Duas questões emergem dessa abordagem que pode levar a problemas de:

  • em primeiro lugar, temos a possibilidade de mudar o processo init a qualquer processo de usuário, então, como podemos assegurar que o processo de inicialização sempre será pai de todos os processos órfãos? Bem, no código de chamada de sistema exit há uma verificação explícita para ver se o processo de executar a chamada é o processo init (o processo com pid igual a 1) e se esse for o caso, os kernel panic (Não deve ser mais capaz de manter a hierarquia processo), por isso não é permitido para o processo init para fazer uma chamada exit(2).
  • Em segundo lugar, há uma condição de corrida no teste básico exposto acima. Init processo id é assumido historicamente ser 1, mas isso não é garantido pela abordagem POSIX, que os estados (como exposto em outra resposta) que somente ID do processo de um sistema é reservado para o efeito. Quase nenhuma implementação posix faz isso, e você pode assumir em UNIX original derivada sistemas que ter 1 como a resposta de chamada de sistema getppid(2) é suficiente para assumir o processo é órfão. Outra forma de verificar é fazer uma getppid(2) logo após o garfo e comparar esse valor com o resultado de uma nova chamada. Isso simplesmente não funciona em todos os casos, tanto como chamada não estão juntos atômica, e o processo pai pode morrer após o fork(2) e antes da primeira chamada de sistema getppid(2). O processparent id only changes once, when its parent does anexit (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) `, mas você pode seguramente assumir esses processos como tendo nenhum pai quer (exceto quando você substituir em um sistema o processo init)

Outra maneira de fazer isso que é específica do Linux é ter o pai ser criado em um novo namespace PID. Será então PID 1 em que namespace, e quando ele sai tudo ele é de crianças será imediatamente morto com SIGKILL.

Infelizmente, a fim de criar um novo namespace PID você tem que ter CAP_SYS_ADMIN. Mas, este método é muito eficaz e não requer nenhuma mudança real para os pais ou os filhos para além do lançamento inicial do pai.

clone (2) , noreferrer pid_namespaces (7) , e unshare (2) .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top