Comment un binaire devient un processus en cours d’exécution
- 21 avril 2025
- 13 mins de lecture
- Systèmes d'exploitation , Concepts de programmation
Table des matières
Vous êtes-vous déjà demandé ce qui se passe réellement lorsque vous lancez un programme ?
Pas simplement cliquer sur « Run » ou exécuter une commande dans le terminal, mais tout ce qui se passe en coulisses — du fichier exécutable stocké sur le disque jusqu’à un processus pleinement actif en mémoire.
Dans cet article, nous allons voir comment les processus sont créés, comment leur mémoire est organisée, et comment cela fonctionne aussi bien sous Windows que sous les systèmes de type Unix.
Que vous écriviez du code en C, Python ou Rust, comprendre ce mécanisme vous rendra plus efficace et plus pertinent en tant que développeur.
Fiches pratiques gratuites sur la création de processus
Obtenez le guide de référence rapide pour fork(), execve(), CreateProcessA(), et bien d’autres appels système — en anglais — plus les mises à jour sur les nouveaux contenus.
​
Fichiers exécutables : le point de départ d’un processus
Au cœur de chaque processus en cours d’exécution se trouve un fichier exécutable — un fichier binaire contenant des instructions que le CPU peut comprendre et exécuter directement.
Si vous développez avec des langages compilés comme C, C++, Rust ou Go, votre code est traduit par un compilateur en un exécutable binaire. C’est ce binaire que le système d’exploitation charge et exécute.
À l’inverse, pour les langages interprétés ou basés sur une machine virtuelle comme Python, JavaScript ou Java, votre code s’exécute via un interpréteur ou une machine virtuelle.
Ces interpréteurs sont eux-mêmes des binaires compilés. Lorsque vous lancez un script, c’est donc l’interpréteur qui devient le processus.
- 📖 Analyse détaillée :
- Pour comprendre en détail comment ces différents langages transforment un code écrit en langage humain en instructions binaires, consultez mon guide : Du code source au code machine : les deux voies vers un programme exécutable.
Chaque système d’exploitation utilise un format spécifique pour les fichiers exécutables :
- Windows : PE (Portable Executable)
- Linux : ELF (Executable and Linkable Format)
- macOS : Mach-O (Mach Object)
Les fichiers exécutables contiennent des sections structurées, chacune ayant un rôle précis. Même si les noms varient légèrement selon les formats — ELF (Linux), PE (Windows) et Mach-O (macOS) — les concepts restent globalement identiques :
-
Code et données
.text→ instructions machine- PE :
.text, Mach-O :__TEXT,__text
- PE :
.data→ variables initialisées- PE :
.data, Mach-O :__DATA,__data
- PE :
.bss→ variables non initialisées- PE :
.bss, Mach-O :__DATA,__bss
- PE :
.rodata→ données en lecture seule (ex. chaînes de caractères)- PE :
.rdata, Mach-O :__TEXT,__const
- PE :
-
Tables des symboles et des chaînes
.symtab→ table des symboles- PE : symboles COFF ou fichier
.pdbexterne, Mach-O : viaLC_SYMTAB
- PE : symboles COFF ou fichier
.strtab→ table des chaînes (noms des symboles)- PE : partie de COFF, Mach-O : également via
LC_SYMTAB
- PE : partie de COFF, Mach-O : également via
-
Informations de relocalisation
.reloc→ utilisé pour l’édition de liens dynamique et l’ajustement des adresses- PE :
.reloc, Mach-O : entrées de relocalisation spécifiques aux sections
- PE :
…et bien d’autres, selon la plateforme et les options de compilation.
Certaines sections sont en lecture seule (comme .rodata et .text).
D’autres sont lisibles et modifiables (comme .bss et .data).
Le système d’exploitation s’appuie sur cette structure pour mapper correctement l’exécutable en mémoire.
Dans cet exemple :
1const int G_ARGV_INDEX = 1;
2char g_element;
3int g_index = 0;
4int main(int argc, char *argv[]) {
5 char *match = "Hello";
6 if (argc < G_ARGV_INDEX) {
7 return 1;
8 }
9 // ...
10 return 0;
11}
La section .text contient le code machine compilé de la fonction main() et des autres fonctions.
La section .data contient les variables globales ou statiques initialisées comme g_index, tandis que g_element se trouve dans .bss car elle n’est pas initialisée.
La section .rodata contient la chaîne "Hello" ainsi que la constante G_ARGV_INDEX.
Les sections .symtab et .strtab incluent les noms des symboles et leurs adresses, permettant à l’éditeur de liens de résoudre les références entre les différentes parties du programme.
Note
Contrairement au code source où les variables sont représentées par leurs noms, dans le fichier exécutable elles sont représentées par leurs adresses. Le chargeur du système d’exploitation utilise ces informations pour mapper correctement les sections en mémoire.
​
Qu’est-ce qu’un processus ?
Lorsqu’un fichier exécutable est lancé, le système d’exploitation le transforme en processus. Un processus ne se limite pas à du code. C’est une instance en cours d’exécution, avec sa propre mémoire et ses structures de contrôle.
Chaque processus comporte deux éléments clés.
​
1. Espace mémoire
C’est là que le contenu du fichier exécutable est chargé en RAM.
On y retrouve les mêmes sections — .text, .data, .bss, etc.
Le chargeur du système d’exploitation se charge de les copier et de les mapper en mémoire.
À la fin de cet espace mémoire, le système stocke également :
- Les variables d’environnement (par exemple
MY_ENV=helloavec$ MY_ENV=hello python hello.py) - Les arguments du programme (comme
hello.pyavec$ python hello.py)
Entre les deux, on trouve des zones essentielles à l’exécution du programme : la pile, le tas et les mappages mémoire.
- Pile (stack) : stocke les variables locales et les informations d’appel de fonctions. Elle grandit et rétrécit selon les appels et retours de fonctions.
- Tas (heap) : utilisé pour l’allocation dynamique de mémoire. Il évolue au fil des allocations et libérations.
- Mappages mémoire : emplacement des bibliothèques partagées et des ressources chargées dynamiquement dans l’espace d’adressage du processus.
Le système d’exploitation utilise un système de mémoire virtuelle, permettant l’isolation des processus et empêchant toute interférence entre eux.
​
2. Process Control Block (PCB)
Le PCB est une structure de données maintenue par le système d’exploitation. Elle contient toutes les métadonnées liées au processus.
Cela inclut :
- Identifiant du processus (PID)
- Pointeur vers le PCB du processus parent
- Code de retour
- État du processus
- Descripteurs de fichiers ouverts
- Registres CPU
- Compteur ordinal
- Gestionnaires de signaux
- Priorité du processus
- Répertoire de travail courant
- …et bien plus encore
Sous Linux, le PCB est représenté par une structure C appelée task_struct, définie dans le code source du noyau.
Cette structure contient tous les champs nécessaires à la gestion et à l’ordonnancement du processus par le noyau.
Elle est stockée dans l’espace noyau, et non dans l’espace utilisateur, garantissant que seul le système d’exploitation peut y accéder.
L’ensemble des PCB est conservé dans une structure globale appelée table des processus, qui suit tous les processus en cours d’exécution.
Encore plus intéressant : chaque processus est généralement créé par un autre processus. C’est une chaîne continue de créations.
Par exemple, voici un arbre de processus simplifié sur un système Linux :
1user@host:~$ pstree -as 16980
2init(Ubuntu-20.
3 └─SessionLeader
4 └─Relay(11773)
5 └─bash
6 └─python
(Vous pouvez exécuter pstree vous-même pour explorer les liens entre les processus sur votre machine.)
Lorsque vous lancez l’interpréteur Python, il devient un processus enfant du shell (bash, zsh, etc.).
Le shell est lui-même un descendant du processus init (ou systemd sur les systèmes Linux modernes), qui est le tout premier processus lancé au démarrage du système.
Cette hiérarchie apparaît naturellement lors de la création et de la gestion des processus par le système d’exploitation.
Comprendre cette relation parent-enfant est essentiel. Mais comment un nouveau processus est-il réellement créé ? Voyons cela étape par étape.
​
Création d’un processus : étape par étape
Comment le système d’exploitation passe-t-il d’un fichier exécutable à un nouveau processus actif ?
Voici les principales étapes.
-
Duplication du parent Un nouveau processus est créé en copiant l’espace mémoire et le PCB d’un processus existant (le parent).
Certains champs, comme le PID et le parent, sont mis à jour pour refléter l’identité du processus enfant.
D’autres champs, comme le compteur ordinal ou les descripteurs de fichiers ouverts, sont généralement identiques à ceux du parent.
-
Chargement de l’exécutable Le nouveau processus remplace ensuite son espace mémoire par le contenu du fichier exécutable. Les sections sont mappées exactement comme défini par le format du fichier.
La copie d’espaces mémoire volumineux étant coûteuse, les systèmes utilisent souvent le Copy-On-Write (COW). Le parent et l’enfant partagent initialement les mêmes pages mémoire.
Si l’un des deux modifie une page, le système crée alors une copie distincte pour le processus concerné. Cela économise du temps et de la mémoire.
​
Comment un parent surveille ses processus enfants
Lorsqu’un processus parent crée un processus enfant (par exemple via fork()), il doit souvent surveiller son état.
En général, le parent :
- Attend la fin de l’exécution du processus enfant.
- Récupère son code de retour.
Sur les systèmes de type Unix, les appels système comme wait() et waitpid() sont utilisés.
Ils permettent au parent de bloquer son exécution jusqu’à la terminaison de l’enfant, puis d’analyser la manière dont il s’est terminé (succès, échec ou signal).
Sous Windows, des fonctions équivalentes existent, comme WaitForSingleObject(), qui attend la fin d’un processus, et GetExitCodeProcess(), qui récupère son code de retour.
Cette surveillance est essentielle pour la gestion des ressources, le traitement des erreurs et pour éviter les processus zombies ou orphelins.
Fiches pratiques gratuites sur la création de processus
Obtenez le guide de référence rapide pour fork(), execve(), CreateProcessA(), et bien d’autres appels système — en anglais — plus les mises à jour sur les nouveaux contenus.
​
Créer des processus dans le code
​
Sous Unix / Linux / macOS (systèmes POSIX)
Vous pouvez créer un nouveau processus avec l’appel système fork().
Il duplique le processus appelant.
Exemple simple en C :
1#include <stdio.h>
2#include <unistd.h>
3int main() {
4 pid_t pid = fork();
5 if (pid < 0) {
6 // Fork a échoué
7 fprintf(stderr, "Fork failed\n");
8 return 1;
9 }
10 printf("The value of pid is %d.\n", pid);
11 return 0;
12}
La sortie sera :
The value of pid is 2623.
The value of pid is 0.
Le parent et l’enfant continuent à exécuter le même code, mais ils peuvent se distinguer grâce à la valeur de retour de fork() :
- L’enfant reçoit
0 - Le parent reçoit le PID de l’enfant
Pour charger un autre exécutable dans le processus enfant, on utilise une fonction de la famille exec().
Ces fonctions remplacent l’image du processus courant par celle d’un nouvel exécutable.
Par exemple, utiliser l’utilitaire printenv pour afficher une variable d’environnement ENV_1 :
1#include <stdio.h>
2#include <unistd.h>
3#include <sys/wait.h>
4int main() {
5 pid_t pid = fork();
6 if (pid < 0) {
7 fprintf(stderr, "Fork failed\n");
8 return 1;
9 }
10 if (pid == 0) {
11 char *args[] = {"printenv", "ENV_1", NULL};
12 char *envp[] = {"ENV_1=Child: env var 1", "ENV_2=2", NULL};
13 execve("/usr/bin/printenv", args, envp);
14 } else {
15 printf("Parent: Hello (child pid is %d).\n", pid);
16 wait(NULL);
17 }
18 return 0;
19}
La sortie affiche la valeur de ENV_1 définie dans le processus enfant, tandis que le parent affiche son propre message :
Parent: Hello (child pid is 7703).
Child: env var 1.
Il existe plusieurs variantes de exec() selon les besoins (execl, execv, execvp, etc.).
Autres mécanismes de création de processus
Même si la combinaison fork() + exec() est la méthode classique sur les systèmes Unix, d’autres appels système existent pour des usages plus spécifiques :
-
clone(): principalement disponible sous Linux,clone()est une version plus flexible defork(). Il permet de contrôler précisément le partage des ressources entre le parent et l’enfant, comme la mémoire, les descripteurs de fichiers ou les espaces de noms.
Contrairement àfork(), qui duplique tout le processus,clone()permet de choisir ce qui est partagé ou isolé.
Il est à la base des bibliothèques de threads commepthread_create()et des technologies de conteneurs. -
posix_spawn(): fait partie du standard POSIX. Cette fonction combinefork()etexec()en un seul appel.
Elle est particulièrement utile dans les environnements sensibles aux performances (macOS, systèmes embarqués), où le coût defork()peut être trop élevé.
Ces alternatives sont utilisées dans des contextes spécifiques, mais elles illustrent bien la flexibilité de la gestion des processus sous Unix.
​
Sous Windows
Sous Windows, la création de processus se fait via la fonction CreateProcessA().
Elle :
- Crée un nouveau processus
- Lance son thread principal
- Alloue un nouvel espace mémoire
- Initialise un nouveau PCB
1#include <windows.h>
2int main() {
3 STARTUPINFOA si = { sizeof(si) };
4 PROCESS_INFORMATION pi;
5 CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
6 WaitForSingleObject(pi.hProcess, INFINITE);
7 CloseHandle(pi.hProcess);
8 CloseHandle(pi.hThread);
9 return 0;
10}
Vous fournissez le chemin vers l’exécutable et pouvez éventuellement passer des arguments et des variables d’environnement.
La fonction nécessite de nombreux paramètres, mais elle offre un contrôle très précis sur le processus créé.
Il existe plusieurs variantes de CreateProcess selon le niveau de contrôle requis.
Fiches pratiques gratuites sur la création de processus
Obtenez le guide de référence rapide pour fork(), execve(), CreateProcessA(), et bien d’autres appels système — en anglais — plus les mises à jour sur les nouveaux contenus.
​
Conclusion
Comprendre la différence entre l’espace mémoire d’un processus et son PCB est fondamental pour la programmation système, le débogage et l’écriture d’applications efficaces.
Ce n’est pas une simple théorie. Cela apporte une vraie confiance quand on travaille près du système.
Savoir comment les langages compilés et interprétés interagissent avec l’OS aide aussi à mieux comprendre les performances et le comportement des programmes.
Que vous exploriez les appels système, le fonctionnement interne d’un OS ou que vous cherchiez simplement à écrire du meilleur code, cette connaissance est un atout solide.
La prochaine fois que vous cliquerez sur « Run » ou que vous créerez un processus par code, vous saurez exactement ce qui se passe sous la surface.
​
Lectures et ressources complémentaires
Pour aller plus loin sur la création de processus, la gestion mémoire et les formats exécutables, consultez ces ressources de qualité (en Anglais):
- Comprendre le gestionnaire de mémoire virtuelle Linux – Chapitre sur l’espace d’adressage des processus
- Notes de cours sur les systèmes d’exploitation – UIC : création de processus
- CS140 : cours sur les processus – Université de Stanford
- Gestion des processus sous Linux (PDF) – Université de Boston
- Notes sur les systèmes d’exploitation – Université de l’Illinois à Chicago
- Analyse de la structure des exécutables PE (Medium)