Lorsqu'un processus est en mode utilisateur, il peut être interrompu à tout moment (passage en mode noyau). Lorsque le noyau revient en mode utilisateur, il vérifie s'il y a des signaux en attente (y compris ceux qui sont utilisés pour tuer le processus, comme SIGTERM
et SIGKILL
). Cela signifie qu'un processus ne peut être tué qu'au retour en mode utilisateur.
La raison pour laquelle un processus ne peut pas être tué en mode noyau est qu'il pourrait potentiellement corrompre les structures du noyau utilisées par tous les autres processus de la même machine (de la même manière que tuer un thread peut potentiellement corrompre les structures de données utilisées par d'autres threads dans le même processus) .
Lorsque le noyau doit faire quelque chose qui peut prendre beaucoup de temps (attendre un tube écrit par un autre processus ou attendre que le matériel fasse quelque chose, par exemple), il dort en se marquant comme dormant et en appelant le planificateur pour passer à un autre processus (s'il n'y a pas de processus non endormi, il passe à un processus "factice" qui demande au processeur de ralentir un peu et se place dans une boucle - la boucle inactive).
Si un signal est envoyé à un processus endormi, il doit être réveillé avant qu'il ne retourne dans l'espace utilisateur et traite ainsi le signal en attente. Ici, nous avons la différence entre les deux principaux types de sommeil :
TASK_INTERRUPTIBLE
, le sommeil interruptible. Si une tâche est marquée avec ce drapeau, elle est en sommeil, mais peut être réveillée par des signaux. Cela signifie que le code qui a marqué la tâche comme endormie attend un signal possible et, après son réveil, le vérifiera et reviendra de l'appel système. Une fois le signal traité, l'appel système peut potentiellement être redémarré automatiquement (et je n'entrerai pas dans les détails de son fonctionnement).TASK_UNINTERRUPTIBLE
, le sommeil ininterrompu. Si une tâche est marquée avec cet indicateur, elle ne s'attend pas à être réveillée par autre chose que ce qu'elle attend, soit parce qu'elle ne peut pas être redémarrée facilement, soit parce que les programmes s'attendent à ce que l'appel système soit atomique. Cela peut également être utilisé pour les sommeils connus pour être très courts.
TASK_KILLABLE
(mentionné dans l'article LWN lié à la réponse de ddaa) est une nouvelle variante.
Cela répond à votre première question. Quant à votre deuxième question :vous ne pouvez pas éviter les sommeils ininterrompus, ils sont une chose normale (cela se produit, par exemple, chaque fois qu'un processus lit/écrit depuis/vers le disque) ; cependant, ils ne devraient durer qu'une fraction de seconde. S'ils durent beaucoup plus longtemps, cela signifie généralement un problème matériel (ou un problème de pilote de périphérique, qui ressemble au noyau), où le pilote de périphérique attend que le matériel fasse quelque chose qui n'arrivera jamais. Cela peut également signifier que vous utilisez NFS et que le serveur NFS est en panne (il attend que le serveur se rétablisse ; vous pouvez également utiliser l'option "intr" pour éviter le problème).
Enfin, la raison pour laquelle vous ne pouvez pas récupérer est la même raison pour laquelle le noyau attend le retour en mode utilisateur pour délivrer un signal ou tuer le processus :cela pourrait potentiellement corrompre les structures de données du noyau (le code en attente d'une mise en veille interruptible peut recevoir une erreur qui lui indique pour retourner dans l'espace utilisateur, où le processus peut être tué ; le code en attente d'une mise en veille sans interruption n'attend aucune erreur).
Les processus non interruptibles attendent GÉNÉRALEMENT des E/S suite à un défaut de page.
Considérez ceci :
- Le thread essaie d'accéder à une page qui n'est pas dans le noyau (soit un exécutable chargé à la demande, une page de mémoire anonyme qui a été échangée, ou un fichier mmap() chargé à la demande, qui sont à peu près la même chose)
- Le noyau est en train (d'essayer de) le charger
- Le processus ne peut pas continuer tant que la page n'est pas disponible.
Le processus/tâche ne peut pas être interrompu dans cet état, car il ne peut gérer aucun signal ; si c'était le cas, un autre défaut de page se produirait et il reviendrait là où il était.
Quand je dis "processus", je veux vraiment dire "tâche", qui sous Linux (2.6) se traduit approximativement par "thread" qui peut ou non avoir une entrée individuelle "thread group" dans /proc
Dans certains cas, il peut attendre longtemps. Un exemple typique de ceci serait lorsque le fichier exécutable ou mmap'd se trouve sur un système de fichiers réseau où le serveur a échoué. Si l'E/S réussit finalement, la tâche se poursuivra. Si elle échoue finalement, la tâche obtiendra généralement un SIGBUS ou quelque chose du genre.
Un processus non interruptible est un processus qui se trouve être dans un appel système (fonction noyau) qui ne peut pas être interrompu par un signal.
Pour comprendre ce que cela signifie, vous devez comprendre le concept d'un appel système interruptible. L'exemple classique est read()
. Il s'agit d'un appel système qui peut prendre beaucoup de temps (secondes) car il peut potentiellement impliquer de faire tourner un disque dur ou de déplacer des têtes. Pendant la majeure partie de ce temps, le processus sera en veille, bloquant sur le matériel.
Pendant que le processus dort dans l'appel système, il peut recevoir un signal asynchrone Unix (par exemple, SIGTERM), puis ce qui suit se produit :
- L'appel système se termine prématurément et est configuré pour renvoyer -EINTR dans l'espace utilisateur.
- Le gestionnaire de signal est exécuté.
- Si le processus est toujours en cours d'exécution, il obtient la valeur de retour de l'appel système et peut refaire le même appel.
Le retour précoce de l'appel système permet au code de l'espace utilisateur de modifier immédiatement son comportement en réponse au signal. Par exemple, terminer proprement en réaction à SIGINT ou SIGTERM.
D'autre part, certains appels système ne peuvent pas être interrompus de cette manière. Si les appels système se bloquent pour une raison quelconque, le processus peut rester indéfiniment dans cet état impossible à tuer.
LWN a publié un bel article sur ce sujet en juillet.
Pour répondre à la question initiale :
-
Comment éviter que cela ne se produise :identifiez le pilote qui vous cause des problèmes, puis arrêtez de l'utiliser ou devenez un pirate du noyau et corrigez-le.
-
Comment tuer un processus ininterruptible sans redémarrer :faire en sorte que l'appel système se termine. Souvent, la manière la plus efficace de le faire sans appuyer sur l'interrupteur d'alimentation consiste à tirer sur le cordon d'alimentation. Vous pouvez également devenir un hacker du noyau et faire en sorte que le pilote utilise TASK_KILLABLE, comme expliqué dans l'article LWN.
À votre 3ème question :je pense que vous pouvez tuer les processus ininterruptibles en exécutant sudo kill -HUP 1
.Il redémarrera init sans mettre fin aux processus en cours et après l'avoir exécuté, mes processus ininterruptibles ont disparu.