Niveau :      
Résumé : processus ; thread

Quelques personnes m'ont récemment demandé la différence entre un processus et un thread. Avant de bien en comprendre la différence, nous avons besoin de savoir deux choses : à quoi sert un noyau ? et comment fonctionne un processus ?

Base

Un noyau sert, entre autre, à gérer des tâches. Il permet à différents codes d'être exécutés chacun dans un monde séparé. Ces différents code ne peuvent pas empiéter sur ce que fait le voisin car le noyau les en empêche. C'est la protection, qu'on trouve implémentée au niveau même du processeur (celle qui défaillait dans windows 95).

Le noyau fournit aux processus un moyen de communiquer avec le monde extérieur ou entre eux (par des fichiers puisque tout est fichier). Ainsi chaque processus se voit comme ayant un accès complet au processeur et à une RAM de 4Go (pour un processeur 32 bits). Une partie de ces 4Go n'est en pratique pas modifiable car elle appartient au noyau (le dernier Giga). C'est le noyau qui se débrouille pour lui mettre à disposition ces 4Go, soit en lui associant de la vraie ram à la demande (en utilisant la notion de paging du processeur, à travers la fonction brk du noyau), soit en en mappant une partie sur le disque à la demande (fonction map), soit enfin en lui cachant qu'ils ne sont pas réellement disponibles (en stockant le contenu dans la swap).

Exemple de mapping mémoire : ram.png

Le noyau fait aussi en sorte que les processus ne consomment pas tout le processeur, il les répartit dans le temps pour leur en donner un morceau à chacun. C'est un service rendu par le scheduler du noyau.

Processus

Mais qu'est-ce qu'un processus ? Un processus est un exécutable qu'on a mis en mémoire, qui a accès à l'intégralité de sa propre mémoire et qui exécute des instructions. Il a donc une partie de sa mémoire qui est du code, et une partie qui sont des données. Dans ces données, on différencie deux choses : la pile et le tas. La pile est l'endroit où on stocke les variables locales ainsi que les adresses de retour des fonctions. Le tas, c'est tout le reste de la mémoire qui est allouée par à coups à travers la fonction malloc(). Le tas contient donc des données en pagaille, il n'a pas d'ordre et peut contenir des trous.

process.png

  • (1) : Organisation de la mémoire d'un processus sous linux (en assembleur vous faites ce que vous voulez)
  • (2) : Organisation de la mémoire d'un processus elf utilisant la libc

Vu du noyau un processus est caractérisé par son mapping mémoire, par ses fichiers ouverts et surtout par l'état du processeur à un instant donné. Celui-ci contient entre autre un pointeur indiquant où se trouve la pile et où se trouve l'instruction en cours d'exécution. Pour lire le mapping mémoire d'un processus :

# on lit init au moins on en connait le pid
$ cat /proc/1/maps

Pour créer un processus un faut un autre processus (oeuf ou poule, c'est le noyau, qui lance le premier processus). Cet autre processus va simplement lancer la commande fork() qui ne fait qu'une chose (et la fait bien), se dupliquer lui-même. Donc si bash veut lancer un nouveau processus, il va se dupliquer, on se retrouve alors avec 2 bash, dans un état totalement identique. Rassurez-vous, cela ne consomme pas de ressources, car il n'y a pas de duplication physique, ce n'est qu'en cas de modification de la mémoire que le contenu sera réellement dupliqué.

Mais cela ne suffit pas toujours, dans le cas de bash, il va ensuite se remplacer lui-même avec la commande exec(). Celle-ci ne fait qu'une chose (bien) elle remplace le processus courant par un nouvel exécutable. Ce nouvel exécutable pourrait par exemple être le binaire /bin/ls. Tous les processus ne font pas nécessairement cela, par exemple apache ne fait que des forks car il ne lance que des copies de lui-même.

Thread

Le problème d'un processus c'est que c'est un peu lourd, cela implique des recopies de mémoire et surtout cela implique de passer par le système pour communiquer avec d'autre processus. Il existe donc un autre concept nommé thread.

Un thread, c'est un découpage d'un processus. Cela veut dire qu'il utilise exactement la même mémoire qu'un processus, ainsi que les mêmes descripteurs de fichiers. Ce qui diffère d'un thread à l'autre ce sont uniquement les pointeurs d'exécution et de pile. ce qui veut dire que 2 threads peuvent exécuter des bouts de code différents d'un même exécutable et avoir des variables locales différentes de celles des autres threads du processus.

Du point de vue du noyau un thread est donc uniquement caractérisé par l'état du processeur spécifiquement à l'intérieur d'un processus. Pour créer un nouveau thread, un thread existant (oeuf ou poule, un processus est déjà un thread) va appeler la fonction pthread_create() qui duplique la pile du thread existant et qui duplique l'état du processeur. Le scheduler du noyau va donc voir une nouvelle entrée.

Exemple de mémoire d'un processus auant 2 threads : thread.png

Différence

La différence, vous l'avez tout de suite comprise :

  • les processus sont plus lourd que les threads à créer
  • les processus sont réellement indépendants
  • les threads peuvent se partager des informations facilement
  • les threads doivent eux-mêmes faire attention à ne pas se marcher sur les pieds

D'un point de vue programmation :

  • pour passer un programme en multithread il faut avoir codé proprement et évité les variables globales
  • pour passer un programme en multithread il faut faire attention aux accès en mémoire qui sont potentiellement partagés)
  • en multithread, fermer un fichier déjà ouvert le ferme pour tous les threads et pas seulement celui qui a appelé close()
  • pour passer un programme en multiprocessus il suffit d'appeler fork
  • en multiprocessus, fermer un fichier déjà ouvert ne le ferme que pour le fils qui a appelé close()