Mate todos os processos descendentes [duplicados]

39

Estou escrevendo um aplicativo. Tem a capacidade de gerar vários processos externos. Quando o aplicativo é fechado, quero que todos os processos que ele gerou sejam mortos.

Parece fácil o suficiente, certo? Procure o meu PID e ande de forma recursiva na árvore de processos, matando tudo o que estiver à vista, estilo bottom-up.

Só que isso não funciona . Em um caso específico, eu gero foo , mas foo apenas gera bar e, em seguida, sai imediatamente, deixando bar em execução. Existe agora sem registro do fato de que bar era uma vez parte da árvore de processos do aplicativo. E, portanto, o aplicativo não tem como saber que ele deve matar bar .

Tenho certeza que não posso ser a primeira pessoa na Terra a tentar fazer isso. Então, qual é a solução padrão? Eu acho que estou realmente procurando por uma maneira de "marcar" um processo de modo que qualquer processo gerado herde incondicionalmente a mesma tag.

(Até agora, o melhor que posso fazer é rodar o aplicativo como um usuário diferente. Dessa forma, você pode simplesmente matar todos os processos que afetam o usuário. Mas isso tem todos os tipos de problemas de permissão de acesso ... )

    
por MathematicalOrchid 10.04.2014 / 17:45

5 respostas

33

Atualizar

Este é um daqueles em que eu deveria claramente ter lido a questão com mais cuidado (embora aparentemente este seja o caso com a maioria das respostas a esta pergunta). Deixei a resposta original intacta, porque ela fornece boas informações, mesmo que claramente perca o ponto de vista da questão.

Usando o SID

Acho que a abordagem mais geral e robusta aqui (pelo menos para o Linux) é usar SID (ID de sessão) em vez de PPID ou PGID. É muito menos provável que isso seja alterado por processos filho e, no caso de script de shell, o comando setsid pode ser usado para iniciar uma nova sessão. Fora do shell, a chamada do sistema setuid pode ser usada.

Para um shell que é um líder de sessão, você pode matar todos os outros processos da sessão (o shell não se mata):

kill $(ps -s $$ -o pid=)

Observação: o sinal de trailing equals no argumento pid= remove o cabeçalho da coluna PID .

Caso contrário, usando chamadas do sistema, a chamada getsid para cada processo parece ser a única maneira.

Usando um PID namspace

Essa é a abordagem mais robusta, mas as desvantagens são que ela é apenas Linux e que precisa de privilégios de root. Além disso, as ferramentas shell (se usadas) são muito novas e não estão amplamente disponíveis.

Para uma discussão mais detalhada dos namespaces do PID, consulte esta questão - Maneira confiável de processar processos filhos usando 'nsenter:' . A abordagem básica aqui é que você pode criar um novo namespace PID usando o sinalizador CLONE_NEWPID com a chamada do sistema clone (ou por meio do comando unshare ).

Quando um processo em um namespace PID é órfão (ou seja, quando o processo pai é concluído), ele é enviado novamente para o processo de namespace PID de nível superior, em vez do init . Isso significa que você sempre pode identificar todos os descendentes do processo de nível superior percorrendo a árvore de processos. No caso de um shell script, a abordagem PPID abaixo mataria de forma confiável todos os descendentes.

Outras leituras nos namespaces do PID:

Resposta Original

Matando processos filhos

A maneira mais fácil de fazer isso em um script de shell, desde que pkill esteja disponível, é:

pkill -P $$

Isso mata todos os filhos do processo atual ( $$ se expande para o PID do shell atual).

Se pkill não estiver disponível, uma maneira compatível com POSIX é:

kill $(ps -o pid= --ppid $$)

Matando todos os processos descendentes

Outra situação é que você pode querer matar todos os descendentes do processo shell atual, bem como apenas os filhos diretos. Neste caso, você pode usar a função shell recursiva abaixo para listar todos os PIDs descendentes, antes de passá-los como argumentos a serem mortos:

list_descendants ()
{
  local children=$(ps -o pid= --ppid "$1")

  for pid in $children
  do
    list_descendants "$pid"
  done

  echo "$children"
}

kill $(list_descendants $$)

Garfos duplos

Uma coisa a se tomar cuidado, que pode impedir que o acima funcione como esperado, é a técnica do fork() duplo. Isso é comumente usado durante a daemonização de um processo. Como o nome sugere, o processo a ser iniciado é executado na segunda bifurcação do processo original. Depois que o processo é iniciado, o primeiro fork sai, o que significa que o processo se torna órfão.

Nesse caso, ele se tornará filho do processo init em vez do processo original do qual foi iniciado. Não existe uma maneira robusta de identificar qual processo era o pai original, portanto, se este for o caso, você não pode esperar poder eliminá-lo sem ter outros meios de identificação (um arquivo PID, por exemplo). No entanto, se esta técnica tiver sido usada, você não deve tentar matar o processo sem uma boa razão.

Leitura adicional:

por 10.04.2014 / 20:39
20

Você pode usar:

kill -TERM -- -XXX

em que XXX é o número do grupo do grupo de processos que você deseja eliminar. Você pode verificar usando:

 $ ps x -o  "%p %r %c"
 PID   PGID COMMAND
 2416  1272 gnome-keyring-d
 2427  2427 gnome-session
 2459  2427 lightdm-session <defunct>
 2467  2467 ssh-agent
 2470  2427 dbus-launch
 2471  2471 dbus-daemon
 2484  2427 gnome-settings-
 2489  2471 gvfsd
 2491  2471 gvfs-fuse-daemo
 2499  2427 compiz
 2502  2471 gconfd-2
 2508  2427 syndaemon
 2513  2512 pulseaudio
 2517  2512 gconf-helper
 2519  2471 gvfsd-metadata

Para mais detalhes sobre o ID de grupos de processos, você pode ver man setpgid :

DESCRIPTION
       All  of  these interfaces are available on Linux, and are used for get‐
       ting and setting the process group ID (PGID) of a  process.   The  pre‐
       ferred,  POSIX.1-specified  ways  of doing this are: getpgrp(void), for
       retrieving the calling process's PGID; and  setpgid(),  for  setting  a
       process's PGID.

       setpgid()  sets  the  PGID of the process specified by pid to pgid.  If
       pid is zero, then the process ID of the calling process  is  used.   If
       pgid is zero, then the PGID of the process specified by pid is made the
       same as its process ID.  If setpgid() is used to move  a  process  from
       one  process  group to another (as is done by some shells when creating
       pipelines), both process groups must be part of the same  session  (see
       setsid(2)  and  credentials(7)).   In  this case, the pgid specifies an
       existing process group to be joined and the session ID  of  that  group
       must match the session ID of the joining process.
    
por 10.04.2014 / 17:53
10

Se você conhece o PID de processos pai, você pode fazer isso usando pkill .

Exemplo

$ pkill -TERM -P 27888

Onde o PPID é 27888.

trecho de pkill man

   -P, --parent ppid,...
          Only match processes whose parent process ID is listed.

Qual é o meu PID em um script?

Esta é provavelmente a sua próxima pergunta, por isso, quando estiver em um script Bash, você poderá descobrir o PID do script usando $$ no topo.

Exemplo

Digamos que eu tenha este script:

$ more somescript.bash 
#!/bin/bash

echo "top: $$"
sleep 5
echo "bottom: $$"

Agora eu corri, com fundo:

$ ./somescript.bash &
[2] 28007
top: 28007

Espreitando-o com pgrep mostra que temos o PID certo:

$ pgrep somescript.bash
28007
$ bottom: 28007

[2]+  Done                    ./somescript.bash

Usando um processo 'PGID

Se você usar esse comando ps , poderá descobrir um PGID de processos, que pode ser eliminado usando.

Usando agora este script, killies.bash :

$ more killies.bash 
#!/bin/bash

sleep 1000 &
sleep 1000 &
sleep 1000 &

sleep 100

Nós o rodamos assim:

$ killies.bash &

Verificando:

$ ps x -o  "%p %r %c"
  PID  PGID COMMAND
28367 28367 killies.bash
28368 28367 sleep
28369 28367 sleep
28370 28367 sleep
28371 28367 sleep

Agora, eliminamos o PGID:

$ pkill -TERM -g 28367
[1]+  Terminated              ./killies.bash

Métodos adicionais

Se você der uma olhada neste SO Q & A, você encontrará ainda mais métodos para fazer o que quiser:

Referências

por 10.04.2014 / 17:49
3

A melhor maneira de fazer isso é usar systemd (ou outra maneira usando cgroups) para iniciar e parar o aplicativo. Um processo pode deixar seu grupo de processos, mas nunca (pelo menos não sem privilégio de root) deixar seu cgroup. Assim, systemd cria um novo cgroup para o novo processo e depois simplesmente mata tudo no cgroup.

    
por 10.04.2014 / 19:51
2
${PROC_CMD} &
pid=$!
kids=$(grep -l "PPid.*$$" /proc/*/status | grep -o "[0-9]*"
    for kid in $(cat /proc/$pid/task/*/children); do 
        kids="$kid $kids $(cat /proc/$kid/task/*/children)"
    done
    printf '%s ' $kids)
kill $kids

Isso mata todos os filhos de ${PROC_CMD}, , que é capturado em segundo plano na primeira linha e seus $pid capturados no próximo.

cat /proc/$pid/task/*/children

Os itens acima apenas listarão os processos filhos.

É importante lembrar que um processo pode escapar seu $PPID. Por exemplo:

echo $( grep "Pid" /proc/self/status & )

Pid: 349 PPid: 1 TracerPid: 0
    
por 10.04.2014 / 18:04

Tags