web-dev-qa-db-fra.com

Tuez tous les processus descendants

J'écris une candidature. Il a la capacité de générer divers processus externes. Lorsque l'application se ferme, je veux que tous les processus qu'elle a engendrés soient tués.

Cela semble assez facile, non? Recherchez mon PID et parcourez récursivement l'arborescence des processus, en tuant tout en vue, de bas en haut.

Sauf que cela ne fonctionne pas travail. Dans un cas spécifique, j'apparaît foo, mais foo ne fait qu'apparaître bar, puis se ferme immédiatement, laissant bar en cours d'exécution. Il y a maintenant pas d'enregistrement du fait que bar faisait autrefois partie de l'arbre de processus de l'application. Et par conséquent, l'application n'a aucun moyen de savoir qu'elle doit tuer bar.

Je suis presque sûr que je ne peux pas être la première personne sur Terre à essayer de faire ça. Alors, quelle est la solution standard? Je suppose que je cherche vraiment un moyen de "marquer" un processus de telle manière que tout processus qu'il engendrera héritera inconditionnellement de la même balise.

(Jusqu'à présent, le mieux que je puisse trouver est d'exécuter l'application en tant qu'utilisateur différent. De cette façon, vous pouvez simplement tuer sans discrimination tous les processus appartenant à cet utilisateur. Mais cela a toutes sortes de problèmes de permission d'accès ...)

62
MathematicalOrchid

Mise à jour

C'est l'un de ceux où j'aurais clairement dû lire la question plus attentivement (bien que ce soit apparemment le cas avec la plupart des réponses à cette question). J'ai laissé la réponse originale intacte car elle donne de bonnes informations, même si elle manque clairement le point de la question.

Utilisation de SID

Je pense que l'approche la plus générale et la plus robuste ici (au moins pour Linux) consiste à utiliser SID (Session ID) plutôt que PPID ou PGID. Cela est beaucoup moins susceptible d'être modifié par les processus enfants et, dans le cas du script Shell, la commande setsid peut être utilisée pour démarrer une nouvelle session. En dehors du shell, l'appel système setuid peut être utilisé.

Pour un Shell qui est un leader de session, vous pouvez tuer tous les autres processus de la session en faisant (le Shell ne se tuera pas):

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

Remarque: l'argument de connexion égal à la fin pid= Supprime l'en-tête de colonne PID.

Sinon, en utilisant les appels système, appeler getsid pour chaque processus semble être le seul moyen.

Utilisation d'un espace de noms PID

C'est l'approche la plus robuste, mais les inconvénients sont qu'il s'agit uniquement de Linux et qu'il a besoin des privilèges root. Les outils Shell (s'ils sont utilisés) sont également très récents et peu disponibles.

Pour une discussion plus détaillée des espaces de noms PID, veuillez consulter cette question - Moyen fiable pour emprisonner des processus enfants en utilisant `nsenter:` . L'approche de base ici est que vous pouvez créer un nouvel espace de noms PID en utilisant l'indicateur CLONE_NEWPID Avec l'appel système clone (ou via la commande unshare).

Lorsqu'un processus dans un espace de noms PID est orphelin (c'est-à-dire lorsque le processus parent se termine), il est re-parenté vers le processus d'espace de noms PID de niveau supérieur plutôt que init. Cela signifie que vous pouvez toujours identifier tous les descendants du processus de niveau supérieur en parcourant l'arborescence des processus. Dans le cas d'un script Shell, l'approche PPID ci-dessous tuerait alors de manière fiable tous les descendants.

Pour en savoir plus sur les espaces de noms PID:

Réponse originale

Tuer les processus enfants

La manière la plus simple de le faire dans un script Shell, à condition que pkill soit disponible est:

pkill -P $$

Cela tue tous les enfants du processus donné en cours ($$ Se développe en PID du shell actuel).

Si pkill n'est pas disponible, une méthode compatible POSIX est:

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

Tuer tous les processus descendants

Une autre situation est que vous voudrez peut-être tuer tous les descendants du processus Shell actuel ainsi que les enfants directs. Dans ce cas, vous pouvez utiliser la fonction Shell récursive ci-dessous pour répertorier tous les PID descendants, avant de les passer comme arguments à tuer:

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

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

  echo "$children"
}

kill $(list_descendants $$)

Fourches doubles

Une chose à prendre en compte, qui pourrait empêcher ce qui précède de fonctionner comme prévu, est la technique double fork(). Ceci est couramment utilisé lors de la démonétisation d'un processus. Comme son nom l'indique, le processus qui doit être démarré s'exécute dans la deuxième fourchette du processus d'origine. Une fois le processus démarré, le premier fork se termine, ce qui signifie que le processus devient orphelin.

Dans ce cas, il deviendra un enfant du processus init au lieu du processus d'origine à partir duquel il a été démarré. Il n'y a aucun moyen robuste d'identifier quel processus était le parent d'origine, donc si c'est le cas, vous ne pouvez pas vous attendre à pouvoir le tuer sans avoir d'autres moyens d'identification (un fichier PID par exemple). Cependant, si cette technique a été utilisée, vous ne devriez pas essayer de tuer le processus sans raison valable.

Lectures complémentaires:

57
Graeme

Vous pouvez utiliser:

kill -TERM -- -XXX

XXX est le numéro de groupe du groupe de processus que vous voulez tuer. Vous pouvez le vérifier en utilisant:

 $ 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

Pour plus de détails sur l'ID des groupes de processus, vous pouvez voir 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.
23
cuonglm

Si vous connaissez le processus PID parent, vous pouvez le faire en utilisant pkill.

Exemple

$ pkill -TERM -P 27888

Où le PPID est 27888.

extrait de pkill man

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

Quel est mon PID dans un script?

C'est probablement votre prochaine question, donc quand dans un script Bash vous pouvez trouver le PID du script en utilisant $$ au sommet.

Exemple

Disons que j'ai ce script:

$ more somescript.bash 
#!/bin/bash

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

Maintenant, je l'exécute, en arrière-plan:

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

Un coup d'œil avec pgrep montre que nous avons le bon PID:

$ pgrep somescript.bash
28007
$ bottom: 28007

[2]+  Done                    ./somescript.bash

Utilisation du PGID d'un processus

Si vous utilisez cette commande ps, vous pouvez trouver un PGID de processus que vous pouvez tuer à la place.

En utilisant maintenant ce script, killies.bash:

$ more killies.bash 
#!/bin/bash

sleep 1000 &
sleep 1000 &
sleep 1000 &

sleep 100

Nous le faisons comme ça:

$ killies.bash &

Enregistrement dessus:

$ 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

Maintenant, nous tuons le PGID:

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

Méthodes supplémentaires

Si vous regardez ceci SO Q&A, vous trouverez encore plus de méthodes pour faire ce que vous voulez:

Références

11
slm

La meilleure façon de procéder consiste à utiliser systemd (ou une autre façon à l'aide de cgroups) pour démarrer et arrêter l'application. Un processus peut quitter son groupe de processus mais ne peut jamais (du moins pas sans privilège root) quitter son groupe de contrôle. Ainsi, systemd crée un nouveau groupe de contrôle pour le nouveau processus et tue plus tard tout simplement dans le groupe de contrôle.

5
Hauke Laging
${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

Cela tue tous les enfants de ${PROC_CMD}, qui est en arrière-plan dans la première ligne et son $pid capturé dans le prochain.

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

Ce qui précède énumérera simplement les processus enfants.

Il est important de se rappeler qu'un processus peut s'échapper son $PPID. Par exemple:

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

Pid: 349 PPid: 1 TracerPid: 0
2
mikeserv