htop expliqué, partie 5 : l’état des processus

Article arrivé 1er sur Hacker News, 1er sur /r/sysadmin, 2nd sur /r/linux), traduit avec l’accord de son auteur Pēteris Ņikiforovs, il présente la commande htop et les notions de base des composants d’un système GNU/Linux que cette commande affiche.

Voici les différents chapitres déjà publiés :

  1. l’uptime
  2. la load average (charge moyenne)
  3. les processus
  4. les processus utilisateur

Aujourd’hui : les différents états possibles des processus


htop

L’état des processus

Nous allons maintenant jeter un œil à la colonne de l’état des processus dans htop qui est notée simplement avec la lettre S.

Voilà les différentes valeurs possibles :

R – en cours d’exécution ou possible à exécuter (dans la file d’exécution)
S – sommeil interruptible (en attente de l’accomplissement d’un événement)
D – sommeil non-interruptible (en général en train de réaliser des entrées-sorties)
Z – processus défunt (zombie), terminé mais le retour n’a pas été recueilli par son parent
T – stoppé par un signal de contrôle de tâche
t – stoppé par un debugger pendant l’étude de la trace
X – mort (ne devrait jamais être vu)

Je les ai ordonnés ici par la fréquence à laquelle je les vois.

Notez que si vous exécutez ps, il montrera aussi des sous-états comme Ss, Ss+, etc.

$ ps x
 PID TTY STAT TIME COMMAND
 1688 ? Ss 0:00 /lib/systemd/systemd --user
 1689 ? S 0:00 (sd-pam)
 1724 ? S 0:01 sshd: vagrant@pts/0
 1725 pts/0 Ss 0:00 -bash
 2628 pts/0 R+ 0:00 ps x

R – en cours d’exécution ou possible à exécuter (dans la file d’exécution)

Dans cet état, le processus est en cours d’exécution ou dans une file d’attente à attendre d’être exécuté.

Qu’est-ce que ça signifie ?

Quand vous compilez le code source d’un programme que vous avez écrit, ce code machine est composé d’instructions CPU. Il est sauvé dans un fichier qui sera exécuté. Quand vous lancez un programme, il est chargé en mémoire et ensuite le CPU exécute ces instructions.

Basiquement, cela signifie que le CPU est physiquement en train d’exécuter les instructions. Ou, dans d’autres termes, en train de dévorer des nombres.

S – sommeil interruptible (en attente de l’accomplissement d’un événement)

Cela signifie que les instructions du code de ce processus ne sont pas en train d’être exécutées sur le CPU. À la place, ce processus est en attente que quelque chose – un événement ou une condition – arrive. Quand cet événement arrive, le noyau passe l’état à en cours d’exécution.

Un exemple est l’utilitaire sleep de coreutils. Il va s’endormir pendant un nombre spécifique de secondes (approximativement).

$ sleep 1000 &
[1] 10089
$ ps f
 PID TTY STAT TIME COMMAND
 3514 pts/1 Ss 0:00 -bash
10089 pts/1 S 0:00 \\_ sleep 1000
10094 pts/1 R+ 0:00 \\_ ps f

Donc c’est un sommeil interruptible. Comme pouvons-nous l’interrompre ?

En envoyant un signal.

Vous pouvez envoyer un signal dans htop en appuyant sur F9 en ensuite en choisissant l’un des signaux dans le menu à gauche.

Envoyer un signal est aussi connu comme tuer. C’est parce que kill est un appel système qui peut envoyer un signal à un processus. Il y a un programme /bin/kill qui peut effectuer cet appel système depuis l’espace utilisateur et le signal par défaut à utiliser est TERM qui demandera au processus de se terminer ou en d’autres mots d’essayer de le tuer.

Un signal est juste un nombre. Il est difficile de se souvenir des nombres, donc on leur a donné des noms. Les noms de signaux sont écrits en majuscule et peuvent être préfixés de SIG.

Certains signaux habituellement utilisés sont INT, KILL, STOP, CONT, HUP.

Interrompons un processus en sommeil en lui envoyant un INT connu aussi comme SIGINT ou 2 ou signal d’interruption terminale.

$ kill -INT 10089
[1]+ Interrupt sleep 1000

C’est ce qui arrive quand vous appuyez sur CTRL+C sur votre clavier. bash enverra au processus en premier plan le signal SIGINT comme nous venons de le faire manuellement.

Au fait, en bash, kill est une commande interne, même s’il y a un /bin/kill sur la plupart des systèmes. Pourquoi ? Cela autorise les processus à être tués si la limite des processus que vous pouvez créer est atteinte.

Ces commandes font la même chose :

kill -INT 10089
kill -2 10089
/bin/kill -2 10089

Un autre signal utile à connaître est SIGKILL aussi connu comme 9. Vous pourriez être amené à l’utiliser pour tuer un processus qui ne répondrait pas à vos frénétiques CTRL-C au clavier.

Quand vous écrivez un programme, vous pouvez mettre en place des gestionnaires de signaux qui sont des fonctions appelées quand votre processus reçoit un signal. En d’autres mots, vous pouvez attraper un signal et en faire quelque chose, par exemple, nettoyer et arrêter proprement. Donc envoyer SIGINT (l’utilisateur veut interrompre le processus) et SIGTERM (l’utilisateur veut terminer le processus) ne signifie pas que le processus va être arrêté.

Vous avez peut-être vu cette exception en exécutant un script Python :

$ python -c 'import sys; sys.stdin.read()'
^C
Traceback (most recent call last):
 File "<string>", line 1, in <module>
KeyboardInterrupt

Vous pouvez dire au noyau d’arrêter de force un processus et ne pas lui laisser une chance de répondre en envoyant le signal KILL.

$ sleep 1000 &
[1] 2658
$ kill -9 2658
[1]+ Killed sleep 1000

D – sommeil non-interruptible (en général en train de réaliser des entrées-sorties)

À la différence du sommeil interruptible, vous ne pouvez pas réveiller ce processus avec un signal. C’est pourquoi tant de gens ont peur de voir cet état. Vous ne pouvez pas tuer ce type de processus parce que tuer signifie ici envoyer des SIGKILL aux processus.

Cet état est utilisé si le processus doit attendre sans interruption ou quand il est attendu qu’un événement se produise rapidement. Comme lire ou écrire depuis un disque. Mais cela devrait arriver en une fraction de seconde.

Nous trouvons une belle réponse sur StackOverflow.

Les processus non-interruptibles sont D’HABITUDE en attente d’entrée/sortie à la suite d’une erreur de page. Le processus/tâche ne peut être interrompu dans cet état, parce qu’il ne peut gérer aucun signal; s’il l’était une autre erreur de page se produirait et il serait de retour là où il était.

Dans d’autres mots, cela pourrait arriver si vous utilisez le système de fichier Network File System (NFS) et cela prend un moment d’y lire ou écrire.

Ou de mon expérience cela peut aussi signifier que des processus sont en train de swapper ce qui signifie que vous n’avez pas assez de mémoire disponible.

Essayons de mettre un processus en état non-interruptible.

8.8.8.8 est un serveur DNS publique fourni par Google. Ils n’ont pas un NFS ouvert là-bas. Mais cela ne va pas nous arrêter.

$ sudo mount 8.8.8.8:/tmp /tmp &
[1] 12646
$ sudo ps x | grep mount.nfs
12648 pts/1 D 0:00 /sbin/mount.nfs 8.8.8.8:/tmp /tmp -o rw

Comment trouver ce qui cause cela ? strace !

Utilisons la commande strace sur la sortie de ps ci-dessus.

$ sudo strace /sbin/mount.nfs 8.8.8.8:/tmp /tmp -o rw
...
mount("8.8.8.8:/tmp", "/tmp", "nfs", 0, ...

Donc l’appel système mount bloque le processus.

Si vous le vous demandez, vous pouvez monter avec l’option intr pour l’exécuter en tant qu’interruptible :

sudo mount 8.8.8.8:/tmp /tmp -o intr.

Z – processus défunt (zombie), terminé mais le retour n’a pas été recueilli par son parent

Quand un processus prend fin via exit et qu’il a toujours des processus enfants, ces processus enfants deviennent des processus zombies.

  • Si des processus zombies existent pendant un court moment, c’est parfaitement normal
  • Les processus zombies qui existent longtemps peuvent indiquer un bug dans un programme
  • Les processus zombies ne consomment pas de mémoire, juste un identifiant de processus
  • Vous ne pouvez pas tuer un processus zombie
  • Vous pouvez demander gentiment à un processus parent de recueillir les zombies (le signal SIGCHLD)
  • Vous pouvez tuer le processus parent d’un zombie pour vous débarrasser du parent et de ses zombies

Je vais écrire un peu de code en C pour montrer cela.

Voici notre programme.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
 printf("Running\\n");

int pid = fork();

if (pid == 0) {
 printf("I am the child process\\n");
 printf("The child process is exiting now\\n");
 exit(0);
 } else {
 printf("I am the parent process\\n");
 printf("The parent process is sleeping now\\n");
 sleep(20);
 printf("The parent process is finished\\n");
 }

return 0;
}

Installons le compilateur GNU C (GCC).

sudo apt install -y gcc

Compilez-le et ensuite exécutons-le

gcc zombie.c -o zombie
./zombie

Jetons un œil à l’arborescence des processus

$ ps f
 PID TTY STAT TIME COMMAND
 3514 pts/1 Ss 0:00 -bash
 7911 pts/1 S+ 0:00 \\_ ./zombie
 7912 pts/1 Z+ 0:00 \\_ [zombie] <defunct>
 1317 pts/0 Ss 0:00 -bash
 7913 pts/0 R+ 0:00 \\_ ps f

Nous avons notre zombie !

Quand le processus parent a fini, le zombie disparaît.

$ ps f
 PID TTY STAT TIME COMMAND
 3514 pts/1 Ss+ 0:00 -bash
 1317 pts/0 Ss 0:00 -bash
 7914 pts/0 R+ 0:00 \\_ ps f

Si vous remplacez sleep(20) par while (true) ; les zombies disparaîtraient immédiatement.

Avec exit, toute la mémoire et les ressources associées sont désallouées afin qu’elles soient utilisées par d’autres processus.

Pourquoi alors garder les processus zombies ?

Le processus parent a une option pour trouver le code de fin du processus enfant (dans un gestionnaire de signal) avec l’appel système wait. Si un processus est endormi, alors il a besoin d’attendre qu’il se réveille.

Pourquoi ne pas simplement le réveiller et le tuer ? Pour la même raison que vous ne jetez pas votre enfant à la poubelle quand vous en êtes fatigué. Des problèmes pourraient survenir.

T – stoppé par un signal de contrôle de tâche

J’ai ouvert deux terminaux et je regarde mes processus utilisateur avec ps u.

$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 1317 0.0 0.9 21420 4992 pts/0 Ss+ Jun07 0:00 -bash
ubuntu 3514 1.5 1.0 21420 5196 pts/1 Ss 07:28 0:00 -bash
ubuntu 3528 0.0 0.6 36084 3316 pts/1 R+ 07:28 0:00 ps u

Je vais omettre le -bash et les processus ps u dans la sortie ci-dessous.

Maintenant lançons cat /dev/urandom > /dev/null dans un terminal. Son état est R+ ce qui signifie qu’il est en train de s’exécuter.

$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3540 103 0.1 6168 688 pts/1 R+ 07:29 0:04 cat /dev/urandom

Appuyez CTRL+Z pour stopper le processus.

$ # CTRL+Z
[1]+ Stopped cat /dev/urandom > /dev/null
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3540 86.8 0.1 6168 688 pts/1 T 07:29 0:15 cat /dev/urandom

Son état est maintenant T.

Lancez fg dans le premier terminal pour le relancer.

Un autre moyen pour stopper un processus comme ça est de lancer un signal STOP avec kill pour ce processus. Pour résumer l’exécution du processus, vous pouvez utiliser le signal CONT.

t – stoppé par un debugger durant le suivi de la trace

Commençons par installer le debugger GNU (gdb)

sudo apt install -y gdb

Exécutons un programme qui écoutera les connexions réseau entrantes sur le port 1234.

$ nc -l 1234 &
[1] 3905

Il est endormi, ce qui signifie qu’il est en attente de données depuis le réseau.

$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3905 0.0 0.1 9184 896 pts/0 S 07:41 0:00 nc -l 1234

Lançons le debugger et attachons-le au processus avec l’identifiant 3905

sudo gdb -p 3905

Vous verrez que l’état est t, ce qui signifie que le processus est tracé par le debugger.

$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 3905 0.0 0.1 9184 896 pts/0 t 07:41 0:00 nc -l 1234

Après ce long tour d’horizon des différents états possibles des processus de votre système GNU/Linux, nous nous intéresserons aux autres caractéristiques des processus.

Vus : 581
Publié par Carl Chenet : 277