Gestion des processus

Les systèmes d’exploitation de type Unix sont multitâches et multi-utilisateurs. Cela signifie qu’il est possible d’exécuter simultanément plusieurs programmes qui appartiennent potentiellement à différents utilisateurs. Sous Unix, l’unité d’exécution d’un programme est appelée un processus. Lorsque vous exécutez un programme C que vous avez compilé depuis la ligne de commande, le shell lance un nouveau processus. Chaque processus est identifié par le système d’exploitation via son pid ou process identifier. Ce pid est alloué par le système d’exploitation au moment de la création du processus. À tout instant, le système d’exploitation maintient une table des processus qui contient la liste de tous les processus qui sont en cours d’exécution. Comme nous aurons l’occasion de nous en rendre compte plus tard, cette table contient énormément d’informations qui sont utiles au système. À ce stade, l’information importante qui se trouve dans la table des processus est le pid de chaque processus et l’utilisateur qui possède le processus. La commande ps(1) permet de consulter de façon détaillée la table des processus sur un système Unix. Voici un exemple d’utilisation de cette commande sur un système Linux.

$ ps u
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
obo      16272  0.0  0.0 110464  1884 pts/1    Ss   11:35   0:00 -bash
obo      16353  0.0  0.0 110184  1136 pts/1    R+   11:43   0:00 ps u

Dans cet exemple, l’utilisateur obo possède actuellement deux processus. Le premier est l’interpréteur de commande bash(1) et le second le processus ps(1). L’interpréteur de commande a 16272 comme pid tandis que le pid de ps(1) est 16353.

ps(1) n’est pas la seule commande permettant de consulter la table des processus. Parmi les autres commandes utiles, on peut mentionner top(1) qui permet de visualiser les processus qui s’exécutent actuellement et le temps CPU qu’ils consomment ou pstree(1) qui présente les processus sous la forme d’un arbre. Sous Linux, le répertoire /proc, qui est documenté dans proc(5) contient de nombreux pseudos fichiers avec énormément d’informations relatives aux processus qui sont en cours d’exécution. Parcourir le répertoire /proc et visualiser avec less(1) les fichiers qui s’y trouvent est une autre façon de consulter la table des processus sous Linux.

Pour comprendre le fonctionnement des processus, il est intéressant d’expérimenter avec le processus ci-dessous. Celui-ci utilise l’appel système getpid(2) pour récupérer son pid, l’affiche et utilise la fonction sleep(3) de la librairie pour se mettre en veille pendant trente secondes avant de se terminer.

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

int main(int argc, char *argv[]) {

  unsigned int sec=30;
  int pid=(int) getpid();
  
  printf("Processus : %d\n",pid);
  printf("[pid=%d] Sleep : %d secondes\n",pid, sec);
  sec=sleep(sec);
  if(sec==0) {
    printf("[pid=%d] Fin du processus\n",pid );
    return(EXIT_SUCCESS);
  }
  else {
    printf("[pid=%d] Interrompu alors qu'il restait %d secondes\n",pid,sec);
    return(EXIT_FAILURE);
  }  
}

Ce programme peut être compilé avec gcc(1) pour produire un exécutable.

$ ls -l getpid*
-rw-r--r--  1 obo  obo   608 10 fév 12:11 getpid.c
$ gcc -Wall -o getpid getpid.c
$ ls -l getpid*
-rwxr-xr-x  1 obo  obo  8800 10 fév 12:12 getpid
-rw-r--r--  1 obo  obo   608 10 fév 12:11 getpid.c

Cet exemple utilise la commande ls(1) pour lister le contenu d’un répertoire. L’argument -l permet de d’obtenir pour chaque fichier son nom, sa date de modification, sa taille, l’utilisateur et le groupe auquel il appartient ainsi que ses permissions. Sous Unix, les permissions associées à un fichier sont divisées en trois blocs. Le premier bloc correspond aux permissions qui sont applicables à l’utilisateur qui possède le fichier. Pour l’exécutable getpid, les permissions du propriétaire sont rwx. Elles indiquent que le propriétaire peut lire le fichier (permission r), l’écrire ou l’effacer (permission w) et l’exécuter (permission x). Sous Unix, seuls les fichiers qui possèdent la permission à l’exécution peuvent être lancés depuis l’interpréteur. Ces permissions peuvent être modifiées en utilisant la commande chmod(1). Les deux autres blocs de permissions sont relatifs aux membres du même groupe que le propriétaire et à un utilisateur quelconque. Nous y reviendrons plus en détail lorsque nous abordons les systèmes de fichiers. En pratique, il est important de savoir qu’un fichier shell ou un fichier compilé qui n’a pas le bit de permission x ne peut pas être exécuté par le système. Ceci est illustré par l’exemple ci-dessous.

$ chmod -x getpid
$ ls -l getpid*
-rw-r--r--  1 obo  obo  8800 10 fév 12:12 getpid
-rwxr-xr-x  1 obo  obo  8800 10 fév 12:11 getpid.o
$ ./getpid
-bash: ./getpid: Permission denied
$ chmod +x getpid
$ ./getpid
Processus : 11147

L’interpréteur de commande bash(1) permet lancer plusieurs processus en tâche de fond. Cela se fait en suffixant la commande avec &. Des détails complémentaires sont disponibles dans la section JOB CONTROL du manuel de bash(1). Lorsqu’un processus est lancé en tâche de fond, il est détaché et n’a plus accès à l’entrée standard. Par contre, il continue à pouvoir écrire sur la sortie standard et la sortie d’erreur standard. L’exemple ci-dessous illustre l’exécution de deux instances de getpid.

$ ./getpid &
[1] 10975
$ Processus : 10975
[pid=10975] Sleep : 30 secondes
$ ./getpid &
[2] 10976
$ Processus : 10976
[pid=10976] Sleep : 30 secondes
ps u
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
obo   8361   0,0  0,0  2435548    208 s003  S+    9:24     0:00.14 -bash
obo  10975   0,0  0,0  2434832    340 s000  S    12:05     0:00.00 ./getpid
obo  10976   0,0  0,0  2434832    340 s000  S    12:05     0:00.00 ./getpid
[pid=10975] Fin du processus
[pid=10976] Fin du processus
[1]-  Done                    ./getpid
[2]+  Done                    ./getpid

Ces deux instances partagent la même sortie standard. En pratique, lorsque l’on lance un processus en tâche de fond, il est préférable de rediriger sa sortie et son erreur standard. Lorsque l’on développe de premiers programmes en C, il arrive que celui-ci se lance dans une boucle infinie. Deux techniques sont possibles pour interrompre un tel processus qui consomme inutilement les ressources de la machine et peut dans certains cas la surcharger fortement.

Si le programme a été lancé depuis un shell, il suffit généralement de taper sur Ctrl-C pour interrompre son exécution, comme dans l’exemple ci-dessous.

$ ./getpid
Processus : 11281
[pid=11281] Sleep : 30 secondes
^C

Parfois cependant Ctrl-C n’est pas suffisant. C’est le cas notamment lorsqu’un processus a été lancé en tâche de fond. Dans ce cas, la meilleure technique est d’utiliser ps(1) pour trouver l’identifiant du processus et l’interrompre via la commande kill(1). Cette commande permet d’envoyer un signal au processus. Nous verrons plus tard le fonctionnement des signaux sous Unix. À ce stade, le signal permettant de terminer avec certitude un processus est le signal KILL. C’est celui qui est utilisé dans l’exemple ci-dessous.

$ ./getpid &
[1] 11285
$ Processus : 11285
[pid=11285] Sleep : 30 secondes
ps
PID TTY           TIME CMD
384 ttys000      0:00.32 -bash
11285 ttys000    0:00.00 ./getpid
$ kill -KILL 11285
$ ps
 PID TTY           TIME CMD
 384 ttys000    0:00.33 -bash
 [1]+  Terminated              ./getpid