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 ...)
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.
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.
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:
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 $$)
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 $$)
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:
Vous pouvez utiliser:
kill -TERM -- -XXX
où 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.
Si vous connaissez le processus PID parent, vous pouvez le faire en utilisant pkill
.
$ 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.
C'est probablement votre prochaine question, donc quand dans un script Bash vous pouvez trouver le PID du script en utilisant $$
au sommet.
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
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
Si vous regardez ceci SO Q&A, vous trouverez encore plus de méthodes pour faire ce que vous voulez:
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.
${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