Supposons, par exemple, que vous ayez un script shell semblable à:
longrunningthing &
p=$!
echo Killing longrunningthing on PID $p in 24 hours
sleep 86400
echo Time up!
kill $p
Devrait faire le tour, ne devrait pas? Sauf que le processus s'est peut-être terminé tôt et que son PID a peut-être été recyclé, ce qui signifie que certains travaux innocents obtiennent une bombe dans sa file d'attente de signaux. En pratique, cela a peut-être de l'importance, mais cela m'inquiète néanmoins. Le piratage à long terme pour laisser tomber tout seul, ou conserver / supprimer son PID sur le système de service ferait l'affaire, mais je pense à la situation générique ici.
killall
correspondances sur le nom, de sorte qu'au moins vous ne supprimez qu'un processus portant le même nom quelongrunningthing
. En supposant que vous n’en aurez qu’un seul à la fois.Réponses:
Le mieux serait d'utiliser la
timeout
commande si vous l'avez, ce qui est fait pour ça:L’implémentation GNU actuelle (8.23) fonctionne au moins en utilisant
alarm()
ou l’équivalent en attendant le processus enfant. Il ne semble pas se prémunir contre laSIGALRM
livraison entre lewaitpid()
retour et latimeout
sortie (ce qui annule effectivement cette alarme ). Pendant cette petite fenêtre, iltimeout
est même possible d’écrire des messages sur stderr (par exemple si l’enfant dumpait un noyau), ce qui élargirait encore cette fenêtre de course (indéfiniment si stderr est un tube plein, par exemple).Personnellement, je peux vivre avec cette limitation (qui sera probablement corrigée dans une future version).
timeout
fera également très attention de signaler le bon état de sortie, de gérer les autres cas (comme SIGALRM bloqué / ignoré au démarrage, de traiter les autres signaux ...) mieux que vous ne le feriez probablement à la main.À titre approximatif, vous pouvez l'écrire
perl
comme suit:Il y a une
timelimit
commande sur http://devel.ringlet.net/sysutils/timelimit/ (devance GNUtimeout
de quelques mois).Celui-ci utilise un
alarm()
mécanisme semblable à celui d'un utilisateur mais installe un gestionnaireSIGCHLD
(en ignorant les enfants arrêtés) pour détecter l'enfant en train de mourir. Il annule également l’alarme avant son exécutionwaitpid()
(cela n’annule pas la livraisonSIGALRM
si elle était en attente, mais de la façon dont elle est écrite, je ne vois pas cela comme un problème) et tue avant d’ appelerwaitpid()
(donc ne peut pas tuer un pid réutilisé.) )Netpipes a également une
timelimit
commande. Celle-ci est antérieure à toutes les autres de plusieurs décennies, adopte une autre approche, mais ne fonctionne pas correctement pour les commandes arrêtées et renvoie un1
statut de sortie à l'expiration du délai.Pour répondre plus directement à votre question, vous pouvez faire quelque chose comme:
C’est-à-dire, vérifiez que le processus est toujours un de nos enfants. Là encore, il y a une petite fenêtre de course (entre
ps
récupérer le statut de ce processus et lekill
tuer) au cours de laquelle le processus pourrait mourir et son pid être réutilisé par un autre processus.Avec quelques coquilles (
zsh
,bash
,mksh
), vous pouvez passer des spécifications d'emploi au lieu de pid.Cela ne fonctionne que si vous ne créez qu'un seul job d'arrière-plan (sinon, il n'est pas toujours possible d'obtenir la bonne spécification d'emploi de manière fiable).
Si cela pose un problème, démarrez simplement une nouvelle instance de shell:
Cela fonctionne parce que le shell supprime le travail de la table des travaux lors du décès de l'enfant. Dans ce cas, il ne devrait y avoir aucune fenêtre de course car, au moment où le shell appelle
kill()
, le signal SIGCHLD n’a pas été traité et le pid ne peut pas être réutilisé (car il n’a pas été attendu), ou il a été traité et le Le travail a été supprimé de la table des processus (etkill
signalerait une erreur).bash
« skill
au moins des blocs SIGCHLD avant qu'il accède à sa table de travail pour étendre%
et débloque après lakill()
.Une autre option pour éviter d'avoir ce
sleep
processus en suspens même aprèscmd
sa mort, avecbash
ouksh93
consiste à utiliser un tuyau avecread -t
au lieu desleep
:Celui-ci a toujours des conditions de course et vous perdez le statut de sortie de la commande. Il suppose également que
cmd
ne ferme pas son fd 4.Vous pouvez essayer de mettre en œuvre une solution sans race dans
perl
les cas suivants:(bien qu’il faille l’améliorer pour traiter d’autres types d’affaires).
Une autre méthode sans race pourrait utiliser des groupes de processus:
Cependant, notez que l'utilisation de groupes de processus peut avoir des effets secondaires si des entrées / sorties sont impliquées dans un terminal. Il a cependant l'avantage supplémentaire de tuer tous les autres processus supplémentaires engendrés par
cmd
.la source
timeout
n'est pas portable, la réponse mentionnait d'abord une solution portable.jobs
, puis le savoir (puisqu'il s'agit de votre propre shell, dans lequel vous avez le contrôle sur ce qui se passera ensuite) le travail sera N + 1? [alors vous pouvez économiser N et plus tard tuer% N + 1])En général, vous ne pouvez pas. Toutes les réponses données jusqu'à présent sont des heuristiques boguées. Dans un seul cas, vous pouvez utiliser le pid en toute sécurité pour envoyer des signaux: lorsque le processus cible est un enfant direct du processus qui enverra le signal et que le parent ne l’a pas encore attendu. Dans ce cas, même s'il est sorti, le pid est réservé (c'est ce qu'est un "processus zombie") jusqu'à ce que le parent l'attende. Je ne suis au courant d'aucun moyen de le faire proprement avec la coque.
Un autre moyen sûr de supprimer les processus consiste à les démarrer avec un terminal de contrôle configuré sur un pseudo-terminal pour lequel vous possédez le côté maître. Vous pouvez ensuite envoyer des signaux via le terminal, par exemple en écrivant le caractère pour
SIGTERM
ouSIGQUIT
sur le pty.Une autre méthode plus pratique en script consiste à utiliser une
screen
session nommée et à envoyer des commandes à la session écran pour y mettre fin. Ce processus se déroule sur un tuyau ou un socket Unix nommé en fonction de la session d'écran, qui ne sera pas automatiquement réutilisé si vous choisissez un nom unique sécurisé.la source
Lors du lancement du processus, enregistrez son heure de début:
Avant d'essayer de tuer le processus, arrêtez-le (ce n'est pas vraiment essentiel, mais c'est un moyen d'éviter les conditions de concurrence: si vous arrêtez le processus, son pid ne pourra pas être réutilisé)
Vérifiez que le processus avec ce PID a la même heure de début et si oui, tuez-le, sinon laissez le processus se poursuivre:
Cela fonctionne car il ne peut y avoir qu'un seul processus avec le même PID et la même heure de démarrage sur un système d'exploitation donné.
Arrêter le processus pendant le contrôle évite que les conditions de course ne soient un problème. Évidemment, cela pose le problème suivant: certains processus aléatoires peuvent être arrêtés pendant quelques millisecondes. Selon le type de processus, cela peut poser problème ou non.
Personnellement, j'utiliserais simplement python et celui
psutil
qui gère automatiquement la réutilisation des PID:la source
ps -o start=
format change de 18h12 à 26 janvier après un certain temps. Méfiez-vous des changements de l'heure d'été. Si vous êtes sous Linux, vous préférerez probablementTZ=UTC0 ps -o lstart=
.lstart
, je vais le modifier.Sur un système Linux, vous pouvez vous assurer qu'un pid ne sera pas réutilisé en conservant son espace de noms pid actif. Cela peut être fait via le
/proc/$pid/ns/pid
fichier.man namespaces
-init
.man pid_namespaces
-util-linux
paquet fournit de nombreux outils utiles pour manipuler les espaces de noms. Par exemple, il existeunshare
cependant des droits de superutilisateur si vous n’avez pas encore défini ses droits dans un espace de noms d’utilisateur:Si vous n'avez pas prévu d'espace de nom d'utilisateur, vous pouvez toujours exécuter en toute sécurité des commandes arbitraires en supprimant immédiatement les privilèges. La
runuser
commande est un autre binaire (non-setuid) fourni par leutil-linux
paquet et son intégration pourrait ressembler à:...etc.
Dans l'exemple ci-dessus, deux commutateurs sont passés à
unshare(1)
l'--fork
indicateur qui fait dush -c
processus appelé le premier enfant créé et assure soninit
statut, ainsi que l'--pid
indicateur qui demande launshare(1)
création d'un espace de nom pid.Le
sh -c
processus génère cinq shells enfants avec arrière-plan, chacun unewhile
boucle inifinite qui continuera à ajouter le résultat dedate
à la fin delog
tant que la valeur estsleep 1
true. Après la création de ces processus, lessh
appelssleep
durent 5 secondes supplémentaires puis se terminent.Il est peut-être intéressant de noter que si le
-f
drapeau n'était pas utilisé, aucune deswhile
boucles d' arrière-plan ne se terminerait, mais avec elle ...SORTIE:
la source
Pensez à améliorer votre
longrunningthing
comportement, un peu plus semblable à un démon. Par exemple, vous pouvez lui faire créer un fichier pid qui permettra au moins un contrôle limité du processus. Il y a plusieurs façons de le faire sans modifier le binaire d'origine, impliquant toutes un wrapper. Par exemple:un simple script d'encapsuleur qui démarrera le travail requis en arrière-plan (avec une redirection de sortie facultative), écrira le PID de ce processus dans un fichier, puis attendra que le processus se termine (en utilisant
wait
) et supprime le fichier. Si pendant le processus d’attente le processus est tué, par exemple par quelque chose commele wrapper s'assurera simplement que le pidfile est supprimé.
un wrapper de moniteur, qui mettra son propre PID quelque part et captera (et répondra) les signaux qui lui sont envoyés. Exemple simple:
Maintenant, comme l'ont souligné @R .. et @ StéphaneChazelas, ces approches ont souvent une condition de concurrence critique ou imposent une restriction du nombre de processus que vous pouvez générer. De plus, il ne gère pas les cas où le
longrunningthing
fils peut se séparer (ce qui n’est probablement pas le problème dans la question initiale).Avec les noyaux Linux récents (lus il y a quelques années), cela peut être traité de manière satisfaisante en utilisant des groupes de contrôle , à savoir le congélateur - qui, je suppose, est ce que certains systèmes d'initialisation Linux modernes utilisent.
la source
longrunningthing
c'est que vous n'avez aucun contrôle sur ce que c'est. J'ai également donné un exemple de script shell car il expliquait le problème. J'aime la vôtre et toutes les autres solutions créatives ici, mais si vous utilisez Linux / bash, il existe un "timeout" intégré pour cela. Je suppose que je devrais obtenir la source à cela et voir comment il le fait!timeout
n'est pas un shell intégré. Il y a eu diverses implémentations d'unetimeout
commande pour Linux, une récemment (2008) ajoutée à GNU coreutils (donc non spécifique à Linux), et c'est ce que la plupart des distributions Linux utilisent de nos jours.Si vous utilisez Linux (et quelques autres * nix), vous pouvez vérifier si le processus que vous souhaitez tuer est toujours utilisé et que la ligne de commande correspond à votre processus long. Quelque chose comme :
Une alternative peut être de vérifier pendant combien de temps le processus que vous avez l'intention de tuer est en cours, avec quelque chose comme
ps -p $p -o etime=
. Vous pouvez le faire vous-même en extrayant ces informations/proc/$p/stat
, mais ce serait délicat (le temps est mesuré en un tournemain et vous devrez aussi utiliser la disponibilité du système/proc/stat
).Quoi qu’il en soit, vous ne pouvez généralement pas vous assurer que le processus n’est pas remplacé après votre contrôle et avant de le tuer.
la source
cat pidfile
résultat. Je ne me souviens pas d'une manière propre de le faire en shell uniquement. La réponse proposée pour l’espace de nommage semble toutefoisC'est en fait une très bonne question.
La manière de déterminer l'unicité des processus consiste à examiner (a) où il se trouve dans la mémoire; et (b) ce que cette mémoire contient. Pour être plus précis, nous voulons savoir où se trouve en mémoire le texte du programme pour l’appel initial, car nous savons que la zone de texte de chaque thread occupera un emplacement différent en mémoire. Si le processus meurt et qu'un autre est lancé avec le même pid, le texte du programme du nouveau processus n'occupera pas la même place en mémoire et ne contiendra pas les mêmes informations.
Ainsi, immédiatement après le lancement de votre processus, faites
md5sum /proc/[pid]/maps
et enregistrez le résultat. Plus tard, quand vous voulez tuer le processus, faites un autre md5sum et comparez-le. Si cela correspond, tuez le pid. Si non, ne le faites pas.pour vous en rendre compte, lancez deux obus bash identiques. Examinez le
/proc/[pid]/maps
pour eux et vous constaterez qu'ils sont différents. Pourquoi? Car même s’il s’agit du même programme, ils occupent différents emplacements en mémoire et les adresses de leur pile sont différentes. Ainsi, si votre processus meurt et que son PID est réutilisé, même si la même commande est relancée avec les mêmes arguments , le fichier "maps" sera différent et vous saurez que vous ne traitez pas avec le processus d'origine.Voir: page de manuel proc pour plus de détails.
Notez que le fichier
/proc/[pid]/stat
contient déjà toutes les informations mentionnées par les autres afficheurs dans leurs réponses: âge du processus, pid parent, etc. Ce fichier contient à la fois des informations statiques et dynamiques, donc si vous préférez utiliser ce fichier comme base de comparaison, puis au lancement de votrelongrunningthing
, vous devez extraire les champs statiques suivants dustat
fichier et les enregistrer pour comparaison ultérieure:pid, nom de fichier, pid du parent, identifiant du groupe de processus, terminal de contrôle, processus temporel démarré après le démarrage du système, taille du groupe de résidents, adresse du début de la pile,
pris ensemble, les éléments ci-dessus identifient de manière unique le processus, ce qui représente un autre chemin à parcourir. En réalité, vous pouvez vous contenter de "pid" et d'un "processus temporel démarré après le démarrage du système" avec un degré de confiance élevé. Extrayez simplement ces champs du
stat
fichier et enregistrez-le quelque part lors du lancement de votre processus. Plus tard, avant de le tuer, extrayez-le à nouveau et comparez. S'ils concordent, vous êtes assuré de consulter le processus d'origine.la source
/proc/[pid]/maps
mémoire supplémentaire est allouée, si la pile s'agrandit ou si les nouveaux fichiers sont mappés au fil du temps ... Et que signifie immédiatement après le lancement ? Après toutes les bibliothèques ont été mappés? Comment déterminez-vous cela?md5sum
sur leurs fichiers de cartes. Je vais le laisser fonctionner pendant un jour ou deux et faire rapport ici avec les résultats.Une autre façon serait de vérifier l'âge du processus avant de le tuer. De cette façon, vous pouvez vous assurer que vous ne tuez pas un processus qui ne se déclenche pas en moins de 24 heures. Vous pouvez ajouter une
if
condition en fonction de cela avant de tuer le processus.Cette
if
condition vérifie si l'ID de processus$p
est inférieur à 24 heures (86 400 secondes).PS: - La commande
ps -p $p -o etime=
aura le format<no.of days>-HH:MM:SS
la source
mtime
of/proc/$p
n'a rien à voir avec l'heure de début du processus.if
condition. S'il vous plaît n'hésitez pas à commenter si son buggy.Ce que je fais est, après avoir mis fin au processus, le refait. Chaque fois que je fais cela, la réponse revient, "pas de tel processus"
Ne pourrait pas être plus simple et je fais cela depuis des années sans aucun problème.
la source