Licence CC BY-NC-ND

Mécanismes d'abstraction des périphériques

Au tout début de l'informatique, avant l'invention des systèmes d'exploitation, les programmes étaient conçus pour un ordinateur en particulier et la compatibilité entre les différents ordinateurs était médiocre. Avec le temps, les informaticiens se sont rendus compte qu'il était nettement mieux de faire en sorte qu'un programme puisse s'exécuter sur plusieurs ordinateurs avec des matériels différents. Il a donc fallu créer des techniques qui permettent à un ou plusieurs programmes de s'exécuter sur toutes sortes de matériels différents : c'est ce qu'on appelle l'abstraction matérielle.

Pour cela, les informaticiens ont inventé différents programmes chargés de la gestion du matériel et qui permettent de plus ou moins faire passer le problème de la compatibilité à la trappe. Le système d'exploitation de notre ordinateur est l'un d'entre eux.

Pourquoi utiliser un OS ?

Sans système d'exploitation, un seul et unique programme dans l'ordinateur se charge de tout faire, y compris manipuler le matériel. Dès lors, un programmeur qui souhaite se passer de système d'exploitation doit tout programmer, y compris les parties du programme en charge de la gestion des périphériques, des ports d'entrée-sortie, de la mémoire… Vu qu'on ne gère pas les périphériques et/ou la mémoire de la même façon sur tous les ordinateurs, ce que le programmeur aura réalisé sera difficilement portable sur d'autres ordinateurs, si tant est que cela soit possible.

Programmes systèmes et applicatifs

Mais plutôt que de créer un seul et unique programme informatique chargé de tout gérer, on peut segmenter ce programme en plusieurs programmes séparés. Ces programmes peuvent être divisés en deux types :

  • les programmes systèmes gèrent la mémoire, les périphériques, et les autres programmes applicatifs ;
  • les programmes applicatifs sont des programmes qui délèguent la gestion de la mémoire et des périphériques aux programmes systèmes.

Pour information, voici une liste des tâches qui sont déléguées aux programmes systèmes :

  • gérer une partie de ce qui concerne la mémoire ;
  • la gestion de l’exécution de plusieurs programmes sur un même ordinateur ;
  • permettre à plusieurs programmes d'échanger des données entre eux si besoin est ;
  • gérer tous les périphériques de l'ordinateur ;
  • la gestion des fichiers, du réseau, du son et de l'affichage ;

Généralement, les programmes systèmes sont tous regroupés dans ce qu'on appelle un système d'exploitation, aussi appelé OS (operating system en anglais). En plus de ces programmes systèmes, le reste de l'OS est composé de programmes applicatifs, dont certains permettent d'afficher une interface qui permet à l'utilisateur de pouvoir utiliser l'ordinateur comme il le souhaite. Évidement, les programmes systèmes ne sont pas les mêmes sur tous les systèmes d'exploitation : c'est une des raisons qui font que certains programmes ne sont compatibles qu'avec (mettez ici le nom de n'importe quel OS).

Image de Juju2004, CC-BY-SA 3.0, wikicommons

L'intérêt de cette séparation ?

Très simple : plutôt que de devoir reprogrammer à chaque fois la gestion de la mémoire et des périphériques, ce travail de programmation est déjà fait. Les programmeurs peuvent se contenter de créer des programmes applicatifs et n'ont pas besoin d’écrire les programmes systèmes.

Cependant, un système d'exploitation ne sait pas utiliser tous les périphériques et ports d'entrée-sortie existants. Pour cela, on a inventé les pilotes de périphériques, des programmes systèmes qui permettent à un OS de communiquer avec un périphérique. Évidement, la façon dont le pilote de périphérique va communiquer avec le système d'exploitation est standardisée pour faciliter le tout.

Appels système

L'ensemble des opérations qui permettent à notre programme applicatif d’exécuter des programmes systèmes au besoin s'appelle un appel système. Pour en effectuer, les programmes applicatifs vont utiliser des fonctionnalités du processeur qu'on appelle des interruptions.

Interruptions

Holà, c'est quoi une interruption ?

C'est une fonctionnalité de notre processeur qui va permettre d’arrêter temporairement l’exécution d'un programme pour en exécuter un autre. Ces interruptions ont pour but d'interrompre un programme, effectuer un traitement et de rendre la main au programme stoppé. L'interruption va exécuter un petit programme auquel on a donné le nom technique de routine d'interruption.

Interruption

L'OS et nos pilotes fournissent toutes les routines d'interruptions de bases pour que notre matériel fonctionne : vous voulez écrire une donnée sur le disque dur ? Un programme système exécutant des interruptions est fourni par votre OS. C'est ainsi que les programmes applicatifs peuvent appeler à la demande un programme système : en exécutant l'interruption qui va bien.

Vecteur d'interruption

Devant la multiplicité des périphériques, on se doute bien qu'il existe plusieurs routines d'interruption : un programme envoyant un ordre au disque dur sera différent d'un programme agissant sur une carte graphique. Mais il faut bien décider quelle est l'interruption à exécuter suivant la situation : par exemple, exécuter l'interruption de gestion du clavier alors qu'on souhaite communiquer avec le disque dur donnerait un résultat plutôt comique. :p

Pour retrouver la routine en mémoire, certains ordinateurs utilisent un tableau qui stocke les adresses de chaque routine d'interruption appelé le vecteur d'interruption. Ce vecteur d'interruption est initialisé par le BIOS au démarrage de l'ordinateur, mais les pilotes et l'OS vont fournir leurs propres routines. Pour que celles-ci soient exécutées, il suffit à l'OS de « détourner l'interruption », c'est-à-dire de remplacer et mettre à jour le vecteur d'interruption avec les adresses des routines de l'OS. En clair, le vecteur d'interruption ne contiendra plus l'adresse servant à localiser la routine du BIOS, mais renverra vers l'adresse localisant la routine de l'OS.

Comment profiter de ces interruptions ?

Pour déclencher une interruption, les programmes applicatifs exécutent une instruction machine spéciale. Sur les processeurs x86, on utilise l'instruction machine int. Cette instruction machine a besoin de quelques petites informations pour faire ce qui est demandé et notamment de savoir quelle interruption exécuter. Pour cela, elle a besoin du numéro ou de l'adresse de l'interruption dans le vecteur d'interruption.

Une grande partie de ces routines a besoin qu'on leur fournisse des paramètres, des informations pour qu'elles fassent leur boulot. Par exemple, une routine devant afficher une lettre à l'écran aura besoin qu'on lui passe en entrée la lettre à afficher. Pour chaque routine, il suffira de copier ces paramètres (ou un pointeur vers ceux-ci) dans des petites mémoires ultra-rapides intégrées dans le processeur qu'on appelle les registres.

Noyau d'un OS

On l'a vu auparavant, les programmes systèmes et les programmes applicatifs n'ont pas vraiment les mêmes droits : certains peuvent accéder à la mémoire et d'autres non. Pour le moment, on a aucune certitude qu'un programme applicatif n'accédera pas aux périphériques ou fasse des manipulations dangereuses avec la mémoire. Il faut donc trouver un moyen de protéger le matériel contre les actions dangereuses des programmes applicatifs et faire en sorte que ceux-ci restent à leur place. Ce moyen, c'est une technique incorporée dans les processeurs actuels : les anneaux mémoires.

Espace noyau et espace utilisateur

Les anneaux mémoire sont des zones de mémoire avec des droits d'accès au matériel différents. Pour obtenir une séparation entre programmes systèmes et programmes applicatifs, il faut au minimum deux niveaux de privilèges distincts : un anneau pour les programmes systèmes et un autre pour les programmes applicatifs. L'anneau des programmes systèmes est ce qu'on appelle l'espace noyau, alors que l'autre est appelé l'espace utilisateur. Il peut y en avoir plus sur certains ordinateurs, par exemple, un processeur x86 32 bits supporte 4 niveaux de privilèges, même si seulement deux sont utilisés en pratique.

Sur certains systèmes, la mémoire n'est pas séparée comme suit, tout dépend de comment le CPU gère la mémoire !

Noyau d'un OS

Tout programme qui s'exécute avec le niveau de privilège de l'espace noyau va pouvoir faire tout ce qu'il souhaite : accéder aux périphériques et aux ports d'entrée-sortie, manipuler l'intégralité de la mémoire, etc. Tous les programmes de notre système d’exploitation placés dans l'espace noyau sont ce qu'on appelle le noyau du système d’exploitation. La moindre erreur de programmation d'un programme en espace noyau a des conséquences graves : tous vos écrans bleus ou vos kernel panic (l'équivalent chez les OS UNIX) sont dus à une erreur en espace noyau (généralement par un pilote de carte 3D ou un problème matériel).

Les programmes en espace utilisateur ne peuvent pas écrire ou lire dans la mémoire des autres programmes ou communiquer avec un périphérique, il doivent déléguer cette tâche à un programme système via une interruption. L'avantage, c'est qu'une erreur commise par un programme en espace utilisateur n'entraine pas d'écrans bleus. C'est donc un gage de sûreté et de fiabilité.

Dans l'espace utilisateur, diverses instructions machines du processeur ne sont pas autorisées car jugées trop dangereuses. Par exemple, sur un processeur à architecture x86, les instruction in et out, qui permettent respectivement de lire ou écrire un octet depuis un périphérique sont interdites. Dans le même genre, certaines routines d'interruptions ne sont pas exécutables directement en espace utilisateur.

Implémentation

Les anneaux mémoires sont gérés par un circuit qui gère tout ce qui a rapport avec la gestion de la mémoire : la Memory Management Unit, abréviée en MMU. Le niveau de privilège d'un programme en cours d'exécution est connu grâce à des bits contenus dans des registres spéciaux placés dans le processeur, ce qui permet à la MMU de détecter les violations de droits d'accès. Un programme ne peut changer d'anneau mémoire au cours de son exécution.

Lorsqu'un programme effectue une instruction ou demande l’exécution d'une fonctionnalité du CPU interdite par son niveau de privilège, l'unité de gestion mémoire (la MMU, donc) va déclencher une interruption d'un type un peu particulier : une exception matérielle. Généralement, le programme est arrêté sauvagement et un message d'erreur est affiché. Vous savez maintenant d'où viennent vos messages d'erreurs sous votre OS préféré.

Principaux types de noyaux

Exécuter un appel système est très lent. Dès lors, exécuter des instructions juste pour changer de niveau de privilège et demander l’exécution d'une routine, devrait de préférence être évité. On se retrouve donc avec deux contraintes : les performances et la sécurité. Avoir de bonnes performances nécessite de diminuer le nombre d'appels systèmes, ce qui se fait en laissant des programmes dans le noyau. Par contre, la sécurité s'obtiendra en mettant un maximum de trucs en espace utilisateur, ce qui augmente le nombre d'appels systèmes. On peut ainsi classer les noyaux en plusieurs types, selon leurs priorité et la façon dont ils gèrent ce genre de problèmes.

Noyaux mégalithiques

Avec les noyaux mégalithiques, tout l'OS est dans l'espace noyau.

Noyau monolithique

Dans le cas du noyau monolithique, un maximum de programmes systèmes est placé dans l'espace noyau. Avec ce genre d'organisation, très peu d'appels systèmes sont effectués, ce qui est un avantage en terme de performances. Mais cela est aussi un désavantage en terme de sûreté.

Un noyau modulaire est un noyau monolithique qui est divisé en plusieurs parties bien distinctes nommées les modules. Par exemple, chaque pilote de périphérique sera stocké dans un module séparé du reste du noyau. Avec ce genre d'organisation, on peut ne charger que ce dont on a besoin au lancement de l'ordinateur (par exemple, cela permet de ne pas charger le pilote d'un périphérique qui n'est pas branché sur l'ordinateur). Cela permet aussi de rajouter plus facilement des modules dans le noyau sans avoir à refaire celui-ci depuis le début.

Image de Julien Sopena, wikicommons, GFDL et CC-BY-SA 3.0

Micro noyau

Pour gagner en sureté de fonctionnement, certains créateurs de systèmes d'exploitation ont décidé de ne laisser dans le noyau que les programmes qui ont absolument besoin d'un niveau de privilège élevé. Ces micro-noyaux sont souvent très légers : peu de programmes systèmes sont présents dans le noyaux, les autres étant évacués dans l'espace utilisateur. L'avantage, c'est qu'un bug a plus de chances de se retrouver dans l'espace utilisateur. Mais cela implique de nombreux appels systèmes entre les programmes systèmes en espace utilisateur et ceux en espace noyau, ce qui réduit les performances.

Image de Raphael Javaux, GFDL et CC-BY-SA 3.0, wikicommons

Noyau hybride

Dans les noyaux hybrides, on garde la même philosophie que pour les micro-noyaux, en étant un peu plus souple : on évacue un maximum de programmes systèmes dans l'espace utilisateur. Néanmoins, certains programmes systèmes, très demandeurs en appels systèmes sont placés en espace noyau. Cela évite de plomber les performances en générant trop d'appels systèmes.

Image de Raphael Javaux, GFDL et CC-BY-SA 3.0, wikicommons

Exokernels

Enfin, une dernière catégorie de noyaux existe : les exokernels. Celle-ci consiste à extraire le plus de programmes systèmes du noyau tout en conservant la sécurité de fonctionnement en utilisant les anneaux mémoires. En clair, même des programmes systèmes deviennent des programmes en espace utilisateur qui utilisent un ensemble minimal de fonctionnalités fournies par un noyau réduit à son strict minimum.

Les différentes abstractions matérielles, qui permettent de simplifier la façon dont un programme va devoir gérer la mémoire, le disque dur, le processeur et tout le reste sont donc placés dans l'espace utilisateur. Seul un minuscule noyau existe, qui se charge simplement de contenir quelques routines qu'on a pas pu évacuer dans l'espace utilisateur. Par exemple, dans le cas du disque dur : un exokernel n'implémentera pas de quoi camoufler l’organisation physique du disque dur (cylindre, têtes et pistes) et ne gérera pas de systèmes de fichiers.

L'avantage, c'est qu'un programmeur peut reprogrammer tout seul la gestion de la mémoire, du disque dur ou du reste du système d’exploitation. Dans certains cas particuliers, cela permet soit d'avoir une meilleure sécurité, soit de pouvoir programmer en étant bien plus proche du matériel ou en optimisant nettement mieux notre application.