Concurrence et synchronisation
Les systèmes d'exploitation actuels induisent inévitablement de la concurrence, car plusieurs fils d'exécution sont simultanés (machines multiprocesseurs, multicœurs, etc.). De plus, l'existence d'instructions non-atomiques ainsi que de la préemption font que nous n'avons aucune garantie que quelqu'un d'autre n'est pas en train de modifier la même ressource que nous.
Deux types de concurrence sont observables : la concurrence physique de différentes tâches (possible uniquement avec une machine multiprocesseur), provoquée par un vrai accès concurrent au matériel, et la concurrence logique, pouvant être provoquée par un accès interrompu par la préemption ou par une interruption matérielle.
Une différence notable concernant la gestion de la concurrence entre le noyau et l'espace utilisateur est la capacité du noyau à maîtriser la préemption et les interruptions. En effet, il peut empêcher la concurrence logique en empêchant les autres acteurs ou événements de s'exécuter. L'espace utilisateur, quant à lui, n'en a aucune maîtrise.
Pour contrer cela, nous allons utiliser des verrous. Rappelons qu'il existe deux types de verrous : ceux à attente active, qui consomment beaucoup de CPU mais sont très réactifs, et ceux à attente passive, qui mettent la tâche en sommeil jusqu'à ce qu'elle soit réveillée par une autre tâche via l'ordonnanceur. Cela est peu réactif mais consomme peu de CPU. Ces mécanismes vont permettre de mettre en place des protections contre la famine ou les deadlocks (selon l'implémentation). En revanche, en espace utilisateur, l'ordonnanceur noyau n'a pas connaissance des verrous pris. La préemption peut rendre une section critique très longue et peut passer la main à une tâche qui va essayer de prendre le même verrou. En somme, en espace utilisateur l'attente passive est préférable.
Dans le noyau Linux, le coût de synchronisation est critique : il existe beaucoup de variantes de verrous, notamment à attente active (spinlocks). De plus, la préemption est désactivée quand un verrou à attente active est pris (garantie d'avoir une section critique courte). Pour éviter de faire exploser le coût en utilisant uniquement des verrous, il est bon d'utiliser des instructions atomiques, qui permettent de modifier une donnée partagée sans verrou.
Concernant les interruptions, le processeur peut être interrompu n'importe quand (périphériques, horloge) y compris au milieu d'une section critique ! Il est donc nécessaire de désactiver les interruptions lors des sections critiques partagées avec un traitant d'interruption. Cela est faisable grâce à des instructions dédiées qui désactivent les interruptions sur le processeur courant (sur un multiprocesseur, il faut en plus prendre un verrou), mais il ne faut pas les désactiver trop longtemps (cela nuit à la réactivité).