Fonctionnement d'un ordinateur/Les interruptions et exceptions
Les interruptions sont des fonctionnalités du processeur qui ressemblent beaucoup aux appels de fonctions, mais avec quelques petites différences. Les interruptions, comme leur nom l'indique, interrompent temporairement l’exécution d'un programme pour effectuer un sous-programme nommé routine d'interruption. Lorsqu'un processeur exécute une interruption, celui-ci :
- arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
- exécute la routine d'interruption ;
- restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.

L'appel d'une routine d'interruption est très similaire à un appel de fonction et implique les mêmes chose : sauvegarder les registres du processeur, l'adresse de retour, etc. Tout ce qui a été dit pour les fonctions marche aussi pour les interruptions. La différence est que la routine d'interruption appartient au système d'exploitation ou à un pilote de périphérique, mais pas au programme en cours d'exécution.
Les interruptions sont classées en trois types distincts, aux utilisations très différentes : les exceptions matérielles, les interruptions matérielles et les interruptions logicielles. Les deux premières sont des interruptions générés par un évènement extérieur au programme, alors que les interruptions logicielles sont déclenchées quand le programme éxecute une instruction précise pour s'interrompre lui-même, afin d'éxecuter du code appartenant au système d'exploitation ou à un pilote de périphérique.
Les interruptions et les exceptions matérielles
[modifier | modifier le wikicode]Les exceptions matérielles et les interruptions matérielles permettent de réagir à un événement extérieur : communication avec le matériel, erreur fatale d’exécution d'un programme. Le programme en cours d'exécution est alors stoppé pour réagir, avant d'en reprendre l'exécution. Elles sont initiés par un évènement extérieur au programme, contrairement aux interruptions logicielles.

Les exceptions matérielles
[modifier | modifier le wikicode]Une exception matérielle est une interruption déclenchée par un évènement interne au processeur, par exemple une erreur d'adressage, une division par zéro... Le processeur intègre des circuits qui détectent l'évènement déclencheur, ainsi que des circuits pour déclencher l'exception matérielle. Prenons l'exemple d'une exception déclenchée par une division par zéro : le processeur doit détecter les divisions par zéro. Lorsqu'une exception matérielle survient, la routine exécutée corrige l'erreur qui a été la cause de l'exception matérielle, et prévient le système d'exploitation si elle n'y arrive pas. Elle peut aussi faire planter l'ordinateur, si l'erreur est grave, ce qui se traduit généralement par un écran bleu soudain.
Pour donner un exemple d'utilisation, sachez qu'il existe une exception matérielle qui se déclenche quand on souhaite exécuter une instruction non-reconnue par le processeur. Rappelons que les instructions sont codées par des suites de bits en mémoire, codées sur quelques octets. Mais cela ne signifie pas que toutes les suites de bits correspondent à des instructions : certaines suites ne correspondent pas à des instructions et ne sont pas reconnues par le processeur. Dans ce cas, le chargement dans le processeur d'une telle suite de bit déclenche une exception matérielle "Instruction non-reconnue".
Et cela a été utilisé pour émuler des instructions sur les nombres flottants sur des processeurs qui ne les géraient pas. Autrefois, à savoir il y a une quarantaine d'années, les processeurs n'étaient capables d'utiliser que des nombres entiers et aucune instruction machine ne pouvait manipuler de nombres flottants. On devait alors émuler les calculs flottants par une suite d'instructions machine effectuées sur des entiers. Cette émulation était effectuée soit par une bibliothèque logicielle, soit par le système d'exploitation par le biais d'exceptions matérielles. Pour cela, on modifiait la routine de l'exception "Instruction non-reconnue" de manière à ce qu'elle reconnaisse les suites de bits correspondant à des instructions flottantes et exécute une suite d'instruction entière équivalente.
Les interruptions matérielles
[modifier | modifier le wikicode]Les interruptions matérielles, aussi appelées IRQ, sont des interruptions déclenchées par un périphérique ou un circuit extérieur au processeur. Elles sont soit générées par un circuit sur la carte mère, soit par un périphérique, l'essentiel est qu'elles proviennent de l'extérieur du processeur et ne sont pas d'origine logicielle.
L'exemple d'utilisation typique des interruptions matérielles est la gestion de certains périphériques. Par exemple, quand vous tapez sur votre clavier, celui-ci émet une interruption à chaque appui/relevée de touche. Ainsi, le processeur est prévenu quand une touche est appuyée, le système d'exploitation qu'il doit regarder quelle touche est appuyée, etc. La routine d'interruption est alors fournie par le pilote du périphérique. Du moins, c'est comme ça sur le matériel moderne, les anciens PC utilisaient des routines d'interruption fournies par le BIOS. Ce sont celles qui vont nous intéresser dans le chapitre sur la communication avec les périphériques, mais nous n'en parlerons pas dans le détail avant quelques chapitres.
Un autre exemple est la gestion des timers. Par exemple, imaginons que vous voyiez à un jeu vidéo, et qu'il vous reste 2 minutes 45 secondes pour sortir d'un laboratoire de recherche avant que l'auto-destruction ne s'active. La durée de 2 minutes 45 est programmée dans un timer, un circuit compteur qui permet de compter une durée. Le jeu vidéo programme le timer pour qu'il compte durant 2 minutes 45 secondes, puis attend que ce dernier ait finit de compter. Une fois la durée atteinte, le timer déclenche une interruption, pour stopper l'exécution du jeu vidéo. La routine d'interruption prévient le système d'exploitation que le timer a fini de compter, le jeu vidéo est alors prévenu, et fait ce qu'il a à faire.
Un autre exemple, qui n'est plus d'actualité, est le rafraichissement mémoire des DRAM sur quelques anciens ordinateurs. Dans l'ancien temps, le rafraichissement mémoire était géré par le processeur, pas par le contrôleur mémoire. La plupart des processeurs intégraient des optimisations pour gérer le rafraichissement mémoire par eux-même, sans recourir à des interruptions. Mais quelques ordinateurs ont tenté de relier des processeurs très simples à une mémoire DRAM, directement, alors que les processeurs n'avaient aucune optimisation du rafraichissement mémoire. La gestion du rafraichissement était alors gérée via des interruptions : tous les x millisecondes, un timer déclenchait une interruption de rafraichissement mémoire, qui rafraichissait la mémoire ou une adresse précise. Cette solution était très peu performante.
Les interruptions logicielles
[modifier | modifier le wikicode]Les interruptions logicielles sont différentes des deux précédentes dans le sens où elles ne sont pas déclenchées par un évènement extérieur. A la place, elles sont déclenchées par un programme en cours d'exécution, via une instruction d'interruption. On peut les voir comme des appels de fonction un peu particuliers, si ce n'est que la routine d'interruption exécutée n'est pas fournie par le programme exécuté, mais par le système d'exploitation, un pilote de périphérique ou le BIOS. Le code éxecuté ne fait pas partie du programme éxecuté, mais en est extérieur, et cela change beaucoup de choses.
Sur les PC anciens, le BIOS fournissait les routines de base et le système d'exploitation se contentait d’exécuter les routines fournies par le BIOS. Mais de nos jours, les routines d'interruptions du BIOS sont utilisées lors du démarrage de l'ordinateur, mais ne sont plus utilisées une fois le système d'exploitation lancé. Le système d'exploitation fournit ses propres routines et n'a pas plus besoin des routines du BIOS.
Les interruptions du BIOS et des autres firmwares
[modifier | modifier le wikicode]Le BIOS fournit des routines d'interruption pour gérer les périphériques et matériels les plus courants. Il y a une interruption pour communiquer avec le port série RS232 de notre ordinateur, une autre pour le port parallèle, une autre pour le clavier, une autre pour la carte graphique, et quelques autres. Les interruptions en questions gèrent des standards de base, utilisés pour gérer les périphériques et matériels les plus courants. Par exemple, tant que la carte graphique supporte le standard VGA, le BIOS peut l'utiliser, bien que partiellement et seulement pour gérer les fonctions de base. idem avec le clavier : les standards PS/2 et USB sont gérés de base par le BIOS. Ce n'est pas pour rien que « BIOS » est l'abréviation de Basic Input Output System, ce qui signifie « programme basique d'entrée-sortie ».
Par exemple, si aucune ROM vidéo n'est détectée, le BIOS peut quand même communiquer directement avec la carte graphique en utilisant une interruption dédiée. Elle a plusieurs utilités différentes, mais est généralement limitée à l’affichage de texte (la carte graphique est gérée en mode texte). Dans ce mode, elle peut tout aussi bien envoyer du texte à l'écran (sortie) que renvoyer la position du curseur à l'écran (entrée).
L'usage de ces standards matériel était extrêmement puissant malgré sa simplicité. Il était possible de créer un OS complet en utilisant juste des appels de routine du BIOS. Par exemple, le DOS, ancêtre de Windows, utilisait exclusivement les interruptions du BIOS ! Mais une fois le système d'exploitation démarré, les interruptions du BIOS ne servent plus, les pilotes de périphériques prennent le relai. De nos jours, l'UEFI fournit encore un équivalent des anciennes interruptions du BIOS, mais seulement pour la rétrocompatibilité. Les interruptions ne sont plus utilisées lors du démarrage de l'ordinateur, le firmware est programmé plus finement et gére le matériel d'une manière autre.
Certaines routines peuvent effectuer plusieurs traitements : par exemple, la routine qui permet de communiquer avec le disque dur peut aussi bien lire un secteur, l'écrire, etc. Pour spécifier le traitement à effectuer, on doit placer une certaine valeur dans le registre AH du processeur : la routine est programmée pour déduire le traitement à effectuer uniquement à partir de la valeur du registre AH. Mais certaines routines ne font pas grand-chose : par exemple, l'interruption 0x12h ne fait que lire la taille de la mémoire conventionnelle, qui est mémorisée à un endroit bien précis en mémoire RAM.
Voici une description assez succincte de ces routines. Vous remarquerez que je n'ai pas vraiment détaillé ce que font ces interruptions, ni comment les utiliser. Il faut dire que de nos jours, ce n'est pas franchement utile. Mais si vous voulez en savoir plus, je vous invite à lire la liste des interruptions du BIOS de Ralf Brown, disponible via ce lien : Liste des interruptions du BIOS, établie par Ralf Brown.
Adresse de la routine dans le vecteur d'interruption | Description succinte |
---|---|
10h | Si aucune ROM vidéo n'est détectée, le BIOS peut quand même communiquer directement avec la carte graphique grâce à cette routine. Elle a plusieurs fonctions différentes et peut tout aussi bien envoyer un caractère à l'écran que renvoyer la position du curseur. |
13h | Cette routine du BIOS permet de lire ou d'écrire sur le disque dur ou sur une disquette. Plus précisément, cette routine lui sert à lire les premiers octets d'un disque dur afin de pouvoir charger le système d'exploitation. Elle était aussi utilisée par les systèmes d'exploitation du style MS-DOS pour lire ou écrire sur le disque dur. |
14h | La routine 14h était utilisée pour communiquer avec le port série RS232 de notre ordinateur. |
15h | La routine 15h a des fonctions diverses et variées, toutes plus ou moins rattachées à la gestion du matériel. Le BIOS était autrefois en charge de la gestion de l'alimentation de notre ordinateur : il se chargeait de la mise en veille, de réduire la fréquence du processeur, d'éteindre les périphériques inutilisés. Pour cela, la routine 15h était utilisée. Ses fonctions de gestion de l'énergie étaient encore utilisées jusqu'à la création de Windows 95.
De nos jours, avec l'arrivée de la norme ACPI, le système d'exploitation gère tout seul la gestion de l'énergie de notre ordinateur et cette routine est donc obsolète. À toute règle, il faut une exception : cette routine est utilisée par certains systèmes d'exploitation modernes à leur démarrage afin d'obtenir une description correcte et précise de l'organisation de la mémoire de l'ordinateur. Pour cela, nos OS configurent cette routine en plaçant la valeur 0x0000e820 dans le registre EAX. |
16h | La routine 16h permet de gérer le clavier et de le configurer. Cette routine est utilisée tant que le système d'exploitation n'a pas démarré, c'est pour cela que vous pouvez utiliser le clavier pour naviguer dans l'écran de configuration de votre BIOS. En revanche, aucune routine standard ne permet la communication avec la souris : il est impossible d'utiliser la souris dans la plupart des BIOS. Certains BIOS possèdent malgré tout des routines capables de gérer la souris, mais ils sont très rares. |
17h | Cette routine permet de communiquer avec une imprimante sur le port parallèle de l'ordinateur. Comme les autres, on la configure avec le registre AH. |
19h | Cette routine est celle qui s'occupe du démarrage du système d'exploitation. Elle sert donc à lancer le système d'exploitation lors du démarrage d'un ordinateur, mais elle sert aussi en cas de redémarrage. |
Les routines du BIOS étaient parfois recopiées dans la mémoire RAM afin de rendre leur exécution plus rapide. Certaines options du BIOS, souvent nommées BIOS memory shadowing, permettent justement d'autoriser ou d'interdire cette copie du BIOS dans la RAM.
Les appels systèmes des systèmes d'exploitation
[modifier | modifier le wikicode]
Avant de poursuivre, rappelons que le système d'exploitation sert d'intermédiaire entre les autres logiciels et le matériel. Les programmes ne sont pas censés accéder d'eux-mêmes au matériel, pour des raisons de portabilité et de sécurité. Ils ne peuvent pas accéder directement au disque dur, au clavier, à la carte son, etc. À la place, ils demandent au système d'exploitation de le faire à leur place et de leur transmettre les résultats. Il y a donc une séparation stricte entre :
- les programmes systèmes qui gèrent la mémoire et les périphériques ;
- les programmes applicatifs ou applications, qui délèguent la gestion de la mémoire et des périphériques aux programmes systèmes.
Les programmes systèmes sont en réalité des sous-programmes, des fonctions utilisées pour accéder à la carte graphique, manipuler la mémoire, gérer des fichiers, etc. Les fonctions en question sont exécutées en faisant des appels de fonction classiques, appelés des appels système. Par exemple, linux fournit les appels systèmes open, read, write et close pour manipuler des fichiers, ou encore les appels brk, sbrk, pour allouer et désallouer de la mémoire. Évidemment, ceux-ci ne sont pas les seuls : linux fournit environ 380 appels systèmes distincts.
Les appels systèmes permettent aux programmes d'exécuter des fonctions pré-programmées, qui agissent sur le matériel. La communication entre OS et programmes est donc standardisée, limitée par une interface, ce qui limite les problèmes de sécurité et simplifie la programmation des applications. Les appels systèmes sont un concept des systèmes d'exploitation, qui peuvent se mettre en œuvre de plusieurs manières. On peut les implémenter de plusieurs manières différentes, mais ils sont presque toujours des interruptions logicielles.
Les programmes systèmes sont le plus souvent des routines d'interruptions, fournies par l'OS ou les pilotes de périphérique. Un appel système n'est donc qu'une interruption logicielle qui exécute à la demande la routine adéquate. Pour simplifier, l'ensemble de ces routines d'interruption porte le nom de noyau du système d'exploitation. Il regroupe les programmes systèmes, qu'on peut appeler avec des appels système. Le noyau d'un OS est la partie de l'OS qui s'occupe de la gestion du matériel, des périphériques et des opérations demandant de reconfigurer le processeur.
Mais sans intervention du matériel, rien n’empêche à un programma applicatif de lire ou d'écrire dans les registres des périphériques, par exemple. Mais les processeurs utilisent des sécurités pour cela, que nous allons voir dans ce qui suit.
Les niveaux de privilège : systèmes d'exploitation et virtualisation
[modifier | modifier le wikicode]Nous venons juste de voir que les interruptions logicielles sont surtout utilisées pour manipuler un périphérique, accéder au matériel. Et ce qu'elles soient fournies par le BIOS ou le système d'exploitation. C'est une différence fondamentale entre interruption logicielle et simple appel de fonction. Les interruptions peuvent manipuler le matériel, mais du code normal en est incapable. La raison tient à une sécurité incorporée dans tous les systèmes modernes : les niveaux de privilèges, aussi appelés des anneaux mémoires.
Pour simplifier, le processeur peut fonctionner en plusieurs modes, qui sont appelés : mode utilisateur, noyau, hyperviseur et système. Les quatre modes précédents ont une utilisation spécifique. Par utilisation spécifique, on veut dire qu'il y a un mode réservé au système d'exploitation, un autre pour le firmware/BIOS, un autre pour les logiciels usuels, et un autre pour les hyperviseurs. Les hyperviseurs sont des logiciels de virtualisation, dont le rôle est de tourner plusieurs OS en même temps. Ils sont en quelque sorte situés sous le système d'exploitation et on peut les voir comme une sorte de sous-système d'exploitation. Le mode utilisateur est réservé aux logiciels basiques, le mode noyau est réservé au noyau du système d'exploitation (d'où son nom), le mode hyperviseur est quant à lui utilisé par l'hyperviseur si l'ordinateur utilise la virtualisation, le mode système est réservé au firmware/BIOS.
Suivant le mode de fonctionnement, certaines opérations sensibles sont interdites. Par exemple, l'accès aux périphériques est interdit en mode utilisateur, mais autorisé dans les autres modes. Des instructions précises sont interdites en mode utilisateur, d'autres sont interdites en mode noyau et utilisateur, etc. Seul le mode système permet absolument tout.
Les anneaux mémoire/niveaux de privilèges étaient initialement gérés par des mécanismes purement logiciels, mais sont actuellement gérés par le processeur. Pour cela, le registre de contrôle du processeur contient un bit qui précise si le programme en cours est en mode noyau, utilisateur, hyperviseur ou système. À chaque accès mémoire ou exécution d'instruction, le processeur vérifie si le niveau de privilège permet l'opération demandée. Lorsqu'un programme effectue une instruction interdite pour le mode en cours, une exception matérielle est levée. Généralement, le programme est arrêté sauvagement et un message d'erreur est affiché.
Les interruptions basculent en mode noyau/système/hyperviseur
[modifier | modifier le wikicode]L'ordinateur démarre généralement en mode système, puis il bascule en mode hyperviseur, puis en mode noyau, et enfin en mode utilisateur. Il peut revenir vers un mode antérieur sous certaines conditions. Et justement, toute interruption bascule automatiquement le processeur dans l'espace noyau, voire système. C'est une nécessité pour les interruptions logicielles, afin de passer d'un programme en espace utilisateur à une routine qui est en espace noyau. Les interruptions matérielles doivent aussi faire la transition en espace noyau ou système, car l'accès au matériel n'est pas possible en espace utilisateur.
Par exemple, une interruption qui fait passer du mode utilisateur vers le noyau permet à un logiciel de déléguer une tâche au noyau du système d'exploitation. De même, une interruption qui fait passer du mode noyau vers le mode système permet à l'OS de communiquer avec le firmware, de déléguer une fonction vers le BIOS. Il s'agit là d'une sécurité : le passage d'un mode à un autre est contrôlé et n'est autorisé qu'en utilisant des instructions très précises, en l’occurrence des interruptions.
Le passage en mode noyau n'est cependant pas gratuit, de même que l'interruption qui lui est associée. Ainsi, les interruptions sont généralement considérées comme lentes, très lentes. Elles sont beaucoup plus lentes que les appels de fonction normaux, qui sont beaucoup plus simples. Les raisons à cela sont multiples, mais la principale est la suivante : les mémoires caches doivent être vidés lors des transferts entre mode noyau et mode utilisateur. Alors attention : diverses optimisations font que seuls certains caches spécialisés dont nous n'avons pas encore parlé, comme les TLB, doivent être vidés. Mais malgré tout, cela prend beaucoup de temps.
Le mode noyau et le mode utilisateur : logiciels et OS
[modifier | modifier le wikicode]
Tous les processeurs des PC modernes (x86 64 bits) gèrent au moins deux niveaux de privilèges : un mode noyau pour le noyau de l'OS et un mode utilisateur pour les applications. Tout est autorisé en mode noyau, alors que le mode utilisateur ne peut pas accéder aux périphériques, ni gérer certaines portions protégées de la mémoire. C'est un mécanisme qui force à déléguer la gestion du matériel au système d'exploitation.
Le mode utilisateur n'a pas accès à certaines instructions importantes, appelées des instructions privilégiées, qui ne s'exécutent qu'en espace noyau. Elles regroupent les instructions pour accéder aux entrées-sorties et celles pour configurer le processeur. On peut considérer qu'il s'agit d'instructions que seul l'O.S peut utiliser. À côté, on trouve des instructions non-privilégiées qui peuvent s’exécuter aussi bien en mode noyau qu'en mode utilisateur. Si un programme tente d'exécuter une instruction privilégiée en espace utilisateur, le processeur considère qu'une erreur a eu lieu et lance une exception matérielle.
De plus, l'espace utilisateur restreint l'accès à la mémoire par divers mécanismes dits de protection mémoire, alors que le mode noyau n'a pas de restrictions. Un programme en mode utilisateur se voit attribuer une certaine portion de la mémoire RAM, et ne peut accéder qu'à celle-ci. En clair, les programmes sont isolés les uns des autres : un programme ne peut pas aller lire ou écrire dans la mémoire d'un autre, les programmes ne se marchent pas sur les pieds, les bugs d'un programme ne débordent pas sur les autres programmes, etc.
La séparation en mode noyau et utilisateur explique pourquoi les appels systèmes sont implémentés avec des interruptions, et non des appels de fonction basiques. La raison est qu'un appel système branche vers un programme système en espace noyau, alors que le programme qui lance l'appel système est en espace utilisateur. Même sans accès aux périphériques, le passage en mode noyau est nécessaire pour passer outre la protection mémoire. La routine de l'appel système est dans une portion de mémoire réservée à l'OS, auquel le programme exécutant n'a pas accès en espace utilisateur. Il en est de même pour certaines structures de données du système d'exploitation, accessibles seulement dans l'espace noyau. Or, les appels de fonction et branchements ne permettent pas de passer de l'espace utilisateur à l'espace noyau, alors que les interruptions le font automatiquement.
Vu qu'une interruption logicielle est assez lente, divers processeurs incorporent des techniques pour rendre les appels systèmes plus rapides, en remplaçant les interruptions logicielles par des instructions spécialisées (SYSCALL/SYSRET et SYSENTER/SYSEXIT d'AMD et Intel). D'autres techniques similaires tentent de faire la même chose, à savoir changer le niveau de privilège sans utiliser d'interruptions : les call gate d’Intel, les Supervisor Call instruction des IBM 360, etc. Ce qui fait qu'assimiler interruptions logicielles et appels systèmes est en soi une erreur, mais même si les deux sont très liés.
Quelques processeurs ont des registres d'état séparés pour le mode noyau et le mode utilisateur. Le registre d'état du mode noyau ne peut être consulté que si le processeur est en mode noyau, l'autre registre d'état est consultable à la fois en mode noyau et utilisateur.
Le modes hyperviseur pour la virtualisation
[modifier | modifier le wikicode]Sur les CPU modernes, d'autres niveaux de privilèges existent, avec encore plus de privilèges que l'espace noyau. Par exemple, il existe un niveau de privilège appelé le mode hyperviseur qui est utilisé pour les techniques de virtualisation. Ces dernières permettent à plusieurs systèmes d'exploitation de tourner en même temps sur la même machine. Ils sont isolés les uns des autres, dans le sens où ils ont là aussi chacun leur propre mémoire dédiée, leur propre portion de RAM, que les autres OS ne peuvent pas voir.
Pour cela, un logiciel appelé l'hyperviseur gère la virtualisation et il a besoin d'un mode sous le mode noyau, dédié à la virtualisation, qui n'est autre que le mode hyperviseur. Le mode hyperviseur est implémenté sur les processeurs x86 avec deux standards incompatibles entre eux : l'un sur les CPU Intel, l'autre sur les CPU AMD. Ils portent les noms d'Intel VT-x et d'AMD-V. Grâce à eux, le noyau de l'OS est bel et bien en mode noyau.

Le mode système pour les firmwares
[modifier | modifier le wikicode]Dans le mode de gestion système, dans lequel l'exécution du système d'exploitation est suspendue et laisse la main au firmware. Il est possible d’entrer dans ce mode en utilisant une interruption spéciale, appelée System Management Interrupt (SMI ). Il est utilisé pour la gestion de l'énergie de l'ordinateur, la gestion thermique, pour gérer des interruptions non-masquables, pour gérer des défaillances matérielles graves, pour gérer certains périphériques (émuler les claviers/souris PS/2, certaines fonctionnalités USB/Thunderbolt), éventuellement pour communiquer avec la puce TPM du chipset.
Sur les CPU x86, il s'appelle le System Management Mode, abrévié SMM. Il permet à l'OS de laisser la main au BIOS pour exécuter des interruptions spécifiques. Il a été rendu disponible sur les CPU 386, et a ensuite été utilisé pour faciliter l'implémentation du standard APM, un standard de gestion de l'énergie assez ancien qui a laissé sa place à l'ACPI. ACPI qui utilisait aussi ce mode dans ses premières implémentations et l'utilise encore sur certaines cartes mères. Par sécurité, ce mode utilise un espace d'adressage différent de celui utilisé par l'OS afin de garantir un minimum de protection mémoire. Ce qui empêche pas certains malwares d'utiliser le mode SMM pour faire leur travail, voire se cacher de l'OS.
Il faut noter que le passage en mode système se fait en mode noyau, mais n'est pas disponible en mode utilisateur. Il s'agit d'une sécurité, qui garantit que les logiciels n'ont pas accès aux interruptions du firmware/BIOS directement. Le système d'exploitation peut communiquer avec le BIOS, pour que ce dernier l'aide à gérer le matériel. Par exemple, le système d'exploitation peut demander au BIOS quels sont les périphériques installés sur l'ordinateur. L'OS peut ainsi savoir quelle est la carte graphique ou la carte son installée, il a juste à demander au BIOS. Mais un logiciel utilisateur n'est pas censé pouvoir faire ça, seul le noyau est censé le faire.
Les modes intermédiaires pour les pilotes de périphériques
[modifier | modifier le wikicode]
Sur certains processeurs, on trouve des niveaux de privilèges intermédiaires entre l'espace noyau et l'espace utilisateur. Les processeurs x86 des PC 32 bits contiennent 4 niveaux de privilèges. Le système Honeywell 6180 en possédait 8, de même que le Multics system original. À l'origine, ceux-ci ont été inventés pour faciliter la programmation des pilotes de périphériques. Mais force est de constater que ceux-ci ne sont pas vraiment utilisés, seuls les espaces noyau et utilisateur étant pertinents.
Sur PC, les 4 niveaux de privilèges étaient autrefois utilisés pour la virtualisation. Les anciens processeurs x86 n'avaient pas de mode hyperviseur. Alors à la place, le noyau du système d'exploitation était placé dans le second niveau de privilège, celui juste après le mode noyau. La majorité des opérations de l'OS étaient possibles dans ce mode, sauf quelques unes qui requéraient le mode noyau. L'hyperviseur émulait ces interruptions qui demandaient le mode noyau en fournissant ses routines à lui, conçues pour gérer la virtualisation. L'ajout d'un réel mode hyperviseur a changé la donne.
Sur les processeurs Data General Eclipse MV/8000, les modes disposaient chacun de zones mémoires séparées. Les trois bits de poids fort du program counter étaient utilisés pour déterminer le niveau de privilége. Le processeur était un processeur 32 bits, ce qui fait que les 4 gibioctets de RAM adressables étaient découpés en 8 blocs de 512 mébioctets. Le code exécuté avait son niveau de privilège qui dépendait du bloc de 512 mébioctet dans lequel il était. Tout branchement qui modifiait les 3 bits de poids fort du program counter entrainait automatiquement un changement de niveau de privilège.
L'implémentation des interruptions
[modifier | modifier le wikicode]Toutes les interruptions, qu'elles soient logicielles ou matérielles, ne s'implémentent pas exactement de la même manière. Mais certaines choses sont communes à toutes les interruptions et à toutes leurs mises en œuvre. Par exemple, on s'attend à ce que la majeure partie des processeurs qui supportent les interruptions disposent des fonctionnalités que nous allons voir dans ce qui suit, à savoir : un vecteur d'interruption, une pile dédiée aux interruptions, la possibilité de désactiver les interruptions, etc. Elles ne sont pas tout le temps présentes, mais leur absence est plus une exception que la régle.
Le vecteur d'interruption
[modifier | modifier le wikicode]Vu le grand nombre d'interruptions logicielles/appels système, on se doute bien qu'il y a a peu-près autant de routines d'interruptions différentes. Et celles-ci sont placées à des endroits différents en mémoire RAM. Appeler une interruption demande techniquement de connaitre son adresse, pour effectuer un branchement vers celle-ci. Mais comment déterminer son adresse ?
La solution la plus simple est de placer chaque routine d'interruption systématiquement au même endroit en mémoire, ce qui fait que l'adresse est connue à l'avance. Les appels systèmes sont alors des appels de fonctions basiques, avec un branchement inconditionnel vers une adresse fixe. La technique marche bien pour les interruptions du firmware, comme celles du BIOS, qui sont placées en ROM à une position fixe. Mais elle est trop contraignante dès qu'un système d'exploitation est impliqué. Fixer la position et la taille de chaque routine d'interruption ne marche pas si on ne connait pas à l'avance ni le nombre, ni la taille, ni la fonction des routines.
Pour résoudre ce problème, les systèmes d'exploitation modernes font autrement. Ils numérotent les interruptions, à savoir qu'ils leur attribuent un numéro en commençant par 0. Un PC X86 moderne gère 256 interruptions, numérotées de 0 à 255. Un appel système ne précise pas l'adresse vers laquelle faire un branchement, mais précise le numéro de l'interruption à exécuter. Le système d'exploitation s'occupe ensuite de retrouver l'adresse de la routine à partir du numéro de l'interruption.
Pour cela, le système d'exploitation mémorise une table de correspondance qui associe chaque numéro à l'adresse de l'interruption. La table de correspondance s'appelle le vecteur d'interruption. Par exemple, la dixième adresse de la table pointe vers la dixième interruption, à savoir l'interruption qui gère le disque dur ou le lecteur de disquette. Il s'agit plus précisément un tableau d'adresses, à savoir que les adresses de chaque interruption sont placées dans un bloc de mémoire, les unes à la suite des autres.
Le vecteur d'interruption mémorise les adresses pour toutes les routines, sans exceptions. Non seulement il mémorise celles des appels systèmes, mais aussi les routines des exceptions matérielles, ainsi que les routines des interruptions matérielles. Sur les PC modernes, le vecteur d'interruption est stocké dans les 1024 premiers octets de la mémoire. Il gère 256 interruptions, et les 32 premières sont réservées aux exceptions matérielles.
- Pour ceux qui connaissent la programmation, le vecteur d'interruption est un tableau de pointeurs sur fonction, les fonctions étant les routines à exécuter.
L'avantage est que l'adresse de la routine n'a pas à être précisée lors de la conception de l'OS, et elle peut même changer lors de l'exécution d'un programme ! Le vecteur d'interruption peut être mis à jour, les adresses changées, ce qui permet de remplacer à la volée les routines d'interruptions utilisées. Une adresse qui pointe vers telle routine peut être remplacée par une autre adresse qui pointe vers une autre routine. On dit qu'on déroute ou qu'on détourne le vecteur d'interruption.
Tous les systèmes d'exploitation modernes le font après le démarrage de l'ordinateur, pour remplacer les interruptions du BIOS par les interruptions fournies par le système d'exploitation et les pilotes. Le vecteur d'interruption est placé en mémoire RAM est initialisé au démarrage de l'ordinateur. Il est initialisé avec les adresses des routines du Firmware, à savoir les routines du BIOS ou de l'UEFI sur les PCs. Mais une fois que le système d'exploitation démarré, les adresses sont mises à jour pour pointer vers les routines du système d'exploitation et des pilotes de périphériques. Cette mise à jour est effectuée par le système d'exploitation, une fois que le BIOS lui a laissé les commandes.
L'usage d'un vecteur d'interruption permet donc une plus grande flexibilité et une compatibilité maximale. Elle permet au système d'exploitation de configurer les interruptions comme il le souhaite et de placer les routines d'interruption où il veut. Par contre, elle a un léger cout en performance, très mineur. La raison est que déterminer l'adresse d'une routine d'interruption se fait en deux temps, au lieu d'un. Au lieu de faire un branchement vers une adresse connue à l'avance, on doit récupérer l'adresse dans le vecteur d'interruption, puis faire le branchement. Il y a un accès mémoire en plus, il y a un niveau d'indirection en plus. Cela explique pourquoi un appel système n'est pas qu'un simple appel de fonction, pourquoi il est préférable d'avoir une instruction spécifique pour le processeur, séparée de l'instruction d'appel de fonction normale.
La conversion d'un numéro d'interruption en adresse peut se faire au niveau matériel ou logiciel. S'il est fait au niveau matériel, l'instruction d'interruption logicielle lit l'adresse automatiquement dans le vecteur d'interruption, idem avec les exceptions matérielles et les IRQ. Avec la solution logicielle, on délègue ce choix au système d'exploitation. Dans ce cas, le processeur contient un registre qui stocke le numéro de l'interruption, ou du moins de quoi déterminer la cause de l'interruption : est-ce le disque dur qui fait des siennes, une erreur de calcul dans l'ALU, une touche appuyée sur le clavier, etc.
Le masquage d'interruptions : désactiver les interruptions
[modifier | modifier le wikicode]Il est possible de désactiver temporairement l’exécution des interruptions, quelle qu’en soit la raison. Le terme utilisé n'est pas désactivation des interruption, mais masquage des interruptions. Le masquage d'interruption permet de bloquer des interruptions temporairement, pour soit les ignorer, soit les exécuter ultérieurement. La désactivation peut-être totale ou partielle : totale quand toutes les interruptions sont désactivées, partielle quand seule une minorité l'est.
Le registre de contrôle, qui permet de configurer le processeur, incorpore souvent un bit qui permet d'activer/désactiver les interruptions de manière globale. En modifiant ce bit, on peut activer ou désactiver les interruptions. Le bit en question n'est modifiable qu'en mode noyau. D'autres bits du registre de contrôle permettent de désactiver certaines interruptions précises, voir de choisir lesquelles activer/désactiver. Et ce n'est pas le seul, d'autres bits de configuration ne sont modifiables qu'en mode noyau, pas en mode utilisateur.
Désactiver les interruptions est utile dans certaines situations assez complexes, notamment quand le système d'exploitation en a besoin. C'est aussi utilisé dans certains systèmes dit temps réels, où les concepteurs ont besoin de garanties assez fortes pour le temps d’exécution. Une contrainte est que chaque fonction doit s’exécuter en un temps définit à l'avance, qu'il ne doit pas dépasser. Par exemple, prenons le cas d'une fonction devant s’exécuter en moins de 300 millisecondes. Le code en question prend 200 ms sans interruption, ce qui fait 100ms de marge de sureté. Si plusieurs interruptions surviennent, les 100ms de marge de sureté peuvent être dépassées. Désactiver les interruptions pendant le temps d’exécution du code permet d'éviter cela.
Et à ce petit jeu, il faut distinguer les interruptions masquables qui peuvent être ignorées ou retardées, des interruptions non-masquables, à savoir des interruptions qui ne doivent pas être masquées, quelle que soit la situation. Le terme "interruption non-masquable" est souvent abrévié en NMI, ce qui signifie Non Maskable Interrupt. Dans ce qui suit, nous parlerons parfois de NMI par abus de langage, pour simplifier l'écriture.
Les interruptions non-masquables sont généralement générées en cas de défaillances matérielles graves, qui demandent une intervention immédiate du processeur. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Les défaillances matérielles en question regroupent des situations très variées : une perte de l'alimentation, une erreur de parité mémoire, une surchauffe du processeur, etc. Elles sont généralement détectées par un paquet de circuits dédiés, souvent par des circuits placés sur la carte mère, en dehors d'un contrôleur de périphérique : un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Un exemple d'utilisation des interruptions non-masquable est celui d'une surchauffe du processeur. Le processeur et la carte mère contiennent de nombreux capteurs de température, eux-même connectés à des circuits de surveillance. Si la température est trop élevée, les circuits de surveillance déclenchent une interruption non-masquable. La routine d'interruption non-masquable effectue quelques manipulations d'urgence et éteint l'ordinateur par sécurité. Mais elle l'éteint d'une manière assez propre, en faisant quelques manipulations de dernière seconde.
Même chose en cas de défaillance de l'alimentation électrique, par exemple lorsqu'on débranche la prise, une coupure de courant, un problème matériel avec les régulateurs de tension, des condensateurs de la carte mère qui fondent, etc. Les ordinateurs modernes peuvent fonctionner durant quelques millisecondes lors d'une défaillance de l'alimentation, parce que la carte mère contient des condensateurs qui maintienne la tension d'alimentation pendant quelques millisecondes. Ce qui lui laisse le temps de faire quelques sauvegardes mineures, comme générer un crash dump, avant d'éteindre l'ordinateur proprement.
Outre les défaillances matérielles, les interruptions non-masquables sont aussi utilisée pour la gestion du watchdog timer. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'il suspecte qu'il a planté. C'est un compteur/décompteur connecté à l'entrée RESET du processeur, pour qu'un débordement d'entier du compteur déclenche un RESET. Pour éviter cela, une interruption non-masquable réinitialise le watchdog timer régulièrement, avant qu'il déborde. L'interruption est programmée soit par le watchdog timer, soit par un autre timer, peu importe. L'interruption en question doit être non-masquable, car on ne veut pas que l’ordinateur redémarre car l'interruption du watchdog timer a été masqué pendant trop longtemps, même si le masquage était pertinent.

Les optimisations des interruptions
[modifier | modifier le wikicode]En soi, les interruptions sur des appels de fonction améliorés. Les optimisations générales pour les appels de fonction marchent aussi pour les interruptions. Par exemple, les interruptions peuvent profiter du fenêtrage de registres. Lorsqu'une interruption se déclenche, elle se voit allouer sa propre fenêtre de registres, séparée des autres. Cependant, de nombreux processeurs incorporent des optimisations pour accélérer spécifiquement le traitement des interruptions, pas seulement les appels de fonction. Il s'agit souvent de processeurs dédiés à l'embarqué, qui sont peu puissants et doivent consommer peu, et qui doivent communiquer avec un grand nombre d'entrée-sorties. Les optimisations en question fournissent des registres dédiés aux interruptions, parfois une pile d'appel dédiée.
Les registres dédiés aux interruptions
[modifier | modifier le wikicode]Sur certains processeurs, les registres généraux sont dupliqués en deux ensembles identiques. Le premier ensemble est utilisé pour exécuter les programmes normaux, alors que le second ensemble est dédié aux interruptions. Mais les noms de registres sont identiques dans les deux ensembles.
Prenons l'exemple du processeur Z80 pour simplifier les explications. Comme dit plus haut, ce processeur a beaucoup de registres et tous ne sont pas dupliqués. Les registres pour la pile ne sont pas dupliqués, le program counter non plus. Par contre, les autres registres, qui contiennent des données, sont dupliqués. Il s'agit des registres nommés A (accumulateur), le registre d'état F et les registres généraux B, C, D, E, H, L, ainsi que les registres temporaires W et Z. L'ensemble de registres pour interruption dispose lui aussi de registres nommés A, F et les registres généraux B, C, D, E, H, L, mais il s'agit de registres différents. Pour éviter les confusions, ils sont notés A', F', B', C', D', E', H', L', mais les noms de registres sont en réalité identiques. Le processeur ne confond pas les deux, car il sait s'il est dans une interruption ou non.
Les deux ensembles de registres sont censés être isolés, il n'est pas censé y avoir d'échanges de données entre les deux. Mais le Z80 permettait d'échanger des données entre les deux ensembles de registres. Dans le détail, une instruction permettait d'échanger le contenu d'une paire de registres avec la même paire dans l'autre ensemble. En clair, on peut échanger les registres BC avec BC', DE avec DE′, et HL avec HL′. Par contre, il est impossible d'échanger le registre A avec A', et F avec F'. Le résultat est que les programmeurs utilisaient l'autre ensemble de registres comme registres pour les programmes, même s'ils n'étaient pas prévus pour.
Ce système permet de simplifier grandement la gestion des interruptions matérielles. Lors d'une interruption sur un processeur sans ce système, l'interruption doit sauvegarder les registres qu'elle manipule, qui sont potentiellement utilisés par le programme qu'elle interrompt. Avec ce système, il n'y a pas besoin de sauvegarder les registres lors d'une interruption, car les registres utilisés par le programme et l'interruption ne sont en réalité pas les mêmes. Les interruptions sont alors plus rapides.
Notons qu'avec ce système, seuls les registres adressables par le programmeur sont dupliqués. Les registres comme le pointeur de pile ou le program counter ne sont pas dupliqués, car ils n'ont pas à l'être. Et attention : certains registres doivent être sauvegardés par l'interruption. Notamment, l'adresse de retour, qui permet de reprendre l'exécution du programme interrompu au bon endroit. Elle est réalisée automatiquement par le processeur.
La pile dédiée aux interruptions
[modifier | modifier le wikicode]La plupart des systèmes d'exploitation utilisent une pile d'appel dédiée, séparée, pour les interruptions. Les raisons à cela sont multiples. La principale est que sur les systèmes d'exploitation capables de gérer plusieurs programmes en même temps, c'est une solution assez évidente. Chaque programme a sa propre pile d'appel, séparée des autres. Et la routine d'interruption est un programme comme un autre, qui doit donc avoir sa propre pile d'appel.