Dans les années 1970, les ordinateurs personnels étaient limités et ne pouvaient exécuter qu’une seule tâche à la fois. Les systèmes d’exploitation modernes, tels que Windows, Linux ou macOS, permettent l’exécution simultanée de plusieurs tâches, donnant ainsi l’impression qu’elles s’exécutent en même temps. Ainsi, à un instant donné, plusieurs programmes peuvent être en cours d’exécution sur un ordinateur, appelés processus. Le système d’exploitation a pour rôle d’allouer les ressources nécessaires à chaque processus en termes de mémoire, d’entrées-sorties ou de temps processeur, tout en veillant à ce qu’ils ne se gênent pas mutuellement.
En tant qu’utilisateurs, nous sommes tous confrontés à la gestion des processus dans un système d’exploitation. Lorsque nous cliquons sur l’icône d’un programme, nous créons un ou plusieurs processus liés à ce programme. De même, lorsque le programme ne répond plus, nous avons recours au gestionnaire des tâches pour arrêter le processus défectueux.
Dans cette séquence, nous allons examiner en détail comment les processus sont gérés dans le système d’exploitation Linux.
Qu’est-ce qu’un processus ?
Un processus est un programme en cours d’exécution sur un ordinateur. Il est caractérisé par :
- Un ensemble d’instructions à exécuter, souvent stocké dans un fichier que l’on lance pour démarrer un programme.
- Un espace mémoire dédié au processus pour lui permettre de manipuler ses propres données. Par exemple, si vous ouvrez deux instances de Firefox, chacune travaillera de manière indépendante avec ses propres données.
- Des ressources matérielles comme le processeur et les entrées-sorties, telles que l’accès à Internet via une connexion Wi-Fi.
Il est important de ne pas confondre le fichier contenant un programme (souvent avec l’extension .exe sous Windows) et le ou les processus qu’il engendre lorsqu’ils sont exécutés. Un programme est simplement un fichier contenant une suite d’instructions (par exemple, firefox.exe), tandis que les processus sont des instances de ce programme ainsi que les ressources nécessaires à leur exécution (plusieurs fenêtres de Firefox ouvertes en même temps).
Création d’un processus
Un processus peut être créé de différentes manières :
- Au démarrage du système.
- Par un appel d’un autre processus.
- Par une action de l’utilisateur (lancement d’une application).
Sur Linux, la création d’un processus se fait en clonant un autre processus à l’aide d’un appel système appelé fork().
- Le processus qui effectue l’appel à fork() est appelé le processus père.
- Le processus cloné par cette opération est appelé le processus fils.
- Après le clonage, un processus peut remplacer son programme par un autre programme grâce à l’appel système exec().
Arborescence de processus
Ce mécanisme de création particulier, souvent appelé fork/exec, conduit à la formation d’une arborescence de processus. Un processus père engendre un ou plusieurs fils, qui à leur tour engendrent d’autres fils, et ainsi de suite. Sur Linux, le père de tous les processus est appelé init et est créé au démarrage du système. Vous pouvez visualiser l’arbre des processus en utilisant la commande pstree.
PID, PPID
Chaque processus est identifié par un identifiant unique appelé PID (Process Identifier). Lorsqu’un processus engendre un fils, le système d’exploitation attribue un nouveau numéro de processus à ce fils. Le fils est également informé du PID de son père, appelé PPID (Parent Process Identifier).
Gérer les processus sur un système Linux
Il est possible de visualiser les processus en utilisant la commande ps -eF ou ps -eF | more pour une visualisation page par page.
À vous de jouer
Voici quelques questions pour tester vos connaissances sur les processus Linux :
- Quel est le PID du processus init ?
- Quel est le PPID de init ?
- init a-t-il un frère ?
- Citez quelques descendants directs de init.
Inspecter les processus en temps réel
La commande top est très utile pour examiner les processus en temps réel. Lancez cette commande dans un terminal pour obtenir un affichage similaire à celui-ci :
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 user 20 0 123456 65432 12345 S 0.0 0.0 0:00.00 process1
5678 user 20 0 987654 32198 98765 S 0.0 0.0 0:00.00 process2
...
L’affichage de top se met à jour en temps réel, contrairement à la commande ps qui fournit un instantané. top offre également de nombreuses fonctionnalités supplémentaires. Vous pouvez naviguer dans les options à l’aide de raccourcis clavier. Voici quelques exemples :
h
: affiche l’aide.M
: trie la liste par ordre décroissant de consommation mémoire.P
: trie la liste par ordre décroissant de consommation CPU.i
: filtre les processus inactifs pour ne montrer que ceux qui sont réellement en train de travailler.k
: permet de tuer un processus (à condition que vous en soyez le propriétaire). Essayez de tuer init !V
: affiche une vue arborescente des processus.q
: quitte top.
Terminer un processus
Repérez le PID de top et terminez-le à l’aide de la commande k
.
Pour terminer un processus, un signal de terminaison est envoyé. Les deux signaux les plus couramment utilisés sont :
- SIGTERM (15) : demande la terminaison d’un processus en lui permettant de libérer les ressources qu’il utilise.
- SIGKILL (9) : demande la terminaison immédiate et inconditionnelle d’un processus. Ce signal est utilisé uniquement sur les processus récalcitrants qui ne répondent pas au signal SIGTERM.
Pour terminer top de manière propre, vous pouvez donc lui envoyer le signal SIGTERM en tapant le numéro 15. Cela équivaut à la commande shell kill -15 PID
, où PID est le numéro du processus à terminer de manière propre.
Si le processus ne répond pas à ce signal, vous pouvez le tuer en utilisant kill -9 PID
. Toutefois, veuillez noter que cette action est violente et doit être utilisée avec précaution.
Exercice
Voici un exercice pour mettre en pratique la terminaison d’un processus :
- Lancez l’éditeur de texte.
- Repérez son PID à l’aide de la commande
ps
outop
. - Quittez l’application à l’aide de la commande
kill
.
Ordonnancement des processus par l’OS
Dans un système multitâche, plusieurs processus sont actifs simultanément. Toutefois, un processeur (à un seul cœur) ne peut exécuter qu’une seule instruction à la fois. Par conséquent, il est nécessaire de partager le temps processeur disponible entre tous les processus. C’est le rôle de l’ordonnanceur (ou scheduler en anglais) de sélectionner le processus suivant à exécuter parmi ceux qui sont prêts.
Un processus peut donc se trouver dans différents états :
- Prêt (ready) : le processus attend son tour pour être exécuté.
- En exécution (running) : le processus a accès au processeur pour exécuter ses instructions.
- En attente (sleeping) : le processus attend qu’un événement se produise, comme une saisie clavier, la réception de données via le réseau ou le disque dur.
- Arrêté (stopped) : le processus a terminé son travail ou a reçu un signal de terminaison (SIGTERM, SIGKILL, etc.). Il libère les ressources qu’il utilise.
- Zombie : Après avoir été arrêté, le processus informe son parent qu’il peut être supprimé de la table des processus. Bien que cet état soit temporaire, il peut persister si le parent meurt avant d’effectuer cette tâche. Dans ce cas, le processus fils reste dans l’état zombie…
Les trois premiers états sont les plus importants, car ils décrivent le cycle de vie normal d’un processus. Afin de choisir quel processus sera exécuté ensuite, l’ordonnanceur applique un algorithme prédéfini lors de la conception du système d’exploitation. Le choix de cet algorithme a un impact direct sur la réactivité du système et sur les différentes utilisations possibles. Il s’agit donc d’un élément critique du système d’exploitation.
Sous Linux, il est possible de donner des instructions à l’ordonnanceur en définissant des priorités pour les processus dont vous êtes propriétaire. Cette priorité est un nombre compris entre -20 (plus prioritaire) et +20 (moins prioritaire). Vous pouvez agir à deux niveaux :
- Fixer une priorité à une nouvelle tâche dès son démarrage à l’aide de la commande
nice
. - Modifier la priorité d’un processus en cours d’exécution grâce à la commande
renice
.
Les colonnes PR et NI de la commande top indiquent le niveau de priorité de chaque processus. Le lien entre PR et NI est simple : PR = NI + 20. Ainsi, une priorité PR de 0 équivaut à un niveau de priorité maximal.
Exemple : Pour réduire la priorité du processus Terminator, dont le PID est 21523, il suffit de taper :
renice +10 21523
À vous de jouer
Nous allons tester l’efficacité du paramètre nice de l’ordonnanceur sur le temps d’exécution d’un programme Python. Pour cela, nous allons charger le processeur de la machine, puis chronométrer le temps d’exécution d’un script Python pour différentes valeurs du paramètre nice.
Pour cet exercice, n’hésitez pas à ouvrir plusieurs fenêtres de terminal côte à côte.
- Utilisez la commande
cat /proc/cpuinfo
pour connaître le nombre de processeurs disponibles sur votre machine. - Créez un programme Python nommé infini.py contenant une boucle infinie.
- Créez un deuxième programme de test.
- Lancez un interpréteur Python3 et notez son PID.
- Dans l’interpréteur Python, tapez les commandes suivantes pour lancer la fonction bidon 100 fois et obtenir le temps d’exécution moyen.
- Tapez la commande
python3 infini.py &
autant de fois qu’il y a de processeurs sur votre machine. Le symbole&
indique au shell de lancer le programme en arrière-plan. Ainsi, nous monopolisons toutes les ressources processeurs de la machine avec des boucles infinies. L’action de l’ordonnanceur sera clairement visible car les ressources processeur vont se raréfier. - Relancez
timeit(test.bidon, number=100)
dans l’interpréteur Python. Vous devriez constater un ralentissement par rapport à la première exécution. En effet, le processeur a moins de temps à consacrer à l’exécution de la fonction bidon. - Modifiez la priorité de l’interpréteur Python en utilisant
nice +10
. - Relancez
timeit(test.bidon, number=100)
dans l’interpréteur Python. Que constatez-vous ?
Interblocage (ou deadlock)
Les situations d’interblocage surviennent également dans la vie quotidienne. Par exemple, pensez à un carrefour où la priorité est donnée aux véhicules venant de la droite. Chaque véhicule est bloqué car il doit laisser passer le véhicule à sa droite.
En informatique, un interblocage peut également se produire lorsque des processus concurrents s’attendent mutuellement. Les processus bloqués dans cet état le restent indéfiniment. Ce scénario catastrophe peut survenir dans un environnement où des ressources sont partagées entre plusieurs processus, et où l’un d’entre eux détient indéfiniment une ressource nécessaire à un autre processus.
Cette situation d’interblocage a été théorisée par l’informaticien Edward Coffman (1934-), qui a énoncé quatre conditions (appelées conditions de Coffman) conduisant à l’interblocage :
- Exclusion mutuelle : au moins une ressource du système doit être en accès exclusif.
- Rétention des ressources : un processus détient au moins une ressource et requiert une autre ressource détenue par un autre processus.
- Non préemption : seul le détenteur d’une ressource peut la libérer.
- Attente circulaire : chaque processus attend une ressource détenue par un autre processus. P_1 attend une ressource détenue par P_2, qui à son tour attend une ressource détenue par P_3, etc., jusqu’à ce que P_1 attende une ressource détenue par P_1, ce qui boucle et crée l’interblocage.
Il existe heureusement des stratégies pour éviter ces situations. Nous ne rentrerons pas dans les détails ici, car cela dépasse le cadre de cet article.
À vous de jouer
Voici deux exercices pour approfondir votre compréhension de l’interblocage :
- Identifiez et expliquez les quatre conditions de Coffman conduisant à l’interblocage, en utilisant comme exemple le carrefour à priorité à droite.
- Imaginez d’autres situations de la vie quotidienne où un interblocage peut se produire.
En conclusion, la gestion des processus est une partie essentielle des systèmes d’exploitation modernes. Comprendre comment les processus sont créés, gérés et ordonnancés est crucial pour optimiser les performances et éviter les problèmes tels que l’interblocage.