Cours 1: Système: Rôle, principales composantes d'un noyau.

Un système d'exploitation d'ordinateur, ou plus simplement système quand le contexte informatique est sous-entendu, est un logiciel offrant trois services: piloter les périphériques (clavier, écran, réseau, disque, etc ...), gérer les ressources de mémorisation (mémoire centrale et mémoire de swap, mémoire graphique, etc ...) et partager les ressources de calcul (processeur(s)).

Le système est composé d'un ensemble de services accessibles au programmeur sous forme de fonctions (les appels système), elles-mêmes faisant appel aux fonctions internes du noyau. Le tout est organisé en couches comme le montre le schéma ci-dessous.

La couche la plus externe est celle des fonctions systèmes de la librairie standard C. Elle est indépendante du système, ses fonctions étant disponibles aussi bien dans le monde des systèmes ouverts que dans le monde Windows. Cette couche concerne essentiellement les entrés-sorties et l'allocation mémoire.

Dans le monde des systèmes ouverts (Unix, Linux, etc ...), la couche suivante est celle des appels POSIX. Les prototypes des fonctions sont normalisés ainsi que les structures de données du système auxquelles elles accèdent.

Les couches suivantes sont internes (les fonctions concernées sont appelées directement par le noyau via les appels systèmes et inaccessibles au programmeur). Par exemple pour les entrées-sorties, on trouve successivement la couche des pilotes logiques (articulation entre une opération générale d'entrée-sortie et le pilote physique du périphérique concerné au moment de l'appel), la couche des pilotes physiques (initiation et terminaison d'un échange avec un périphérique) et enfin la couche des gérants d'interruptions (traitement asynchrone d'un échange avec un périphérique). La couche logique est indépendante des périphériques (écriture sur un fichier, que la destination réelle soit sur disque ou à l'écran). La couche physique est indépendante du matériel (accès à un périphérique générique: un clavier, un écran, un disque). La couche d'interruption est la seule dépendante du matériel (dialogue direct avec le contrôleur du périphérique).

Pour illustrer le fonctionnement général d'un système, nous allons présenter trois exemples. Le premier montre la traversée des couches du noyau lors d'un appel système d'entrée-sortie à destination du clavier (getc(stdin)). Le second décrit les actions du noyau lorsqu'on référence une variable en mémoire (cas d'une faute de page). Le troisième montre comment le noyau procède à un changement de contexte (passage d'un processus à un autre).

Lecture d'un caractère au clavier.

1 et 2: Appel à la fonction standard getc (on accède à une table des appels systèmes qui est un relais vers la fonction dans le noyau.

3: Appel du pilote logique de lecture (read), pour un caractère, à ranger dans un buffer local (buf).

4: Le périphérique de l'entrée standard (stdin) est dirigé vers le fichier spécial /dev/tty, dont l'inode nous donne le couple (majeur, mineur). Le numéro de majeur est un accès au pilote physique. Appel du pilote physique du clavier (kbd).

5: Le numéro de mineur détermine le clavier précis duquel le processus est lecteur. Cela fixe dans le noyau la zone mémoire servant de tampon d'entrée. Si le tampon est vide (ce qui est probablement le cas), le processus lecteur doit s'endormir. Le noyau effectue un changement de contexte. Le processus lecteur est mis en sommeil en attente d'un réveil de l'interruption clavier. Un autre processus reprend son exécution.

6: De façon asynchrone, le contrôleur de clavier interrompt le processeur (le processus interrompu n'est pas le processus lecteur qui dort toujours). Le caractère reçu est ajouté au tampon d'entrée. Selon le paramètrage de la ligne d'entrée (réveil des lecteurs en fin de ligne ou autres modes non canoniques), les processus en attente d'une interruption clavier sont éventuellement réveillés (en mode canonique, ils sont tous réveillés à la réception d'une fin de ligne).

7 et 8: Lors d'un changement de contexte, le processus lecteur reprend son exécution en réexaminant le tampon d'entrée. S'il n'est pas vide, il en lit le caractère de tête et le retourne en résultat au pilote logique.

9 et 10: Le pilote logique et l'appel initial propagent le caractère lu au processus lecteur.

11: Le processus lecteur range sa lecture dans la variable locale c.

Accès mémoire et faute de page.

1: A l'élaboration de la partie déclarative de la fonction f, on accède en écriture à la variable locale v. L'adresse virtuelle de v est traduite en adresse réelle (traduction prise en charge par le matériel et faisant partie de l'instruction machine d'écriture en mémoire). En supposant que la dernière page allouée à la pile est pleine, la variable v est située dans une nouvelle page de la mémoire virtuelle. Celle-ci n'ayant encore jamais été référencée, elle n'a pas encore d'existence en mémoire réelle. Le matériel ne peut effectuer la traduction d'adresse et émet une exception de faute de page. L'exception est traitée par un gérant interne au noyau.

2: Le gérant d'exception endort le processus en attente d'un réveil par le swapper (processus 0). Un changement de contexte est effectué qui active le swapper. Celui-ci alloue une page en zone de swap (si la zone de swap n'a plus de page libre, le processus devra se terminer) et met à jour les tables de page du processus (la page allouée fixe l'adresse réelle de la page virtuelle du processus). Le swapper alloue une page en mémoire centrale. La page allouée est la version résidente d'une autre page réelle. Si cette copie est modifiée, le swapper effectue une sauvegarde en zone de swap (plus précisément, il commande le transfert au contrôleur de mémoire). Une fois la page en mémoire centrale libérée de son emploi antérieur, elle peut être réutilisée pour conserver la version résidente de la page allouée pour le processus. Le swapper peut réveiller le processus.

3: Le processus reprend son exécution en réexécutant l'instruction d'écriture en mémoire. La traduction d'adresse qui en découle produit le numéro de la page en mémoire centrale et l'accès est effectué par le matériel. La page est marquée comme ayant été écrite.

Changement de contexte.

On suppose avant tout que le processus 2 est prêt à s'exécuter. Il a été mis en sommeil par l'appel système S2, puis réveillé. Le processus 1 est en exécution.

1: Appel système S1.

2: S1 doit effectuer un changement de contexte. Appel de la fonction interne du noyau chgt_ctx. L'adresse de retour R1 dans l'appel système S1 est empilée sur la pile P1 du processus 1. Le contexte du processus partant (i.e. le processus 1) est sauvegardé en mémoire. Cela concerne tous les registres du processeur et en particulier celui qui sert de pointeur de pile P1. La fonction sélectionne le processus prêt le plus prioritaire pour succéder au processus 1. On suppose que c'est le processus 2 qui est élu. Le contexte du processus 2 est restauré, ce qui inclus le registre pointeur de pile P2.

3: Le retour se fait dans l'appel système S2 (adresse de retour R2 dépilée de la pile P2).

4: Retour de S2 dans le processus 2.

5: Appel S3.

6: Changement de contexte. Sauvegarde du contexte du processus 2 (adresse de retour dans S3). Restauration du contexte du processus 1 (pile P1, adresse de retour R1 dans S1).

7 et 8: Retour dans S1, puis dans le processus 1.

Le découpage en processus est une abstraction du système. Pour le processeur, on exécute une suite d'instructions sans séparation. Pour le système, on exécute A, S1, chgt_ctx, S2, D, S3, chgt_ctx, S1 et B.