Wikilivres
frwikibooks
https://fr.wikibooks.org/wiki/Accueil
MediaWiki 1.45.0-wmf.4
first-letter
Média
Spécial
Discussion
Utilisateur
Discussion utilisateur
Wikilivres
Discussion Wikilivres
Fichier
Discussion fichier
MediaWiki
Discussion MediaWiki
Modèle
Discussion modèle
Aide
Discussion aide
Catégorie
Discussion catégorie
Transwiki
Discussion Transwiki
Wikijunior
Discussion Wikijunior
TimedText
TimedText talk
Module
Discussion module
LispWorks CAPI/Pour débuter
0
36150
744360
271152
2025-06-09T17:27:55Z
2A01:CB18:1150:4600:C5FF:3372:4F9:EE19
Coorec de "si ils" en "s'ils"
744360
wikitext
text/x-wiki
{{LispWorks CAPI}}
Ce chapitre présente quelques-uns des éléments les plus fondamentaux des fonctions du CAPI. L'intention est simplement que vous devriez vous familiariser avec les éléments les plus utiles, avant d'apprendre comment vous pouvez les utiliser de façon constructive. Il est fortement recommandé de travailler à travers les exemples dans ce chapitre.
Une application CAPI se compose d'une hiérarchie d'objets CAPI. Les objets du CAPI sont créés en utilisant '''make-instance''', et même s'ils sont de série d'objets standard de CLOS, les éléments du CAPI doivent généralement être consultés à l'aide des accesseurs documentés, et non pas en utilisant les fonctions des éléments CLOS. Vous ne devez pas compter sur les éléments en raison de la mise en œuvre de la classe du CAPI qui peut évoluer.
Une fois que l'instance d'un objet CAPI a été créée dans une interface, il peut être affiché sur votre écran en utilisant la fonction d'affichage.
* [[LispWorks CAPI/Utiliser la librairie CAPI|2.1 Utiliser la librairie CAPI]]
* [[LispWorks CAPI/Créer une première fenêtre|2.2 Créer une première fenêtre]]
* [[LispWorks CAPI/Lier le code en éléments CAPI|2.3 Lier le code en éléments CAPI]]
[[Catégorie:LispWorks CAPI (livre)]]
momp142uj01772xben6tluoidvxobuf
Fonctionnement d'un ordinateur/L'adressage des périphériques
0
65771
744370
738556
2025-06-09T18:31:55Z
Mewtow
31375
/* Les entrées-sorties mappées en mémoire avec des configurations de bus spéciales */
744370
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons vu que les périphériques, leurs registres d’interface et leurs contrôleurs, ont chacun une adresse bien précise. Nous avions vu comment le contrôleur de périphérique adresse les périphériques et comment les contrôleurs de périphériques eux-mêmes ont des adresses. Mais nous n'avons pas vu comment le processeur utilise ces adresses.
==Rappels : l'espace d'adressage unifié ou séparé==
Comment s'opère le mélange entre adresses mémoires et adresses de périphérique ? Comment le processeur évite les confusions entre adresses de périphériques et adresses mémoire. Pour cela, il y a plusieurs manières. La plus simple revient à séparer les adresses mémoire et les adresses périphériques, qui ne sont pas transmises sur les mêmes bus. L'autre méthode revient à utiliser un seul ensemble d'adresse, certaines étant allouées à la mémoire, d'autres aux périphériques. Les deux techniques portent des noms assez clairs : l''''espace d'adressage séparé''' pour la première, l''''espace d'adressage unifié''' pour la seconde. Voyons dans le détail ces deux techniques.
===L'espace d’adressage séparé===
Avec la première technique, mémoire et entrées-sorties sont adressées séparément, comme illustré dans le schéma ci-dessous. La mémoire et les entrées-sorties ont chacune un ensemble d'adresse, qui commence à 0 et va jusqu’à une adresse maximale. On dit que la mémoire et les entrées-sorties ont chacune leur propre espace d'adressage.
[[File:Espaces d'adressages séparés entre mémoire et périphérique.png|centre|vignette|upright=2|Espaces d'adressages séparés entre mémoire et périphérique.]]
Avec cette technique, le processeur doit avoir des instructions séparées pour gérer les périphériques et adresser la mémoire. Il a des instructions de lecture/écriture pour lire/écrire en mémoire, et d'autres pour lire/écrire les registres d’interfaçage. L'existence de ces instructions séparées permet de faire la différence entre mémoire et périphérique. Sans cela, le processeur ne saurait pas si une adresse est destinée à un périphérique ou à la mémoire.
===Les entrées-sorties mappées en mémoire===
La seconde technique s'appelle l'espace d'adressage unifie, ou encore les '''entrées-sorties mappées en mémoire'''. Avec cette technique, certaines adresses mémoires sont redirigées automatiquement vers les périphériques. Le périphérique se retrouve inclus dans l'ensemble des adresses utilisées pour manipuler la mémoire : on dit qu'il est mappé en mémoire.
[[File:IO mappées en mémoire.png|centre|vignette|upright=2.0|IO mappées en mémoire]]
L'avantage de cette méthode est la simplicité pour les programmeurs. Il n'y a pas besoin d'instructions différentes pour accéder aux périphériques et à la mémoire. Tout peut être fait par une seule instruction, qui n'a pas besoin de positionner un quelconque bit IO qui n'existe plus. Le processeur possède donc un nombre plus limité d'instructions machines, et est donc plus simple à fabriquer. Mais surtout, les programmeurs peuvent accéder aux périphériques beaucoup plus simplement, en lisant ou écrivant directement dans certaines adresses associées aux périphériques. Les transferts entre mémoire et périphériques sont fortement simplifiés, par exemple.
==Les bus unifiés et les bus d'entrée-sortie==
Maintenant que nous venons de rappeler ce que sont les espaces d'adressage unifiés et séparés, il est temps de voir comment ils sont implémentés en matériel. Et pour cela, nous allons encore une fois faire un rappel sur les bus.
Nous avons vu dans les chapitres précédents qu'il existe en gros trois configurations de base pour les bus.
La première est celle du '''bus système''', un bus unique qui relie la mémoire RAM, la mémoire ROM, le processeur, et les entrées-sorties.
[[File:Bus unique avec entrées mappées en mémoire.png|centre|vignette|upright=2.0|Bus unique avec entrées mappées en mémoire.]]
La seconde est celle d'un bus séparé pour les entrées-sorties, appelé le '''bus d'entrée-sortie'''. Il est en théorie possible d'avoir un bus séparé par controleur de périphérique ou entrée-sortie, mais le nombre de broches utilisé devient rapidement important, ce qui fait que cette technique n'est jamais implémentée.
[[File:Bus d'entrées-sorties multiplexé.png|centre|vignette|upright=2|Bus entre processeur et contrôleur de périphérique.]]
La troisième intercale un '''circuit répartiteur''' entre le processeur et les deux bus. Il s'occupe alors de la gestion des adresses.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
===L'implémentation d'un bus IO dédié avec un multiplexeur d'IO===
Les anciens ordinateurs des années 80-90 utilisaient un mélange des techniques 2 et 3 présentées juste au-dessus. Les processeurs avaient un bus d'entrée-sortie séparé des autres, et notamment séparé du bus mémoire. Mais ce bus était connecté à un répartiteur spécialisé dans les IO, qui s'occupait uniquement des entrées-sorties. Les répartiteurs étaient nombreux à l'époque et étaient appelés des circuits de '''''parallel IO''''', bien que ce terme signifie autre chose de nos jours. Les plus connus sont le 8255 d'Intel, le Motorola 6820 PIA (Peripheral Interface Adapter), le WDC 65C21, le MOS Technology 6522 et le MOS Technology CIA.
Pour simplifier les explications, le circuit répartiteur sera appelé un '''multiplexeur d'entrées-sorties''' ou encore un IO MUX. En effet, c'est fondamentalement un multiplexeur/démultiplexeur amélioré. Pour simplifier, un IO MUX dispose de plusieurs ports d'entrée-sortie, un pour le processeur et les autres pour les contrôleurs de périphérique. La liaison point à point entre le CPU et l'IOMUX se faisait sur des broches dédiées, regroupées dans le '''port CPU''', ou port ''processeur''. L'IO MUX avait plusieurs '''ports IO''', ou ports d'entrées-sorties, sur lesquels on connectait un contrôleur de périphérique via une liaison point à point.
[[File:IO MUX.png|centre|vignette|upright=2|IO MUX]]
[[File:8255.svg|vignette|8255]]
Un exemple est celui du 8255, qui disposait de trois ports IO et d'un port CPU. Le port CPU est un port de 8 bits, qui correspond aux broches D0 à D7. Les ports IO sont des ports de 8 bits et sont appelés les ports A, B et C. Leurs broches sont respectivement les broches PA0 à PA7 pour le port A, les broches PB0 à PB7 pour le port B, les broches PC0 à PC7 pour le port C.
: Le 8255 était plus complexe que ce qui est décrit. Le port C servait soit de port IO proprement dit, soit regroupait les bits de contrôle des ports A et B, à savoir les bits de contrôle pour les interruptions et le ''handshaking''. Le 8255 avait aussi plusieurs modes de fonctionnement où les ports IO étaient configurés différemment, le choix du mode étant fait en configurant un registre de contrôle interne au 8255. Le registre de contrôle était adressé via les lignes 10 et A1 qu'on verra plus bas.
Les ports CPU et IO pouvaient fonctionner comme entrée ou sortie et changeaient de rôle suivant la situation, suivant que le CPU pouvait émettre des données en direction d'un périphérique, ou en recevoir. L'IO MUX fonctionnait soit comme un multiplexeur, soit comme un démultiplexeur. Lorsque le processeur envoyait une donnée vers un périphérique, il fonctionnait en démultiplexeur, pour envoyer la donnée vers le bon périphérique, le bon port. En réception, il fonctionnait en multiplexeur et choisissait quel port était connecté au port CPU, quel port envoyait ses données vers le CPU.
Le choix entre multiplexage et démultiplexage se faisait selon que le processeur voulait faire une lecture ou une écriture. Le choix entre les deux était donc le fait d'une entrée de l'IO MUX, l'entrée R/W, qui indiquait s'il fallait faire une lecture ou une écriture.
Qui dit multiplexage/démultiplexage dit : choisir le port IO à connecter au port CPU. Pour cela, les ports étaient numérotés et le CPU pouvait préciser le numéro du port voulu. Et le numéro du port voulu était présenté sur une entrée dédiée, comme sur un MUX ou DEMUX normal. En soit, ce numéro est équivalent à une adresse de périphérique/port, ce qui fait que cette entrée était en réalité un bus d'adresse, appartenant au port CPU. Sur le 8255, l'envoi de l'adresse se faisait sur les deux broches A0 et A1, qui codaient un numéro de 2 bits. Les valeurs étaient les suivantes : 00 = port A, 01 = port B, 10 = port C, 11 = registre de contrôle.
Mais l'IO MUX n'est pas qu'un simple MUX/DEMUX configurable. Il pouvait ''générer des signaux d'interruption''. Quand un périphérique envoyait une donnée à l'IO MUX, il générait un signal d'interruption pour prévenir le processeur qu'une IO a envoyé une donnée. De plus, le répartiteur pouvait ''mettre en attente les données dans des registres'', qui servaient de registres d’interfaçage. Par exemple, une donnée lue sur un port IO était mémorisée dans le répartiteur en attendant que le processeur la récupère. Et inversement, le processeur pouvait envoyer une donnée à un périphérique par l'intermédiaire d'un registre dans le répartiteur. Il écrivait dans ce registre, la donnée était mise en attente dedans en attendant que le périphérique soit libre, et le répartiteur envoyait la donnée quand ce dernier était libéré.
[[File:MOS6526.svg|vignette|MOS 6526.]]
Il faut noter que les ports IO peuvent être aussi bien série que parallèle. Le 8255 avait trois ports IO de 8 bits, qui sont donc tous les trois des ports parallèles. Mais il a existé des IO MUX disposant de deux ports parallèles et un port série. Tel est le cas du MOS Technology 6522 et de son successeur, le MOS Technology CIA. C'était des IO MUX utilisés dans les ordinateurs Commodore, l'Apple III, et quelques autres ordinateurs anciens renommés.
Ils disposaient de deux ports parallèles de 8 bits (PA0-7, PB0-7), chacun ayant 4 lignes de contrôles à leur disposition pour les interruptions, et d'un port série (CB1 et CB2). Le port série était connecté à un registre à décalage de 8 bits, ce qui lui permettait d'envoyer/recevoir un octet à la fois. Ils intégraient aussi des ''timers'' de 16 bits, ainsi qu'une ''Real Time Clock'' pour gérer l'heure.
===Le contrôleur DMA sur un bus partagé ou avec un répartiteur===
L'usage d'un bus dédié a des avantages et des désavantages. L'un des désavantages est lié à l'implémentation du ''Direct Memory Access''. Un bus dédié aux IO marche assez mal avec le DMA. Les échanges entre mémoire RAM et IO doivent passer par le processeur, vu que le bus mémoire est séparé du bus des IO et que le seul point de contact entre les deux est le CPU.
A l'opposé, un contrôleur DMA est très adapté à un système avec un bus système, un bus partagé entre CPU, RAM et IO. Le controleur DMA est alors connecté au bus et il se réserve l'accès au bus quand il effectue un transfert DMA.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
L'usage d'un répartiteur ne pose pas de problèmes particuliers pour implémenter le DMA. La seule contrainte est que le contrôleur DMA soit intégré dans le répartiteur. Les échanges entre IO et mémoire passent par le répartiteur, qui fait le pont entre bus mémoire et bus des IO.
[[File:Implémentation du DMA avec un répartiteur.png|centre|vignette|upright=2|Implémentation du DMA avec un répartiteur]]
===Les coprocesseurs d'entrée-sortie sur les bus partagés===
Les bus partagés étaient parfois couplés avec des contrôleurs DMA améliorés, qui étaient rendus programmables. De tels contrôleurs DMA améliorés étaient appelés avec des termes très divers : ''Channel I/O'', ''I/O processor'', ''I/O controller'', ''I/O synchronizer'', et autres. Dans ce qui va suivre, nous allons utiliser le terme de '''coprocesseur périphérique'''.
De tels coprocesseurs périphériques permettent de décharger le processeur principal, le CPU (''Central Processing Unit''), de tout ce qui a trait aux entrées-sorties. Il est moins performant en calcul que le processeur principal, l'accent étant mis sur les accès mémoire. Sauf erreur, ils ne sont présents que sur les systèmes avec un bus partagé, il n'en existe pas pour les systèmes avec un répartiteur ou uns IO dédié.
[[File:Asmp 2.gif|centre|vignette|upright=2|Co-processeur pour l'accès aux entrées-sorties.]]
Les coprocesseurs périphériques exécutent un programme qui agit sur les entrées-sorties, souvent appelé un '''programme périphérique'''. Il y a généralement un programme périphérique pour chaque périphérique. Par exemple, il y a un programme pour le contrôleur de disquette, un autre pour le contrôleur du lecteur de bande magnétique, un autre pour la carte graphique, etc. Les programmes périphériques sont fournis par le système d'exploitation et on peut les voir comme des pilotes de périphériques simplifiés. Ils sont équivalents à ce que donnerait des routines d'interruptions déportées sur un autre processeur .
Avec un coprocesseur périphérique, le CPU ne dispose que d'une seule instruction liées aux entrées-sorties. Elle initie l'exécution d'un programme d'entrée-sortie sur le coprocesseur périphérique, en lui fournissant un pointeur qui pointe vers le programme périphérique (l'adresse du programme). Suite à cette instruction, le coprocesseur périphérique exécute son programme dans son coin, séparément du processeur. Quand le programme périphérique s'est terminé, le coprocesseur périphérique envoie une interruption au CPU pour le prévenir qu'il a terminé. Idem en cas d'erreur lors de la transmission.
Dans le cas général, le programme émule les transferts de données entre RAM et périphériques, qui sont autrement gérés par des transferts DMA. Pour cela, les coprocesseurs périphériques peuvent émuler le DMA avec un programme spécialisé qui copie les mots mémoire un par un avec une boucle. Le coprocesseur périphérique incorpore pour cela plusieurs registres spécialisés : des registres contenant des pointeurs, des registres pour des indices de boucle ou des compteurs. Les registres pointeurs sont généralement incrémentés ou décrémentés, suivant que le transfert se fasse par adresses croissantes ou décroissantes. Les compteurs de boucles sont décrémentés à chaque itération et mémorisent à tout instant combien de mots mémoire il reste à copier.
Le jeu d'instruction du coprocesseur périphérique est conçu pour implémenter de telles boucles. Il peut exécuter des instructions assez diverses : branchements, instructions d'accès mémoire, additions/soustractions. Ses instructions se concentrent surtout sur les accès mémoire et laissent de côté les instructions de calcul. Il ne dispose souvent que d'instructions d'addition/soustraction/comparaisons, pas d'instructions de multiplication ou de divisions. Les opérations arithmétiques complexes sont inutiles pour les tâches de gestion des périphériques, elles ne sont donc pas implémentées.
La connexion entre le coprocesseur périphérique et les contrôleurs de périphérique peut s'implémenter de deux manières. La première utilise un bus qui relie le coprocesseur périphérique et les contrôleurs de périphériques. L'implémentation demande peu de circuits et peu gérer un grand nombre de contrôleurs et de périphériques. En contrepartie que le coprocesseur ne peut communiquer qu'avec un seul contrôleur de périphérique à la fois. De tels coprocesseurs périphériques sont appelés des '''''Selector Channel'''''.
Une autre possibilité connecte chaque contrôleur de périphérique sur un port dédié, avec une liaison point à point dédiée. Le cout en interconnexion est grand, cela ne marche pas au-delà de quelques périphériques, mais cela permet de gérer plusieurs communications simultanées. De tels coprocesseurs IO sont appelés des '''''Multiplexer Channel'''''. Il est possible de les voir comme une sorte d'équivalent programmable du multiplexeur d'entrée-sortie des bus dédiés, mais pour les bus partagés.
==L'implémentation matérielle de l'espace d'adressage séparé/unifié==
Intuitivement, on se dit que le bus système va de concert avec un espace d'adressage unifié, avec des entrées-sorties mappées en mémoire. De même, utiliser un bus séparé pour les entrées-sorties va de pair avec des espaces d'adressage séparés. Et dans les grandes lignes, c'est autant vrai que faux.
Un bus système peut implémenter les deux solutions d'adressage, tout dépend de comment on gère le décodage d'adresse (voir plus bas). Il en est de même que la solution avec un répartiteur, tout dépend de comment le répartiteur gère l'espace d'adressage. Par contre, deux bus séparés implique forcément un espace d'adressage séparé. Dans le sens inverse, un espace d'adressage séparé peut être réalisé par toutes les configurations, alors que les entrées-sorties mappées en mémoire impliquent forcément un bus système.
{|class="wikitable"
|-
!
! Espace d'adressage unifié (entrées-sorties mappées en mémoire)
! Espace d'adressage séparé
|-
! Bus système
| rowspan="2" colspan="2" | Oui
|-
! Bus séparé avec répartiteur
|-
! Bus séparé pour les IO
| Non, sauf exceptions
| Oui, obligatoire
|}
Il est possible d'utiliser des configurations intermédiaires, qui permettent d'implémenter des espaces d'adressages séparés ou unifiés. Mais nous verrons cela dans ce qui suit.
===Les entrées-sorties mappées en mémoire avec un bus système===
Dans son implémentation la plus simple, les entrées-sorties mappées en mémoire utilisent un bus système, un bus unique pour les mémoires et les contrôleurs de périphériques. L'avantage est que cela économise beaucoup de fils, sans compter que le bit IO disparait. Par contre, impossible d'accéder à la fois à la mémoire et à un contrôleur d'entrées-sorties en parallèle.
Le principe des entrées-sorties mappées en mémoire est qu'une partie des adresses pointe vers un périphérique, d'autres vers la RAM ou la ROM. L'important est que le bon composant réponde lors d'un accès mémoire/périphérique. Si on accède à une adresse attribuée à la RAM, la RAM doit répondre, les périphériques doivent ignorer l'accès. Et inversement pour un accès périphérique.
La redirection vers le bon destinataire est faite par décodage partiel d'adresse. Pour rappel, le décodage d'adresse agit sur la mémoire ou les périphériques, et les connecte ou déconnecte du bus suivant les besoins. Pour rappel, chaque périphérique/mémoire possède une entrée CS, qui connecte ou déconnecte le composant du bus. Le circuit de décodage d'adresse prend en entrée l'adresse et commande les bits CS pour désactiver les composants non-concernés et activer la destination. Le circuit de décodage partiel d'adresse va ainsi placer le bit CS de la mémoire à 1 pour les adresses invalidées, l’empêchant de répondre à ces adresses.
[[File:Décodage d'adresse avec entrées-sorties mappées en mémoire.png|centre|vignette|upright=2.0|Décodage d'adresse avec entrées-sorties mappées en mémoire.]]
===L'espace d'adressage séparé avec un bus système===
Il est possible d'implémenter l'espace d'adressage séparé sans recourir à des bus séparés. Toutes les configurations de bus possibles sont compatibles avec un espace d'adressage séparé pour les IO, même un bus système unique. Mais comment faire pour l'implémenter avec un bus système ? Là encore, on utilise un système de décodage partiel d'adresse, mais qui est simplifié par rapport à celui des entrée-sorties mappées en mémoire.
[[File:Chipselectfr.png|vignette|Exemple détaillé.]]
Le décodage d'adresse part du principe que le bit de poids fort de l'adresse indique si l'adresse est celle d'un périphérique ou d'une mémoire. Le bit de poids fort de l'adresse, appelé le '''bit I/O''', est mis à 0 pour une adresse mémoire, 1 pour un registre d’interfaçage. Tout cela est réalisé par l'instruction adéquate : une instruction d'accès mémoire positionnera ce bit à 0, alors qu'une instruction d'accès IO le positionnera à 1. L'adresse envoyée sur le bus est formée en récupérant l'adresse à lire/écrire et en positionnant le bit I/O à sa bonne valeur.
Un défaut de cette solution est qu'elle impose d'avoir deux espaces d'adressage de même taille, un pour la/les mémoires, un autre pour les périphériques. Pas question d'avoir un espace d'adressage plus petit pour les périphériques, alors que ce serait possible avec deux bus séparés.
[[File:Bit IO.png|centre|vignette|upright=2|Bit IO.]]
Un avantage de cette méthode est qu'elle marche avec des configurations de bus un peu spéciales, qui sont intermédiaire entre des bus séparés et un bus système. Par exemple, il est possible d'avoir un bus d'adresse partagé, mais pas les autres. Ou encore, il est possible de mutualiser le bus d'adresse et de données, en conservant deux bus de commandes, un pour le périphérique et un pour la mémoire. Le bit IO fonctionne avec toutes ces configurations, la seule contrainte est que le bus d'adresse soit partagé. Mais le processeur doit gérer correctement le bus de données et envoyer les données sur le bon bus de données.
[[File:Espace d'adressage séparé.png|centre|vignette|upright=2|Espace d'adressage séparé.]]
===Les entrées-sorties mappées en mémoire avec des configurations de bus spéciales===
Il est possible d'implémenter les entrées-sorties mappées en mémoire sans utiliser un bus unique, avec des configurations de bus assez spéciales, dans lesquelles on a bien deux bus séparés, mais qui communiquent entre eux. Elles sont très rares, et nous en parlons ici par pur but d'exhaustivité.
La première, de loin la plus simple, consiste à accéder à la RAM d'abord, puis aux périphériques si elle ne répond pas. Une tentative d'accès en RAM fonctionnera du premier coup si l'adresse en question est attribuée à la RAM. Mais si l'adresse est associée à un périphérique, la RAM ne répondra pas et on doit retenter l'accès sur le bus pour les périphériques. L'implémentation est cependant compliquée, sans compter que les performances sont alors réduites, du fait des deux tentatives consécutives.
Les autres solutions font communiquer les deux bus pour que la RAM ou les périphériques détectent précocement les accès qui leur sont dédiés. La première solution de ce type consiste à ajouter un dispositif qui transmet les accès du bus mémoire vers le bus des périphériques. Mais le bus pour les périphériques est souvent moins rapide que le bus mémoire et l'adaptation des vitesses pose des problèmes.
[[File:Espace d'adressage séparé, implémentation avec deux bus séparés.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus]]
{{AutoCat}}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les méthodes de synchronisation entre processeur et périphériques
| prevText=Les méthodes de synchronisation entre processeur et périphériques
| next=Les périphériques et les cartes d'extension
| nextText=Les périphériques et les cartes d'extension
}}
</noinclude>
6g9k4zpunkvpi8hcujo80hyr224qces
Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension
0
65774
744369
739701
2025-06-09T18:31:51Z
Mewtow
31375
/* La carte graphique */
744369
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier les périphériques et les cartes d'extension. Pour rappel, les '''cartes d'extension''' sont les composants qui se connectent sur la carte mère via un connecteur, comme les cartes son ou les cartes graphiques. Quant aux '''périphériques''', ce sont les composants connectés sur l'unité centrale.
De nos jours, les cartes d'extension se font de plus en plus rares. Il y a quelques décennies, tout ordinateur avait une carte son, une carte réseau, une carte graphique, et éventuellement d'autres cartes d'extension. Mais de nos jours, avec les progrés de la miniaturisation, ce n'est plus le cas. La carte son et la carte réseau sont souvent intégrées directement à la carte mère, la carte graphique est intégrée directement dans le processeur. Bref, elles n'existent plus sous forme de cartes d'extension, comme c'était le cas à une époque, la seule exception étant les cartes graphiques à haute performances.
Il n'en reste pas moins que ces circuits sont conçus d'une manière assez spécifique et qu'il est intéressant de les étudier à part. Nous regroupons ces cartes d'extension avec les périphériques, car les deux ont en commun d'être des entrées-sorties assez particulières, qui doivent communiquer avec le reste de la carte mère via une interface dédiée.
==Le clavier==
Les claviers sont les premiers périphériques à avoir été inventés, les tout premiers ordinateurs disposaient de claviers alors que les souris n'avaient pas été inventées. Intuitivement, on se dit qu'un clavier est un périphérique d'entrée, mais ce n'est pas le cas ! Le clavier communique avec l'ordinateur dans les deux sens, contrairement à ce qu'on pourrait penser. Voyons cela en détail.
===La communication entre le clavier et l'ordinateur===
L'interface entre clavier et ordinateur gère deux sens de transmission : du clavier vers l'ordinateur, et de l'ordinateur vers le clavier. Les deux sont gérés différemment. Du point de vue du clavier, la sortie envoie la touche appuyée vers l'ordinateur, alors que son entrée est utilisée pour configurer les diodes du clavier. Voyons maintenant l'interface clavier pour les deux sens, entrée et sortie.
La transmission en sortie indique à l'ordinateur si une touche a été appuyée et laquelle. Pour indiquer quelle touche a été appuyée, le clavier attribue un numéro spécifique à chaque touche, appelé un '''scancode'''. Le scancode d'une touche est standardisé et il existe plusieurs standards de scancodes. Les tout premiers PC utilisaient le '''jeu de scancode XT''', qui codait une touche sur un octet : les 7 bits de poids faible identifient la touche, et le bit de poids fort indique si la touche a été appuyée ou relâchée. Les claviers PS/2 utilisaient le '''jeu de scancode AT'''. Avec ce jeu de scancode, les numéros des touches sont modifiés. Le relâchement d'une touche est indiqué différemment : avec les scancodes AT, il faut ajouter un octet d'une valeur 0xF0 devant le numéro de la touche. Sans ce préfixe, on considère que la touche a été appuyée.
La transmission en entrée sert, entre autres, à configurer les diodes du clavier. Pour mieux comprendre, regardez votre clavier, près du clavier numérique. Vous verrez normalement trois diodes vertes : une pour indiquer si le verrouillage numérique est actif ou non, une autre pour le verrouillage majuscule et une autre pour l'arrêt défil. Et bien c'est l'ordinateur qui configure ces trois diodes, ce n'est pas fait à l'intérieur du clavier ! Un bon moyen de s'en rendre compte est de regarder ce qui se passe quand l'ordinateur plante et se fige : les trois diodes se figent elles aussi.
La communication de l'ordinateur vers le clavier ne se limite pas à cela, c'est juste sa conséquence la plus visible. L'ordinateur peut envoyer d'autres informations de configuration au clavier, mais mettons cela de côté.
===L'intérieur d'un clavier===
À l'intérieur d'un clavier, on trouve un circuit relié aux touches, qui déduit les touches appuyées. Il se charge de convertir l'appui d'une touche en scancode. Il est appelé le '''''Keyboard Encoder''''', ou encore l'encodeur clavier, ce qui trahit sa fonction. Il peut être implémenté avec un vulgaire encodeur, un circuit séquentiel, ou tout autre circuit dédié, mais il est le plus souvent implémenté avec un microcontrôleur. Dans les premiers claviers de PC, l'encodeur clavier était un microcontrôleur Intel 8048. Les claviers actuels utilisent des microcontrôleurs similaires.
Naïvement, on pourrait penser que le microcontrôleur est relié à chaque touche, mais ce genre d'organisation n'est utilisable que pour de tout petits claviers. Sur les claviers avec un faible nombre de touches, toutes les touches sont reliées au microcontrôleur, voire à un simple encodeur dans le cas le plus simple. Si un encodeur est utilisé, il est cependant relié à un microcontrôleur ou un autre circuit, pour gérer l'interface avec l'ordinateur, l'interface avec le bus USB/PS/2 ou autre. Autant utiliser un microcontrôleur à tout faire, qui gère à la fois l'encodage des touches et la communication avec le bus.
[[File:Lock Keyboard Coder 2.png|centre|vignette|upright=1|Exemple de clavier simple, qui utilise un encodeur combinatoire.]]
Avec un clavier à 102 touches, la technique précédente utiliserait 102 fils, ce qui serait difficile à mettre en œuvre. Aussi, une organisation alternative est utilisée. Les touches sont reliées à des fils électriques, organisés en lignes et en colonnes, avec une touche du clavier à chaque intersection ligne/colonne. La '''matrice de touches''' est reliée à un microcontrôleur qui déduit les touches appuyées en utilisant une procédure un peu spéciale, qu'on va détailler dans ce qui suit.
[[File:Lock Keyboard Coder 3.png|centre|vignette|upright=1|Lock Keyboard Coder 3]]
Pour simplifier, les touches agissent comme des interrupteurs : comme un interrupteur fermé quand elles sont appuyées, et comme un interrupteur ouvert quand elles sont relâchées. Si une touche est enfoncée, elle connecte la ligne à la colonne. A l'inverse, si la touche à l'intersection entre ligne et colonne n'est pas enfoncée, la ligne sera déconnectée. Grâce à un petit circuit (des résistances de rappels au zéro volt intégrée dans le micro-contrôleur), cette déconnexion de la ligne et de la colonne est interprétée comme un zéro.
Pour savoir quelles touches sont appuyées, il suffit d'envoyer une tension sur la colonne testée et de regarder s'il y a une tension en sortie sur la ligne. Le micro-contrôleur balaye les colonnes unes par unes et regarde le résultat sur les lignes. Le résultat sur les lignes formé un nombre, qui est traduit par le microcontroleur en un numéro de touche (une simple table de correspondance logicielle fait l'affaire).
[[File:Gestion matrice clavier.png|centre|vignette|upright=1|Gestion matrice clavier]]
Cette organisation a tout de même un léger problème, qui se manifeste quand trois touches ou plus sont appuyées. Dans une telle situation, il se peut que le courant passe à travers les interrupteurs des touches et active des lignes qui ne devraient pas l'être. Pour donner un exemple, prenons la configuration suivante :
[[File:Ghosting - 1.png|centre|vignette|upright=1|Ghosting - 1]]
Les problèmes surviennent quand le contrôleur active la troisième colonne. L'appui de la touche de droite active la colonne et la ligne sur laquelle elle se situe. L'appui de la touche immédiatement à gauche permet au courant sur la ligne de traverser la colonne. La touche en-dessous permet au courant de traverser la ligne elle aussi. Le résultat est que le résultat envoyé au microcontrôleur donnent l'illusion de l'appui d'une touche qui n'est pas appuyée, ici celle située sous la touche de droite.
[[File:Ghosting - 2.png|centre|vignette|upright=1|Ghosting - 2]]
Une solution pour limiter ce phénomène de '''ghosting''' est de coupler chaque touche avec une diode, qui empêche le courant de passer dans les lignes ou colonnes qui ne devraient pas être activées.
[[File:Diode - ghosting.png|centre|vignette|upright=1|Diode - ghosting]]
==La souris==
[[File:Mouse mechanism diagram.svg|vignette|Mécanisme interne d'une souris à boule.]]
Les anciennes souris fonctionnaient avec une boule, en contact avec la surface/le tapis de souris, que le mouvement de la souris faisait rouler sur elle-même. Ce mouvement de rotation de la boule était capté par deux capteurs, un pour les déplacements sur l'axe gauche-droite, et un pour l'axe haut-bas.
Les souris plus récentes contiennent une source de lumière, qui éclaire la surface sur laquelle est posée la souris (le tapis de souris). Cette source de lumière peut être une diode ou un laser, suivant la souris : on parle de '''souris optique''' si c'est une diode, et de '''souris laser''' si c'est un laser. Avec la source de lumière, on trouve une '''caméra''' qui photographie le tapis de souris intervalles réguliers, pour détecter tout mouvement de souris. Le tapis de souris n'est pas une surface parfaite, et contient des aspérités et des irrégularités à l'échelle microscopique. Quand on bouge la souris, les images successives prises par la caméra ne seront donc pas exactement les mêmes, mais seront en partie décalées à cause du mouvement de la souris. La caméra est reliée à un circuit qui détecte cette différence, et calcule le déplacement en question. Précisément, elle vérifie de combien l'image a bougé en vertical et en horizontal entre deux photographies de la surface.
===La sensibilité de la souris===
Le déplacement mesuré par le capteur est envoyé à l'ordinateur. Ce qui est envoyé est le nombre de pixels de différence entre deux images de caméra, en horizontal et en vertical. Le système d'exploitation va alors multiplier ce déplacement par un coefficient multiplicateur, la sensibilité souris, ce qui donne le déplacement du curseur sur l'écran en nombre de pixels). Plus celle-ci est faible, meilleure sera la précision.
Les OS actuels utilisent l''''accélération souris''', à savoir que la sensibilité est variable suivant le déplacement, le calcul du déplacement du curseur étant une fonction non-linéaire du déplacement de la souris. Sur les anciens OS Windows, la sensibilité augmentait par paliers : un déplacement souris faible donnait une sensibilité faible, alors qu'une sensibilité haute était utilisée pour les déplacements plus importants. Cette accélération souris est désactivable, mais cette désactivation n'est conseillée que pour jouer à des jeux vidéos, l'accélération étant utile sur le bureau et dans les autres interfaces graphiques. Elle permet en effet plus de précision lors des mouvements lents (vu que la sensibilité est faible), tout en permettant des mouvements de curseurs rapides quand la souris se déplace vite.
===Les performance de la souris===
Plus la résolution de la caméra de la souris est élevée, plus celle-ci a tendance à être précise. Cette résolution de la caméra de la souris est mesurée en '''DPI''', Dot Per Inche. Il s'agit du nombre de pixels que la caméra utilise pour capturer une distance de 1 pouce (2,54 cm). Il va de soit que plus le DPI est élevé, plus la souris sera sensible. Pour un même déplacement de souris et à sensibilité souris égale, une souris avec un fort DPI entrainera un déplacement du curseur plus grand qu'une souris à faible DPI. Cela est utile quand on utilise un écran à haute résolution, mais pas dans d'autres cas.
Un autre paramètre important de la souris est la '''fréquence de rafraichissement''' déterminant le nombre d'informations envoyé à l'ordinateur par seconde. Suivant le connecteur, celle-ci varie. Une souris connecté au connecteur PS/2 (celui situé l'arrière de l'unité centrale), a une fréquence par défaut de 40 hertz, mais peut monter à 200 Hz si on le configure convenablement dans le panneau de configuration. Un port USB a une fréquence de 125 Hz par défaut, mais peut monter jusqu’à 1000 Hz avec les options de configuration adéquate dans le pilote de la souris. Plus cette fréquence est élevée, meilleure sera la précision et plus le temps de réaction de la souris sera faible, ce qui est utile dans les jeux vidéos. Mais cela entrainera une occupation processeur plus importante pour gérer les interruptions matérielles générées par la souris.
==La carte son==
Dans un ordinateur, toutes les données sont codées sous forme numérique, c'est à dire sous la forme de nombres entiers/ou avec un nombre de chiffres fixes après la virgule. Les ordinateurs actuels utilisent du binaire. Les informations sonores sont aussi stockées dans notre PC en binaire. Toutes vos applications qui émettent du son sur vos haut-parleurs stockent ainsi du son en binaire/numérique. Seulement, les haut-parleurs et microphone sont des composants dits analogiques, qui codent ou décodent l'information sonore sous la forme d'un tension électrique, qui peut prendre toutes les valeurs comprises dans un intervalle. Dans ces conditions, il faut bien faire l'interface entre les informations sonores numérique du PC, et le haut-parleur ou micro-phone analogique. Ce rôle est dévolu à un composant électronique qu'on appelle la '''carte son'''.
===L'architecture globale===
L'usage des microphones et des haut-parleurs demande de faire des conversions entre analogique et numérique. Les microphones transforment un signal sonore en un signal électrique analogique, alors que les haut-parleurs font l'inverse. Mais ces signaux analogiques ne sont pas compréhensibles par le reste de l'ordinateur, qui est composée de circuits numériques. C'est pour cela que la carte son doit faire la conversion des signaux analogiques en signaux numériques, en binaire.
* Pour le micro-phone, la conversion doit traduire un signal analogique, proportionnel à l'intensité sonore, en signal numérique. Le '''convertisseur analogique-numérique''', ou CAN, recueille la tension transmise par le micro-phone et la convertit en binaire. Il est relié à un composant qui mesure la tension envoyée par le microphone à intervalles régulier : l''''échantillonneur'''.
* De même, pour gérer les sorties haut-parleur, la carte son doit traduire le signal numérique provenant de l'ordinateur en un signal analogique utilisable par les haut-parleurs. Le '''convertisseur numérique-analogique''', ou CNA, transforme des informations binaires en tension à envoyer à un haut-parleur.
En plus de tout cela, la carte son contient, au minimum, des circuits chargés de gérer les transferts entre la mémoire et la carte son, des circuits de communication avec le bus.
[[File:CARTE SON - architecture.PNG|centre|vignette|upright=2|Architecture d'une carte son.]]
Il faut signaler que certaines cartes son possèdent un processeur, qui s'occupe du calcul de divers effets sonores. Ce processeur est techniquement un processeur de traitement de signal, aussi appelé DSP, à savoir un type de processeur que nous aborderons en détail d'ici quelques chapitres. Ce DSP est placé en aval du CNA, ce qui est évident quand on sait que le processeur ne peut traiter que des informations numériques.
[[File:CARTE SON avec DSP.PNG|centre|vignette|upright=2|CARTE SON avec DSP]]
En aval du CAN (ou du DSP, cas échéant), on trouve une petite mémoire dans laquelle sont stockées les informations qui viennent d'être numérisée. Cette mémoire sert à interfacer le processeur avec le CNA : le processeur est forcément très rapide, mais il n'est pas disponible à chaque fois que la carte son a fini de traduire un son. Les informations numérisées sont donc accumulées dans une petite mémoire FIFO en attendant que le processeur puisse les traiter. La même chose a lieu pour le CNA. Le processeur étant assez pris, il n'est pas forcément capable d'être disponible tous les 10 millisecondes pour envoyer un son à la carte son. Dans ces conditions, le processeur prépare à l'avance une certaine quantité d'information, suffisamment pour tenir durant quelques millisecondes. Une fois qu'il dispose de suffisamment d’informations sonores, il va envoyer le tout en une fois à la carte son. Celle-ci stockera ces informations dans une petite mémoire FIFO qui les conservera dans l'ordre. Elle convertira ces informations au fur et à mesure, à un fréquence régulière.
[[File:CARTE SON - architecture - 3.PNG|centre|vignette|upright=2|Architecture d'une carte son avec les mémoires tampons.]]
===L'échantillonneur===
La tension transmise par le microphone varie de manière continue, ce qui rend sa transformation en numérique difficile. Pour éviter tout problème, la valeur de la tension est mesurée à intervalle réguliers, tout les 20 millisecondes par exemple. On parle alors d’échantillonnage. Le nombre de fois que notre tension est mesurée par seconde s'appelle la '''fréquence d'échantillonnage'''. Pour donner quelques exemples, le signal sonore d'un CD audio a été échantillonné à 44,1 kHZ, c'est à dire 44100 fois par secondes. Plus cette fréquence est élevée, plus le son sera de qualité, proche du signal analogique mesuré. C'est ce qui explique qu'augmenter la fréquence d'échantillonnage augmente la quantité de mémoire nécessaire pour stocker le son. Sur les cartes sons actuelles, il est possible de configurer la fréquence d'échantillonnage.
L’échantillonnage est réalisé par un circuit appelé l’'''échantillonneur-bloqueur'''. L'échantillonneur-bloqueur le plus simple ressemble au circuit du schéma ci-dessous. Les triangles de ce schéma sont ce qu'on appelle des amplificateurs opérationnels, mais on n'a pas vraiment à s'en préoccuper. Dans ce montage, ils servent juste à isoler le condensateur du reste du circuit, en ne laissant passer les tensions que dans un sens. L'entrée C est reliée à un signal d'horloge qui ouvre ou ferme l'interrupteur à fréquence régulière. La tension va remplir le condensateur quand l'interrupteur se ferme. Une fois le condensateur remplit, l'interrupteur est déconnecté isolant le condensateur de la tension d'entrée. Celui-ci mémorisera alors la tension d'entrée jusqu'au prochain échantillonnage.
[[File:Sample-hold-circuit.svg|centre|vignette|Echantillonneur-bloqueur.]]
===Les convertisseurs entre analogique et numérique===
Pour faire les conversions analogiques-numériques, on utilise deux circuits, vus dans les chapitres en début de cours :
* Le circuit qui convertit un signal analogique en signal numérique cela est un CAN ('''convertisseur analogique-numérique''').
* Le circuit qui fait la conversion inverse est un CNA ('''convertisseur numérique-analogique''').
[[File:CAN & CNA.png|centre|vignette|upright=2|CAN & CNA]]
Le signal numérique de la carte son, utilisé pour coder le son, utilise un certain nombre de bits. Sur les cartes sons actuelles, ce nombre de bits porte un nom : c'est la '''résolution de la carte son'''. Celui-ci varie entre 16 et 24 bits sur les cartes sons récentes.
===Le multiplexeur analogique===
Une carte son peut supporter plusieurs entrées analogiques. Malheureusement, cela coûterait trop cher de mettre plusieurs CAN sur une carte son. À la place, les concepteurs de cartes sons mutualisent le CAN sur plusieurs entrées analogiques grâce à un '''multiplexeur analogique'''. Ce multiplexeur récupère les tensions des différentes entrées, et en choisira une qui est recopiée sur la sortie. Ce multiplexeur comporte une entrée de commande qui permet de choisir quelle entrée sera choisie pour être recopiée sur la sortie. Ce multiplexeur est ensuite suivi par un '''amplificateur''',qui fait rentrer la tension fournit en entrée dans un intervalle de tension compatible avec le CAN.
[[File:Partage de CAN sur plusieurs entrées.PNG|centre|vignette|upright=2|Partage de CAN sur plusieurs entrées.]]
==La carte graphique==
Les cartes graphiques sont des cartes qui s'occupent de communiquer avec l'écran, pour y afficher des images. Leur fonctionnement interne est cependant effroyablement complexe et il serait difficile de tout résumer en quelques lignes. Les anciennes versions de ce wikilivres ont tenté de le faire, mais le résultat était peu convainquant. La raison est que les cartes graphiques gèrent beaucoup de choses : elles gèrent l'affichage proprement dit, mais aussi le rendu 2D ou 3D, et ces trois fonctions sont relativement dépendantes les unes des autres. De plus, expliquer comment fonctionne une carte graphique demande de connaitre quelques bases sur le rendu 3D ou sur la couche logicielle de la programmation graphique, sur le pipeline graphique des API 3D modernes, et ce genre de subtilités. Et même quelque chose de très simple, comme l'affichage d'une image à l'écran, sans rendu 2D ou 3D, demande des explications assez longues, au moins un bon chapitre. Sachez cependant que nous reparlerons rapidement des cartes graphiques modernes dans les derniers chapitres de ce cours, dans le chapitre sur les architectures à parallélisme de données. De plus, je peux vous renvoyer vers un wikilivre spécialement dédié sur le sujet écrit par le même auteur que ce cours :
* [[Les cartes graphiques|Les cartes graphiques]].
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'adressage des périphériques
| prevText=L'adressage des périphériques
| next=Les mémoires de masse : généralités
| nextText=Les mémoires de masse : généralités
}}
</noinclude>
3ogu4q4hm6th4302tucuq5ydqx3whuw
Fonctionnement d'un ordinateur/Sommaire
0
69596
744368
744273
2025-06-09T18:31:34Z
Mewtow
31375
/* Les entrées-sorties et périphériques */
744368
wikitext
text/x-wiki
__NOTOC__
* [[Fonctionnement d'un ordinateur/Introduction|Introduction]]
==Le codage des informations==
* [[Fonctionnement d'un ordinateur/L'encodage des données|L'encodage des données]]
* [[Fonctionnement d'un ordinateur/Le codage des nombres|Le codage des nombres]]
* [[Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur|Les codes de détection/correction d'erreur]]
==Les circuits électroniques==
* [[Fonctionnement d'un ordinateur/Les portes logiques|Les portes logiques]]
===Les circuits combinatoires===
* [[Fonctionnement d'un ordinateur/Les circuits combinatoires|Les circuits combinatoires]]
* [[Fonctionnement d'un ordinateur/Les circuits de masquage|Les circuits de masquage]]
* [[Fonctionnement d'un ordinateur/Les circuits de sélection|Les circuits de sélection]]
===Les circuits séquentiels===
* [[Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit|Les bascules : des mémoires de 1 bit]]
* [[Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones|Les circuits synchrones et asynchrones]]
* [[Fonctionnement d'un ordinateur/Les registres et mémoires adressables|Les registres et mémoires adressables]]
* [[Fonctionnement d'un ordinateur/Les circuits compteurs et décompteurs|Les circuits compteurs et décompteurs]]
* [[Fonctionnement d'un ordinateur/Les timers et diviseurs de fréquence|Les timers et diviseurs de fréquence]]
===Les circuits de calcul et de comparaison===
* [[Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation|Les circuits de décalage et de rotation]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction|Les circuits pour l'addition et la soustraction]]
* [[Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)|Les unités arithmétiques et logiques entières (simples)]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit|Les circuits de calcul logique et bit à bit]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande|Les circuits pour l'addition multiopérande]]
* [[Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division|Les circuits pour la multiplication et la division]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul flottant|Les circuits de calcul flottant]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques|Les circuits de calcul trigonométriques]]
* [[Fonctionnement d'un ordinateur/Les circuits de comparaison|Les circuits de comparaison]]
* [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]]
===Les circuits intégrés à semi-conducteurs===
* [[Fonctionnement d'un ordinateur/Les transistors et portes logiques|Les transistors et portes logiques]]
* [[Fonctionnement d'un ordinateur/Les circuits intégrés|Les circuits intégrés]]
* [[Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus|L'interface électrique entre circuits intégrés et bus]]
==L'architecture d'un ordinateur==
* [[Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur|L'architecture de base d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La hiérarchie mémoire|La hiérarchie mémoire]]
* [[Fonctionnement d'un ordinateur/La performance d'un ordinateur|La performance d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques|La loi de Moore et les tendances technologiques]]
* [[Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur|Les techniques de réduction de la consommation électrique d'un processeur]]
==Les bus électroniques et la carte mère==
* [[Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS|La carte mère, chipset et BIOS]]
* [[Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)|Les bus et liaisons point à point (généralités)]]
* [[Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus|Les encodages spécifiques aux bus]]
* [[Fonctionnement d'un ordinateur/Les liaisons point à point|Les liaisons point à point]]
* [[Fonctionnement d'un ordinateur/Les bus électroniques|Les bus électroniques]]
* [[Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point|Quelques exemples de bus et de liaisons point à point]]
==Les mémoires RAM/ROM==
* [[Fonctionnement d'un ordinateur/Les différents types de mémoires|Les différents types de mémoires]]
* [[Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique|L'interface d'une mémoire électronique]]
* [[Fonctionnement d'un ordinateur/Le bus mémoire|Le bus mémoire]]
===La micro-architecture d'une mémoire adressable===
* [[Fonctionnement d'un ordinateur/Les cellules mémoires|Les cellules mémoires]]
* [[Fonctionnement d'un ordinateur/Le plan mémoire|Le plan mémoire]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire interne|Le contrôleur mémoire interne]]
* [[Fonctionnement d'un ordinateur/Mémoires évoluées|Les mémoires évoluées]]
===Les mémoires primaires===
* [[Fonctionnement d'un ordinateur/Les mémoires ROM|Les mémoires ROM : Mask ROM, PROM, EPROM, EEPROM, Flash]]
* [[Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones|Les mémoires SRAM synchrones]]
* [[Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)|Les mémoires RAM dynamiques (DRAM)]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire externe|Le contrôleur mémoire externe]]
===Les mémoires exotiques===
* [[Fonctionnement d'un ordinateur/Les mémoires associatives|Les mémoires associatives]]
* [[Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO|Les mémoires FIFO et LIFO]]
==Le processeur==
===L'architecture externe===
* [[Fonctionnement d'un ordinateur/Langage machine et assembleur|Langage machine et assembleur]]
* [[Fonctionnement d'un ordinateur/Les registres du processeur|Les registres du processeur]]
* [[Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme|Le modèle mémoire : alignement et boutisme]]
* [[Fonctionnement d'un ordinateur/Les modes d'adressage|Les modes d'adressage]]
* [[Fonctionnement d'un ordinateur/L'encodage des instructions|L'encodage des instructions]]
* [[Fonctionnement d'un ordinateur/Les jeux d'instructions|Les jeux d'instructions]]
* [[Fonctionnement d'un ordinateur/La pile d'appel et les fonctions|La pile d'appel et les fonctions]]
* [[Fonctionnement d'un ordinateur/Les interruptions et exceptions|Les interruptions et exceptions]]
===La micro-architecture===
* [[Fonctionnement d'un ordinateur/Les composants d'un processeur|Les composants d'un processeur]]
* [[Fonctionnement d'un ordinateur/Le chemin de données|Le chemin de données]]
* [[Fonctionnement d'un ordinateur/L'unité de chargement et le program counter|L'unité de chargement et le program counter]]
* [[Fonctionnement d'un ordinateur/L'unité de contrôle|L'unité de contrôle]]
===Les jeux d'instruction spécialisés ou exotiques===
* [[Fonctionnement d'un ordinateur/Les architectures à accumulateur|Les architectures à accumulateur]]
* [[Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins|Les processeurs 8 bits et moins]]
* [[Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire|Les architectures à pile et mémoire-mémoire]]
* [[Fonctionnement d'un ordinateur/Les processeurs de traitement du signal|Les processeurs de traitement du signal]]
* [[Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement|Les architectures actionnées par déplacement]]
===La mémoire virtuelle et la protection mémoire===
* [[Fonctionnement d'un ordinateur/L'espace d'adressage du processeur|L'espace d'adressage du processeur]]
* [[Fonctionnement d'un ordinateur/Le partage de l'espace d'adressage : avec et sans multiprogrammation|Le partage de l'espace d'adressage : avec et sans multiprogrammation]]
* [[Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle|L'abstraction mémoire et la mémoire virtuelle]]
==Les entrées-sorties et périphériques==
* [[Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques|Les méthodes de synchronisation entre processeur et périphériques]]
* [[Fonctionnement d'un ordinateur/L'adressage des périphériques|L'adressage des périphériques]]
* [[Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension|Les périphériques et les cartes d'extension]]
==Les mémoires de stockage==
* [[Fonctionnement d'un ordinateur/Les mémoires de masse : généralités|Les mémoires de masse : généralités]]
* [[Fonctionnement d'un ordinateur/Les disques durs|Les disques durs]]
* [[Fonctionnement d'un ordinateur/Les solid-state drives|Les solid-state drives]]
* [[Fonctionnement d'un ordinateur/Les disques optiques|Les disques optiques]]
* [[Fonctionnement d'un ordinateur/Les technologies RAID|Les technologies RAID]]
==La ou les mémoires caches==
* [[Fonctionnement d'un ordinateur/Les mémoires cache|Les mémoires cache]]
* [[Fonctionnement d'un ordinateur/Le préchargement|Le préchargement]]
* [[Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer|Le ''Translation Lookaside Buffer'']]
==Le parallélisme d’instructions==
* [[Fonctionnement d'un ordinateur/Le pipeline|Le pipeline]]
* [[Fonctionnement d'un ordinateur/Les pipelines de longueur fixe et dynamiques|Les pipelines de longueur fixe et dynamiques]]
===Les branchements et le ''front-end''===
* [[Fonctionnement d'un ordinateur/Les exceptions précises et branchements|Les exceptions précises et branchements]]
* [[Fonctionnement d'un ordinateur/La prédiction de branchement|La prédiction de branchement]]
* [[Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions|Les optimisations du chargement des instructions]]
===L’exécution dans le désordre===
* [[Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions|L'émission dans l'ordre des instructions]]
* [[Fonctionnement d'un ordinateur/Les dépendances de données et l'exécution dans le désordre|Les dépendances de données et l'exécution dans le désordre]]
* [[Fonctionnement d'un ordinateur/Le renommage de registres|Le renommage de registres]]
* [[Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo|Annexe : Le scoreboarding et l'algorithme de Tomasulo]]
===Les accès mémoire avec un pipeline===
* [[Fonctionnement d'un ordinateur/Les unités mémoires à exécution dans l'ordre|Les unités mémoires à exécution dans l'ordre]]
* [[Fonctionnement d'un ordinateur/Les unités mémoires à exécution dans le désordre|Les unités mémoires à exécution dans le désordre]]
* [[Fonctionnement d'un ordinateur/Le parallélisme mémoire au niveau du cache|Le parallélisme mémoire au niveau du cache]]
===L'émission multiple===
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires|Les processeurs superscalaires]]
* [[Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC|Les processeurs VLIW et EPIC]]
* [[Fonctionnement d'un ordinateur/Les architectures dataflow|Les architectures dataflow]]
==Les architectures parallèles==
* [[Fonctionnement d'un ordinateur/Les architectures parallèles|Les architectures parallèles]]
* [[Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs|Les architectures multiprocesseurs et multicœurs]]
* [[Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading|Les architectures multithreadées et Hyperthreading]]
* [[Fonctionnement d'un ordinateur/Les architectures à parallélisme de données|Les architectures à parallélisme de données]]
* [[Fonctionnement d'un ordinateur/La cohérence des caches|La cohérence des caches]]
* [[Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire|Les sections critiques et le modèle mémoire]]
==Annexes==
* [[Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation|L'accélération matérielle de la virtualisation]]
* [[Fonctionnement d'un ordinateur/Le matériel réseau|Le matériel réseau]]
* [[Fonctionnement d'un ordinateur/La tolérance aux pannes|La tolérance aux pannes]]
* [[Fonctionnement d'un ordinateur/Les architectures systoliques|Les architectures systoliques]]
* [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires|Les ordinateurs de première génération : tubes à vide et mémoires]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires|Les ordinateurs à encodages non-binaires]]
* [[Fonctionnement d'un ordinateur/Les circuits réversibles|Les circuits réversibles]]
{{autocat}}
auio1k18bgffqzf7ucm9m21wqtls477
Fonctionnement d'un ordinateur/Les mémoires historiques magnétiques
0
78261
744399
678239
2025-06-10T10:53:26Z
EmausBot
70429
Robot : Correction d’une double redirection depuis [[Fonctionnement d'un ordinateur/Les mémoires historiques]] vers [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires]]
744399
wikitext
text/x-wiki
#REDIRECTION [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires]]
k3g81a8823zuz0hoqxwrbmoi7cddnvk
Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques
0
78309
744354
739745
2025-06-09T16:41:50Z
Mewtow
31375
/* L'usage du DMA pour les transferts mémoire-mémoire */
744354
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une IO-MMU, une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
8w2ajzmx7q3v8bjrfmb5fk166wiaod7
744355
744354
2025-06-09T16:44:55Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744355
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une IO-MMU, une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA. Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique et le système d'exploitation.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues, grâce à des techniques d'extension d'espace d'adressage. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contiguë dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
jpedw1g7q8e8h5qsckj91ok92ne0cmn
744356
744355
2025-06-09T16:54:27Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744356
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
==L'implémentation de la mémoire virtuelle pour les périphériques==
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique et le système d'exploitation. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages peut en théorie être placée soit en RAM, soit dans le périphérique.
Placer la table des pages en mémoire RAM n'est cependant pas pratique : si la TLB ne permet pas d'aobtenir l'adresse physique, il faudrait que le ''page table walkers'' accède à la table des page en RAM à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques. En pratique, il est préférable de mémoriser la table des page dans le périphérique lui-même. Si le périphérique incorpore de la mémoire RAM, il peut le faire sans problème. Là encore les cartes graphiques sont dans ce cas : elles incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
La IOMMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère, ou encore dans un périphérique, voire dans le processeur. Si elle est dans le ''chipset'' ou le processeur, elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues, grâce à des techniques d'extension d'espace d'adressage. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contiguë dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Les exemples les plus communs de IO-MMU sont celles intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
scr66508t0ey752a9ixn9ncoiog1r59
744357
744356
2025-06-09T16:54:46Z
Mewtow
31375
/* L'implémentation de la mémoire virtuelle pour les périphériques */
744357
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique et le système d'exploitation. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages peut en théorie être placée soit en RAM, soit dans le périphérique.
Placer la table des pages en mémoire RAM n'est cependant pas pratique : si la TLB ne permet pas d'aobtenir l'adresse physique, il faudrait que le ''page table walkers'' accède à la table des page en RAM à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques. En pratique, il est préférable de mémoriser la table des page dans le périphérique lui-même. Si le périphérique incorpore de la mémoire RAM, il peut le faire sans problème. Là encore les cartes graphiques sont dans ce cas : elles incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
La IOMMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère, ou encore dans un périphérique, voire dans le processeur. Si elle est dans le ''chipset'' ou le processeur, elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues, grâce à des techniques d'extension d'espace d'adressage. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contiguë dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Les exemples les plus communs de IO-MMU sont celles intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
dikr2wnd9leuntbf8sf9u7hbppf031b
744358
744357
2025-06-09T16:59:00Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744358
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM a cependant un cout en performance
Si la TLB du périphérique ne permet pas d'obtenir l'adresse physique, il faudrait que le ''page table walkers'' accède à la table des page en RAM à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques. En pratique, il est préférable de mémoriser la table des page dans le périphérique lui-même. Si le périphérique incorpore de la mémoire RAM, il peut le faire sans problème. Là encore les cartes graphiques sont dans ce cas : elles incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
La IOMMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère, ou encore dans un périphérique, voire dans le processeur. Si elle est dans le ''chipset'' ou le processeur, elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues, grâce à des techniques d'extension d'espace d'adressage. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contiguë dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Les exemples les plus communs de IO-MMU sont celles intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
j5kjas9pmkbd095ndo7be0uh5pkqq2k
744359
744358
2025-06-09T17:04:43Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744359
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IOMMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère, ou encore dans un périphérique, voire dans le processeur. Si elle est dans le ''chipset'' ou le processeur, elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des page IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleur DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des page IO en RAM, le périphérique devrait accède à la table des page en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des page IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues, grâce à des techniques d'extension d'espace d'adressage. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contiguë dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Les exemples les plus communs de IO-MMU sont celles intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
81ssqgeayx9cf6b7j3mjo1ceys8ie32
744361
744359
2025-06-09T17:40:53Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744361
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IOMMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique, . Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des page IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleur DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des page IO en RAM, le périphérique devrait accède à la table des page en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des page IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues, grâce à des techniques d'extension d'espace d'adressage. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contiguë dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
hcydlwgma5vdktuppgexy48iie3dg7a
744362
744361
2025-06-09T17:49:33Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744362
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les périphériques DMA sont capables de faire la traduction entre adresses virtuelles manipulées par le pilote de périphérique, et adresses physique. Le périphérique DMA manipulera des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, ils incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique, . Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des page IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleur DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des page IO en RAM, le périphérique devrait accèder à la table des page en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des page IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contiguë dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
5bor6633al23nq6bpi32iofb1rkmnjn
744363
744362
2025-06-09T17:55:00Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744363
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
6qfrjhs26bn3962lg8humcpxdayz3hc
744364
744363
2025-06-09T17:55:56Z
Mewtow
31375
/* Le Direct memory access */
744364
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un dernier avantage est que les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
guielm8uoa47jgyypx0v3n71vzxd7p3
744366
744364
2025-06-09T18:30:29Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744366
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées modernes, qui incorporent plusieurs gibi-octets de mémoire RAM, la fameuse mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM installée sur le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur. L'intérêt de l'IO-VM est que le périphérique peut adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la mémoire système. Un bon exemple est celui des cartes graphiques dédiées modernes. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en rab. Et c'est là qu'intervient la première différence avec la CPU-VM : les 2 gigas fictifs sont pris en mémoire système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
a4try2rd0yst2s2yjj28m87b9rf2xt2
744367
744366
2025-06-09T18:31:21Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle */
744367
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART.
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM installée sur le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur.
L'intérêt de l'IO-VM est que le périphérique peut adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la mémoire système. Un bon exemple est celui des cartes graphiques dédiées modernes. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en rab. Et c'est là qu'intervient la première différence avec la CPU-VM : les 2 gigas fictifs sont pris en mémoire système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
k95lubzn3497ri5nl16omui79q3bwt8
744372
744367
2025-06-09T18:33:45Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle : l'IO-MMU */
744372
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART. La première carte graphique NVIDIA, la NV1, utilisait ce système, comme illustré ci-dessous.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]]
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM installée sur le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur.
L'intérêt de l'IO-VM est que le périphérique peut adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la mémoire système. Un bon exemple est celui des cartes graphiques dédiées modernes. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en rab. Et c'est là qu'intervient la première différence avec la CPU-VM : les 2 gigas fictifs sont pris en mémoire système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
4dhm2rtre4n042864wtpg1jzebs1vqp
744373
744372
2025-06-09T18:35:23Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle : l'IO-MMU */
744373
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART. La première carte graphique NVIDIA, la NV1, utilisait ce système, comme illustré ci-dessous.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]]
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages. La carte graphique NV1 de NVIDIA faisait autrement : elle mettait la table des pages dans une mémoire dédiée.
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM installée sur le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur.
L'intérêt de l'IO-VM est que le périphérique peut adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la mémoire système. Un bon exemple est celui des cartes graphiques dédiées modernes. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en rab. Et c'est là qu'intervient la première différence avec la CPU-VM : les 2 gigas fictifs sont pris en mémoire système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
0k4g0nc7b7e8qsrdbntahi33efcrtcp
744374
744373
2025-06-09T18:42:39Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle : l'IO-MMU */
744374
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART. La première carte graphique NVIDIA, la NV1, utilisait ce système, comme illustré ci-dessous.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]]
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de celle du système d'exploitation, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages. La carte graphique NV1 de NVIDIA faisait autrement : elle mettait la table des pages dans une mémoire dédiée.
[[File:IO-MMU et table des pages sur périphérique avec RAM locale.png|centre|vignette|upright=2|IO-MMU et table des pages sur périphérique avec RAM locale]]
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM installée sur le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur.
L'intérêt de l'IO-VM est que le périphérique peut adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la mémoire système. Un bon exemple est celui des cartes graphiques dédiées modernes. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en rab. Et c'est là qu'intervient la première différence avec la CPU-VM : les 2 gigas fictifs sont pris en mémoire système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
8zp6viez1l1ay8a5xk2ahi1k7kxxs9g
744375
744374
2025-06-09T18:44:05Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle : l'IO-MMU */
744375
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART. La première carte graphique NVIDIA, la NV1, utilisait ce système, comme illustré ci-dessous.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]]
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de la table des page pour les processus, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages. La carte graphique NV1 de NVIDIA faisait autrement : elle mettait la table des pages dans une mémoire dédiée.
[[File:IO-MMU et table des pages sur périphérique avec RAM locale.png|centre|vignette|upright=2|IO-MMU et table des pages sur périphérique avec RAM locale]]
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM installée sur le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur.
L'intérêt de l'IO-VM est que le périphérique peut adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la mémoire système. Un bon exemple est celui des cartes graphiques dédiées modernes. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en rab. Et c'est là qu'intervient la première différence avec la CPU-VM : les 2 gigas fictifs sont pris en mémoire système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
ntv57k3aaxp942rf5e184jrxp1wkj8q
744376
744375
2025-06-09T18:46:08Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle : l'IO-MMU */
744376
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Dans tout ce qui a été dit plus nous sommes parti du principe que le contrôleur DMA manipule des adresses physiques, compréhensibles directement par la mémoire RAM. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le pilote de périphérique manipule uniquement des adresses virtuelles, et n'a aucun moyen de récupérer les adresses physiques associées. Il va donc configurer le contrôleur DMA avec des adresses virtuelles, ce qui posera problème.
Pour éviter cela, les contrôleurs DMA sont capables de faire la traduction entre adresses virtuelles fournies par le pilote de périphérique, et adresses physique. Le contrôleur DMA manipule des adresses virtuelles nativement, il fera des calculs d'adresse si besoin, mais les traduira en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART. La première carte graphique NVIDIA, la NV1, utilisait ce système, comme illustré ci-dessous.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]]
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de la table des page pour les processus, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages. La carte graphique NV1 de NVIDIA faisait autrement : elle mettait la table des pages dans une mémoire dédiée.
[[File:IO-MMU et table des pages sur périphérique avec RAM locale.png|centre|vignette|upright=2|IO-MMU et table des pages sur périphérique avec RAM locale]]
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM dans le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur. L'IO-MMU permet d'adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la RAM système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en mémoire système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
g4gebs2b0qslarczu8r09wrnbss8x0v
744378
744376
2025-06-09T20:17:31Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle : l'IO-MMU */
744378
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Le contrôleur DMA est initialisé par le processeur avec des adresses de départ, qu'il incrémente automatiquement. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le processeur manipule uniquement des adresses virtuelles et initialisera le contrôleur DMA avec, ce qui pose problème.
Pour éviter cela, les contrôleurs DMA modernes font la traduction entre adresses virtuelles et adresses physiques. Le contrôleur DMA incrémente des adresses virtuelles nativement, mais les traduit en adresse physique avant tout accès à la RAM. Pour cela, les contrôleurs DMA incorporent une '''IO-MMU''', une unité de traduction d'adresse qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU intégrée au processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans un contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est généralement partagée entre plusieurs périphériques. Typiquement, il y a une IOMMU par bus. Mais il n'est pas rare que la IO-MMU soit dans un contrôleur DMA intégré dans un périphérique. L'exemple type est celui des IO-MMU intégrées aux cartes graphiques. La plupart des cartes graphiques AGP et PCI-Express utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table'', abrévié en GART. La première carte graphique NVIDIA, la NV1, utilisait ce système, comme illustré ci-dessous.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]]
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et tout ce qui va avec l'incorporation de la mémoire virtuelle. La table des pages de l'IO-MMU est séparée de la table des page pour les processus, bien que certaines optimisations permettent de fusionner les deux. Elle peut en théorie être placée soit en RAM, soit dans le périphérique. Placer la table des pages en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Par contre, les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait accéder à la table des pages en RAM, à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès mémoire. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et peut donc mettre la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages. La carte graphique NV1 de NVIDIA faisait autrement : elle mettait la table des pages dans une mémoire dédiée.
[[File:IO-MMU et table des pages sur périphérique avec RAM locale.png|centre|vignette|upright=2|IO-MMU et table des pages sur périphérique avec RAM locale]]
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM dans le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur. L'IO-MMU permet d'adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la RAM système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en mémoire système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
qjvr9mi7rqg59b280d1neat3gcjf4im
744379
744378
2025-06-09T20:23:44Z
Mewtow
31375
/* L'interaction entre DMA et mémoire virtuelle : l'IO-MMU */
744379
wikitext
text/x-wiki
Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.
==Le contrôleur de périphériques==
Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.
[[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Contrôleur de périphériques]]
===Les registres d’interfaçage===
Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
[[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]]
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''.
La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.
===Les interruptions de type IRQ===
La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé '''routine d'interruption''' ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
[[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]]
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
[[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]]
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. 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. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.
===Les FIFO internes au contrôleur de périphérique===
Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.
===Un contrôleur de périphérique peut gérer plusieurs périphériques===
Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons.
[[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]]
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.
[[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]]
==Les entrées d'interruption du processeur==
Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.
===L'entrée d'interruption : niveaux logiques ou fronts===
Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.
===Les deux entrées d'interruption : masquable et non-masquable===
Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. 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. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.
==Le contrôleur d'interruption==
Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
[[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]]
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
[[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]]
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
[[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]]
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.
===Les contrôleurs d'interruption en cascade===
Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
[[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]]
[[File:Intel 8259.svg|vignette|Intel 8259]]
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.
===Les ''Message Signaled Interrupts''===
Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.
===Les généralités sur les interruptions matérielles===
Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
[[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]]
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.
==Le ''Direct memory access''==
Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.
===Le contrôleur DMA===
Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
[[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]]
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.
===Les modes de transfert DMA===
Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent.
Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.
===Les limitations en termes d’adressage===
Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.
===Les limitations en termes d’alignement===
La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.
===Le 8237 et son usage dans les PC===
Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.
====L'usage du 8237 sur les premiers IBM PC====
Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.
====Les deux 8237 des bus ISA====
Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.
===L'usage du DMA pour les transferts mémoire-mémoire===
Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA.
[[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]]
===DMA et cohérence des caches===
Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
[[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]]
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.
===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU===
Le contrôleur DMA est initialisé par le processeur avec des adresses de départ. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le processeur initialise le contrôleur DMA avec des adresses virtuelles, ce qui pose problème. Pour éviter cela, les contrôleurs DMA modernes incrémentent des adresses virtuelles nativement, mais les traduisent en adresse physique avant tout accès à la RAM. En clair, les contrôleurs DMA incorporent une '''IO-MMU''', un circuit qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU du processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA.
La IO-MMU est intégrée dans le contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est partagée entre plusieurs périphériques. Mais il n'est pas rare que la IO-MMU soit intégré dans un périphérique. L'exemple type est celui des cartes graphiques AGP et PCI-Express, qui utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table''. La première carte graphique NVIDIA, la NV1, utilisait un système similaire, comme illustré ci-dessous.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]]
Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et toutes les optimisations associées. La table des pages de l'IO-MMU est séparée des autres tables des pages, bien que certaines optimisations permettent de fusionner les deux. Placer la table des pages IO en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Mais les périphériques qui incorporent un contrôleur DMA font autrement.
Avec une table des pages IO en RAM, le périphérique devrait y accéder à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès à la table des pages IO. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et met la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages. La carte graphique NV1 de NVIDIA faisait autrement : elle mettait la table des pages dans une mémoire dédiée.
[[File:IO-MMU et table des pages sur périphérique avec RAM locale.png|centre|vignette|upright=2|IO-MMU et table des pages sur périphérique avec RAM locale]]
L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser.
Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique.
Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle.
Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM dans le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur. L'IO-MMU permet d'adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la RAM système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en mémoire système.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
==L'intégration dans le ''chipset'' de la carte mère==
Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère, et plus précisément le ''southbridge''. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', les contrôleurs S-ATA. Le contenu du ''chipset'' dépend fortement de la carte mère.
[[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]]
===Les ''chipsets'' sont des regroupements de circuits très divers===
Le regroupement de nombreux circuits dans un ''chipset'' a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les ''chipsets'' intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le ''chipset''. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les ''chipsets'' soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le ''chipset'', comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les ''chipsets'' tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des ''timers'', les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la ''Real Time Clock'', sont de nos jours intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un ''chipset'' est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".
===Les ''chipsets'' servent de répartiteur entre CPU/RAM/IO===
L'usage d'un ''chipset'' a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans ''chipset'', il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un ''chipset'', les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au ''chipset'', qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
[[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]]
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le ''chipset'' de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en ''northbridge'' et ''southbridge'', puis avec une intégration de plus en plus poussée du ''chipset'' dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
[[File:Chipset schematic.svg|vignette|upright=1.0|Chipset séparé en northbridge et southbridge.]]
Dans les années 2000, les ''chipsets'' monolithiques ont laissé à la place à des ''chipsets'' séparés en deux circuits intégrés distincts, appelés ''northbridge'' et ''southbridge''. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le ''southbridge'' était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au ''northbridge''.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le ''southbridge''que se trouvent les contrôleurs de périphériques, ainsi que les ''timers''. Le ''southbridge'' était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le ''northbridge'' pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le ''northbridge'' est donc passé dans le processeur, mais le ''southbridge'' est resté. Le ''southbridge'' et le ''chipset'' sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le ''chipset'' est surtout spécialisé dans la gestion des périphériques/IO et des ''timers'', entre autres fonctions importantes.
===Les premiers ''chipsets'' des PC x86===
Les premières cartes mères PC n'avaient pas de ''chipset'', mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers''.
Le premier ''chipset'' inventé pour les processeurs x86 a été le 82C100, aussi appelé ''chipset'' NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le ''Programmable Interval Timer'' 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''.
Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
[[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]]
Un autre exemple est le ''chipset'' utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des ''timers'' et des circuits multiplieurs d'horloge.
[[File:Intel486 System Controller.png|centre|vignette|upright=2|Intel 486 System Controller.]]
En clair, les premiers ''chipsets'' incorporaient déjà beaucoup de fonctions différentes, comme les ''chipset'' modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du ''southbridges'' dans les ''chipsets'' modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le ''northbridge'' : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de ''timers'', et de la RTC/CMOS RAM est aussi chose commune dans les ''chipset'' modernes. Les ''chipsets'' modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'abstraction mémoire et la mémoire virtuelle
| prevText=L'abstraction mémoire et la mémoire virtuelle
| next=L'adressage des périphériques
| nextText=L'adressage des périphériques
}}
</noinclude>
9aram8p90d2fjp1zqkxzitdg9enoamd
Fonctionnement d'un ordinateur/Version imprimable 2
0
78962
744377
744225
2025-06-09T18:48:34Z
Mewtow
31375
744377
wikitext
text/x-wiki
__NOTOC__
{{:Fonctionnement d'un ordinateur/Introduction}}
=Le codage des informations=
{{:Fonctionnement d'un ordinateur/L'encodage des données}}
{{:Fonctionnement d'un ordinateur/Le codage des nombres}}
{{:Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur}}
=Les circuits électroniques=
{{:Fonctionnement d'un ordinateur/Les portes logiques}}
==Les circuits combinatoires==
{{:Fonctionnement d'un ordinateur/Les circuits combinatoires}}
{{:Fonctionnement d'un ordinateur/Les circuits de masquage}}
{{:Fonctionnement d'un ordinateur/Les circuits de sélection}}
==Les circuits séquentiels==
{{:Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit}}
{{:Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones}}
{{:Fonctionnement d'un ordinateur/Les registres et mémoires adressables}}
{{:Fonctionnement d'un ordinateur/Les circuits compteurs et décompteurs}}
{{:Fonctionnement d'un ordinateur/Les timers et diviseurs de fréquence}}
==Les circuits de calcul et de comparaison==
{{:Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation}}
{{:Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction}}
{{:Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)}}
{{:Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit}}
{{:Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande}}
{{:Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division}}
{{:Fonctionnement d'un ordinateur/Les circuits de calcul flottant}}
{{:Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques}}
{{:Fonctionnement d'un ordinateur/Les circuits de comparaison}}
{{:Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique}}
==Les circuits intégrés==
{{:Fonctionnement d'un ordinateur/Les transistors et portes logiques}}
{{:Fonctionnement d'un ordinateur/Les circuits intégrés}}
{{:Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus}}
=L'architecture d'un ordinateur=
{{:Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur}}
{{:Fonctionnement d'un ordinateur/La hiérarchie mémoire}}
{{:Fonctionnement d'un ordinateur/La performance d'un ordinateur}}
{{:Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques}}
{{:Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur}}
=Les bus et liaisons point à point=
{{:Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS}}
{{:Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)}}
{{:Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus}}
{{:Fonctionnement d'un ordinateur/Les liaisons point à point}}
{{:Fonctionnement d'un ordinateur/Les bus électroniques}}
{{:Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point}}
=Les mémoires=
{{:Fonctionnement d'un ordinateur/Les différents types de mémoires}}
{{:Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique}}
{{:Fonctionnement d'un ordinateur/Le bus mémoire}}
==La micro-architecture d'une mémoire adressable==
{{:Fonctionnement d'un ordinateur/Les cellules mémoires}}
{{:Fonctionnement d'un ordinateur/Le plan mémoire}}
{{:Fonctionnement d'un ordinateur/Contrôleur mémoire interne}}
{{:Fonctionnement d'un ordinateur/Mémoires évoluées}}
==Les mémoires primaires==
{{:Fonctionnement d'un ordinateur/Les mémoires ROM}}
{{:Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones}}
{{:Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)}}
{{:Fonctionnement d'un ordinateur/Contrôleur mémoire externe}}
==Les mémoires exotiques==
{{:Fonctionnement d'un ordinateur/Les mémoires associatives}}
{{:Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO}}
=Le processeur=
==L'architecture externe==
{{:Fonctionnement d'un ordinateur/Langage machine et assembleur}}
{{:Fonctionnement d'un ordinateur/Les registres du processeur}}
{{:Fonctionnement d'un ordinateur/Les modes d'adressage}}
{{:Fonctionnement d'un ordinateur/L'encodage des instructions}}
{{:Fonctionnement d'un ordinateur/Les jeux d'instructions}}
{{:Fonctionnement d'un ordinateur/La pile d'appel et les fonctions}}
{{:Fonctionnement d'un ordinateur/Les interruptions et exceptions}}
{{:Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme}}
==La micro-architecture==
{{:Fonctionnement d'un ordinateur/Les composants d'un processeur}}
{{:Fonctionnement d'un ordinateur/Le chemin de données}}
{{:Fonctionnement d'un ordinateur/L'unité de chargement et le program counter}}
{{:Fonctionnement d'un ordinateur/L'unité de contrôle}}
==Les jeux d’instructions spécialisés==
{{:Fonctionnement d'un ordinateur/Les architectures à accumulateur}}
{{:Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins}}
{{:Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire}}
{{:Fonctionnement d'un ordinateur/Les processeurs de traitement du signal}}
{{:Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement}}
==La mémoire virtuelle et la protection mémoire==
{{:Fonctionnement d'un ordinateur/L'espace d'adressage du processeur}}
{{:Fonctionnement d'un ordinateur/Le partage de l'espace d'adressage : avec et sans multiprogrammation}}
{{:Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle}}
=Les entrées-sorties et périphériques=
{{:Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques}}
{{:Fonctionnement d'un ordinateur/L'adressage des périphériques}}
{{:Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension}}
=Les mémoires de masse=
{{:Fonctionnement d'un ordinateur/Les mémoires de masse : généralités}}
{{:Fonctionnement d'un ordinateur/Les disques durs}}
{{:Fonctionnement d'un ordinateur/Les solid-state drives}}
{{:Fonctionnement d'un ordinateur/Les disques optiques}}
{{:Fonctionnement d'un ordinateur/Les technologies RAID}}
=La mémoire cache=
{{:Fonctionnement d'un ordinateur/Les mémoires cache}}
{{:Fonctionnement d'un ordinateur/Le préchargement}}
{{:Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer}}
=Le parallélisme d’instructions=
{{:Fonctionnement d'un ordinateur/Le pipeline}}
{{:Fonctionnement d'un ordinateur/Les pipelines de longueur fixe et dynamiques}}
==Les branchements et le front-end==
{{:Fonctionnement d'un ordinateur/Les exceptions précises et branchements}}
{{:Fonctionnement d'un ordinateur/La prédiction de branchement}}
{{:Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions}}
==L’exécution dans le désordre==
{{:Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions}}
{{:Fonctionnement d'un ordinateur/Les dépendances de données et l'exécution dans le désordre}}
{{:Fonctionnement d'un ordinateur/Le renommage de registres}}
{{:Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo}}
==Les accès mémoire avec un pipeline==
{{:Fonctionnement d'un ordinateur/Les unités mémoires à exécution dans l'ordre}}
{{:Fonctionnement d'un ordinateur/Les unités mémoires à exécution dans le désordre}}
{{:Fonctionnement d'un ordinateur/Le parallélisme mémoire au niveau du cache}}
==L'émission multiple==
{{:Fonctionnement d'un ordinateur/Les processeurs superscalaires}}
{{:Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC}}
{{:Fonctionnement d'un ordinateur/Les architectures dataflow}}
=Les architectures parallèles=
{{:Fonctionnement d'un ordinateur/Les architectures parallèles}}
{{:Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs}}
{{:Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading}}
{{:Fonctionnement d'un ordinateur/Les architectures à parallélisme de données}}
{{:Fonctionnement d'un ordinateur/La cohérence des caches}}
{{:Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire}}
=Annexes=
{{:Fonctionnement d'un ordinateur/Les mémoires historiques}}
{{:Fonctionnement d'un ordinateur/Le matériel réseau}}
{{:Fonctionnement d'un ordinateur/La tolérance aux pannes}}
{{:Fonctionnement d'un ordinateur/Les architectures systoliques}}
{{:Fonctionnement d'un ordinateur/Les architectures neuromorphiques}}
{{:Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires}}
{{:Fonctionnement d'un ordinateur/Les circuits réversibles}}
{{autocat}}
m38u8yxtck43id3o7ba1lslzgvimk2u
Les cartes graphiques/La mémoire unifiée et la mémoire vidéo dédiée
0
80571
744371
744054
2025-06-09T18:33:09Z
Mewtow
31375
/* Historique de la mémoire virtuelle sur les GPU */
744371
wikitext
text/x-wiki
Pour rappel, il existe deux types de cartes graphiques : les cartes dédiées et les cartes intégrées. Les '''cartes graphiques dédiées''' sont des cartes graphiques branchées sur des connecteurs/ports de la carte mère. A l'opposé, tous les processeurs modernes intègrent une carte graphique, appelée '''carte graphique intégrée''', ou encore '''IGP''' (''Integrated Graphic Processor''). En somme, les cartes dédiées sont opposées à celles intégrées dans les processeurs modernes.
La différence a un impact sur la mémoire vidéo. Les cartes graphiques dédiées ont souvent de la mémoire vidéo intégrée à la carte graphique. Il y a des exceptions, mais on en parlera plus tard. Les cartes graphiques intégrées au processeur n'ont pas de mémoire vidéo dédiée, vu qu'on ne peut pas intégrer beaucoup de mémoire vidéo dans un processeur. La conséquence est qu'il existe deux grandes manières d'organiser la mémoire à laquelle la carte graphique a accès.
* La première est celle de la '''mémoire vidéo dédiée''', à savoir que la carte graphique dispose de sa propre mémoire rien qu'à elle, séparée de la mémoire RAM de l'ordinateur. On fait alors la distinction entre ''RAM système'' et ''RAM vidéo''. Si les premières cartes graphiques n'avaient que quelques mégaoctets de RAM dédiée, elles disposent actuellement de plusieurs gigas-octets de RAM.
* A l'opposé, on trouve la '''mémoire unifiée''', avec une seule mémoire RAM est partagée entre le processeur et la carte graphique. Le terme "unifiée" sous-entend que l'on a unifié la mémoire vidéo et la mémoire système (la RAM).
[[File:Répartition de la mémoire entre RAM système et carte graphique.png|centre|vignette|upright=2.5|Répartition de la mémoire entre RAM système et carte graphique]]
Dans la grosse majorité des cas, les cartes vidéos dédiées ont une mémoire dédiée, alors que les cartes graphiques intégrées doivent utiliser la mémoire unifiée. Mais outre les cartes dédiées et intégrées, il faut aussi citer les cartes graphiques soudées sur la carte mère. Elles étaient utilisées sur les consoles de jeu vidéos assez anciennes, elles sont encore utilisées sur certains PC portables puissants, destinés aux ''gamers''. Pour ces dernières, il est possible d'utiliser aussi bien de la mémoire dédiée que de la mémoire unifiée. D'anciennes consoles de jeu avaient une carte graphique soudée sur la carte mère, qu'on peut facilement repérer à l’œil nu, avec une mémoire unifiée. C'est notamment le cas sur la Nintendo 64, pour ne citer qu'elle. D'autres avaient leur propre mémoire vidéo dédiée.
L'usage d'une carte vidéo dédiée se marie très bien avec une mémoire vidéo dédiée, mais il existe de nombreux cas où une carte vidéo dédiée est associée à de la mémoire unifiée. Comme exemple, la toute première carte graphique AGP, l'Intel 740, ne possédait pas de mémoire vidéo proprement dite, juste un simple ''framebuffer''. Tout le reste, texture comme géométrie, était placé en mémoire système et la carte graphique allait lire/écrire les données directement en mémoire RAM système ! Les performances sont généralement ridicules, pour des raisons très diverses, mais les cartes de ce type sont peu chères. Outre l'économie liée à l'absence de mémoire vidéo, les cartes graphiques de ce type sont peu puissantes, l'usage de la mémoire unifiée simplifie leur conception, etc. Par exemple, l'Intel 740 a eu un petit succès sur les ordinateurs d'entrée de gamme.
==Le partage de la mémoire unifiée==
Avec la mémoire unifiée, la quantité de mémoire système disponible pour la carte graphique est généralement réglable avec un réglage dans le BIOS. On peut ainsi choisir d'allouer 64, 128 ou 256 mégaoctets de mémoire système pour la carte vidéo, sur un ordinateur avec 4 gigaoctets de RAM. L'interprétation de ce réglage varie grandement selon les cartes mères ou l'IGP.
Pour les GPU les plus anciens, ce réglage implique que la RAM sélectionnée est réservée uniquement à la carte graphique, même si elle n'en utilise qu'une partie. La répartition entre mémoire vidéo et système est alors statique, fixée une fois pour toutes. Dans ce cas, la RAM allouée à la carte graphique est généralement petite par défaut. Les concepteurs de carte mère ne veulent pas qu'une trop quantité de RAM soit perdu et inutilisable pour les applications. Ils brident donc la carte vidéo et ne lui allouent que peu de RAM.
Heureusement, les GPU modernes sont plus souples. Ils fournissent deux réglages : une quantité de RAM minimale, totalement dédiée au GPU, et une quantité de RAM maximale que le GPU ne peut pas dépasser. Par exemple, il est possible de régler le GPU de manière à ce qu'il ait 64 mégaoctets rien que pour lui, mais qu'il puisse avoir accès à maximum 1 gigaoctet s'il en a besoin. Cela fait au total 960 mégaoctets (1024-64) qui peut être alloués au choix à la carte graphique ou au reste des programmes en cours d’exécution, selon les besoins. Il est possible d'allouer de grandes quantités de RAM au GPU, parfois la totalité de la mémoire système.
[[File:Partage de la mémoire unifiée entre CPU et GPU.png|centre|vignette|upright=2|Répartition de la mémoire entre RAM système et carte graphique]]
==Le partage de la mémoire système : la mémoire virtuelle des GPUs dédiés==
Après avoir vu la mémoire unifiée, voyons maintenant la mémoire dédiée. Intuitivement, on se dit que la carte graphique n'a accès qu'à la mémoire vidéo dédiée et ne peut pas lire de données dans la mémoire système, la RAM de l'ordinateur. Cependant, ce n'est pas le cas. Et ce pour plusieurs raisons. La raison principale est que des données doivent être copiées de la mémoire RAM vers la mémoire vidéo. Les copies en question se font souvent via ''Direct Memory Access'', ce qui fait que les GPU intègrent un contrôleur DMA dédié. Et ce contrôleur DMA lit des données en RAM système pour les copier en RAM vidéo.
La seconde raison est que les cartes graphiques intègrent presque toutes des technologies pour lire directement des données en RAM, sans forcément les copier en mémoire vidéo. Les technologies en question permettent à la carte graphique d'adresser plus de RAM qu'en a la mémoire vidéo. Par exemple, si la carte vidéo a 4 giga-octets de RAM, la carte graphique peut être capable d'en adresser 8 : 4 gigas en RAM vidéo, et 4 autres gigas en RAM système.
Les technologies de ce genre ressemblent beaucoup à la mémoire virtuelle des CPU, avec cependant quelques différences. La mémoire virtuelle permet à un processeur d'utiliser plus de RAM qu'il n'y en a d'installée dans l'ordinateur. Par exemple, elle permet au CPU de gérer 4 gigas de RAM sur un ordinateur qui n'en contient que trois, le gigaoctet de trop étant en réalité simulé par un fichier sur le disque dur. La technique est utilisée par tous les processeurs modernes. La mémoire virtuelle des GPUs dédiés fait la même chose, sauf que le surplus d'adresses n'est pas stockés sur le disque dur dans un fichier pagefile, mais est dans la RAM système. Pour le dire autrement, ces cartes dédiées peuvent utiliser la mémoire système si jamais la mémoire vidéo est pleine.
[[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]]
===La ''IO-Memory Management Unit'' (IOMMU)===
Pour que la carte graphique ait accès à la mémoire système, elle intègre un circuit appelé la '''''Graphics address remapping table''''', abrévié en GART. Cela vaut aussi bien pour les cartes graphiques utilisant le bus AGP que pour celles en PCI-Express. La GART est techniquement une une ''Memory Management Unit'' (MMU), à savoir un circuit spécialisé qui prend en charge la mémoire virtuelle. La dite MMU étant intégrée dans un périphérique d'entrée-sortie (IO), ici la carte graphique, elle est appelée une IO-MMU (''Input Output-MMU'').
Le GPU utilise la technique dite de la pagination, à savoir que l'espace d'adressage est découpée en pages de taille fixe, généralement 4 kilo-octets. La traduction des adresses virtuelles en adresses physique se fait au niveau de la page. Une adresse est coupée en deux parts : un numéro de page, et la position de la donnée dans la page. La position dans la page ne change pas lors de la traduction d'adresse, mais le numéro de page est lui traduit. Le numéro de page virtuel est remplacé par un numéro de page physique lors de la traduction.
Pour remplacer le numéro de page virtuel en numéro physique, il faut utiliser une table de translation, appelée la '''table des pages''', qui associe un numéro de page logique à un numéro de page physique. Le système d'exploitation dispose de sa table des pages, qui n'est pas accesible au GPU. Par contre, le GPU dispose d'une sorte de mini-table des pages, qui contient les associations page virtuelle-physique utiles pour traiter les commandes GPU, et rien d'autre. En clair, une sorte de sous-ensemble de la table des pages de l'OS, mais spécifique au GPU. La mini-table des pages est gérée par le pilote de périphérique, qui remplit la mini-table des pages. La mini-table des pages est mémorisée dans une mémoire intégrée au GPU, et précisément dans la MMU.
===Historique de la mémoire virtuelle sur les GPU===
La technologie existait déjà sur certaines cartes graphiques au format PCI, mais la documentation est assez rare. La carte graphique NV1 de NVIDIA, leur toute première carte graphique, disposait déjà de ce système de mémoire virtuelle. La carte graphique communiquait avec le processeur grâce à la fonctionnalité ''Direct Memory Access'' et intégrait donc un contrôleur DMA. Le ''driver'' de la carte graphique programmait le contrôleur DMA en utilisant les adresses fournies par les applications/logiciels. Et il s'agissait d'adresses virtuelles, non d'adresses physiques en mémoire RAM. Pour résoudre ce problème, le contrôleur DMA intégrait une MMU, une unité de traduction d'adresse, qui traduisait les adresses virtuelles fournies par les applications en adresse physique en mémoire système.
: Le fonctionnement de cette IOMMU est décrite dans le brevet "US5758182A : DMA controller translates virtual I/O device address received directly from application program command to physical i/o device address of I/O device on device bus", des inventeurs David S. H. Rosenthal et Curtis Priem.
[[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture du GPU NV1 de NVIDIA]]
La technologie s'est démocratisée avec le bus AGP, dont la fonctionnalité dite d'''AGP texturing'' permettait de lire ou écrire directement dans la mémoire RAM, sans passer par le processeur. D'ailleurs, la carte graphique Intel i740 n'avait pas de mémoire vidéo et se débrouillait uniquement avec la mémoire système. C'est l'AGP qui a introduit le GART, la fameuse IO-MMU mentionnée plus haut.
L'arrivée du bus PCI-Express ne changea pas la donne, si ce n'est que le bus était plus rapide, ce qui améliorait les performances. Au début, seules les cartes graphiques PCI-Express d'entrée de gamme pouvaient accéder à certaines portions de la mémoire RAM grâce à des technologies adaptées, comme le TurboCache de NVIDIA ou l'HyperMemory d'AMD. Mais la technologie s'est aujourd'hui étendue. De nos jours, toutes les cartes vidéos modernes utilisent la RAM système en plus de la mémoire vidéo, mais seulement en dernier recours, soit quand la mémoire vidéo est quasiment pleine, soit pour faciliter les échanges de données avec le processeur. C'est typiquement le pilote de la carte graphique qui décide ce qui va dans la mémoire vidéo et la mémoire système, et il fait au mieux de manière à avoir les performances optimales.
===L'espace d'adressage du CPU et du GPU===
L'espace d'adressage est l'ensemble des adresses géré par le processeur ou la carte graphique. En général, l'espace d'adressage du processeur et de la carte graphique sont séparés, mais des standards comme l''''''Heterogeneous System Architecture''''' permettent au processeur et à une carte graphique de partager le même espace d'adressage. Une adresse mémoire est alors la même que ce soit pour le processeur ou la carte graphique.
{|class="wikitable"
|-
! !! Mémoire vidéo dédiée !! Mémoire vidéo unifiée
|-
! Sans HSA
| [[File:Desktop computer bus bandwidths.svg|400px|Desktop computer bus bandwidths]]
| [[File:Integrated graphics with distinct memory allocation.svg|400px|Integrated graphics with distinct memory allocation]]
|-
! Avec HSA
| [[File:HSA-enabled virtual memory with distinct graphics card.svg|400px|HSA-enabled virtual memory with distinct graphics card]]
| [[File:HSA-enabled integrated graphics.svg|400px|HSA-enabled integrated graphics]]
|}
==Les échanges entre processeur et mémoire vidéo==
Quand on charge un niveau de jeux vidéo, on doit notamment charger la scène, les textures, et d'autres choses dans la mémoire RAM, puis les envoyer à la carte graphique. Ce processus se fait d'une manière fortement différente selon que l'on a une mémoire unifiée ou une mémoire vidéo dédiée.
===Avec la mémoire unifiée===
Avec la mémoire unifié, les échanges de données entre processeur et carte graphique sont fortement simplifiés et aucune copie n'est nécessaire. La carte vidéo peut y accéder directement, en lisant leur position initiale en RAM. Une partie de la RAM est visible seulement pour le CPU, une autre seulement pour le GPU, le reste est partagé. Les échanges de données entre CPU et GPU se font en écrivant/lisant des données dans la RAM partagée entre CPU et GPU. Pas besoin de faire de copie d'une mémoire à une autre : la donnée a juste besoin d'être placée au bon endroit. Le chargement des textures, du tampon de commandes ou d'autres données du genre, est donc très rapide, presque instantané.
Par contre, le débit de la RAM unifiée est partagé entre la carte graphique et le processeur. Alors qu'avec une mémoire dédiée, tout le débit de la mémoire vidéo aurait été dédié au GPU, le CPU ayant quant à lui accès à tout le débit de la RAM système. De plus, le partage du débit n'est pas chose facile. Les deux se marchent sur les pieds. La carte graphique doit parfois attendre que le processeur lui laisse l'accès à la RAM et inversement. Divers circuits d'arbitrage s'occupent de répartir équitablement les accès à RAM entre les deux, mais cela ne permet que d'avoir un compromis imparfait qui peut réduire les performances. Le seul moyen pour réduire la casse est d'ajouter des mémoires caches entre le GPU et la RAM, mais l'efficacité des caches est relativement limitée pour le rendu 3D.
[[File:Echanges de données entre CPU et GPU avec une mémoire unifiée.png|centre|vignette|upright=2|Échanges de données entre CPU et GPU avec une mémoire unifiée]]
Un autre défaut survient sur les cartes dédiées à mémoire unifiée, par exemple l'Intel 740. Pour lire en mémoire RAM, elles doivent passer par l'intermédiaire du bus AGP, PCI ou PCI-Express. Et ce bus est très lent, bien plus que ne le serait une mémoire vidéo normale. Aussi, les performances sont exécrables. J'insiste sur le fait que l'on parle des cartes graphiques dédiées, mais pas des cartes graphiques soudées des consoles de jeu.
D'ailleurs, de telles cartes dédiées incorporent un ''framebuffer'' directement dans la carte graphique. Il n'y a pas le choix, le VDC de la carte graphique doit accéder à une mémoire suffisamment rapide pour alimenter l'écran. Ils ne peuvent pas prendre le risque d'aller lire la RAM, dont le temps de latence est élevé, et qui peut potentiellement être réservée par le processeur pendant l’affichage d'une image à l'écran.
===Avec une mémoire vidéo dédiée===
Avec une mémoire vidéo dédiée, on doit copier les données adéquates dans la mémoire vidéo, ce qui implique des transferts de données passant par le bus PCI-Express. Le processeur voit une partie de la mémoire vidéo, dans laquelle il peut lire ou écrire comme bon lui semble. Le reste de la mémoire vidéo est invisible du point de vue du processeur, mais manipulable par le GPU à sa guise. Il est possible pour le CPU de copier des données dans la portion invisible de la mémoire vidéo, mais cela se fait de manière indirecte en passant par le GPU d'abord. Il faut typiquement envoyer une commande spéciale au GPU, pour lui dire de charger une texture en mémoire vidéo, par exemple. Le GPU effectue alors une copie de la mémoire système vers la mémoire vidéo, en utilisant un contrôleur DMA intégré au GPU.
[[File:Interaction du GPU avec la mémoire vidéo et la RAM système sur une carte graphique dédiée.png|centre|vignette|upright=2|Échanges de données entre CPU et GPU avec une mémoire vidéo dédiée]]
La gestion de la mémoire vidéo est prise en charge par le pilote de la carte graphique, sur le processeur. Elle a tendance à allouer les textures et d'autres données de grande taille dans la mémoire vidéo invisible, le reste étant placé ailleurs. Les copies DMA vers la mémoire vidéo invisible sont adaptées à des copies de grosses données comme les textures, mais elles marchent mal pour des données assez petites. Or, les jeux vidéos ont tendance à générer à la volée de nombreuses données de petite taille, qu'il faut copier en mémoire vidéo. Et c'est sans compter sur des ressources du pilote de périphériques, qui doivent être copiées en mémoire vidéo, comme le tampon de commande ou d'autres ressources. Et celles-ci ne peuvent pas forcément être copiées dans la mémoire vidéo invisible. Si la mémoire vidéo visible par le CPU est trop petite, les données précédentes sont copiées dans la mémoire visible par le CPU, en mémoire système, mais leur accès par le GPU est alors très lent. Aussi, plus la portion visible de la mémoire vidéo est grande, plus simple est la gestion de la mémoire vidéo par le pilote graphique. Et de ce point de vue, les choses ont évolué récemment.
Pour accéder à un périphérique PCI-Express, il faut configurer des registres spécialisés, appelés les ''Base Address Registers'' (BARs). La configuration des registres précise quelle portion de mémoire vidéo est adressable par le processeur, quelle est sa taille, sa position en mémoire vidéo, etc. Avant 2008, les BAR permettaient d’accéder à seulement 256 mégaoctets, pas plus. La gestion de la mémoire vidéo était alors difficile. Les échanges entre portion visible et invisible de la mémoire vidéo étaient complexes, demandaient d’exécuter des commandes spécifiques au GPU et autres. Après 2008, la spécification du PCI-Express ajouta un support de la technologie ''resizable bar'', qui permet au processeur d’accéder directement à plus de 256 mégaoctets de mémoire vidéo, voire à la totalité de la mémoire vidéo. De nombreux fabricants de cartes graphiques commencent à incorporer cette technologie, qui demande quelques changements au niveau du système d'exploitation, des pilotes de périphériques et du matériel.
{{NavChapitre | book=Les cartes graphiques
| prev=La microarchitecture des processeurs de shaders
| prevText=La microarchitecture des processeurs de shaders
| next=La hiérarchie mémoire d'un GPU
| netxText=La hiérarchie mémoire d'un GPU
}}{{autocat}}
c6gajn4cky8zg8aw0eja824wbp0em6b
Discussion Wikilivres:Le Bistro/2025
5
82063
744398
744027
2025-06-10T01:16:41Z
MediaWiki message delivery
36013
/* Actualités techniques n° 2025-24 */ nouvelle section
744398
wikitext
text/x-wiki
== Actualités techniques n° 2025-03 ==
<section begin="technews-2025-W03"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/03|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Le système de connexion utilisateur unique (SUL) va être mis à jour durant les prochains mois. Il permet aux utilisateurs et utilisatrices d’être connectés sur tous les sites en même temps après avoir renseigné leurs identifiants sur un site Wikimedia. La mise à jour est nécessaire car les navigateurs restreignent de plus en plus les témoins de connexion inter-domaines. Pour s’adapter à ces restrictions, les pages de connexion et de création de compte seront déplacées vers un domaine central, mais cela apparaitra toujours comme si vous étiez sur le wiki d’origine. Le code mis à jour sera activé cette semaine pour les utilisations sur les wikis de test. Ce changement devrait être déployé pour tous durant février et mars. Consultez [[mw:Special:MyLanguage/MediaWiki Platform Team/SUL3#Deployment|la page du projet SUL3]] pour plus d’informations et un calendrier.
'''Actualités pour la contribution'''
* Sur les wikis ayant [[mw:Special:MyLanguage/Extension:PageAssessments|PageAssessments]] (évaluation des pages) installée, vous pouvez désormais [[mw:Special:MyLanguage/Extension:PageAssessments#Search|filtrer les résultats de recherche]] aux pages dans un projet donné à l’aide du mot-clé <code dir=ltr>inproject:</code>. (Ces wikis : {{int:project-localized-name-arwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-enwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-enwikivoyage/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-frwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-huwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-newiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-trwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-zhwiki/fr}}.) [https://phabricator.wikimedia.org/T378868]
* Un nouveau wiki a été créé : une Wikipédia en [[d:Q34129|tigré]] ([[w:tig:|<code>w:tig:</code>]]) [https://phabricator.wikimedia.org/T381377]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:35|la tâche soumise|les {{formatnum:35}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:35||s}} la semaine dernière]]. Par exemple, il y avait un beugue de mise à jour du compteur de modifications de quelqu’un effectuant une annulation d’une autre modification : cela est maintenant corrigé. [https://phabricator.wikimedia.org/T382592]
'''Actualités pour la contribution technique'''
* [[File:Octicons-tools.svg|12px|link=|class=skin-invert|Sujet technique]] Les utilisateurs et utilisatrices de l’API REST de Wikimedia (par exemple pour des robots ou des outils) peuvent être impactés par des mises à jour en cours. À partir de la semaine du 13 janvier, nous commencerons à rediriger [[phab:T374683|certains points terminaux de contenu de page]] depuis RESTbase vers les nouveaux points terminaux de l’API REST de MediaWiki pour tous les projets wiki. Ce changement était disponible sur testwiki, et ne devrait pas affecter les fonctionnalités existantes, mais les utilisateurs actifs des points terminaux concernés peuvent signaler directement à l’[[phab:project/view/6931/|équipe des interfaces de MediaWiki]] tout problème qui arriverait.
* Les personnes maintenant des outils sur Toolforge peuvent désormais partager leurs retour sur Toolforge UI, un projet visant à fournir une plateforme web pour la création et la gestion d’outils Toolforge depuis une interface graphique, en plus des processus existant par ligne de commande. Ce projet vise à simplifier les tâches des mainteneurs et mainteneuses actifs, ainsi qu’à rendre l’inscription et les procédures de déploiement plus accessibles aux nouveaux et nouvelles créatrices d’outils. Le projet en est encore à ses balbutiements et l’équipe des services en infonuage recueille des retours de la communauté Toolforge pour aiderà concevoir la solution correspondant à leurs besoins. [[wikitech:Wikimedia Cloud Services team/EnhancementProposals/Toolforge UI|En savoir plus et donner son avis sur Toolforge UI]].
* [[File:Octicons-tools.svg|12px|link=|class=skin-invert|Sujet technique]] <span class="mw-translate-fuzzy">Pour le développement d’outil et bibliothèque qui utilisent le système OAuth : le point terminal d’identité utilisé pour [[mw:Special:MyLanguage/OAuth/For Developers#Identifying the user|OAuth 1]] et [[mw:Special:MyLanguage/OAuth/For Developers#Identifying the user 2|OAuth 2]] retournait un objet JSON avec un entier dans le sous-champ, ce qui était incorrect (le champ doit toujours être une chaine de caractère); Cela a été corrigé ; le correctif sera déployé sur les wikis Wikimedia la semaine du 13 janvier.</span> [https://phabricator.wikimedia.org/T382139]
* De nombreux wikis utilisent actuellement le [[:mw:Parsoid/Parser Unification/Cite CSS|CSS de Cite]] pour insérer des marqueurs de note de bas de page personnalisés dans la sortie de Parsoid. À partir du 20 janvier, ces règles seront désactivées, mais les développeurs vous demandent de ''ne pas'' nettoyer votre <bdi lang="en" dir="ltr">[[MediaWiki:Common.css]]</bdi> avant le 20 février pour éviter des problèmes pendant la migration. Vos wikis rencontreront peut-être des petits changements dans les marqueurs de notes de bas page dans l’éditeur visuel ou en utilisant le mode de lecture expérimental Parsoid, mais s’il y a des changements, ils devraient garder le rendu cohérent avec la sortie de l’analyseur classique. [https://phabricator.wikimedia.org/T370027]
'''Rencontres et évènements'''
* Les prochaines réunions de la série des [[c:Special:MyLanguage/Commons:WMF support for Commons/Commons community calls|Discussions communautaires entre Wikimedia Foundation et la communauté de Wikimedia Commons]] aura lieu le [[m:Special:MyLanguage/Event:Commons community discussion - 15 January 2025 08:00 UTC|15 janvier à 8 h UTC]] et [[m:Special:MyLanguage/Event:Commons community discussion - 15 January 2025 16:00 UTC|à 16 h UTC]]. Le sujet de cette conférence porte sur la définition des priorités d’investissement en outils pour Commons. Les contributeurs et contributrices de tous les wikis sont les bienvenus pour participer, notamment celles et ceux qui maintiennent des outils pour Commons.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/03|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W03"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 14 janvier 2025 à 02:42 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28048614 -->
== Launching! Join Us for Wiki Loves Ramadan 2025! ==
Dear All,
We’re happy to announce the launch of [[m:Wiki Loves Ramadan 2025|Wiki Loves Ramadan 2025]], an annual international campaign dedicated to celebrating and preserving Islamic cultures and history through the power of Wikipedia. As an active contributor to the Local Wikipedia, you are specially invited to participate in the launch.
This year’s campaign will be launched for you to join us write, edit, and improve articles that showcase the richness and diversity of Islamic traditions, history, and culture.
* Topic: [[m:Event:Wiki Loves Ramadan 2025 Campaign Launch|Wiki Loves Ramadan 2025 Campaign Launch]]
* When: Jan 19, 2025
* Time: 16:00 Universal Time UTC and runs throughout Ramadan (starting February 25, 2025).
* Join Zoom Meeting: https://us02web.zoom.us/j/88420056597?pwd=NdrpqIhrwAVPeWB8FNb258n7qngqqo.1
* Zoom meeting hosted by [[m:Wikimedia Bangladesh|Wikimedia Bangladesh]]
To get started, visit the [[m:Wiki Loves Ramadan 2025|campaign page]] for details, resources, and guidelines: Wiki Loves Ramadan 2025.
Add [[m:Wiki Loves Ramadan 2025/Participant|your community here]], and organized Wiki Loves Ramadan 2025 in your local language.
Whether you’re a first-time editor or an experienced Wikipedian, your contributions matter. Together, we can ensure Islamic cultures and traditions are well-represented and accessible to all.
Feel free to invite your community and friends too. Kindly reach out if you have any questions or need support as you prepare to participate.
Let’s make Wiki Loves Ramadan 2025 a success!
For the [[m:Wiki Loves Ramadan 2025/Team|International Team]] 16 janvier 2025 à 13:08 (CET)
<!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=27568454 -->
== Actualités techniques n° 2025-04 ==
<section begin="technews-2025-W04"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/04|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* <span lang="en" dir="ltr" class="mw-content-ltr">Administrators can mass-delete multiple pages created by a user or IP address using [[mw:Special:MyLanguage/Extension:Nuke|Extension:Nuke]]. It previously only allowed deletion of pages created in the last 30 days. It can now delete pages from the last 90 days, provided it is targeting a specific user or IP address.</span> [https://phabricator.wikimedia.org/T380846]
* <span lang="en" dir="ltr" class="mw-content-ltr">On [[phab:P72148|wikis that use]] the [[mw:Special:MyLanguage/Help:Patrolled edits|Patrolled edits]] feature, when the rollback feature is used to revert an unpatrolled page revision, that revision will now be marked as "manually patrolled" instead of "autopatrolled", which is more accurate. Some editors that use [[mw:Special:MyLanguage/Help:New filters for edit review/Filtering|filters]] on Recent Changes may need to update their filter settings.</span> [https://phabricator.wikimedia.org/T302140]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:31|la tâche soumise|les {{formatnum:31}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:31||s}} la semaine dernière]]. <span lang="en" dir="ltr" class="mw-content-ltr">For example, the Visual Editor's "Insert link" feature did not always suggest existing pages properly when an editor started typing, which has now been [[phab:T383497|fixed]].</span>
'''Actualités pour la contribution technique'''
* <span lang="en" dir="ltr" class="mw-content-ltr">The Structured Discussion extension (also known as Flow) is being progressively removed from the wikis. This extension is unmaintained and causes issues. It will be replaced by [[mw:Special:MyLanguage/Help:DiscussionTools|DiscussionTools]], which is used on any regular talk page. [[mw:Special:MyLanguage/Structured Discussions/Deprecation#Deprecation timeline|The last group of wikis]] ({{int:project-localized-name-cawikiquote/en}}{{int:comma-separator/en}}{{int:project-localized-name-fiwikimedia/en}}{{int:comma-separator/en}}{{int:project-localized-name-gomwiki/en}}{{int:comma-separator/en}}{{int:project-localized-name-kabwiki/en}}{{int:comma-separator/en}}{{int:project-localized-name-ptwikibooks/en}}{{int:comma-separator/en}}{{int:project-localized-name-sewikimedia/en}}) will soon be contacted. If you have questions about this process, please ping [[m:User:Trizek (WMF)|Trizek (WMF)]] at your wiki.</span> [https://phabricator.wikimedia.org/T380912]
* <span lang="en" dir="ltr" class="mw-content-ltr">The latest quarterly [[mw:Technical_Community_Newsletter/2025/January|Technical Community Newsletter]] is now available. This edition includes: updates about services from the Data Platform Engineering teams, information about Codex from the Design System team, and more.</span>
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/04|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W04"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 21 janvier 2025 à 02:36 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28129769 -->
== Universal Code of Conduct annual review: provide your comments on the UCoC and Enforcement Guidelines ==
<div lang="en" dir="ltr" class="mw-content-ltr">
My apologies for writing in English.
{{Int:Please-translate}}.
I am writing to you to let you know the annual review period for the Universal Code of Conduct and Enforcement Guidelines is open now. You can make suggestions for changes through 3 February 2025. This is the first step of several to be taken for the annual review.
[[m:Special:MyLanguage/Universal_Code_of_Conduct/Annual_review|Read more information and find a conversation to join on the UCoC page on Meta]].
The [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee]] (U4C) is a global group dedicated to providing an equitable and consistent implementation of the UCoC. This annual review was planned and implemented by the U4C. For more information and the responsibilities of the U4C, [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Charter|you may review the U4C Charter]].
Please share this information with other members in your community wherever else might be appropriate.
-- In cooperation with the U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 24 janvier 2025 à 02:10 (CET)
</div>
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=27746256 -->
== Actualités techniques n° 2025-05 ==
<section begin="technews-2025-W05"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/05|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* <span lang="en" dir="ltr" class="mw-content-ltr">Patrollers and admins - what information or context about edits or users could help you to make patroller or admin decisions more quickly or easily? The Wikimedia Foundation wants to hear from you to help guide its upcoming annual plan. Please consider sharing your thoughts on this and [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Product & Technology OKRs|13 other questions]] to shape the technical direction for next year.</span>
'''Actualités pour la contribution'''
* <span lang="en" dir="ltr" class="mw-content-ltr">iOS Wikipedia App users worldwide can now access a [[mw:Special:MyLanguage/Wikimedia Apps/Team/iOS/Personalized Wikipedia Year in Review/How your data is used|personalized Year in Review]] feature, which provides insights based on their reading and editing history on Wikipedia. This project is part of a broader effort to help welcome new readers as they discover and interact with encyclopedic content.</span>
* [[File:Octicons-gift.svg|12px|link=|class=skin-invert|Concerne un souhait]] <span lang="en" dir="ltr" class="mw-content-ltr">Edit patrollers now have a new feature available that can highlight potentially problematic new pages. When a page is created with the same title as a page which was previously deleted, a tag ('Recreated') will now be added, which users can filter for in [[{{#special:RecentChanges}}]] and [[{{#special:NewPages}}]].</span> [https://phabricator.wikimedia.org/T56145]
* <span lang="en" dir="ltr" class="mw-content-ltr">Later this week, there will be a new warning for editors if they attempt to create a redirect that links to another redirect (a [[mw:Special:MyLanguage/Help:Redirects#Double redirects|double redirect]]). The feature will recommend that they link directly to the second redirect's target page. Thanks to the user SomeRandomDeveloper for this improvement.</span> [https://phabricator.wikimedia.org/T326056]
* [[File:Octicons-tools.svg|12px|link=|class=skin-invert|Sujet technique]] <span lang="en" dir="ltr" class="mw-content-ltr">Wikimedia wikis allow [[w:en:WebAuthn|WebAuthn]]-based second factor checks (such as hardware tokens) during login, but the feature is [[m:Community Wishlist Survey 2023/Miscellaneous/Fix security key (WebAuthn) support|fragile]] and has very few users. The MediaWiki Platform team is temporarily disabling adding new WebAuthn keys, to avoid interfering with the rollout of [[mw:MediaWiki Platform Team/SUL3|SUL3]] (single user login version 3). Existing keys are unaffected.</span> [https://phabricator.wikimedia.org/T378402]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* <span lang="en" dir="ltr" class="mw-content-ltr">For developers that use the [[wikitech:Data Platform/Data Lake/Edits/MediaWiki history dumps|MediaWiki History dumps]]: The Data Platform Engineering team has added a couple of new fields to these dumps, to support the [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts|Temporary Accounts]] initiative. If you maintain software that reads those dumps, please review your code and the updated documentation, since the order of the fields in the row will change. There will also be one field rename: in the <bdi lang="zxx" dir="ltr"><code>mediawiki_user_history</code></bdi> dump, the <bdi lang="zxx" dir="ltr"><code>anonymous</code></bdi> field will be renamed to <bdi lang="zxx" dir="ltr"><code>is_anonymous</code></bdi>. The changes will take effect with the next release of the dumps in February.</span> [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/LKMFDS62TXGDN6L56F4ABXYLN7CSCQDI/]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/05|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W05"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 janvier 2025 à 23:14 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28149374 -->
== Reminder: first part of the annual UCoC review closes soon ==
<div lang="en" dir="ltr" class="mw-content-ltr">
My apologies for writing in English.
{{Int:Please-translate}}.
This is a reminder that the first phase of the annual review period for the Universal Code of Conduct and Enforcement Guidelines will be closing soon. You can make suggestions for changes through [[d:Q614092|the end of day]], 3 February 2025. This is the first step of several to be taken for the annual review.
[[m:Special:MyLanguage/Universal_Code_of_Conduct/Annual_review|Read more information and find a conversation to join on the UCoC page on Meta]]. After review of the feedback, proposals for updated text will be published on Meta in March for another round of community review.
Please share this information with other members in your community wherever else might be appropriate.
-- In cooperation with the U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 3 février 2025 à 01:48 (CET)
</div>
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28198931 -->
== <span lang="en" dir="ltr">Tech News: 2025-06</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2025-W06"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2025/06|Translations]] are available.
'''Updates for editors'''
* Editors who use the "Special characters" editing-toolbar menu can now see the 32 special characters you have used most recently, across editing sessions on that wiki. This change should help make it easier to find the characters you use most often. The feature is in both the 2010 wikitext editor and VisualEditor. [https://phabricator.wikimedia.org/T110722]
* Editors using the 2010 wikitext editor can now create sublists with correct indentation by selecting the line(s) you want to indent and then clicking the toolbar buttons.[https://phabricator.wikimedia.org/T380438] You can now also insert <code><nowiki><code></nowiki></code> tags using a new toolbar button.[https://phabricator.wikimedia.org/T383010] Thanks to user stjn for these improvements.
* Help is needed to ensure the [[mw:Special:MyLanguage/Citoid/Enabling Citoid on your wiki|citation generator]] works properly on each wiki.
** (1) Administrators should update the local versions of the page <code dir=ltr>MediaWiki:Citoid-template-type-map.json</code> to include entries for <code dir=ltr>preprint</code>, <code dir=ltr>standard</code>, and <code dir=ltr>dataset</code>; Here are example diffs to replicate [https://en.wikipedia.org/w/index.php?title=MediaWiki%3ACitoid-template-type-map.json&diff=1189164774&oldid=1165783565 for 'preprint'] and [https://en.wikipedia.org/w/index.php?title=MediaWiki%3ACitoid-template-type-map.json&diff=1270832208&oldid=1270828390 for 'standard' and 'dataset'].
** (2.1) If the citoid map in the citation template used for these types of references is missing, [[mediawikiwiki:Citoid/Enabling Citoid on your wiki#Step 2.a: Create a 'citoid' maps value for each citation template|one will need to be added]]. (2.2) If the citoid map does exist, the TemplateData will need to be updated to include new field names. Here are example updates [https://en.wikipedia.org/w/index.php?title=Template%3ACitation%2Fdoc&diff=1270829051&oldid=1262470053 for 'preprint'] and [https://en.wikipedia.org/w/index.php?title=Template%3ACitation%2Fdoc&diff=1270831369&oldid=1270829480 for 'standard' and 'dataset']. The new fields that may need to be supported are <code dir=ltr>archiveID</code>, <code dir=ltr>identifier</code>, <code dir=ltr>repository</code>, <code dir=ltr>organization</code>, <code dir=ltr>repositoryLocation</code>, <code dir=ltr>committee</code>, and <code dir=ltr>versionNumber</code>. [https://phabricator.wikimedia.org/T383666]
* One new wiki has been created: a {{int:project-localized-name-group-wikipedia/en}} in [[d:Q15637215|Central Kanuri]] ([[w:knc:|<code>w:knc:</code>]]) [https://phabricator.wikimedia.org/T385181]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:27}} community-submitted {{PLURAL:27|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the [[mediawikiwiki:Special:MyLanguage/Help:Extension:Wikisource/Wikimedia OCR|OCR (optical character recognition) tool]] used for Wikisource now supports a new language, Church Slavonic. [https://phabricator.wikimedia.org/T384782]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2025/06|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2025-W06"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 4 février 2025 à 01:08 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28203495 -->
== <span lang="en" dir="ltr">Tech News: 2025-07</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2025-W07"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2025/07|Translations]] are available.
'''Weekly highlight'''
* The Product and Technology Advisory Council (PTAC) has published [[m:Special:MyLanguage/Product and Technology Advisory Council/February 2025 draft PTAC recommendation for feedback|a draft of their recommendations]] for the Wikimedia Foundation's Product and Technology department. They have recommended focusing on [[m:Special:MyLanguage/Product and Technology Advisory Council/February 2025 draft PTAC recommendation for feedback/Mobile experiences|mobile experiences]], particularly contributions. They request community [[m:Talk:Product and Technology Advisory Council/February 2025 draft PTAC recommendation for feedback|feedback at the talk page]] by 21 February.
'''Updates for editors'''
* The "Special pages" portlet link will be moved from the "Toolbox" into the "Navigation" section of the main menu's sidebar by default. This change is because the Toolbox is intended for tools relating to the current page, not tools relating to the site, so the link will be more logically and consistently located. To modify this behavior and update CSS styling, administrators can follow the instructions at [[phab:T385346|T385346]]. [https://phabricator.wikimedia.org/T333211]
* As part of this year's work around improving the ways readers discover content on the wikis, the Web team will be running an experiment with a small number of readers that displays some suggestions for related or interesting articles within the search bar. Please check out [[mw:Special:MyLanguage/Reading/Web/Content Discovery Experiments#Experiment 1: Display article recommendations in more prominent locations, search|the project page]] for more information.
* [[File:Octicons-tools.svg|12px|link=|class=skin-invert|Advanced item]] Template editors who use TemplateStyles can now customize output for users with specific accessibility needs by using accessibility related media queries (<code dir=ltr>[https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion prefers-reduced-motion]</code>, <code dir=ltr>[https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-transparency prefers-reduced-transparency]</code>, <code dir=ltr>[https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast prefers-contrast]</code>, and <code dir=ltr>[https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors forced-colors]</code>). Thanks to user Bawolff for these improvements. [https://phabricator.wikimedia.org/T384175]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:22}} community-submitted {{PLURAL:22|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the global blocks log will now be shown directly on the {{#special:CentralAuth}} page, similarly to global locks, to simplify the workflows for stewards. [https://phabricator.wikimedia.org/T377024]
'''Updates for technical contributors'''
* Wikidata [[d:Special:MyLanguage/Help:Default values for labels and aliases|now supports a special language as a "default for all languages"]] for labels and aliases. This is to avoid excessive duplication of the same information across many languages. If your Wikidata queries use labels, you may need to update them as some existing labels are getting removed. [https://phabricator.wikimedia.org/T312511]
* The function <code dir="ltr">getDescription</code> was invoked on every Wiki page read and accounts for ~2.5% of a page's total load time. The calculated value will now be cached, reducing load on Wikimedia servers. [https://phabricator.wikimedia.org/T383660]
* As part of the RESTBase deprecation [[mw:RESTBase/deprecation|effort]], the <code dir="ltr">/page/related</code> endpoint has been blocked as of February 6, 2025, and will be removed soon. This timeline was chosen to align with the deprecation schedules for older Android and iOS versions. The stable alternative is the "<code dir="ltr">morelike</code>" action API in MediaWiki, and [[gerrit:c/mediawiki/services/mobileapps/+/982154/13/pagelib/src/transform/FooterReadMore.js|a migration example]] is available. The MediaWiki Interfaces team [[phab:T376297|can be contacted]] for any questions. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/GFC2IJO7L4BWO3YTM7C5HF4MCCBE2RJ2/]
'''In depth'''
* The latest quarterly [[mw:Special:MyLanguage/Wikimedia Language and Product Localization/Newsletter/2025/January|Language and Internationalization newsletter]] is available. It includes: Updates about the "Contribute" menu; details on some of the newest language editions of Wikipedia; details on new languages supported by the MediaWiki interface; updates on the Community-defined lists feature; and more.
* The latest [[mw:Extension:Chart/Project/Updates#January 2025: Better visibility into charts and tabular data usage|Chart Project newsletter]] is available. It includes updates on the progress towards bringing better visibility into global charts usage and support for categorizing pages in the Data namespace on Commons.
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2025/07|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2025-W07"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 11 février 2025 à 01:11 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28231022 -->
== Actualités techniques n° 2025-08 ==
<section begin="technews-2025-W08"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/08|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Les communautés utilisant les outils de croissance peuvent désormais mettre en avant un évènement pour les nouveaux contributeurs sur la <code>{{#special:Homepage}}</code>. Cette fonctionnalité aidera les nouveaux venus à être informés des activités d'édition auxquels ils peuvent participer. Les administrateurs peuvent ajouter un nouvel évènement à mettre en avant sur <code>{{#special:CommunityConfiguration}}</code>. Pour en apprendre davantage sur cette nouvelle fonctionnalité, vous pouvez lire [[diffblog:2025/02/12/community-updates-module-connecting-newcomers-to-your-initiatives/|l'annonce sur Diff]], la [[mw:Special:MyLanguage/Help:Growth/Tools/Community updates module|documentation]] ou [[mw:Talk:Growth|contacter l'équipe Croissance]].
'''Actualités pour la contribution'''
[[File:Page Frame Features on desktop.png|thumb|Mise en évidence des améliorations aux pages de discussion]]
* À partir de la semaine prochaine, les pages de discussions des wikis suivants recevront [[diffblog:2024/05/02/making-talk-pages-better-for-everyone/|une nouvelle présentation]] : {{int:project-localized-name-eswiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-frwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-itwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-jawiki/fr}}. Ce changement a été largement testé en tant que fonctionnalité beta et il s'agit de la dernière étape des [[mw:Special:MyLanguage/Talk pages project/Feature summary|améliorations aux pages de discussion]]. [https://phabricator.wikimedia.org/T379102]
* Vous pouvez désormais visualiser une page de redirection directement depuis ses pages d'action, comme la page d'historique. Auparavant, vous étiez automatiquement redirigé vers la page cible et deviez manuellement revenir à la page de redirection. Ce changement devrait aider les rédacteurs travaillant avec les redirections. Merci à stjn pour cette amélioration. [https://phabricator.wikimedia.org/T5324]
* Quand une référence est utilisée de nombreuses fois, les wikis affichent actuellement des nombres comme 1.23 ou des marqueurs avec des lettres comme a, b, c dans la liste de références. Avant, quand le nombre de références était trop important et que toutes les lettres avaient été utilisées, un [[MediaWiki:Cite error references no backlink label|message d'erreur]] était affiché. Dans le cadre des travaux pour la [[phab:T383036|modernisation de la personnalisation des références]], ces erreurs ne seront plus affichées et des marqueurs numériques comme 1.27 seront utilisés par défaut après épuisement des marqueurs alphabétiques.
* Les entrées de journal pour chaque changement aux groupes utilisateur d'un éditeur ont été clarifiés pour indiquer exactement ce qui a été modifié. Elles contenaient auparavant les deux listes des groupes avant et après le changement. Les traducteurs sont invités à [[phab:T369466|aider à traduire les messages système associés]]. Merci à Msz2001 pour ces améliorations.
* Un nouveau filtre a été ajouté à [[{{#special:Nuke}}]] — outil permettant aux administrateurs de supprimer en masse des pages — pour permettre aux utilisateurs de filtrer les pages en fonction de leur taille en octets. Cela permet par exemple de supprimer uniquement des pages inférieures à une certaine taille. [https://phabricator.wikimedia.org/T378488]
* Les non-administrateurs peuvent maintenant voir quelles pages peuvent être supprimées à l'aide de [[{{#special:Nuke}}]]. Merci à MolecularPilot pour cette amélioration et les précédentes. [https://phabricator.wikimedia.org/T376378]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:25|la tâche soumise|les {{formatnum:25}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:25||s}} la semaine dernière]]. Par exemple, un bug a été corrigé dans la configuration du format de fichier vidéo AV1, ce qui permet à ces fichiers d'être lus à nouveau. [https://phabricator.wikimedia.org/T382193]
'''Actualités pour la contribution technique'''
* Parsoid Read Views sera déployé sur la plupart des Wiktionnaires dans les prochaines semaines, à la suite de la transition avec succès des Wikivoyage à Parsoid l'année dernière. Pour davantage d'informations, voir la page du projet [[mw:Special:MyLanguage/Parsoid/Parser Unification|Parsoid/Parser Unification]]. [https://phabricator.wikimedia.org/T385923][https://phabricator.wikimedia.org/T371640]
* Les développeurs d'outils sur wiki sont informés que <code dir=ltr>mw.Uri</code> est obsolète. Les outils nécessitant <code dir=ltr>mw.Uri</code> doivent déclarer explicitement <code dir=ltr>mediawiki.Uri</code> comme une dépendance de ResourceLoader, et devraient migrer vers l'API <code dir=ltr>URL</code> native du navigateur prochainement. [https://phabricator.wikimedia.org/T384515]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/08|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W08"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 17 février 2025 à 22:16 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28275610 -->
== <span lang="en" dir="ltr"> Upcoming Language Community Meeting (Feb 28th, 14:00 UTC) and Newsletter</span> ==
<div lang="en" dir="ltr">
<section begin="message"/>
Hello everyone!
[[File:WP20Symbols WIKI INCUBATOR.svg|right|frameless|150x150px|alt=An image symbolising multiple languages]]
We’re excited to announce that the next '''Language Community Meeting''' is happening soon, '''February 28th at 14:00 UTC'''! If you’d like to join, simply sign up on the '''[[mw:Wikimedia_Language_and_Product_Localization/Community_meetings#28_February_2025|wiki page]]'''.
This is a participant-driven meeting where we share updates on language-related projects, discuss technical challenges in language wikis, and collaborate on solutions. In our last meeting, we covered topics like developing language keyboards, creating the Moore Wikipedia, and updates from the language support track at Wiki Indaba.
'''Got a topic to share?''' Whether it’s a technical update from your project, a challenge you need help with, or a request for interpretation support, we’d love to hear from you! Feel free to '''reply to this message''' or add agenda items to the document '''[[etherpad:p/language-community-meeting-feb-2025|here]]'''.
Also, we wanted to highlight that the sixth edition of the Language & Internationalization newsletter (January 2025) is available here: [[:mw:Special:MyLanguage/Wikimedia Language and Product Localization/Newsletter/2025/January|Wikimedia Language and Product Localization/Newsletter/2025/January]]. This newsletter provides updates from the October–December 2024 quarter on new feature development, improvements in various language-related technical projects and support efforts, details about community meetings, and ideas for contributing to projects. To stay updated, you can subscribe to the newsletter on its wiki page: [[:mw:Wikimedia Language and Product Localization/Newsletter|Wikimedia Language and Product Localization/Newsletter]].
We look forward to your ideas and participation at the language community meeting, see you there!
<section end="message"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 22 février 2025 à 09:28 (CET)
<!-- Message envoyé par User:SSethi (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28217779 -->
== Actualités techniques n° 2025-09 ==
<section begin="technews-2025-W09"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/09|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Les administrateurs peuvent désormais personnaliser la manière dont les catégories de [[m:Special:MyLanguage/User language|Babel]] sont créées en utilisant [[{{#special:CommunityConfiguration/Babel}}]]. Ils peuvent renommer les catégories de langues, choisir si elles doivent être créées automatiquement et ajuster d'autres paramètres. [https://phabricator.wikimedia.org/T374348]
* Le portail <bdi lang="en" dir="ltr">[https://www.wikimedia.org/ wikimedia.org]</bdi> a été mis à jour pour moderniser et améliorer l'accessibilité de nos pages de portail. Il dispose désormais d'un meilleur support pour les mises en page mobiles, de meilleures formulations et liens et d'un support linguistique amélioré. De plus, tous les portails du projet Wikimedia, comme <bdi lang="en" dir="ltr">[https://wikibooks.org wikibooks.org]</bdi>, prennent maintenant en charge le mode sombre lorsqu'un lecteur utilise ce paramètre système. [https://phabricator.wikimedia.org/T373204][https://phabricator.wikimedia.org/T368221][https://meta.wikimedia.org/wiki/Project_portals]
* Un nouveau wiki a été créé : un {{int:project-localized-name-group-wiktionary/fr}} en [[d:Q33965|Santali]] ([[wikt:sat:|<code>wikt:sat:</code>]]) [https://phabricator.wikimedia.org/T386619]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, un bogue qui empêchait de cliquer sur les résultats de recherche de l'interface web sur certaines configurations mobiles avec Firefox a été corrigé. [https://phabricator.wikimedia.org/T381289]
'''Rencontres et évènements'''
* La prochaine rencontre de la communauté linguistique aura lieu le 28 février à [https://zonestamp.toolforge.org/1740751200 14:00 UTC]. La rencontre de cette semaine couvrira : les points importants et mises-à-jour techniques pour les langues samis, les contributions à translatewiki.net de la part de la communauté Bahasa Lampung en Indonésie et une FAQ technique. Si vous souhaitez participer, inscrivez-vous sur la [[mw:Wikimedia Language and Product Localization/Community meetings#28 February 2025|page wiki]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/09|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W09"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 25 février 2025 à 01:41 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28296129 -->
== Actualités techniques n° 2025-10 ==
<section begin="technews-2025-W10"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/10|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Les utilisateurs et utilisatrices connectés utilisant l’affichage mobile peuvent désormais modifier une page complète. Le lien « {{int:Minerva-page-actions-editfull}} » est accessible dans le menu « {{int:minerva-page-actions-overflow}} » de la barre d’outils. Ce lien était auparavant disponible uniquement lorsque le [[mw:Special:MyLanguage/Reading/Web/Advanced mobile contributions|mode avancé]] était activé. [https://phabricator.wikimedia.org/T387180]
* Les admins d’interface peuvent désormais retirer les utilisations de la classe CSS « <code dir="ltr">mw-ref</code> » de leur <bdi lang="en" dir="ltr">[[MediaWiki:Common.css]]</bdi> local. Cette classe de l’extension <span lang="en">Cite</span> est obsolète. La liste des wikis l’utilisant peut être trouvée par [https://global-search.toolforge.org/?q=mw-ref%5B%5E-a-z%5D®ex=1&namespaces=8&title=.*css cette recherche globale] et dans [https://ace.wikipedia.org/w/index.php?title=MediaWiki:Common.css&oldid=145662#L-139--L-144 cet exemple]. D’autres informations sur les manières d’aider sont données sur la [[mw:Parsoid/Parser Unification/Cite CSS|page du projet de migration du CSS]]. Les appels de note (<code dir="ltr">[1]</code>) sont désormais rendus par [[mw:Special:MyLanguage/Parsoid|Parsoid]] ; le CSS obsolète n’est plus nécessaire. Le CSS pour les rétroliens « <code dir="ltr">mw:referencedBy</code> » doit rester en place pour le moment. Ce nettoyage ne devrait pas avoir d’effet visible pour les lecteurs et lectrices. Merci d’aider à retirer ce code avant le 30 mars, après quoi l’équipe de développement le fera pour vous.
* Lorsque les contributeurs ajoutent un fichier (par exemple <code><nowiki>[[File:MediaWiki.png]]</nowiki></code>) sur une page protégée par une protection en cascade, le logiciel ne restreindra plus les modifications à la page de description du fichier, mais uniquement aux nouveaux téléchargements de fichiers. [https://phabricator.wikimedia.org/T24521] A l’inverse, la transclusion d’une page de description de fichier (par exemple <code><nowiki>{{:File:MediaWiki.png}}</nowiki></code>) dans une page protégée en cascade provoquera désormais une restriction des modifications à la page.[https://phabricator.wikimedia.org/T62109]
* Remettre un fichier dans une version antérieure nécessitera désormais les mêmes autorisations que le téléchargement d'une nouvelle version du fichier. Le logiciel vérifie désormais la possession des droits « <i lang="en">reupload</i> » ou « <i lang="en">reuplod-own</i> » [https://phabricator.wikimedia.org/T304474], et respecte la protection en cascade. [https://phabricator.wikimedia.org/T140010]
* Lorsque les admins listent des pages à supprimer avec l’outil Nuke, ils peuvent désormais également répertorier les pages de discussion associées et les redirections à supprimer, en plus des pages créées par la cible, plutôt que de devoir supprimer manuellement ces pages. [https://phabricator.wikimedia.org/T95797]
* La mise à jour [[m:Special:MyLanguage/Tech/News/2025/03|mentionnée précédemment]] de la connexion utilisateur unifiée, qui prendra en compte les restrictions du navigateur sur les cookies inter-domaines en déplaçant la connexion et la création de compte vers un domaine central, sera déployée pour tous les utilisateurs et utilisatrices en mars et avril. L'équipe prévoit de l'activer pour toutes les nouvelles créations de compte sur les wikis du [[wikitech:Deployments/Train#Tuesday|groupe 0]] cette semaine. Consultez la [[mw:Special:MyLanguage/MediaWiki Platform Team/SUL3#Deployment|page du projet SUL3]] pour plus de détails et un calendrier mis à jour.
* Depuis la semaine dernière, un bogue cause l'affichage de certaines icônes d'interface sous forme de carrés noirs jusqu'à ce que la page soit entièrement chargée. Cela sera corrigé cette semaine. [https://phabricator.wikimedia.org/T387351]
* Un nouveau wiki a été créé : une {{int:project-localized-name-group-wikipedia/fr}} en [[d:Q2044560|sylheti]] ([[w:syl:|<code>w:syl:</code>]]) [https://phabricator.wikimedia.org/T386441]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]]. Par exemple, un bogue avec le chargement d'images dans de très anciennes versions du navigateur Firefox sur mobile a été corrigé. [https://phabricator.wikimedia.org/T386400]
'''Actualités pour la contribution technique'''
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.44/wmf.19|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/10|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W10"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 4 mars 2025 à 03:30 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28334563 -->
== Universal Code of Conduct annual review: proposed changes are available for comment ==
<div lang="en" dir="ltr" class="mw-content-ltr">
My apologies for writing in English.
{{Int:Please-translate}}.
I am writing to you to let you know that [[m:Special:MyLanguage/Universal_Code_of_Conduct/Annual_review/Proposed_Changes|proposed changes]] to the [[foundation:Special:MyLanguage/Policy:Universal_Code_of_Conduct/Enforcement_guidelines|Universal Code of Conduct (UCoC) Enforcement Guidelines]] and [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Charter|Universal Code of Conduct Coordinating Committee (U4C) Charter]] are open for review. '''[[m:Special:MyLanguage/Universal_Code_of_Conduct/Annual_review/Proposed_Changes|You can provide feedback on suggested changes]]''' through the [[d:Q614092|end of day]] on Tuesday, 18 March 2025. This is the second step in the annual review process, the final step will be community voting on the proposed changes.
[[m:Special:MyLanguage/Universal_Code_of_Conduct/Annual_review|Read more information and find relevant links about the process on the UCoC annual review page on Meta]].
The [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee]] (U4C) is a global group dedicated to providing an equitable and consistent implementation of the UCoC. This annual review was planned and implemented by the U4C. For more information and the responsibilities of the U4C, [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Charter|you may review the U4C Charter]].
Please share this information with other members in your community wherever else might be appropriate.
-- In cooperation with the U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] 7 mars 2025 à 19:50 (CET)
</div>
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28307738 -->
== Actualités techniques n° 2025-11 ==
<section begin="technews-2025-W11"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/11|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Les contributeurs qui utilisent des gestionnaires de mots de passe sur plusieurs wikis peuvent remarquer des changements à l’avenir. La manière dont nos wikis fournissent des informations aux gestionnaires de mots de passe sur la réutilisation des mots de passe entre les domaines a été récemment mise à jour, de sorte que certains gestionnaires de mots de passe peuvent maintenant vous proposer des identifiants de connexion que vous avez sauvegardés pour un autre site Wikimedia. Certains gestionnaires de mots de passe l'ont déjà fait, et le font maintenant pour d'autres domaines de Wikimedia. Cela fait partie du projet [[mw:Special:MyLanguage/MediaWiki Platform Team/SUL3|SUL3]] qui vise à améliorer le fonctionnement de notre connexion unifiée et à la rendre compatible avec les changements en cours dans les navigateurs web que nous utilisons. [https://phabricator.wikimedia.org/T385520][https://phabricator.wikimedia.org/T384844]
* L'équipe des applications Wikipédia invite les utilisateurs intéressés à contribuer à l'amélioration de l'utilisation de Wikipédia hors ligne ou en internet limité. Après les discussions de [[m:Afrika Baraza|Afrika Baraza]] et la dernière [[m:Special:MyLanguage/ESEAP Hub/Meetings|conférence ESEAP]], des défis clés comme la recherche, la modification et l'accès hors ligne sont explorés, avec des groupes de discussion à venir pour approfondir ces sujets. Toutes les langues sont les bienvenues et des interprètes seront disponibles. Vous souhaitez partager vos idées ? [[mw:Special:MyLanguage/Wikimedia Apps/Improving Wikipedia Mobile Apps for Offline & Limited Internet Use|Participez à la discussion]] ou envoyez un courriel à <bdi lang="en" dir="ltr">aramadan@wikimedia.org</bdi> !
* Tous les wikis seront en lecture seule pendant quelques minutes le 19 mars, à [https://zonestamp.toolforge.org/1742392800 14 h UTC]. De plus amples informations seront publiées dans les ''Actualités techniques'' et seront également publiées sur chaque wiki dans les semaines à venir.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.44/wmf.20|MediaWiki]]
'''En détails'''
* La dernière [[mw:Special:MyLanguage/Growth/Newsletters/33|infolettre trimestrielle du département Croissance]] est disponible. Elle présente : le lancement du module Actualités de la communauté, les dernières modifications de la configuration communautaire et le test à venir des suggestions d'articles pour les personnes contribuant pour la première fois.
* Une ancienne API utilisée dans l'application Android Wikipedia sera supprimée à la fin du mois de mars. Il n'y a pas d'utilisation logicielle en cours, mais les utilisateurs de l'application dont la version date de plus de 6 mois au moment de la suppression (2025-03-31), n'auront plus accès à la fonction Suggested Edits, jusqu'à ce qu'ils mettent à jour leur application. Vous pouvez [[diffblog:2025/02/24/sunset-of-wikimedia-recommendation-api/|lire plus de détails sur ce changement]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/11|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W11"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 11 mars 2025 à 00:09 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28372257 -->
== Votre wiki sera bientôt en lecture seule ==
<section begin="server-switch"/><div class="plainlinks">
[[:m:Special:MyLanguage/Tech/Server switch|Lire ce message dans une autre langue]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-Tech%2FServer+switch&language=&action=page&filter= {{int:please-translate}}]
La [[foundation:|Fondation Wikimedia]] va basculer le trafic entre ses centres de données. Cela permettra de s’assurer que Wikipédia et les autres wikis de Wikimedia peuvent rester en ligne même après une catastrophe.
Le trafic sera basculé le '''{{#time:j xg|2025-03-19|fr}}'''. La bascule débutera à '''[https://zonestamp.toolforge.org/{{#time:U|2025-03-19T14:00|en}} {{#time:H:i e|2025-03-19T14:00}}]'''.
Malheureusement, en raison de certaines limites de [[mw:Special:MyLanguage/Manual:What is MediaWiki?|MediaWiki]], toutes les modifications de pages devront être arrêtées durant le passage d’un centre de données à l’autre. Nous nous excusons pour ce dérangement, que nous nous efforçons de réduire pour le futur.
Une bannière sera affichée sur tous les wikis 30 minutes avant le début de l’opération. Cette bannière restera visible jusqu’à la fin de l’opération.
'''Pendant une courte période, vous pourrez lire les wikis mais pas les modifier.'''
*Vous ne pourrez pas effectuer de modification pendant une durée pouvant aller jusqu’à une heure, le {{#time:l j xg Y|2025-03-19|fr}}.
*Si vous essayez de faire une modification ou de sauvegarder pendant cette période, vous verrez un message d’erreur. Nous espérons qu’aucune modification ne sera perdue durant ce temps, mais nous ne pouvons le garantir. Si vous voyez un message d’erreur, merci de patienter jusqu’au retour à la normale. Vous pourrez alors enregistrer votre modification. Nous vous conseillons cependant de faire une copie de votre modification avant, au cas où.
''Autres conséquences :''
*Les tâches de fond seront ralenties et certaines pourraient être stoppées. Les liens rouges ne seront pas mis à jour aussi vite que d’habitude. Si vous créez un article qui est déjà lié depuis une autre page, le lien rouge pourrait rester rouge plus longtemps que d’habitude. Certains scripts ayant un long temps d’exécution devront être stoppés.
* Le déploiement de code devrait se dérouler comme chaque semaine. Cependant, certains codes particuliers pourraient être gelés si l’opération le nécessitait.
* [[mw:Special:MyLanguage/GitLab|GitLab]] sera indisponible durant environ 90 minutes.
Ce projet pourra être reporté si nécessaire. Vous pouvez [[wikitech:Switch_Datacenter|consulter le calendrier sur wikitech.wikimedia.org]]. Tout changement sera annoncé dans le calendrier.
'''Merci de partager ces informations avec votre communauté.'''</div><section end="server-switch"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 15 mars 2025 à 00:14 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=28307742 -->
== <span lang="en" dir="ltr">Tech News: 2025-12</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2025-W12"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2025/12|Translations]] are available.
'''Weekly highlight'''
* Twice a year, around the equinoxes, the Wikimedia Foundation's Site Reliability Engineering (SRE) team performs [[m:Special:MyLanguage/Tech/Server switch|a datacenter server switchover]], redirecting all traffic from one primary server to its backup. This provides reliability in case of a crisis, as we can always fall back on the other datacenter. [http://listen.hatnote.com/ Thanks to the Listen to Wikipedia] tool, you can hear the switchover take place: Before it begins, you'll hear the steady stream of edits; Then, as the system enters a brief read-only phase, the sound stops for a couple of minutes, before resuming after the switchover. You can [[diffblog:2025/03/12/hear-that-the-wikis-go-silent-twice-a-year/|read more about the background and details of this process on the Diff blog]]. If you want to keep an ear out for the next server switchover, listen to the wikis on [https://zonestamp.toolforge.org/1742392800 March 19 at 14:00 UTC].
'''Updates for editors'''
* The [https://test.wikipedia.org/w/index.php?title=Special:ContentTranslation&filter-type=automatic&filter-id=previous-edits&active-list=suggestions&from=en&to=es improved Content Translation tool dashboard] is now available in [[phab:T387820|10 Wikipedias]] and will be available for all Wikipedias [[phab:T387821|soon]]. With [[mw:Special:MyLanguage/Content translation#Improved translation experience|the unified dashboard]], desktop users can now: Translate new sections of an article; Discover and access topic-based [https://ig.m.wikipedia.org/w/index.php?title=Special:ContentTranslation&active-list=suggestions&from=en&to=ig&filter-type=automatic&filter-id=previous-edits article suggestion filters] (initially available only for mobile device users); Discover and access the [[mw:Special:MyLanguage/Translation suggestions: Topic-based & Community-defined lists|Community-defined lists]] filter, also known as "Collections", from wiki-projects and campaigns.
* On Wikimedia Commons, a [[c:Commons:WMF support for Commons/Upload Wizard Improvements#Improve category selection|new system to select the appropriate file categories]] has been introduced: if a category has one or more subcategories, users will be able to click on an arrow that will open the subcategories directly within the form, and choose the correct one. The parent category name will always be shown on top, and it will always be possible to come back to it. This should decrease the amount of work for volunteers in fixing/creating new categories. The change is also available on mobile. These changes are part of planned improvements to the UploadWizard.
* The Community Tech team is seeking wikis to join a pilot for the [[m:Special:MyLanguage/Community Wishlist Survey 2023/Multiblocks|Multiblocks]] feature and a refreshed Special:Block page in late March. Multiblocks enables administrators to impose multiple different types of blocks on the same user at the same time. If you are an admin or steward and would like us to discuss joining the pilot with your community, please leave a message on the [[m:Talk:Community Wishlist Survey 2023/Multiblocks|project talk page]].
* Starting March 25, the Editing team will test a new feature for Edit Check at [[phab:T384372|12 Wikipedias]]: [[mw:Special:MyLanguage/Help:Edit check#Multi-check|Multi-Check]]. Half of the newcomers on these wikis will see all [[mw:Special:MyLanguage/Help:Edit check#ref|Reference Checks]] during their edit session, while the other half will continue seeing only one. The goal of this test is to see if users are confused or discouraged when shown multiple Reference Checks (when relevant) within a single editing session. At these wikis, the tags used on edits that show References Check will be simplified, as multiple tags could be shown within a single edit. Changes to the tags are documented [[phab:T373949|on Phabricator]]. [https://phabricator.wikimedia.org/T379131]
* The [[m:Special:MyLanguage/Global reminder bot|Global reminder bot]], which is a service for notifying users that their temporary user-rights are about to expire, now supports using the localized name of the user-rights group in the message heading. Translators can see the [[m:Global reminder bot/Translation|listing of existing translations and documentation]] to check if their language needs updating or creation.
* The [[Special:GlobalPreferences|GlobalPreferences]] gender setting, which is used for how the software should refer to you in interface messages, now works as expected by overriding the local defaults. [https://phabricator.wikimedia.org/T386584]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:26}} community-submitted {{PLURAL:26|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the Wikipedia App for Android had a bug fixed for when a user is browsing and searching in multiple languages. [https://phabricator.wikimedia.org/T379777]
'''Updates for technical contributors'''
* Later this week, the way that Codex styles are loaded will be changing. There is a small risk that this may result in unstyled interface message boxes on certain pages. User generated content (e.g. templates) is not impacted. Gadgets may be impacted. If you see any issues [[phab:T388847|please report them]]. See the linked task for details, screenshots, and documentation on how to fix any affected gadgets.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.44/wmf.21|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2025/12|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2025-W12"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 18 mars 2025 à 00:48 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28412594 -->
== Actualités techniques n° 2025-13 ==
<section begin="technews-2025-W13"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/13|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* La Fondation Wikimédia souhaite recueillir vos commentaires sur les [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Product & Technology OKRs|ébauches des objectifs et des résultats-clés qui façonneront les priorités de la Fondation en matière de produits et de technologies]] pour la prochaine année fiscale (commençant en juillet). Les objectifs sont des domaines généraux et les résultats-clés permettent de mesurer leur réalisation. N'hésitez pas à partager vos commentaires sur la page de discussion, dans n'importe quelle langue, idéalement avant fin avril.
'''Actualités pour la contribution'''
* L'extension [[mw:Special:MyLanguage/Help:Extension:CampaignEvents|CampaignEvents]] sera déployée sur plusieurs wikis (voir le [[m:Special:MyLanguage/CampaignEvents/Deployment status#Global Deployment Plan|plan de déploiement]] pour plus de détails) en avril 2025, et l'équipe a commencé le processus d'engagement des communautés sur les wikis identifiés. L'extension fournit des outils pour organiser, gérer et promouvoir des activités collaboratives (comme des événements, des edit-a-thons et des WikiProjects) sur les wikis. L'extension comporte trois outils : [[m:Special:MyLanguage/Event Center/Registration|Inscription à l'événement]], [[m:Special:MyLanguage/CampaignEvents/Collaboration list|Liste de collaboration]] et [[m:Special:MyLanguage/Campaigns/Foundation Product Team/Invitation list|Liste d'invitation]]. Elle est actuellement présente sur 13 Wikipédias, dont la Wikipédia en anglais, la Wikipédia en français et la Wikipédia en espagnol, ainsi que sur Wikidata. Les questions ou demandes peuvent être adressées sur la [[mw:Help talk:Extension:CampaignEvents|page de discussion de l'extension]] ou sur Phabricator (avec l'étiquette <bdi lang="en" dir="ltr" style="white-space: nowrap;">#campaigns-product-team</bdi>).
* À partir de la semaine du 31 mars, les wikis pourront définir quels groupes d'utilisateurs peuvent voir les inscriptions privées dans [[m:Special:MyLanguage/Event Center/Registration|Inscription à l'événement]], dans le cadre de l'extension [[mw:Special:MyLanguage/Help:Extension:CampaignEvents|CampaignEvents]]. Par défaut, les organisateurs d'événements et les administrateurs du wiki local pourront voir les inscriptions privées. Il s'agit d'un changement par rapport au réglage actuel qui permet seulement aux organisateurs de l'événement de voir les inscriptions privées. Les wikis peuvent modifier la configuration par défaut en demandant un changement de configuration dans Phabricator (et en ajoutant l'étiquette <bdi lang="en" dir="ltr" style="white-space: nowrap;">#campaigns-product-team</bdi>). Les participants aux événements passés peuvent annuler leur inscription à tout moment.
* Les administrateurs des wikis qui disposent d'une barre latérale <bdi lang="en" dir="ltr">[[MediaWiki:Sidebar]]</bdi> personnalisée doivent vérifier si elle contient une entrée pour la liste {{int:specialpages}}. Si ce n'est pas le cas, ils doivent l'ajouter en utilisant <code dir=ltr style="white-space: nowrap;">* specialpages-url|specialpages</code>. Les wikis disposant d'une barre latérale par défaut verront le lien déplacé de la boîte à outils de la page vers le menu de la barre latérale en avril. [https://phabricator.wikimedia.org/T388927]
* L'habillage Minerva (web mobile) combine les notifications d'avis et d'alertes dans l'icône de cloche ([[File:OOjs UI icon bell.svg|16px|link=|class=skin-invert]]). Il existait depuis longtemps un bogue qui faisait qu'une indication de nouvelles notifications n'était affichée que si vous aviez des alertes que vous n'avez pas vues. Ce problème est désormais résolu. À l'avenir, les utilisateurs de Minerva remarqueront un compteur au-dessus de l'icône de la cloche lorsque vous avez un ou plusieurs notifications et/ou alertes non vues. [https://phabricator.wikimedia.org/T344029]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* VisualEditor a introduit un [[mw:VisualEditor/Hooks|nouveau hook côté client]] pour les développeurs à utiliser lors de l'intégration avec le cycle de vie de la cible VisualEditor. Ce hook devrait remplacer les hooks existants liés au cycle de vie et être plus cohérent entre les différentes plateformes. De plus, le nouveau hook s'appliquera aux utilisations de VisualEditor en dehors de l'édition complète d'articles, permettant aux gadgets d'interagir avec l'éditeur dans DiscussionTools également. L'équipe d'édition a l'intention de déprécier et éventuellement de supprimer les hooks de l'ancien cycle de vie, donc tous les cas d'utilisation que ce nouveau hook ne couvre pas seraient intéressants pour l'équipe et peuvent être [[phab:T355555|partagés dans la tâche]].
* Les développeurs qui utilisent la bibliothèque JavaScript <code dir=ltr>mw.Api</code> peuvent désormais identifier l'outil qui l'utilise avec le paramètre <code dir=ltr>userAgent</code> : <code dir=ltr>var api = new mw.Api( { userAgent: 'GadgetNameHere/1.0.1' } );</code>. Si vous gérez un gadget ou un script utilisateur, veuillez définir un agent utilisateur, car cela facilite la maintenance de la bibliothèque et du serveur et permet de différencier le trafic légitime du trafic illégitime. [https://phabricator.wikimedia.org/T373874][https://foundation.wikimedia.org/wiki/Policy:Wikimedia_Foundation_User-Agent_Policy]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.44/wmf.22|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/13|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W13"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 24 mars 2025 à 23:42 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28443127 -->
== Actualités techniques n° 2025-14 ==
<section begin="technews-2025-W14"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/14|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* L'équipe Contribution travaille sur une nouvelle [[mw:Special:MyLanguage/Edit Check|vérification des modifications]] : le [[mw:Special:MyLanguage/Edit check#26 March 2025|contrôle des éloges]]. L'objectif de cette vérification est d’identifier les termes non neutres saisis lors de la modification d’une page Wikipédia, afin d’informer l’auteur ou autrice que son texte devrait peut-être être modifié avant publication. Ce projet n’en est qu’à ses débuts ; l’équipe a besoin de l’avis des communautés. Dans [[phab:T389445|cette tâche Phabricator]], l’équipe rassemble les recommendations internes des wikis, les modèles utilisés pour étiqueter les articles non neutres et les termes (jargon et mots-clés) utilisés dans les résumés de modification pour les langues étudiées actuellement. Vous pouvez participer en modifiant le tableau sur Phabricator, en commentant la tâche ou en envoyant directement un message à [[m:user:Trizek (WMF)|Trizek (WMF)]].
* La [[mw:Special:MyLanguage/MediaWiki Platform Team/SUL3|connexion utilisateur unique]] (SUL) a été mise à jour sur tous les wikis afin de déplacer la connexion et la création de compte vers un domaine central. Cela rend la connexion des contributeurs compatible avec les restrictions des navigateurs sur les cookies inter-domaines, qui ont empêché les utilisateurs de certains navigateurs de rester connectés.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:35|la tâche soumise|les {{formatnum:35}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:35||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* À partir du 31 mars, l'équipe MediaWiki Interfaces va lancer une version limitée des spécifications OpenAPI générées et une expérience de bac à sable basée sur SwaggerUI pour [[mw:Special:MyLanguage/API:REST API|MediaWiki REST APIs]]. L'équipe invite les développeurs d'un groupe limité de communautés Wikipédia non anglophones (arabe, allemand, français, hébreu, interlingua, néerlandais, chinois) à consulter la documentation et à expérimenter le bac à sable dans leur langue de choix. En plus de ces projets Wikipédia spécifiques, le bac à sable et la spécification OpenAPI seront disponibles sur la [[testwiki:Special:RestSandbox|page spéciale test wiki REST Sandbox]] pour les développeurs dont l'anglais est la langue préférée. Pendant la période de prévisualisation, l'équipe MediaWiki Interfaces invite également les développeurs à [[mw:MediaWiki Interfaces Team/Feature Feedback/REST Sandbox|partager leur retour d'expérience]]. L'aperçu durera environ 2 semaines, après quoi le bac à sable et les spécifications OpenAPI seront mis à la disposition de tous les projets wiki.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.44/wmf.23|MediaWiki]]
'''En détails'''
* Parfois, un petit changement de code d'une ligne peut avoir une grande importance : dans ce cas, cela signifie que pour la première fois depuis des années, nous sommes en mesure de faire fonctionner toute la pile qui sert <bdi lang="en" dir="ltr">[http://maps.wikimedia.org/ maps.wikimedia.org]</bdi> - un hôte dédié à servir nos wikis et leurs besoins en cartes multilingues - à partir d'un seul centre de données, ce que nous testons à chaque fois que nous effectuons un [[m:Special:MyLanguage/Tech/Server switch|basculement de centre de données]]. C'est important car cela signifie que si l'un de nos centres de données est affecté par une catastrophe, nous serons toujours en mesure de servir le site. Ce changement est le résultat d'un [[phab:T216826|travail intensif]] de deux développeurs sur le portage du dernier composant de la pile de cartes sur [[w:fr:Kubernetes|kubernetes]], où nous pouvons allouer des ressources plus efficacement qu'auparavant, ce qui nous permet de supporter plus de trafic dans un seul centre de données. Ce travail a nécessité beaucoup d'étapes compliquées car ce logiciel et les bibliothèques logicielles qu'il utilise nécessitaient de nombreuses mises à jour attendues depuis longtemps. Ce type de travail rend l'infrastructure de Wikimedia plus durable.
'''Rencontres et évènements'''
* La [[mw:Special:MyLanguage/MediaWiki Users and Developers Workshop Spring 2025|Conférence des utilisateurs et développeurs de MediaWiki printemps 2025]] se déroulera à Sandusky, aux États-Unis, et en ligne, du 14 au 16 mai 2025. La conférence proposera des discussions autour de l'utilisation du logiciel MediaWiki par et au sein d'entreprises de différents secteurs, et inspirera et embarquera de nouveaux utilisateurs. L'inscription et l'enregistrement des présentations sont maintenant disponibles sur le site web de la conférence.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/14|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W14"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 1 avril 2025 à 02:05 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28473566 -->
== Final proposed modifications to the Universal Code of Conduct Enforcement Guidelines and U4C Charter now posted ==
<div lang="en" dir="ltr" class="mw-content-ltr">
The proposed modifications to the [[foundation:Special:MyLanguage/Policy:Universal_Code_of_Conduct/Enforcement_guidelines|Universal Code of Conduct Enforcement Guidelines]] and the U4C Charter [[m:Universal_Code_of_Conduct/Annual_review/2025/Proposed_Changes|are now on Meta-wiki for community notice]] in advance of the voting period. This final draft was developed from the previous two rounds of community review. Community members will be able to vote on these modifications starting on 17 April 2025. The vote will close on 1 May 2025, and results will be announced no later than 12 May 2025. The U4C election period, starting with a call for candidates, will open immediately following the announcement of the review results. More information will be posted on [[m:Special:MyLanguage//Universal_Code_of_Conduct/Coordinating_Committee/Election|the wiki page for the election]] soon.
Please be advised that this process will require more messages to be sent here over the next two months.
The [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee (U4C)]] is a global group dedicated to providing an equitable and consistent implementation of the UCoC. This annual review was planned and implemented by the U4C. For more information and the responsibilities of the U4C, you may [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Charter|review the U4C Charter]].
Please share this message with members of your community so they can participate as well.
-- In cooperation with the U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User_talk:Keegan (WMF)|talk]]) 4 avril 2025 à 04:04 (CEST)
</div>
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28469465 -->
== Actualités techniques n° 2025-15 ==
<section begin="technews-2025-W15"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/15|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Désormais, les [[m:Special:MyLanguage/Interface administrators|admins d’interface]] et [[m:Special:MyLanguage/Central notice administrators|admins des annonces centrales]] sont contraint techniquement d’activer l’[[m:Special:MyLanguage/Help:Two-factor authentication|authentification à deux facteurs]] avant de pouvoir utiliser leurs privilèges. À l’avenir, cela pourrait être étendu à d’autres groupes ayant des droits avancés. [https://phabricator.wikimedia.org/T150898]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:20|la tâche soumise|les {{formatnum:20}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:20||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* L’équipe Système design prépare la sortie de la nouvelle version majeur de Codex (v2.0.0) pour le 29 avril. Les contributeurices et développeurs et développeuses qui utilisent du CSS de Codex devraient consulter la [[mw:Codex/Release Timeline/2.0|documentation sur l’arrivée de la v2]], elle inclut un guidage pour les ruptures introduites dans cette version, par exemple pour <code dir=ltr style="white-space: nowrap;">font-size</code>, <code dir=ltr style="white-space: nowrap;">line-height</code> et <code dir=ltr style="white-space: nowrap;">size-icon</code>.
* Les résultats de [[mw:Developer Satisfaction Survey/2025|l’enquête 2025 sur la satisfaction des développeurs et développeuses]] est désormais disponible. Merci à tous les participants ! Ces résultats aident Wikimedia à décider ce sur quoi orienter le travail et à évaluer le travail récent.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.44/wmf.24|MediaWiki]]
'''Rencontres et évènements'''
* Le [[mw:Special:MyLanguage/Wikimedia Hackathon 2025|Hackathon Wikimedia 2025]] aura lieu à Istanbul en Turquie, du 2 au 4 mai. Les inscriptions pour participer en présentiel ont lieu jusqu’au 13 avril. Avant de vous inscrire, sachez qu’il vous faudra peut-être un [https://www.mfa.gov.tr/turkish-representations.en.mfa visa] ou un [https://www.mfa.gov.tr/visa-information-for-foreigners.en.mfa e-visa] pour entrer dans le pays.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/15|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W15"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 7 avril 2025 à 20:52 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28507470 -->
== Wikidata and Sister Projects: An online community event ==
''(Apologies for posting in English)''
Hello everyone, I am excited to share news of an upcoming online event called '''[[d:Event:Wikidata_and_Sister_Projects|Wikidata and Sister Projects]]''' celebrating the different ways Wikidata can be used to support or enhance with another Wikimedia project. The event takes place over 4 days between '''May 29 - June 1st, 2025'''.
We would like to invite speakers to present at this community event, to hear success stories, challenges, showcase tools or projects you may be working on, where Wikidata has been involved in Wikipedia, Commons, WikiSource and all other WM projects.
If you are interested in attending, please [[d:Special:RegisterForEvent/1291|register here]].
If you would like to speak at the event, please fill out this Session Proposal template on the [[d:Event_talk:Wikidata_and_Sister_Projects|event talk page]], where you can also ask any questions you may have.
I hope to see you at the event, in the audience or as a speaker, - [[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 11 avril 2025 à 11:18 (CEST)
<!-- Message envoyé par User:Danny Benjafield (WMDE)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Danny_Benjafield_(WMDE)/MassMessage_Send_List&oldid=28525705 -->
== <span lang="en" dir="ltr">Tech News: 2025-16</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2025-W16"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2025/16|Translations]] are available.
'''Weekly highlight'''
* Later this week, the default thumbnail size will be increased from 220px to 250px. This changes how pages are shown in all wikis and has been requested by some communities for many years, but wasn't previously possible due to technical limitations. [https://phabricator.wikimedia.org/T355914]
* File thumbnails are now stored in discrete sizes. If a page specifies a thumbnail size that's not among the standard sizes (20, 40, 60, 120, 250, 330, 500, 960), then MediaWiki will pick the closest larger thumbnail size but will tell the browser to downscale it to the requested size. In these cases, nothing will change visually but users might load slightly larger images. If it doesn't matter which thumbnail size is used in a page, please pick one of the standard sizes to avoid the extra in-browser down-scaling step. [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Images#Thumbnail_sizes][https://phabricator.wikimedia.org/T355914]
'''Updates for editors'''
* The Wikimedia Foundation are working on a system called [[m:Edge Uniques|Edge Uniques]] which will enable [[:w:en:A/B testing|A/B testing]], help protect against [[:w:en:Denial-of-service attack|Distributed denial-of-service attacks]] (DDoS attacks), and make it easier to understand how many visitors the Wikimedia sites have. This is so that they can more efficiently build tools which help readers, and make it easier for readers to find what they are looking for.
* To improve security for users, a small percentage of logins will now require that the account owner input a one-time password [[mw:Special:MyLanguage/Help:Extension:EmailAuth|emailed to their account]]. It is recommended that you [[Special:Preferences#mw-prefsection-personal-email|check]] that the email address on your account is set correctly, and that it has been confirmed, and that you have an email set for this purpose. [https://phabricator.wikimedia.org/T390662]
* "Are you interested in taking a short survey to improve tools used for reviewing or reverting edits on your Wiki?" This question will be [[phab:T389401|asked at 7 wikis starting next week]], on Recent Changes and Watchlist pages. The [[mw:Special:MyLanguage/Moderator Tools|Moderator Tools team]] wants to know more about activities that involve looking at new edits made to your Wikimedia project, and determining whether they adhere to your project's policies.
* On April 15, the full Wikidata graph will no longer be supported on <bdi lang="zxx" dir="ltr">[https://query.wikidata.org/ query.wikidata.org]</bdi>. After this date, scholarly articles will be available through <bdi lang="zxx" dir="ltr" style="white-space:nowrap;">[https://query-scholarly.wikidata.org/ query-scholarly.wikidata.org]</bdi>, while the rest of the data hosted on Wikidata will be available through the <bdi lang="zxx" dir="ltr">[https://query.wikidata.org/ query.wikidata.org]</bdi> endpoint. This is part of the scheduled split of the Wikidata Graph, which was [[d:Special:MyLanguage/Wikidata:SPARQL query service/WDQS backend update/September 2024 scaling update|announced in September 2024]]. More information is [[d:Wikidata:SPARQL query service/WDQS graph split|available on Wikidata]].
* The latest quarterly [[m:Special:MyLanguage/Wikimedia Apps/Newsletter/First quarter of 2025|Wikimedia Apps Newsletter]] is now available. It covers updates, experiments, and improvements made to the Wikipedia mobile apps.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:30}} community-submitted {{PLURAL:30|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]].
'''Updates for technical contributors'''
* The latest quarterly [[mw:Technical Community Newsletter/2025/April|Technical Community Newsletter]] is now available. This edition includes: an invitation for tool maintainers to attend the Toolforge UI Community Feedback Session on April 15th; recent community metrics; and recent technical blog posts.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.44/wmf.25|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2025/16|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2025-W16"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 15 avril 2025 à 02:24 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28540654 -->
== Vote now on the revised UCoC Enforcement Guidelines and U4C Charter ==
<div lang="en" dir="ltr" class="mw-content-ltr">
The voting period for the revisions to the Universal Code of Conduct Enforcement Guidelines ("UCoC EG") and the UCoC's Coordinating Committee Charter is open now through the end of 1 May (UTC) ([https://zonestamp.toolforge.org/1746162000 find in your time zone]). [[m:Special:MyLanguage/Universal_Code_of_Conduct/Annual_review/2025/Voter_information|Read the information on how to participate and read over the proposal before voting]] on the UCoC page on Meta-wiki.
The [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee (U4C)]] is a global group dedicated to providing an equitable and consistent implementation of the UCoC. This annual review of the EG and Charter was planned and implemented by the U4C. Further information will be provided in the coming months about the review of the UCoC itself. For more information and the responsibilities of the U4C, you may [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Charter|review the U4C Charter]].
Please share this message with members of your community so they can participate as well.
In cooperation with the U4C -- [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User_talk:Keegan (WMF)|talk]]) 17 avril 2025 à 02:34 (CEST)
</div>
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28469465 -->
== Actualités techniques n° 2025-17 ==
<section begin="technews-2025-W17"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/17|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* [[f:Special:MyLanguage/Wikifunctions:Main Page|Wikifunctions]] est désormais intégré à la [[w:dag:Solɔɣu|Wikipédia en dagbani]] depuis le 15 avril. C'est le premier projet qui pourra appeler des [[f:Special:MyLanguage/Wikifunctions:Introduction|fonctions de Wikifonctions]] et les intégrer dans des articles. Une fonction est quelque chose qui prend une ou plusieurs entrées et les transforme en un résultat souhaité, comme l'addition de deux nombres, la conversion de miles en mètres, le calcul du temps écoulé depuis un événement, ou la déclinaison d'un mot en une casse. Les Wikifonctions permettront aux utilisateurs de faire cela par un simple appel d'[[f:Special:MyLanguage/Wikifunctions:Catalogue|une fonction stable et globale]], plutôt que par l'intermédiaire d'un modèle local. [https://www.wikifunctions.org/wiki/Special:MyLanguage/Wikifunctions:Status_updates/2025-04-16]
* Un nouveau type d'erreur ''lint'' a été créé : [[Special:LintErrors/empty-heading|{{int:linter-category-empty-heading}}]] ([[mw:Special:MyLanguage/Help:Lint errors/empty-heading|documentation]]). L'objectif de l'extension [[mw:Special:MyLanguage/Help:Extension:Linter|Linter]] est d'identifier les éléments de wikitexte qui doivent ou peuvent être corrigés dans les pages et de fournir des conseils sur les problèmes posés par ces éléments et sur la manière de les corriger. [https://phabricator.wikimedia.org/T368722]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:37|la tâche soumise|les {{formatnum:37}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:37||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* À la suite de sa publication sur ''HuggingFace'', l'ensemble de données « ''Structured Contents'' », développé par Wikimedia Enterprise, est [https://enterprise.wikimedia.com/blog/kaggle-dataset/ maintenant également disponible sur Kaggle]. Cette initiative bêta vise à rendre les données de Wikimedia plus lisibles par les machines pour les réutilisateurs de gros volumes. Cette version bêta est publiée à un emplacement déjà utilisé par les communautés de données ouvertes, afin de recueillir des commentaires qui permettront d'améliorer le produit en vue d'une future diffusion à plus grande échelle. Vous pouvez en savoir plus sur le projet ''[https://enterprise.wikimedia.com/blog/structured-contents-snapshot-api/#open-datasets Structured Contents]'', et sur la [https://enterprise.wikimedia.com/blog/structured-contents-wikipedia-infobox/ première version librement utilisable].
* Il n'y a pas de nouvelle version de MediaWiki cette semaine.
'''Rencontres et évènements'''
* Les équipes de rédaction et d'apprentissage automatique (''Editing and Machine Learning Teams'') invitent les bénévoles intéressés à une visioconférence pour discuter de la [[mw:Special:MyLanguage/Edit check/Peacock check|vérification « ''peacock'' »]] <small>(NdT : litt. « paon » mais également une expression idiomatique pour le langage vaniteux, prétentieux)</small>, qui est la dernière [[mw:Special:MyLanguage/Edit check|vérification de modification]] qui détectera le langage « trop promotionnel » ou « non neutre » pendant qu'un rédacteur est en train de taper. Les rédacteurs qui travaillent avec les nouveaux arrivants, ou qui aident à corriger ce type d'écriture, ou qui sont intéressés par la manière dont nous utilisons l'intelligence artificielle dans nos projets, sont invités à participer à cette réunion. La [[mw:Special:MyLanguage/Editing team/Community Conversations#Next Conversation|réunion aura lieu le 28 avril 2025]] à [https://zonestamp.toolforge.org/1745863200 18:00-19:00 UTC] et sera hébergée sur Zoom.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/17|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W17"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 21 avril 2025 à 23:00 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28578245 -->
== Actualités techniques n° 2025-18 ==
<section begin="technews-2025-W18"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/18|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* <span lang="en" dir="ltr" class="mw-content-ltr">Event organizers who host collaborative activities on [[m:Special:MyLanguage/CampaignEvents/Deployment status#Global Deployment Plan|multiple wikis]], including Bengali, Japanese, and Korean Wikipedias, will have access to the [[mw:Special:MyLanguage/Extension:CampaignEvents|CampaignEvents extension]] this week. Also, admins in the Wikipedia where the extension is enabled will automatically be granted the event organizer right soon. They won't have to manually grant themselves the right before they can manage events as [[phab:T386861|requested by a community]].</span>
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:19|la tâche soumise|les {{formatnum:19}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:19||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* <span lang="en" dir="ltr" class="mw-content-ltr">The release of the next major version of [[mw:Special:MyLanguage/Codex|Codex]], the design system for Wikimedia, is scheduled for 29 April 2025. Technical editors will have access to the release by the week of 5 May 2025. This update will include a number of [[mw:Special:MyLanguage/Codex/Release_Timeline/2.0#Breaking_changes|breaking changes]] and minor [[mw:Special:MyLanguage/Codex/Release_Timeline/2.0#Visual_changes|visual changes]]. Instructions on handling the breaking and visual changes are documented on [[mw:Special:MyLanguage/Codex/Release Timeline/2.0#|this page]]. Pre-release testing is reported in [[phab:T386298|T386298]], with post-release issues tracked in [[phab:T392379|T392379]] and [[phab:T392390|T392390]].</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">Users of [[wikitech:Special:MyLanguage/Help:Wiki_Replicas|Wiki Replicas]] will notice that the database views of <code dir="ltr">ipblocks</code>, <code dir="ltr">ipblocks_ipindex</code>, and <code dir="ltr">ipblocks_compat</code> are [[phab:T390767|now deprecated]]. Users can query the <code dir="ltr">[[mw:Special:MyLanguage/Manual:Block_table|block]]</code> and <code dir="ltr">[[mw:Special:MyLanguage/Manual:Block_target_table|block_target]]</code> new views that mirror the new tables in the production database instead. The deprecated views will be removed entirely from Wiki Replicas in June, 2025.</span>
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.44/wmf.27|MediaWiki]]
'''En détails'''
* <span lang="en" dir="ltr" class="mw-content-ltr">The latest quarterly [[mw:Special:MyLanguage/Wikimedia Language and Product Localization/Newsletter/2025/April|Language and Internationalization Newsletter]] is now available.</span> <span lang="en" dir="ltr" class="mw-content-ltr">This edition includes an overview of the improved [https://test.wikipedia.org/w/index.php?title=Special:ContentTranslation&campaign=contributionsmenu&to=es&filter-type=automatic&filter-id=previous-edits&active-list=suggestions&from=en#/ Content Translation Dashboard Tool], [[mw:Special:MyLanguage/Wikimedia Language and Product Localization/Newsletter/2025/April#Language Support for New and Existing Languages|support for new languages]], [[mw:Special:MyLanguage/Wikimedia Language and Product Localization/Newsletter/2025/April#Wiki Loves Ramadan Articles Made In Content Translation Mobile Workflow|highlights from the Wiki Loves Ramadan campaign]], [[m:Special:MyLanguage/Research:Languages Onboarding Experiment 2024 - Executive Summary|results from the Language Onboarding Experiment]], an analysis of topic diversity in articles, and information on upcoming community meetings and events.</span>
'''Rencontres et évènements'''
* <span lang="en" dir="ltr" class="mw-content-ltr">The [[Special:MyLanguage/Grants:Knowledge_Sharing/Connect/Calendar|Let's Connect Learning Clinic]] will take place on [https://zonestamp.toolforge.org/1745937000 April 29 at 14:30 UTC]. This edition will focus on "Understanding and Navigating Conflict in Wikimedia Projects". You can [[m:Special:MyLanguage/Event:Learning Clinic %E2%80%93 Understanding and Navigating Conflict in Wikimedia Projects (Part_1)|register now]] to attend.</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">The [[mw:Special:MyLanguage/Wikimedia Hackathon 2025|2025 Wikimedia Hackathon]], which brings the global technical community together to connect, brainstorm, and hack existing projects, will take place from May 2 to 4th, 2025, at Istanbul, Turkey.</span>
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/18|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W18"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 28 avril 2025 à 21:31 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28585685 -->
== Vote sur les modifications proposées aux lignes directrices de l'UCoC et à la charte de l'U4C ==
<section begin="announcement-content" />
La période de vote pour les révisions des directives d'application du Code de conduite universel et de la Charte U4C se termine le 1er mai 2025 à 23:59 UTC ([https://zonestamp.toolforge.org/1746162000 trouver dans votre fuseau horaire]). [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2025/Voter information|Lisez les informations sur la façon de participer et lisez la proposition avant de voter]] sur la page UCoC de Méta-wiki.
Le [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee|Comité de coordination du code de conduite universel (U4C)]] est un groupe mondial qui se consacre à la mise en œuvre équitable et cohérente de l'UCoC. Cet examen annuel a été planifié et mis en œuvre par l'U4C. Pour plus d'informations et pour connaître les responsabilités de l'U4C, vous pouvez [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Charter|réviser la charte U4C]].
Veuillez partager ce message avec les membres de votre communauté dans votre langue, le cas échéant, afin qu'ils puissent également y participer.
En coopération avec l'U4C -- <section end="announcement-content" />
<div lang="en" dir="ltr" class="mw-content-ltr">
[[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 29 avril 2025 à 05:40 (CEST)</div>
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28618011 -->
== Actualités techniques n° 2025-19 ==
<section begin="technews-2025-W19"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/19|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* La Wikimedia Foundation a partagé le dernier projet de mise à jour de son [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026|plan annuel]] pour l'année prochaine (juillet 2025-juin 2026). Cela comprend un [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026|résumé exécutif]] (également sur [[diffblog:2025/04/25/sharing-the-wikimedia-foundations-2025-2026-draft-annual-plan/|Diff]]), des détails sur les trois principaux [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Goals|objectifs]] ([[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Product & Technology OKRs|Infrastructure]], [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Goals/Volunteer Support|Soutien aux bénévoles]] et [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Goals/Effectiveness|Efficacité]]), [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Global Trends|tendances mondiales]], ainsi que le [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Budget Overview|budget]] et le [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026/Financial Model|modèle financier]]. Les réactions et les questions sont les bienvenues sur la [[m:Talk:Wikimedia Foundation Annual Plan/2025-2026|page de discussion]] jusqu'à la fin du mois de mai.
'''Actualités pour la contribution'''
* Pour les wikis qui ont l'extension [[m:Special:MyLanguage/CampaignEvents/Deployment status|CampaignEvents]] activée, deux nouvelles améliorations de fonctionnalités ont été publiées :
** Les administrateurs peuvent désormais choisir les espaces de noms autorisés pour [[m:Special:MyLanguage/Event Center/Registration|Inscription à un événement]] via [[mw:Special:MyLanguage/Community Configuration|Configuration de la communauté]] ([[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Permitted namespaces|documentation]]). Par défaut, l'enregistrement d'un événement est autorisé dans l'espace de noms Event, mais d'autres espaces de noms (tels que l'espace de noms du projet ou l'espace de noms WikiProject) peuvent désormais être ajoutés. Grâce à cette modification, les communautés telles que les WikiProjets peuvent désormais utiliser plus facilement l'enregistrement d'événements pour leurs activités collaboratives.
** Les éditeurs peuvent désormais [[mw:Special:MyLanguage/Transclusion|transclure]] la liste de collaboration sur une page wiki ([[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Collaboration list/Transclusion|documentation]]). La liste de collaboration est une liste automatisée d'événements et de wikiprojets sur les wikis, accessible via {{#special:AllEvents}} ([[w:en:Special:AllEvents|exemple]]). Désormais, la liste de collaboration peut être ajoutée à toutes sortes de pages wiki, telles que : une page principale wiki, une page WikiProjet, une page d'affiliation, une page d'événement, ou même une page d'utilisateur.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* Les développeurs qui utilisent la bibliothèque <code dir=ltr>moment</code> dans les gadgets et les scripts utilisateur doivent réviser leur code pour utiliser des alternatives comme la bibliothèque <code dir=ltr>Intl</code> ou la nouvelle bibliothèque <code dir=ltr>mediawiki.DateFormatter</code>. La bibliothèque <code dir=ltr>moment</code> a été dépréciée et commencera à enregistrer des messages dans la console du développeur. Vous pouvez voir une recherche globale pour les utilisations actuelles, et [[phab:T392532|posez des questions connexes dans cette tâche Phabricator]].
* Les développeurs qui maintiennent un outil qui interroge les tables de stockage de termes de Wikidata (<code dir=ltr style="white-space: nowrap;">wbt_*</code>) doivent mettre à jour leur code pour se connecter à une grappe de base de données séparée. Ces tables sont réparties dans une grappe de base de données distincte. Les outils qui interrogent ces tables via les répliques du wiki doivent être adaptés pour se connecter à la nouvelle grappe. [[wikitech:News/2025 Wikidata term store database split|La documentation et des liens connexes sont à votre disposition]]. [https://phabricator.wikimedia.org/T390954]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.44/wmf.28|MediaWiki]]
'''En détails'''
* La dernière [[mw:Special:MyLanguage/Extension:Chart/Project/Updates|lettre d’information du projet Chart]] est disponible. Il comprend des mises à jour sur la préparation de l'extension du déploiement à d'autres wikis dès cette semaine (à partir du 6 mai) et sur la mise à l'échelle au cours des semaines suivantes, ainsi que sur l'exploration du filtrage et de la transformation des données sources.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/19|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W19"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 6 mai 2025 à 02:14 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28665011 -->
== We will be enabling the new Charts extension on your wiki soon! ==
''(Apologies for posting in English)''
Hi all! We have good news to share regarding the ongoing problem with graphs and charts affecting all wikis that use them.
As you probably know, the [[:mw:Special:MyLanguage/Extension:Graph|old Graph extension]] was disabled in 2023 [[listarchive:list/wikitech-l@lists.wikimedia.org/thread/EWL4AGBEZEDMNNFTM4FRD4MHOU3CVESO/|due to security reasons]]. We’ve worked in these two years to find a solution that could replace the old extension, and provide a safer and better solution to users who wanted to showcase graphs and charts in their articles. We therefore developed the [[:mw:Special:MyLanguage/Extension:Chart|Charts extension]], which will be replacing the old Graph extension and potentially also the [[:mw:Extension:EasyTimeline|EasyTimeline extension]].
After successfully deploying the extension on Italian, Swedish, and Hebrew Wikipedia, as well as on MediaWiki.org, as part of a pilot phase, we are now happy to announce that we are moving forward with the next phase of deployment, which will also include your wiki.
The deployment will happen in batches, and will start from '''May 6'''. Please, consult [[:mw:Special:MyLanguage/Extension:Chart/Project#Deployment Timeline|our page on MediaWiki.org]] to discover when the new Charts extension will be deployed on your wiki. You can also [[:mw:Special:MyLanguage/Extension:Chart|consult the documentation]] about the extension on MediaWiki.org.
If you have questions, need clarifications, or just want to express your opinion about it, please refer to the [[:mw:Special:MyLanguage/Extension_talk:Chart/Project|project’s talk page on Mediawiki.org]], or ping me directly under this thread. If you encounter issues using Charts once it gets enabled on your wiki, please report it on the [[:mw:Extension_talk:Chart/Project|talk page]] or at [[phab:tag/charts|Phabricator]].
Thank you in advance! -- [[User:Sannita (WMF)|User:Sannita (WMF)]] ([[User talk:Sannita (WMF)|talk]]) 6 mai 2025 à 17:07 (CEST)
<!-- Message envoyé par User:Sannita (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Sannita_(WMF)/Mass_sending_test&oldid=28663781 -->
== Actualités techniques n° 2025-20 ==
<section begin="technews-2025-W20"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/20|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Le lien [[m:Special:MyLanguage/Wikimedia URL Shortener|« Obtenir une URL raccourcie »]] dans la barre latérale inclut désormais un [[phab:T393309|code QR]]. Les utilisateurs des sites Wikimedia peuvent maintenant l’utiliser en le scannant ou en le téléchargeant pour partager et accéder rapidement au contenu partagé des sites Wikimedia, de manière pratique.
'''Actualités pour la contribution'''
* La Wikimedia Foundation travaille sur un système appelé [[m:Edge Uniques|« ''Edge Uniques'' »]], qui permettra de réaliser des [[w:en:A/B testing|tests A/B]], d’aider à se protéger contre les [[w:en:Denial-of-service attack|attaques par déni de service distribué]] (attaques DDoS), et de mieux comprendre combien de visiteurs les sites Wikimedia reçoivent. Cela vise à construire plus efficacement des outils utiles aux lecteurs, et de les aider à trouver ce qu'ils cherchent. Les Actualités techniques en ont [[m:Special:MyLanguage/Tech/News/2025/16|déjà parlé]]. Le déploiement sera progressif. Certains pourraient voir le cookie « ''Edge Uniques'' » à partir de la semaine du 19 mai. Vous pouvez en discuter sur la [[m:Talk:Edge Uniques|page de discussion]].
* À partir du 19 mai 2025, les organisateurs d’événements sur les wikis disposant de l’[[mw:Special:MyLanguage/Help:Extension:CampaignEvents|extension « CampaignEvents »]] pourront utiliser l’[[m:Special:MyLanguage/Event Center/Registration|inscription aux événements]] dans l’espace de noms du projet (par exemple, l’espace Wikipédia, l’espace Wikidata). Avec ce changement, les communautés n’ont plus besoin d’administrateurs pour utiliser cette fonctionnalité. Toutefois, les wikis qui ne souhaitent pas ce changement peuvent retirer et ajouter les espaces de noms autorisés sur [[Special:CommunityConfiguration/CampaignEvents]].
* Le projet Wikipédia dispose désormais d’un {{int:project-localized-name-group-wikipedia/fr}} en [[d:Q36720|nupe]] ([[w:nup:|<code>w:nup:</code>]]). Il s’agit d’une langue principalement parlée dans la région du centre-nord du Nigeria. Les locuteurs de cette langue sont invités à contribuer à la [[w:nup:Tatacin feregi|nouvelle Wikipédia]].
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* Les développeurs peuvent désormais accéder à la Wikipédia néerlandaise pré-analysée, parmi d’autres (anglais, allemand, français, espagnol, italien et portugais), via les [https://enterprise.wikimedia.com/docs/snapshot/#structured-contents-snapshot-bundle-info-beta instantanés « ''Structured Contents'' » (bêta)]. Le contenu comprend des résumés Wikipédia analysés, des descriptions, des images principales, des infoboxes, des sections d’articles et des références.
* Le point de terminaison de l’API REST <code dir="ltr">/page/data-parsoid</code> n’est plus utilisé et sera obsolète. Il est [[phab:T393557|prévu qu’il soit désactivé]] le 7 juin 2025.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.45/wmf.1|MediaWiki]]
'''En détails'''
* Le [https://wikitech.wikimedia.org/wiki/News/2025_Cloud_VPS_VXLAN_IPv6_migration support IPv6] est un nouveau réseau virtuel Cloud qui améliore considérablement l’évolutivité, la sécurité et la préparation des plateformes Wikimedia pour l’avenir. Si vous êtes un contributeur technique curieux d’en savoir plus, consultez [https://techblog.wikimedia.org/2025/05/06/wikimedia-cloud-vps-ipv6-support/ ce billet de blogue] pour un aperçu détaillé de la transition vers l’IPv6.
'''Rencontres et évènements'''
* La deuxième édition de 2025 de [[m:Special:MyLanguage/Afrika Baraza|« Afrika Baraza »]], une plateforme virtuelle permettant aux Wikimédiens africains de se connecter, aura lieu le [https://zonestamp.toolforge.org/1747328400 15 mai à 17 h UTC]. Cette édition sera axée sur des discussions concernant la [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2025-2026|planification annuelle et les avancées de Wikimedia]].
* La [[m:Special:MyLanguage/MENA Connect Community Call|« ''MENA Connect Community Call'' »]], une réunion virtuelle permettant aux Wikimédiens de la [[w:fr:MENA|région Moyen-Orient et Afrique du Nord]] (MENA) de se rencontrer, aura lieu le [https://zonestamp.toolforge.org/1747501200 17 mai à 17 h UTC]. Vous pouvez [[m:Event:MENA Connect (Wiki_Diwan) APP Call|vous inscrire dès maintenant]] pour y assister.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/20|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W20"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 13 mai 2025 à 00:37 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28714188 -->
== Appel aux candidatures pour le Comité de Coordination du Code de Conduite Universel (U4C). ==
<section begin="announcement-content" />
Les résultats du vote sur les directives d'application et la charte du Comité de Coordination du Code de Conduite Universel (U4C) sont disponibles [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2025#Results|sur Méta-wiki]].
Vous pouvez désormais [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Election/2025/Candidates|soumettre votre candidature pour siéger à l'U4C]] <s>à partir du 29 mai 2025 à 12:00 UTC</s>. Des informations sur [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Election/2025|l'éligibilité, le processus, et la chronologie sont disponibles sur Méta-wiki]]. Le vote sur les candidats sera ouvert le 1<sup>er</sup> juin 2025 et durera deux semaines, se finissant donc le 15 juin 2025 à 12:00 UTC.
Si vous avez des questions, vous pouvez les poser sur [[m:Talk:Universal Code of Conduct/Coordinating Committee/Election/2025|la page de discussion pour l'élection]]. -- en coopération avec l'U4C, </div><section end="announcement-content" />
<bdi lang="en" dir="ltr">[[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User_talk:Keegan (WMF)|discussion]])</bdi> 16 mai 2025 à 00:06 (CEST)
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28618011 -->
:Rectification : Vous pouvez désormais [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Election/2025/Candidates|soumettre votre candidature pour siéger à l'U4C]] du 14 mai au 28 mai.
:{{BlocCitation|You can submit your candidacy from May 14 until May 28, 2025}}
:-- ◄ [[Utilisateur:DavidL|'''D'''avid '''L''']] • [[Discussion Utilisateur:DavidL|discuter]] ► 16 mai 2025 à 16:36 (CEST)
== <span lang="en" dir="ltr">Tech News: 2025-21</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2025-W21"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2025/21|Translations]] are available.
'''Weekly highlight'''
* The Editing Team and the Machine Learning Team are working on a new check for newcomers: [[mw:Edit check/Peacock check|Peacock check]]. Using a prediction model, this check will encourage editors to improve the tone of their edits, using artificial intelligence. We invite volunteers to review the first version of the Peacock language model for the following languages: Arabic, Spanish, Portuguese, English, and Japanese. Users from these wikis interested in reviewing this model are [[mw:Edit check/Peacock check/model test|invited to sign up at MediaWiki.org]]. The deadline to sign up is on May 23, which will be the start date of the test.
'''Updates for editors'''
* From May 20, 2025, [[m:Special:MyLanguage/Oversight policy|oversighters]] and [[m:Special:MyLanguage/Meta:CheckUsers|checkusers]] will need to have their accounts secured with two-factor authentication (2FA) to be able to use their advanced rights. All users who belong to these two groups and do not have 2FA enabled have been informed. In the future, this requirement may be extended to other users with advanced rights. [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|Learn more]].
* [[File:Octicons-gift.svg|12px|link=|class=skin-invert|Wishlist item]] [[m:Special:MyLanguage/Community Wishlist Survey 2023/Multiblocks|Multiblocks]] will begin mass deployment by the end of the month: all non-Wikipedia projects plus Catalan Wikipedia will adopt Multiblocks in the week of May 26, while all other Wikipedias will adopt it in the week of June 2. Please [[m:Talk:Community Wishlist Survey 2023/Multiblocks|contact the team]] if you have concerns. Administrators can test the new user interface now on your own wiki by browsing to [{{fullurl:Special:Block|usecodex=1}} {{#special:Block}}?usecodex=1], and can test the full multiblocks functionality [[testwiki:Special:Block|on testwiki]]. Multiblocks is the feature that makes it possible for administrators to impose different types of blocks on the same user at the same time. See the [[mw:Special:MyLanguage/Help:Manage blocks|help page]] for more information. [https://phabricator.wikimedia.org/T377121]
* Later this week, the [[{{#special:SpecialPages}}]] listing of almost all special pages will be updated with a new design. This page has been [[phab:T219543|redesigned]] to improve the user experience in a few ways, including: The ability to search for names and aliases of the special pages, sorting, more visible marking of restricted special pages, and a more mobile-friendly look. The new version can be [https://meta.wikimedia.beta.wmflabs.org/wiki/Special:SpecialPages previewed] at Beta Cluster now, and feedback shared in the task. [https://phabricator.wikimedia.org/T219543]
* The [[mw:Special:MyLanguage/Extension:Chart|Chart extension]] is being enabled on more wikis. For a detailed list of when the extension will be enabled on your wiki, please read the [[mw:Special:MyLanguage/Extension:Chart/Project#Deployment Timeline|deployment timeline]].
* [[f:Special:MyLanguage/Wikifunctions:Main Page|Wikifunctions]] will be deployed on May 27 on five Wiktionaries: [[wikt:ha:|Hausa]], [[wikt:ig:|Igbo]], [[wikt:bn:|Bengali]], [[wikt:ml:|Malayalam]], and [[wikt:dv:|Dhivehi/Maldivian]]. This is the second batch of deployment planned for the project. After deployment, the projects will be able to call [[f:Special:MyLanguage/Wikifunctions:Introduction|functions from Wikifunctions]] and integrate them in their pages. A function is something that takes one or more inputs and transforms them into a desired output, such as adding up two numbers, converting miles into metres, calculating how much time has passed since an event, or declining a word into a case. Wikifunctions will allow users to do that through a simple call of [[f:Special:MyLanguage/Wikifunctions:Catalogue|a stable and global function]], rather than via a local template.
* Later this week, the Wikimedia Foundation will publish a hub for [[diffblog:2024/07/09/on-the-value-of-experimentation/|experiments]]. This is to showcase and get user feedback on product experiments. The experiments help the Wikimedia movement [[diffblog:2023/07/13/exploring-paths-for-the-future-of-free-knowledge-new-wikipedia-chatgpt-plugin-leveraging-rich-media-social-apps-and-other-experiments/|understand new users]], how they interact with the internet and how it could affect the Wikimedia movement. Some examples are [[m:Special:MyLanguage/Future Audiences/Generated Video|generated video]], the [[m:Special:MyLanguage/Future Audiences/Roblox game|Wikipedia Roblox speedrun game]] and [[m:Special:MyLanguage/Future Audiences/Discord bot|the Discord bot]].
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:29}} community-submitted {{PLURAL:29|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, there was a bug with creating an account using the API, which has now been fixed. [https://phabricator.wikimedia.org/T390751]
'''Updates for technical contributors'''
* Gadgets and user scripts that interact with [[{{#special:Block}}]] may need to be updated to work with the new [[mw:Special:MyLanguage/Help:Manage blocks|manage blocks interface]]. Please review the [[mw:Help:Manage blocks/Developers|developer guide]] for more information. If you need help or are unable to adapt your script to the new interface, please let the team know on the [[mw:Help talk:Manage blocks/Developers|talk page]]. [https://phabricator.wikimedia.org/T377121]
* The <code dir=ltr>mw.title</code> object allows you to get information about a specific wiki page in the [[w:en:Wikipedia:Lua|Lua]] programming language. Starting this week, a new property will be added to the object, named <code dir=ltr>isDisambiguationPage</code>. This property allows you to check if a page is a disambiguation page, without the need to write a custom function. [https://phabricator.wikimedia.org/T71441]
* [[File:Octicons-tools.svg|15px|link=|class=skin-invert|Advanced item]] User script developers can use a [[toolforge:gitlab-content|new reverse proxy tool]] to load javascript and css from [[gitlab:|gitlab.wikimedia.org]] with <code dir=ltr>mw.loader.load</code>. The tool's author hopes this will enable collaborative development workflows for user scripts including linting, unit tests, code generation, and code review on <bdi lang="zxx" dir="ltr">gitlab.wikimedia.org</bdi> without a separate copy-and-paste step to publish scripts to a Wikimedia wiki for integration and acceptance testing. See [[wikitech:Tool:Gitlab-content|Tool:Gitlab-content on Wikitech]] for more information.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.45/wmf.2|MediaWiki]]
'''Meetings and events'''
* The 12th edition of [[m:Special:MyLanguage/Wiki Workshop 2025|Wiki Workshop 2025]], a forum that brings together researchers that explore all aspects of Wikimedia projects, will be held virtually on 21-22 May. Researchers can [https://pretix.eu/wikimedia/wikiworkshop2025/ register now].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2025/21|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2025-W21"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 20 mai 2025 à 01:12 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28724712 -->
== RfC ongoing regarding Abstract Wikipedia (and your project) ==
<div lang="en" dir="ltr" class="mw-content-ltr">
''(Apologies for posting in English, if this is not your first language)''
Hello all! We opened a discussion on Meta about a very delicate issue for the development of [[:m:Special:MyLanguage/Abstract Wikipedia|Abstract Wikipedia]]: where to store the abstract content that will be developed through functions from Wikifunctions and data from Wikidata. Since some of the hypothesis involve your project, we wanted to hear your thoughts too.
We want to make the decision process clear: we do not yet know which option we want to use, which is why we are consulting here. We will take the arguments from the Wikimedia communities into account, and we want to consult with the different communities and hear arguments that will help us with the decision. The decision will be made and communicated after the consultation period by the Foundation.
You can read the various hypothesis and have your say at [[:m:Abstract Wikipedia/Location of Abstract Content|Abstract Wikipedia/Location of Abstract Content]]. Thank you in advance! -- [[User:Sannita (WMF)|Sannita (WMF)]] ([[User talk:Sannita (WMF)|<span class="signature-talk">{{int:Talkpagelinktext}}</span>]]) 22 mai 2025 à 17:26 (CEST)
</div>
<!-- Message envoyé par User:Sannita (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Sannita_(WMF)/Mass_sending_test&oldid=28768453 -->
== Actualités techniques n° 2025-22 ==
<section begin="technews-2025-W22"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/22|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Une discussion communautaire à l’échelle du mouvement est désormais ouverte sur Meta au sujet d’un point très délicat pour le développement de [[m:Special:MyLanguage/Abstract Wikipedia|« Abstract Wikipedia »]] : où stocker le contenu abstrait qui sera développé à partir des fonctions de Wikifunctions et des données de Wikidata. La discussion est ouverte jusqu’au 12 juin sur [[m:Special:MyLanguage/Abstract Wikipedia/Location of Abstract Content|Abstract Wikipedia/Location of Abstract Content]], et tous les avis sont les bienvenus. La décision sera prise et communiquée par la Fondation à l’issue de la période de consultation.
'''Actualités pour la contribution'''
* Depuis la semaine dernière, sur tous les wikis excepté [[phab:T388604|les vingt plus grands]], les utilisateurs de l’éditeur visuel mobile disposent de [[phab:T385851|nouveaux outils dans la barre de menu]], accessibles via le nouveau bouton de la barre d’outils <code>+</code>. Pour commencer, le nouveau menu proposera des options pour ajouter : des références, des hiéroglyphes et des blocs de code. Le déploiement sur les autres wikis est [[phab:T388605|prévu]] pour le mois de juin.
* [[File:Octicons-tools.svg|12px|link=|class=skin-invert|Sujet technique]] La fonction d’analyse <code dir=ltr>[[mw:Special:MyLanguage/Help:Extension:ParserFunctions##ifexist|#ifexist]]</code> ne créera plus de lien vers sa page cible. Cela améliorera l’utilité de [[{{#special:WantedPages}}]], qui ne listera à terme que les pages réellement ciblées par un lien rouge. Ce changement se fera progressivement à mesure que les pages sources seront mises à jour. [https://phabricator.wikimedia.org/T14019]
* Cette semaine, l’équipe des outils de modération va lancer [[mw:Special:MyLanguage/2025 RecentChanges Language Agnostic Revert Risk Filtering|un nouveau filtre dans les modifications récentes]], en commençant par la Wikipédia en indonésien. Ce nouveau filtre met en évidence les modifications susceptibles d’être annulées. L’objectif est d’aider les patrouilleurs des modifications récentes à repérer les contributions potentiellement problématiques. D’autres wikis bénéficieront de ce filtre à l’avenir.
* Lorsqu’ils cliquent sur une barre de recherche vide, les utilisateurs non connectés verront des suggestions d’articles à lire. Cette fonctionnalité sera disponible à la fois sur ordinateur et sur mobile. Les lecteurs des Wikipédias en catalan, hébreu et italien ainsi que de certains projets frères recevront ce changement entre le 21 mai et la mi-juin. Les lecteurs des autres wikis le recevront plus tard. L’objectif est d’encourager les utilisateurs à lire davantage les wikis. [[mw:Special:MyLanguage/Reading/Web/Content Discovery Experiments/Search Suggestions|En savoir plus]].
* Certains utilisateurs de l’application Wikipédia sur Android peuvent utiliser une nouvelle fonctionnalité destinée aux lecteurs : [[mw:Special:MyLanguage/Wikimedia Apps/Team/Android/TrivaGame|« WikiGames »]], un jeu quotidien de quiz basé sur des événements historiques réels. Le déploiement a commencé sous forme de test A/B, disponible pour 50 % des utilisateurs dans les langues suivantes : anglais, français, portugais, russe, espagnol, arabe, chinois et turc.
* L’[[mw:Special:MyLanguage/Extension:Newsletter|extension « Newsletter »]] disponible sur MediaWiki.org permet la création de [[mw:Special:Newsletters|divers bulletins d’information]] à destination des utilisateurs de tous les wikis. L’extension peut désormais publier de nouveaux numéros sous forme de liens vers des sections sur une page existante, au lieu de nécessiter une nouvelle page pour chaque numéro. [https://phabricator.wikimedia.org/T393844]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* Les champs <code dir=ltr>[[mw:Special:MyLanguage/Manual:Ipblocks table|ipblocks]]</code>, précédemment déclarés obsolètes, seront supprimés début juin dans les [[wikitech:Help:Wiki Replicas|Wiki Replicas]]. Il est recommandé aux utilisateurs d’interroger les nouveaux champs <code dir=ltr>[[mw:Special:MyLanguage/Manual:Block table|block]]</code> et <code dir=ltr>[[mw:Special:MyLanguage/Manual:Block target table|block_target]]</code> à la place.
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.45/wmf.3|MediaWiki]]
'''Rencontres et évènements'''
* [[d:Special:MyLanguage/Event:Wikidata and Sister Projects|« Wikidata et les projets frères »]] est un événement en ligne de plusieurs jours qui portera sur l’intégration de Wikidata à Wikipédia et aux autres projets Wikimedia. L’événement se déroulera du 29 mai au 1<sup>er</sup> juin. Vous pouvez [[d:Special:MyLanguage/Event:Wikidata and Sister Projects#Sessions|consulter le programme]] et [[d:Special:RegisterForEvent/1291|vous inscrire]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/22|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W22"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 26 mai 2025 à 22:04 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28788673 -->
== Sélection 2025 du conseil d'administration de la fondation Wikimédia et appel à questions ==
<section begin="announcement-content" />
:''[[m:Special:MyLanguage/Wikimedia Foundation elections/2025/Announcement/Selection announcement|{{int:interlanguage-link-mul}}]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-{{urlencode:Wikimedia Foundation elections/2025/Announcement/Selection announcement}}&language=&action=page&filter= {{int:please-translate}}]''
Chers tous et toutes,
Cette année, le mandat de deux administrateurs sélectionnés par la communauté et les affiliés au conseil d'administration de la Fondation Wikimédia prendra fin [1]. Le conseil invite l'ensemble du mouvement à participer au processus de sélection de cette année et à voter pour pourvoir ces sièges.
Le Comité des élections supervisera ce processus avec le soutien du personnel de la Fondation [2]. Le Comité de gouvernance, composé de membres du Conseil d'administration non-candidats au processus de sélection des membres du Conseil d'administration 2025 sélectionnés par la communauté et les affiliés (Raju Narisetti, Shani Evenstein Sigalov, Lorenzo Losa, Kathy Collins, Victoria Doronina et Esra'a Al Shafei) [3], est chargé de superviser le processus de sélection des membres du Conseil d'administration 2025 et de tenir le Conseil d'administration au courant de la situation. Pour plus de détails sur les rôles de la commission des élections, du conseil d'administration et du personnel, cliquez ici [4].
En voici les dates clés :
* 22 mai - 5 juin : Annonce ( la présente communication) et période d'appel à questions. [6]
* 17 juin - 1er juillet 2025 : Appel à candidatures
* Juillet 2025 : Si nécessaire, les affiliés votent pour présélectionner les candidats si 10 d'entre eux ou plus se présentent [5].
* Août 2025 : Période de la campagne
* Août - septembre 2025 : Période de vote communautaire de deux semaines
* Octobre - novembre 2025 : Vérification des antécédents des candidats sélectionnés
* Réunion du conseil d'administration en décembre 2025 : Installation des nouveaux membres du conseil d'administration
Pour en savoir plus sur le processus de sélection de 2025 - y compris le calendrier détaillé, le processus de candidature, les règles de la campagne et les critères d'éligibilité des électeurs -, veuillez consulter cette page Meta-wiki. [[m:Special:MyLanguage/Wikimedia_Foundation_elections/2025|[link]]].
'''Appel à questions'''
Lors de chaque processus de sélection, la communauté a la possibilité de soumettre des questions auxquelles les candidats au conseil d'administration devront répondre. Le comité électoral sélectionne les questions à partir de la liste établie par la communauté pour que les candidats y répondent. Les candidats doivent répondre à toutes les questions posées dans le dossier de candidature pour être éligibles, faute de quoi leur candidature sera rejetée. Cette année, le comité électoral sélectionnera 5 questions auxquelles les candidats devront répondre. Les questions sélectionnées peuvent être une combinaison de celles qui ont été soumises par la communauté, si elles sont similaires ou liées. [[m:Special:MyLanguage/Wikimedia_Foundation_elections/2025/Questions_for_candidates|[link]]]
'''Bénévoles des élections'''
Une autre façon de participer au processus de sélection de 2025 est de devenir bénévole des élections. Les bénévoles électoraux constituent un pont entre le comité électoral et leur communauté respective. Ils veillent à ce que leur communauté soit représentée et les incitent à voter. Pour en savoir plus sur le programme et les modalités d'adhésion, consultez cette page Meta-wiki. [[m:Wikimedia_Foundation_elections/2025/Election_volunteers|[link]]].
Je vous remercie !
[1] https://meta.wikimedia.org/wiki/Wikimedia_Foundation_elections/2022/Results
[2] https://foundation.wikimedia.org/wiki/Committee:Elections_Committee_Charter
[3] https://foundation.wikimedia.org/wiki/Resolution:Committee_Membership,_December_2024
[4] https://meta.wikimedia.org/wiki/Wikimedia_Foundation_elections_committee/Roles
[5] https://meta.wikimedia.org/wiki/Wikimedia_Foundation_elections/2025/FAQ
[6] https://meta.wikimedia.org/wiki/Wikimedia_Foundation_elections/2025/Questions_for_candidates
Bien à vous,
Victoria Doronina
Liaison du conseil d'administration avec le comité des élections
Comité de gouvernance<section end="announcement-content" />
[[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 28 mai 2025 à 05:07 (CEST)
<!-- Message envoyé par User:RamzyM (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=28618011 -->
== Actualités techniques n° 2025-23 ==
<section begin="technews-2025-W23"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/23|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L'[[mw:Special:MyLanguage/Extension:Chart|extension Chart]] est maintenant disponible sur tous les wikis Wikimedia. Les éditeurs peuvent utiliser cette nouvelle extension pour créer des visualisations de données interactives comme des diagrammes à barres, à lignes, avec des zones, et circulaires. Chart a été créée pour remplacer la plupart des utilisations de l'ancienne [[mw:Special:MyLanguage/Extension:Graph|extension Graph]].
'''Actualités pour la contribution'''
* Il est maintenant plus simple de configurer les citations automatiques pour votre wiki dans le [[mw:Special:MyLanguage/Citoid/Enabling Citoid on your wiki|générateur de citations]] de l'éditeur visuel. Les administrateurs peuvent maintenant définir un modèle par défaut en utilisant la clé <code dir=ltr>_default</code> dans la page locale <bdi lang="en" dir="ltr">[[MediaWiki:Citoid-template-type-map.json]]</bdi> ([[mw:Special:Diff/6969653/7646386|exemple de modification]]). Définir ce réglage par défaut permettra aussi de pérenniser vos configurations existantes lorsque de [[phab:T347823|nouveaux types d'objets]] seront ajoutés à l'avenir. Vous pouvez toujours définir des modèles pour des types d'objets individuels et ils seront prioritaires par rapport au modèle par défaut. [https://phabricator.wikimedia.org/T384709]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:20|la tâche soumise|les {{formatnum:20}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:20||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* À partir de la semaine du 2 juin, les robots qui utilisent <code dir=ltr>action=login</code> ou <code dir=ltr>action=clientlogin</code> pour s'authentifier auront un taux d'échec plus fréquent. Cela est dû à des protections plus fortes contre les connexions suspectes. Les robots qui utilisent des [[mw:Special:MyLanguage/Manual:Bot passwords|mots de passe de robots]] ou une authentification sans connexion telle que [[mw:Special:MyLanguage/OAuth/Owner-only consumers|OAuth]] ne seront pas affectés. Si votre bot n'utilise aucun des deux, vous devriez le mettre à jour ; utiliser <code dir=ltr>action=login</code> sans un mot de passe de robot a été rendu désuet [[listarchive:list/wikitech-l@lists.wikimedia.org/message/3EEMN7VQX5G7WMQI5K2GP5JC2336DPTD/|en 2016]]. Pour la plupart des robots, cela nécessite seulement de changer quel mot de passe ce dernier utilise. [https://phabricator.wikimedia.org/T395205]
* À partir de cette semaine, les wikis Wikimedia permettront des fonctionnalités ES2017 dans le code JavaScript pour le code officiel, les gadgets et les scripts utilisateurs. La fonctionnalité la plus visible d'ES2017 est la syntaxe <bdi lang="zxx" dir="ltr"><code>async</code>/<code>await</code></bdi>, ce qui permet un code plus facile à lire. Jusqu'à cette semaine, la plateforme ne permettait que jusqu'à ES2016, et quelques mois plus tôt, jusqu'à ES2015. [https://phabricator.wikimedia.org/T381537]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.45/wmf.4|MediaWiki]]
'''Rencontres et évènements'''
* Les demandes de bourse d'études pour participer à la [[m:Special:MyLanguage/GLAM Wiki 2025|conférence GLAM 2025]] sont maintenant ouvertes. La conférence aura lieu du 30 octobre au 1er novembre, à Lisbonne, au Portugal. Les contributeurs GLAM qui n'ont pas les moyens de financer leur participation peuvent [[m:Special:MyLanguage/GLAM Wiki 2025/Scholarships|faire une demande ici]]. La date limite de candidature est le 7 juin.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/23|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W23"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 3 juin 2025 à 01:54 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28819186 -->
== Actualités techniques n° 2025-24 ==
<section begin="technews-2025-W24"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2025/24|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L’[[mw:Special:MyLanguage/Trust and Safety Product|équipe produits Confiance et sûreté]] finalise les travaux nécessaires au déploiement des [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts|comptes temporaires]] sur les grandes Wikipédias plus tard ce mois-ci. L’équipe a collaboré avec les stewards et d’autres utilisateurs disposant de droits étendus afin d’anticiper et de traiter de nombreux cas d’usage qui pourraient se présenter sur les wikis de grande taille, afin que les membres des communautés puissent continuer à modérer et à patrouiller efficacement les comptes temporaires. Il s’agira de la deuxième des trois phases de déploiement ; la dernière aura lieu en septembre au plus tôt. Pour plus d’informations sur les développements récents du projet, [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts/Updates|voir cette mise à jour]]. Si vous avez des commentaires ou des questions, écrivez sur la [[mw:Talk:Trust and Safety Product/Temporary Accounts|page de discussion]] et [[m:Event:CEE Catch up Nr. 10 (June 2025)|rejoignez un « CEE Catch Up »]] ce mardi.
'''Actualités pour la contribution'''
* [[File:Octicons-gift.svg|12px|link=|class=skin-invert|Concerne un souhait]] La fonctionnalité [[mw:Special:MyLanguage/Help:Watchlist expiry|« expiration de la liste de suivi »]] permet aux contributeurs de suivre des pages pendant une durée limitée. Une fois ce délai écoulé, la page est automatiquement retirée de votre liste de suivi. À partir de cette semaine, vous pouvez définir une préférence pour la durée par défaut pendant laquelle vous souhaitez suivre les pages. Les [[Special:Preferences#mw-prefsection-watchlist-pageswatchlist|préférences]] permettent également de définir différentes durées par défaut selon que vous modifiez une page existante, que vous en créez une nouvelle ou que vous utilisez l’annulation rapide (''rollback''). [https://phabricator.wikimedia.org/T265716]
[[File:Talk pages default look (April 2023).jpg|thumb|alt=Capture d'écran des améliorations visuelles apportées aux pages de discussion|Exemple d'une page de discussion avec les améliorations, en français.]]
* L’apparence des pages de discussion va changer sur la quasi-totalité des Wikipédias ([[m:Special:MyLanguage/Tech/News/2024/19|certaines]] ont déjà reçu ce nouveau design, [[phab:T379264|quelques-unes]] le recevront plus tard). Vous pouvez lire les détails concernant ces changements [[diffblog:2024/05/02/making-talk-pages-better-for-everyone/|sur ''Diff'']]. Il est possible de désactiver ces modifications [[Special:Preferences#mw-prefsection-editing-discussion|dans les préférences utilisateur]] (« {{int:discussiontools-preference-visualenhancements}} »). [https://phabricator.wikimedia.org/T319146][https://phabricator.wikimedia.org/T392121]
* Les utilisateurs disposant de certains droits étendus (y compris les administrateurs, bureaucrates, vérificateurs, masqueurs et stewards) peuvent désormais voir les adresses IP de tous les comptes temporaires [[phab:T358853|révélées automatiquement]] pendant des périodes limitées, lorsqu’ils doivent lutter contre du vandalisme rapide impliquant des changements fréquents de compte. Cette fonctionnalité a été demandée par les stewards. [https://phabricator.wikimedia.org/T386492]
* Cette semaine, les équipes des outils de modération et d’apprentissage automatique poursuivent le déploiement [[mw:Special:MyLanguage/2025 RecentChanges Language Agnostic Revert Risk Filtering|d’un nouveau filtre dans les Modifications récentes]], en l’étendant à plusieurs autres Wikipédias. Ce filtre utilise le modèle « Revert Risk », développé par l’équipe de recherche, pour mettre en évidence les modifications susceptibles d’être annulées et aider les patrouilleurs à repérer les contributions potentiellement problématiques. La fonctionnalité sera déployée sur les Wikipédias suivantes : {{int:project-localized-name-afwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-bewiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-bnwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-cywiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-hawwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-iswiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-kkwiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-simplewiki/fr}}{{int:comma-separator/fr}}{{int:project-localized-name-trwiki/fr}}. Le déploiement se poursuivra dans les semaines à venir pour inclure [[mw:Special:MyLanguage/2025 RecentChanges Language Agnostic Revert Risk Filtering|le reste des Wikipédias concernées par ce projet]]. [https://phabricator.wikimedia.org/T391964]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* Les éditeurs de filtres anti-abus actifs sur Meta-Wiki et les grandes Wikipédias sont priés de mettre à jour les filtres pour les rendre compatibles avec les comptes temporaires. Un lien vers les instructions ainsi que vers les listes privées des filtres à vérifier est [[phab:T369611|disponible sur Phabricator]].
* Les modules Lua ont désormais accès au nom de l’image miniature associée à une page, et sur [https://gerrit.wikimedia.org/g/operations/mediawiki-config/+/2e4ab14aa15bb95568f9c07dd777065901eb2126/wmf-config/InitialiseSettings.php#10849 certains wikis], aux informations d’évaluation des WikiProjets. Cela est possible grâce à deux nouvelles propriétés des objets [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#added-by-extensions|mw.title]], nommées <code dir=ltr>pageImage</code> et <code dir=ltr>pageAssessments</code>. [https://phabricator.wikimedia.org/T131911][https://phabricator.wikimedia.org/T380122]
* [[File:Octicons-sync.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.45/wmf.5|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2025/24|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2025-W24"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10 juin 2025 à 03:16 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=28846858 -->
iz0dv31trcokxsmauzbgt5c5leapw43
Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation
0
82429
744339
744325
2025-06-09T15:05:06Z
Mewtow
31375
/* La virtualisation du CPU : le mode hyperviseur */
744339
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du CPU : le mode hyperviseur==
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le mode hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement d'un mode noyau et d'un mode utilisateur normaux, le mode noyau n'ayant alors aucune limitation précise. Mais quand le mode hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
==L'Intel VT-X et l'AMD-V==
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Elle n'ajoutent pas de niveaux de privilèges, mais dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Chaque mode contient sa propre copie des niveaux de privilège : il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, il n'a rien à voir avec le mode réel, protégé, v8086 ou autres, qui sont eux aussi en deux exemplaires.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute certaines instructions prédéfinies, ou quand il exécute certaines interruptions. La transition est immédiate, contrairement aux interruptions qui demandent de sauvegarder/restaurer l'état du processeur, ses registres, etc. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
ipytyvnuj3brujw8oshb7fi45t1he18
744340
744339
2025-06-09T15:17:10Z
Mewtow
31375
/* L'Intel VT-X et l'AMD-V */
744340
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du CPU : le mode hyperviseur==
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le mode hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement d'un mode noyau et d'un mode utilisateur normaux, le mode noyau n'ayant alors aucune limitation précise. Mais quand le mode hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
==L'Intel VT-X et l'AMD-V==
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V.
Elle n'ajoutent pas de niveaux de privilèges, mais dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Le mode racine mode contient sa propre copie des niveaux de privilège : il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, il n'a rien à voir avec le mode réel, protégé, v8086 ou autres, qui sont eux aussi en deux exemplaires.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à une instruction ''vmcall''. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défaut de page déclenchent l'entrée en mode racine.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. Pour cela, le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
1gqnqx6y9hucjuez25xvyg9wki1tdcg
744341
744340
2025-06-09T15:18:14Z
Mewtow
31375
/* L'Intel VT-X et l'AMD-V */
744341
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du CPU : le mode hyperviseur==
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le mode hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement d'un mode noyau et d'un mode utilisateur normaux, le mode noyau n'ayant alors aucune limitation précise. Mais quand le mode hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
==L'Intel VT-X et l'AMD-V==
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V.
Elle n'ajoutent pas de niveaux de privilèges, mais dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Le mode racine mode contient sa propre copie des niveaux de privilège : il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, il n'a rien à voir avec le mode réel, protégé, v8086 ou autres, qui sont eux aussi en deux exemplaires.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défaut de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. Pour cela, le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
4gezkllako26zb8crxlls48eruzu0r9
744342
744341
2025-06-09T15:25:11Z
Mewtow
31375
/* L'Intel VT-X et l'AMD-V */
744342
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du CPU : le mode hyperviseur==
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le mode hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement d'un mode noyau et d'un mode utilisateur normaux, le mode noyau n'ayant alors aucune limitation précise. Mais quand le mode hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
==L'Intel VT-X et l'AMD-V==
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V.
Elle n'ajoutent pas de niveaux de privilèges, mais dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Le mode racine mode contient sa propre copie des niveaux de privilège : il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, il n'a rien à voir avec le mode réel, protégé, v8086 ou autres, qui sont eux aussi en deux exemplaires.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. Pour cela, le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
mf8kmxwd9mlv1d35p5vabx43k9ymrui
744343
744342
2025-06-09T15:28:34Z
Mewtow
31375
/* L'Intel VT-X et l'AMD-V */
744343
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du CPU : le mode hyperviseur==
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le mode hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement d'un mode noyau et d'un mode utilisateur normaux, le mode noyau n'ayant alors aucune limitation précise. Mais quand le mode hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
==L'Intel VT-X et l'AMD-V==
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V.
Elle n'ajoutent pas de niveaux de privilèges, mais dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Le mode racine mode contient sa propre copie des niveaux de privilège : il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, il n'a rien à voir avec le mode réel, protégé, v8086 ou autres, qui sont eux aussi en deux exemplaires.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
6gotf27qb5jimjr5nl0o1vaenjv6493
744344
744343
2025-06-09T15:47:18Z
Mewtow
31375
/* L'Intel VT-X et l'AMD-V */
744344
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du CPU : le mode hyperviseur==
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le mode hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement d'un mode noyau et d'un mode utilisateur normaux, le mode noyau n'ayant alors aucune limitation précise. Mais quand le mode hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
==L'Intel VT-X et l'AMD-V==
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
e7jp3xy8mlhmi4earnl024wzn3vyhvi
744345
744344
2025-06-09T15:55:44Z
Mewtow
31375
/* La virtualisation du CPU : le mode hyperviseur */
744345
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
==L'Intel VT-X et l'AMD-V==
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
iq6x1cmlbhjqg0if0b6g70gp4v0zjyt
744346
744345
2025-06-09T15:55:50Z
Mewtow
31375
/* L'Intel VT-X et l'AMD-V */
744346
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''mode hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en mode noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en mode hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiées, qu'elle soit une instruction système ou non. Mais avec le mode hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
iusrg53k6a01hoig9p6pzky666ung0q
744347
744346
2025-06-09T15:56:38Z
Mewtow
31375
/* Le niveau de privilège hyperviseur */
744347
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
c2ido7t9sl94cc47lcv9rtdqofngal5
744348
744347
2025-06-09T15:57:09Z
Mewtow
31375
/* La méthode trap and emulate basique */
744348
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
6ht8jm21rxm0wiqknjypm7hcdkunm1z
744349
744348
2025-06-09T15:57:42Z
Mewtow
31375
/* La virtualisation de la mémoire RAM : l'extension des tables des pages emboitées */
744349
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
adltvbgofm7l7u2pmzc0ylit538rs60
744350
744349
2025-06-09T16:04:48Z
Mewtow
31375
/* L'extension des tables des pages emboitées */
744350
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
o26kjpo1a3f9n4h2w6kpf2rmy0qzdr4
744351
744350
2025-06-09T16:18:11Z
Mewtow
31375
/* L'extension des tables des pages emboitées */
744351
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Les périphériques doivent idéalement intégrer une IO-MMU pour gérer l'adressage mémoire.
===Les IO-MMU virtualisées===
L'IO-MMU est très importante pour virtualiser les IO, expliquons pourquoi. La raison est globalement la même que pour le partage de la mémoire : les adresses physiques utilisées par l'OS sont en réalité des adresses virtuelles. Pour ce qui est de l'adressage de la RAM, les adresses utilisées par l'OS sont traduites en adresses physiques via une table des page étendue, configurée par l'hyperviseur. Mais cela ne vaut que pour la RAM, pas pour les périphériques.
Le problème est que les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Et les pilotes de périphériques ne savent pas que ce sont des adresses virtuelles, ce qui pose problème avec l'usage du DMA. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
bc6c57reokua8l7yl2aagqtzzzmrjce
744352
744351
2025-06-09T16:28:59Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744352
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM.
===Les IO-MMU virtualisées===
Les périphériques doivent idéalement intégrer une IO-MMU pour gérer l'adressage mémoire. La raison est globalement la même que pour le partage de la mémoire : les adresses physiques utilisées par l'OS sont en réalité des adresses virtuelles. Pour ce qui est de l'adressage de la RAM, les adresses utilisées par l'OS sont traduites en adresses physiques via une table des page étendue, configurée par l'hyperviseur. Mais cela ne vaut que pour la RAM, pas pour les périphériques.
Le problème est que les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Et les pilotes de périphériques ne savent pas que ce sont des adresses virtuelles, ce qui pose problème avec l'usage du DMA. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
obse58ocm4hzd0y9uyooqwl3i4mlco8
744353
744352
2025-06-09T16:33:58Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744353
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===Les IO-MMU virtualisées===
Les périphériques doivent idéalement intégrer une IO-MMU pour gérer l'adressage mémoire. La raison est globalement la même que pour le partage de la mémoire : les adresses physiques utilisées par l'OS sont en réalité des adresses virtuelles. Pour ce qui est de l'adressage de la RAM, les adresses utilisées par l'OS sont traduites en adresses physiques via une table des page étendue, configurée par l'hyperviseur. Mais cela ne vaut que pour la RAM, pas pour les périphériques.
Le problème est que les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Et les pilotes de périphériques ne savent pas que ce sont des adresses virtuelles, ce qui pose problème avec l'usage du DMA. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
i3oaszk5g5zn2dz1gin2jtmf3pklp4x
744365
744353
2025-06-09T18:26:21Z
Mewtow
31375
/* Les IO-MMU virtualisées */
744365
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire RAM==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===Les IO-MMU virtualisées===
Les périphériques doivent idéalement intégrer une IO-MMU pour gérer l'adressage mémoire. La raison est globalement la même que pour le partage de la mémoire : les adresses physiques utilisées par l'OS sont en réalité des adresses virtuelles. Pour ce qui est de l'adressage de la RAM, les adresses utilisées par l'OS sont traduites en adresses physiques via une table des page étendue, configurée par l'hyperviseur. Mais cela ne vaut que pour la RAM, pas pour les périphériques.
Le problème est que les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Et les pilotes de périphériques ne savent pas que ce sont des adresses virtuelles, ce qui pose problème avec l'usage du DMA. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle. Et pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
oau1sgdw4oodpw4jpqoyys6bjhdjeki
744380
744365
2025-06-09T20:53:57Z
Mewtow
31375
/* La virtualisation de la mémoire RAM */
744380
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===Les IO-MMU virtualisées===
Les périphériques doivent idéalement intégrer une IO-MMU pour gérer l'adressage mémoire. La raison est globalement la même que pour le partage de la mémoire : les adresses physiques utilisées par l'OS sont en réalité des adresses virtuelles. Pour ce qui est de l'adressage de la RAM, les adresses utilisées par l'OS sont traduites en adresses physiques via une table des page étendue, configurée par l'hyperviseur. Mais cela ne vaut que pour la RAM, pas pour les périphériques.
Le problème est que les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Et les pilotes de périphériques ne savent pas que ce sont des adresses virtuelles, ce qui pose problème avec l'usage du DMA. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle. Et pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
drxp3jrtset4r0q10p3r9h4blolvwcd
744381
744380
2025-06-09T20:56:18Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744381
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===L'extension des tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle. Et pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
t9rbhcj7t9bu0vlbcomn3ni8mgxg4px
744382
744381
2025-06-09T20:56:32Z
Mewtow
31375
/* L'extension des tables des pages emboitées */
744382
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle. Et pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
b6cdetfbr1lxfkztfu3yvusbknnrdde
744383
744382
2025-06-09T20:57:58Z
Mewtow
31375
/* La virtualisation de l'IO-MMU */
744383
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
b3thdg7sm52k02xlhzqa029mw154cwj
744384
744383
2025-06-09T20:58:31Z
Mewtow
31375
/* La MMU et la virtualisation : les tables des pages emboitées */
744384
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtulles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
1z8jszr5jw26o81g2mug3rlew5sat9b
744385
744384
2025-06-09T21:03:19Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744385
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important. De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
qqqq64az1rlrcqgv3qtlnse17p8owz1
744386
744385
2025-06-09T21:07:10Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744386
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
9tt31hqvt7ov2h6gwkz49u7o8fcaw8q
744387
744386
2025-06-09T22:03:58Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744387
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
Virtualiser les entrées-sorties efficacement est assez complexe. Si virtualiser les entrées-sorties demande seulement l'intervention de l'hyperviseur, le faire avec des bonnes performances demande des modifications du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
gp8i11i3i5fpioeuvomv73opmyk1cfk
744388
744387
2025-06-09T22:08:08Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744388
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
===La virtualisation accélérée en matériel des périphériques===
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous. Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés.
Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===La virtualisation des interruptions===
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
1yy031o8nd6yb6c2ymrrwooowrt6kzy
744389
744388
2025-06-09T22:14:36Z
Mewtow
31375
/* La virtualisation accélérée en matériel des périphériques */
744389
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
===La virtualisation des périphériques accélérée en matériel===
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===La virtualisation des interruptions===
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
9du4aa2unioqdbuqqqfkrdjvekv5nws
744390
744389
2025-06-09T22:19:23Z
Mewtow
31375
/* La virtualisation des périphériques accélérée en matériel */
744390
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
===La virtualisation des périphériques accélérée en matériel===
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' est une amélioration de la technique précédente. Elle demande que le périphérique incorpore une IO-MMU, sans quoi elle ne peut pas fonctionner. L'idée est que les différents OS ont accès au périphérique chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit être prévu pour.
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===La virtualisation des interruptions===
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
d6zqzzkpy7kirabe9xvx5x4kd6q4acy
744391
744390
2025-06-09T22:20:57Z
Mewtow
31375
/* La virtualisation des périphériques accélérée en matériel */
744391
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
===La virtualisation des périphériques accélérée en matériel===
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' donne l'accès au périphérique aux OS chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit idéalement être prévu pour. Si ce n'est pas le cas, l'hyperviseur peut théoriquement lire tous les registres du périphérique et les sauvegarder, mais cela ne fonctionne pas toujours.
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===La virtualisation des interruptions===
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
scovf1fvyon792x7nion4pifkmm6qv8
744392
744391
2025-06-09T22:26:45Z
Mewtow
31375
/* La virtualisation des périphériques accélérée en matériel */
744392
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
===La virtualisation des périphériques : en logiciel et en matériel===
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' donne l'accès au périphérique aux OS chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit idéalement être prévu pour. Si ce n'est pas le cas, l'hyperviseur peut théoriquement lire tous les registres du périphérique et les sauvegarder, mais cela ne fonctionne pas toujours.
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
===La virtualisation des interruptions===
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
lfihmb0q2h318acigs5p9intvlixndy
744393
744392
2025-06-09T22:27:06Z
Mewtow
31375
/* La virtualisation des entrées-sorties */
744393
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
===La virtualisation des périphériques en logiciel===
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' donne l'accès au périphérique aux OS chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit idéalement être prévu pour. Si ce n'est pas le cas, l'hyperviseur peut théoriquement lire tous les registres du périphérique et les sauvegarder, mais cela ne fonctionne pas toujours.
===La virtualisation des interruptions===
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Mais faire cela a un cout en performance assez important.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
goeylma4lj04958dyhyiw4cr5v3fsey
744394
744393
2025-06-09T22:34:02Z
Mewtow
31375
/* La virtualisation des interruptions */
744394
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
===La virtualisation des périphériques en logiciel===
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' donne l'accès au périphérique aux OS chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit idéalement être prévu pour. Si ce n'est pas le cas, l'hyperviseur peut théoriquement lire tous les registres du périphérique et les sauvegarder, mais cela ne fonctionne pas toujours.
===La virtualisation des interruptions===
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU. L'hyperviseur gére les différents vecteurs d'interruption de chaque VM et traduit les interruptions recues en interruptions destinées aux VM/OS.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
dski95llywdol4c7tdfui8wtnevsg9s
744395
744394
2025-06-09T22:37:03Z
Mewtow
31375
/* La virtualisation des interruptions */
744395
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
===La virtualisation des périphériques en logiciel===
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' donne l'accès au périphérique aux OS chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit idéalement être prévu pour. Si ce n'est pas le cas, l'hyperviseur peut théoriquement lire tous les registres du périphérique et les sauvegarder, mais cela ne fonctionne pas toujours.
===La virtualisation des interruptions===
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. En théorie, toutes les interruptions matérielles sont traitées par les routines de l'hyperviseur. Le processeur bascule en niveau de privilège hyperviseur s'il en a un. La routine de l'hyperviseur laisse ensuite la main au système d'exploitation une fois qu'elle a finit son travail. Le système d'exploitation exécute alors sa routine d'interruption, puis communique avec le contrôleur d'interruption pour lui dire qu'il a terminé son travail. Cette communication demande d'interagir avec le contrôleur d'interruption, donc avec le matériel, ce qui déclenche une exception qui appelle l'hyperviseur. L'hyperviseur reçoit alors la communication et signale au contrôleur d'interruption que l'interruption matérielle a été traitée.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU. L'hyperviseur gére les différents vecteurs d'interruption de chaque VM et traduit les interruptions recues en interruptions destinées aux VM/OS.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
nljz8orl1qxn0qdhdxwlzliu4xef513
744396
744395
2025-06-09T22:37:40Z
Mewtow
31375
/* La virtualisation des interruptions */
744396
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
===La virtualisation des périphériques en logiciel===
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' donne l'accès au périphérique aux OS chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit idéalement être prévu pour. Si ce n'est pas le cas, l'hyperviseur peut théoriquement lire tous les registres du périphérique et les sauvegarder, mais cela ne fonctionne pas toujours.
===La virtualisation des interruptions===
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. Les interruptions matérielles sont traitées par les routines de l'hyperviseur. Lors d'une interruption matérielle, le processeur bascule en niveau de privilège hyperviseur s'il en a un et exécute la routine adéquate de l'hyperviseur. La routine de l'hyperviseur laisse ensuite la main au système d'exploitation une fois qu'elle a finit son travail. Le système d'exploitation exécute alors sa routine d'interruption, puis communique avec le contrôleur d'interruption pour lui dire qu'il a terminé son travail. Cette communication demande d'interagir avec le contrôleur d'interruption, donc avec le matériel, ce qui déclenche une exception qui appelle l'hyperviseur. L'hyperviseur reçoit alors la communication et signale au contrôleur d'interruption que l'interruption matérielle a été traitée.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU. L'hyperviseur gére les différents vecteurs d'interruption de chaque VM et traduit les interruptions recues en interruptions destinées aux VM/OS.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
onli49cikv01qewxk9k73nuvrykgvni
744397
744396
2025-06-09T22:57:05Z
Mewtow
31375
/* La virtualisation des interruptions */
744397
wikitext
text/x-wiki
La virtualisation est l'ensemble des techniques qui permettent de faire tourner plusieurs systèmes d'exploitation en même temps. Le terme est polysémique, mais c'est la définition que nous allons utiliser pour ce qui nous intéresse. La virtualisation demande d'utiliser un logiciel dit '''hyperviseur''', qui permet de faire tourner plusieurs OS en même temps. Les hyperviseurs 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, de système d'exploitation pour les systèmes d'exploitation.
[[File:Diagramme ArchiHyperviseur.png|centre|vignette|upright=2|Différence entre système d'exploitation et hyperviseur.]]
Les processeurs modernes intègrent des techniques pour accélérer matériellement la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des techniques de duplication de ces mêmes modes. Les techniques de virtualisation demandent de détourner des interruptions, ce qui fait que ces techniques d'accélération demandent de modifier la manière dont le processeur gère les interruptions. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.
==La virtualisation : généralités==
Pour faire tourner plusieurs OS en même temps, l'hyperviseur recourt à de nombreux stratagèmes. Il doit partager le processeur, la RAM et les entrées-sorties entre plusieurs OS. Les trois demandent des techniques différentes. Le partage de la RAM demande concrètement des modifications au niveau de la mémoire virtuelle. Le partage du processeur est assez simple : les OS s'exécutent à tour de rôle sur le processeur, chacun pendant un temps défini, fixe. Une fois leur temps d'exécution passé, ils laissent la main à l'OS suivant. C'est l’hyperviseur qui s'occupe de tout cela, grâce à une interruption commandée à un ''timer''. Ce système de partage est une forme de '''multiplexage'''.
La gestion des entrées-sorties demande d'utiliser des techniques d''''émulation''', plus complexes à expliquer. Un hyperviseur peut parfaitement simuler du matériel qui n'est pas installé sur l'ordinateur. Par exemple, il peut faire croire à un OS qu'une carte réseau obsolète, datant d'il y a 20 ans, est installée sur l'ordinateur, alors que ce n'est pas le cas. Les commandes envoyées par l'OS à cette carte réseau fictive sont en réalité traitées par une vraie carte réseau par l’hyperviseur. Pour cela, l’hyperviseur intercepte les commandes envoyées aux entrées-sorties, et les traduit en commandes compatibles avec les entrées-sorties réellement installées sur l'ordinateur.
===Les machines virtuelles===
L'exemple avec la carte réseau est un cas particulier, l'hyperviseur faisant beaucoup de choses dans le genre. L'hyperviseur peut faire croire à l'ordinateur qu'il a plus ou moins de RAM que ce qui est réellement installé, par exemple. L'hyperviseur implémente ce qu'on appelle des '''machines virtuelles'''. Il s'agit d'une sorte de faux matériel, simulé par un logiciel. Un logiciel qui s’exécute dans une machine virtuelle aura l'impression de s’exécuter sur un matériel et/ou un O.S différent du matériel sur lequel il est en train de s’exécuter.
: Dans ce qui suit, nous parlerons de V.M (virtual machine), pour parler des machines virtuelles.
[[File:VM-monitor-french.png|centre|vignette|upright=2|Machines virtuelles avec la virtualisation.]]
Avec la virtualisation, plusieurs machines virtuelles sont gérées par l'hyperviseur, chacune étant réservée à un système d'exploitation. D'ailleurs, hyperviseurs sont parfois appelés des ''Virtual Machine Manager''. Nous utiliserons d'ailleurs l'abréviation VMM dans les schémas qui suivent. Il existe deux types d'hyperviseurs, qui sont nommés type 1 et type 2. Le premier type s'exécute directement sur le matériel, alors que le second est un logiciel qui s’exécute sur un OS normal. Pour ce qui nous concerne, la distinction n'est pas très importante.
[[File:Ansatz der Systemvirtualisierung zur Schaffung virtueller Betriebsumgebungen.png|centre|vignette|upright=2.5|Comparaison des différentes techniques de virtualisation : sans virtualisation à gauche, virtualisation de type 1 au milieu, de type 2 à droite.]]
La virtualisation est une des utilisations possibles, mais il y en a d'autres. La plus intéressante est celle des émulateurs. Ces derniers sont des logiciels qui permettent de simuler le fonctionnement d'anciens ordinateurs ou consoles de jeux. L'émulateur crée une machine virtuelle qui est réservée à un programme, à savoir le jeu à émuler.
Il y a une différence de taille entre un émulateur et un hyperviseur. L'émulation émule une machine virtuelle totalement différente, alors que la virtualisation doit émuler les entrées-sorties mais pas le processeur. Avec un hyperviseur, le système d'exploitation s'exécute sur le processeur lui-même. Le code de l'OS est compatible avec le processeur de la machine, dans le sens où il est compilé pour le jeu d'instruction du processeur de la machine réelle. Les instructions de l'OS s'exécutent directement. Par contre, un émulateur exécute un jeu qui est programmé pour une machine dont le processeur est totalement différent. Le jeu d'instruction de la machine virtuelle et celui du vrai processeur n'est pas le même. L'émulation implique donc de traduire les instructions à exécuter dans la V.M par des instructions exécutables par le processeur. Ce n'est pas le cas avec la virtualisation, le jeu d'instruction étant le même.
[[File:Diagramme ArchiEmulateurNonNatif.png|centre|vignette|upright=2|Emulateur]]
===La méthode ''trap and emulate'' basique===
Pour être considéré comme un logiciel de virtualisation, un logiciel doit remplir trois critères :
* L'équivalence : l'O.S virtualisé et les applications qui s’exécutent doivent se comporter comme s'ils étaient exécutés sur le matériel de base, sans virtualisation.
* Le contrôle des ressources : tout accès au matériel par l'O.S virtualisé doit être intercepté par la machine virtuelle et intégralement pris en charge par l'hyperviseur.
* L'efficacité : La grande partie des instructions machines doit s’exécuter directement sur le processeur, afin de garder des performances correctes. Ce critère n'est pas respecté par les émulateurs matériels, qui doivent simuler le jeu d'instruction du processeur émulé.
Remplir ces trois critères est possible sous certaines conditions, établies par les théorèmes de Popek et Goldberg. Ces théorèmes se basent sur des hypothèses précises. De fait, la portée de ces théorèmes est limitée, notamment pour le critère de performance. Ils partent notamment du principe que l'ordinateur utilise la segmentation pour la mémoire virtuelle, et non la pagination. Il part aussi du principe que les interruptions ont un cout assez faible, qu'elles sont assez rares. Mais laissons ces détails de côté, le cœur de ces théorèmes repose sur une hypothèse simple : la présence de différents types d'instructions machines.
Pour rappel, il faut distinguer les instructions privilégiées de celles qui ne le sont pas. Les instructions privilégiées ne peuvent s'exécuter que en mode noyau, les programmes en mode utilisateur ne peuvent pas les exécuter. Parmi les instructions privilégiées on peut distinguer un sous-groupe appelé les '''instructions systèmes'''. Le premier type regroupe les '''instructions d'accès aux entrées-sorties''', aussi appelées instructions sensibles à la configuration. Le second type est celui des '''instructions de configuration du processeur''', qui agissent sur les registres de contrôle du processeur, aussi appelées instructions sensibles au comportement. Elles servent notamment à gérer la mémoire virtuelle, mais pas que.
La théorie de Popek et Goldberg dit qu'il est possible de virtualiser un O.S à une condition : que les instructions systèmes soient toutes des instructions privilégiées, c’est-à-dire exécutables seulement en mode noyau. Virtualiser un O.S demande simplement de le démarrer en mode utilisateur. Quand l'O.S fait un accès au matériel, il le fait via une instruction privilégiée. Vu que l'OS est en mode utilisateur, cela déclenche une exception matérielle, qui émule l'instruction privilégiée.
L'hyperviseur n'est ni plus ni moins qu'un ensemble de routines d'interruptions, chaque routine simulant le fonctionnement du matériel émulé. Par exemple, un accès au disque dur sera émulé par une routine d'interruption, qui utilisera les appels systèmes fournit par l'OS pour accéder au disque dur réellement présent dans l'ordinateur. Cette méthode est souvent appelée la méthode ''trap and emulate''.
[[File:Virtualisation avec la méthode trap-and-emulate.png|centre|vignette|upright=2.0|Virtualisation avec la méthode trap-and-emulate]]
La méthode ''trap and emulate'' ne fonctionne que si certaines contraintes sont respectées. Un premier problème est que beaucoup de jeux d'instructions anciens ne respectent pas la règle "les instructions systèmes sont toutes privilégiées". Par exemple, ce n'est pas le cas sur les processeurs x86 32 bits. Sur ces CPU, les instructions qui manipulent les drapeaux d'interruption ne sont pas toutes des instructions privilégiées, idem pour les instructions qui manipulent les registres de segmentation, celles liées aux ''call gates'', etc. A cause de cela, il est impossible d'utiliser la méthode du ''trap and emulate''. La seule solution qui ne requiert pas de techniques matérielles est de traduire à la volée les instructions systèmes problématiques en appels systèmes équivalents, grâce à des techniques de '''réécriture de code'''.
Enfin, certaines instructions dites '''sensibles au contexte''' ont un comportement différent entre le mode noyau et le mode utilisateur. En présence de telles instructions, la méthode ''trap and emulate'' ne fonctionne tout simplement pas. Grâce à ces instructions, le système d’exploitation ou un programme applicatif peut savoir s'il s'exécute en mode utilisateur ou noyau, ou hyperviseur, ou autre.
La virtualisation impose l'usage de la mémoire virtuelle, sans quoi plusieurs OS ne peuvent pas se partager la même mémoire physique. De plus, il ne faut pas que la mémoire physique, non-virtuelle, puisse être adressée directement. Et cette contrainte est violée, par exemple sur les architectures MIPS qui exposent des portions de la mémoire physique dans certaines zones fixées à l'avance de la mémoire virtuelle. L'OS est compilé pour utiliser ces zones de mémoire pour accéder aux entrées-sorties mappées en mémoire, entre autres. En théorie, on peut passer outre le problème en marquant ces zones de mémoire comme inaccessibles, toute lecture/écriture à ces adresses déclenche alors une exception traitée par l'hyperviseur. Mais le cout en performance est alors trop important.
Quelques hyperviseurs ont été conçus pour les architectures MIPS, dont le projet de recherche DISCO, mais ils ne fonctionnaient qu'avec des systèmes d'exploitation recompilés, de manière à passer outre ce problème. Les OS étaient recompilés afin de ne pas utiliser les zones mémoire problématiques. De plus, les OS étaient modifiés pour améliorer les performances en virtualisation. Les OS disposaient notamment d'appels systèmes spéciaux, appelés des ''hypercalls'', qui exécutaient des routines de l'hyperviseur directement. Les appels systèmes faisant appel à des instructions systèmes étaient ainsi remplacé par des appels système appelant directement l'hyperviseur. Le fait de modifier l'OS pour qu'il communique avec un hyperviseur, dont il a connaissance de l'existence, s'appelle la '''para-virtualisation'''.
[[File:Virtualization - Para vs Full.png|centre|vignette|upright=2.5|Virtualization - Para vs Full]]
==La virtualisation du processeur==
La virtualisation demande de partager le matériel entre plusieurs machines virtuelles. Précisément, il faut partager : le processeur, la mémoire RAM, les entrées-sorties. Les trois sont gérés différemment. Par exemple, la virtualisation des entrées-sorties est gérée par l’hyperviseur, parfois aidé par le ''chipset'' de la carte mère. Virtualiser des entrées-sorties demande d'émuler du matériel inexistant, mais aussi de dupliquer des entrées-sorties de manière à ce le matériel existe dans chaque VM. Partager la mémoire RAM entre plusieurs VM est assez simple avec la mémoire virtuelle, bien que cela demande quelques adaptations. Maintenant, voyons ce qu'il en est pour le processeur.
===Le niveau de privilège hyperviseur===
Sur certains CPU modernes, il existe un niveau de privilège appelé le '''niveau de privilège hyperviseur''' qui est utilisé pour les techniques de virtualisation. Le niveau de privilège hyperviseur est réservé à l’hyperviseur et il a des droits d'accès spécifiques. Il n'est cependant pas toujours activé. Par exemple, si aucun hyperviseur n'est installé sur la machine, le processeur dispose seulement des niveaux de privilège noyau et utilisateur, le mode noyau n'ayant alors aucune limitation précise. Mais quand le niveau de privilège hyperviseur est activé, une partie des manipulations est bloquée en mode noyau et n'est possible qu'en mode hyperviseur.
Le fonctionnement se base sur la différence entre instruction privilégiée et instruction système. Les instructions privilégiées peuvent s'exécuter en niveau noyau, alors que les instructions systèmes ne peuvent s'exécuter qu'en niveau hyperviseur. L'idée est que quand le noyau d'un OS exécute une instruction système, une exception matérielle est levée. L'exception bascule en mode hyperviseur et laisse la main à une routine de l'hyperviseur. L'hyperviseur fait alors des manipulations précise pour que l'instruction système donne le même résultat que si elle avait été exécutée par l'ordinateur simulé par la machine virtuelle.
[[File:Virtualisation avec un mode hyperviseur.png|centre|vignette|upright=2|Virtualisation avec un mode hyperviseur.]]
Il est ainsi possible d'émuler des entrées-sorties avec un cout en performance assez léger. Précisément, ce mode hyperviseur améliore les performances de la méthode du ''trap-and-emulate''. La méthode ''trap-and-emulate'' basique exécute une exception matérielle pour toute instruction privilégiée, qu'elle soit une instruction système ou non. Mais avec le niveau de privilège hyperviseur, seules les instructions systèmes déclenchent une exception, pas les instructions privilégiées non-système. Les performances sont donc un peu meilleures, pour un résultat identique. Après tout, les entrées-sorties et la configuration du processeur suffisent à émuler une machine virtuelle, les autres instructions noyau ne le sont pas.
Sur les processeurs ARM, il est possible de configurer quelles instructions sont détournées vers le mode hyperviseur et celles qui restent en mode noyau. En clair, on peut configurer quelles sont les instructions systèmes et celles qui sont simplement privilégiées. Et il en est de même pour les interruptions : on peut configurer si elles exécutent la routine de l'OS normal en mode noyau, ou si elles déclenchent une exception matérielle qui redirige vers une routine de l’hyperviseur. En l'absence d'hyperviseur, toutes les interruptions redirigent vers la routine de l'OS normale, vers le mode noyau.
Il faut noter que le mode hyperviseur n'est compatible qu'avec les hyperviseurs de type 1, à savoir ceux qui s'exécutent directement sur le matériel. Par contre, elle n'est pas compatible avec les hyperviseurs de type 2, qui sont des logiciels qui s'exécutent comme tout autre logiciel, au-dessus d'un système d'exploitation sous-jacent.
===L'Intel VT-X et l'AMD-V===
Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. A la place, ils incorporent des technologies alternatives nommées Intel VT-X ou l'AMD-V. Les deux ajoutent de nouvelles instructions pour gérer l'entrée et la sortie d'un mode réservé à l’hyperviseur. Mais ce mode réservé à l'hyperviseur n'est pas un niveau de privilège comme l'est le mode hyperviseur.
L'Intel VT-X et l'AMD-V dupliquent le processeur en deux modes de fonctionnement : un mode racine pour l'hyperviseur, un mode non-racine pour l'OS et les applications. Fait important : les niveaux de privilège sont dupliqués eux aussi ! Par exemple, il y a un mode noyau racine et un mode noyau non-racine, idem pour le mode utilisateur, idem pour le mode système (pour le BIOS/UEFI). De même, les modes réel, protégé, v8086 ou autres, sont eux aussi dupliqués en un exemplaire racine et un exemplaire non-racine.
L'avantage est que les systèmes d'exploitation virtualisés s'exécutent bel et bien en mode noyau natif, l'hyperviseur a a sa disposition un mode noyau séparé. D'ailleurs, les deux modes ont des registres d'interruption différents. Le mode racine et le mode non-racine ont chacun leurs espaces d'adressage séparés de 64 bit, avec leur propre table des page. Et cela demande des adaptations au niveau de la TLB.
La transition entre mode racine et non-racine se fait lorsque le processeur exécute une instruction système ou lors de certaines interruptions. Au minimum, toute exécution d'une instruction système fait commuter le processeur mode racine et lance l'exécution des routines de l’hyperviseur adéquates. Les interruptions matérielles et exceptions font aussi passer le CPU en mode racine, afin que l’hyperviseur puisse gérer le matériel. De plus, afin de gérer le partage de la mémoire entre OS, certains défauts de page déclenchent l'entrée en mode racine. Les ''hypercalls'' de la para-virtualisation sont supportés grâce à aux instructions ''vmcall'' et ''vmresume'' qui permettent respectivement d'appeler une routine de l’hyperviseur ou d'en sortir.
La transition demande de sauvegarder/restaurer les registres du processeur, comme avec les interruptions. Mais cette sauvegarde est réalisée automatiquement par le processeur, elle n'est pas faite par les routines de l'hyperviseur. L’implémentation de cette sauvegarde/restauration se fait surtout via le microcode du processeur, car elle demande beaucoup d'étapes. Elle est en conséquence très lente.
Le processeur sauvegarde l'état de chaque machine virtuelle en mémoire RAM, dans une structure de données appelée la ''Virtual Machine Control Structure'' (VMCS). Elle mémorise surtout les registres du processeur à l'instant t. Lorsque le processeur démarre l'exécution d'une VM sur le processeur, cette VMCS est recopiée dans les registres pour rétablir la VM à l'endroit où elle s'était arrêtée. Lorsque la VM est interrompue et doit laisser sa place à l'hyperviseur, les registres et l'état du processeur sont sauvegardés dans la VMCS adéquate.
==La virtualisation de la mémoire : mémoire virtuelle et MMU==
Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique. Et ce partage doit isoler les différents OS les uns des autres. Il est possible de comparer cette isolation avec l'isolation des processus : chaque OS doit avoir sa propre mémoire physique rien qu'à lui. L'idée est d'utiliser la mémoire virtuelle pour cela.
L'espace d'adressage physique vu par chaque OS est en réalité virtualisé, c’est-à-dire que c'est un espace d'adressage fictif, qui ne correspond pas à la mémoire virtuelle. Les adresses physiques vues par l'OS sont en réalité des adresses intermédiaires entre les adresses physiques liées à la RAM, et les adresses virtuelles vues par les processus. Pour les distinguer, nous parlerons d'adresses physiques de l'hôte pour parler des adresses de la RAM, et des adresses physiques invitées pour parler des adresses manipulées par les OS virtualisés. La raison est que les OS sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.
Sans accélération matérielle, la traduction des adresses physiques invitées en adresses hôte est réalisée par une seconde table des pages, appelée la ''shadow page table'', ce qui donnerait '''table des pages cachée''' en français. La table des pages cachée est prise en charge par l'hyperviseur. Toute modification de la table des pages cachée est réalisée par l'hyperviseur, les OS ne savent même pas qu'elle existe.
[[File:Shadowpagetables.png|centre|vignette|upright=2|Table des pages cachée.]]
===La MMU et la virtualisation : les tables des pages emboitées===
Une autre solution demande un support matériel des tables des pages emboitées, à savoir qu'il y a un arbre de table des pages, chaque consultation de la première table des pages renvoie vers une seconde, qui renvoie vers une troisième, et ainsi de suite jusqu'à tomber sur la table des pages finale qui renvoie l'adresse physique réelle.
L'idée est l'utiliser une seule table des pages, mais d'ajouter un ou deux niveaux supplémentaires. Pour l'exemple, prenons le cas des processeurs x86. Sans virtualisation, l'OS utilise une table des pages de 4 niveaux. Avec, la table des pages a un niveau en plus, qui sont ajoutés à la fin de la dernière table des pages normale. Les niveaux ajoutés s'occupent de la traduction des adresses physiques invitées en adresses physiques hôte. On parle alors de '''table des pages étendues''' pour désigner ce nouveau format de table des pages conçu pour la virtualisation.
Il faut que le processeur soit modifié de manière à parcourir automatiquement les niveaux ajoutés, ce qui demande quelques modifications de la TLB et du ''page table walker''. Les modifications en question ne font que modifier le format normal de la table des page, et sont donc assez triviales. Elles ont été implémentées sur les processeurs AMD et Intel. AMD a introduit les tables des pages étendues sur ses processeurs Opteron, destinés aux serveurs, avec sa technologie ''Rapid Virtualization Indexing''. Intel, quant à lui, a introduit la technologie sur les processeurs i3, i5 et i7, sous le nom ''Extended Page Tables''. Les processeurs ARM ne sont pas en reste avec la technologie ''Stage-2 page-tables'', qui est utilisée en mode hyperviseur.
===La virtualisation de l'IO-MMU===
Si la MMU du processeur est modifiée pour gérer des tables des pages étendues, il en est de même pour les IO-MMU des périphériques et contrôleurs DMA.Les périphériques doivent idéalement intégrer une IO-MMU pour faciliter la virtualisation. La raison est globalement la même que pour le partage de la mémoire. Les pilotes de périphériques utilisent des adresses qui sont des adresses physiques sans virtualisation, mais qui deviennent des adresses virtuelles avec. Quand le pilote de périphérique configure un contrôleur DMA, pour transférer des données de la RAM vers un périphérique, il utilisera des adresses virtuelles qu'il croit physique pour adresser les données en RAM.
Pour éviter tout problème, le contrôleur DMA doit traduire les adresses qu'il reçoit en adresses physiques. Pour cela, il y a besoin d'une IO-MMU intégrée au contrôleur DMA, qui est configurée par l'hyperviseur. Toute IO-MMU a sa propre table des page et l'hyperviseur configure les table des pages pour chaque périphérique. Ainsi, le pilote de périphérique manipule des adresses virtuelles, qui sont traduite en adresses physiques directement par le matériel lui-même, sans intervention logicielle.
Pour gérer la virtualisation, on fait la même chose qu'avec une table des page emboitée habituelle : on l'étend en ajoutant des niveaux. L'IO-MMU peut fonctionner dans un mode normal, sans virtualisation, où les adresses virtuelles recues du ''driver'' sont traduite avec une table des page normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.
==La virtualisation des entrées-sorties==
Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des port IO, soit avec des entrées-sorties mappées en mémoire. Le périphérique répond avec des interruptions ou via des transferts DMA. Virtualiser les périphériques demande alors d'émuler les ports IO, les entrées-sorties mappées en mémoire, le DMA et les interruptions.
===La virtualisation des périphériques en logiciel===
Émuler les ports IO est assez simple, vu que l'OS lit ou écrit dedans grâce à des instructions IO spécialisées. Vu que ce sont des instructions système, la méthode ''trap and emulate'' suffit. Pour les entrées-sorties mappées en mémoire, l'hyperviseur a juste à marquer les adresses mémoires concernées comme étant réservées/non-allouées/autre. Tout accès à ces adresses lèvera une exception matérielle d'accès mémoire interdit, que l’hyperviseur intercepte et gère via ''trap and emulate''. L'émulation du DMA est triviale, vu que l'hyperviseur a accès direct à celui-ci, sans compter que l'usage d'une IO-MMU résout beaucoup de problèmes. La gestion des interruptions est quant à elle plus complexe.
Si la méthode ''trap and emulate'' fonctionne, ses performances ne sont cependant pas forcément au rendez-vous.Tous les matériels ne se prêtent pas tous bien à la virtualisation, surtout les périphériques anciens. Pour éliminer une partie de ces problèmes, il existe différentes techniques, accélérées en matériel ou non. Elles permettent aux machines virtuelles de communiquer directement avec les périphériques, sans passer par l'hyperviseur.
La première est l''''assignement direct''', qui alloue un périphérique à une machine virtuelle et pas aux autres. Par exemple, il est possible d'assigner la carte graphique à une machine virtuelle tournant sur Windows, mais les autres machines virtuelles ne verront même pas la carte graphique. Même l'hyperviseur n'a pas accès directement à ce matériel. L'assignement direct est très utile sur les serveurs, qui disposent souvent de plusieurs cartes réseaux et peuvent en assigner une à chaque machine virtuelle. Mais dans la plupart des cas, elle ne marche pas. De plus, sur les périphériques sans IO-MMU, elle ouvre la porte à des attaques DMA, où une machine virtuelle accède à la mémoire physique de la machine en configurant le contrôleur DMA de son périphérique assigné.
L''''assignement au tour par tour''' donne l'accès au périphérique aux OS chacun à leur tour, l'un après l'autre. Pour fonctionner, il faut cependant sauvegarder et restaurer l'état du périphérique lors d'un changement d'OS. Pensez à ce qu'il se passe lors d'une commutation de contexte entre deux programmes, ou lors d'une interruption sur un processeur : il faut sauvegarder les registres du processeur lors d'une interruption, et les restaurer en sortant. L'idée est la même, sauf que ce sont les registres du périphérique en général qui doivent être sauvegardé. Le périphérique doit idéalement être prévu pour. Si ce n'est pas le cas, l'hyperviseur peut théoriquement lire tous les registres du périphérique et les sauvegarder, mais cela ne fonctionne pas toujours.
===La virtualisation des interruptions===
Virtualiser les entrées-sorties avec de bonnes performances est assez complexe et demande une intervention du matériel. Le ''chipset'' de la carte mère, les différents contrôleurs d'interruption et bien d'autres circuits doivent être modifiés. Diverses techniques permettent de faciliter le partage de certaines entrées-sorties entre machines virtuelles. Par exemple, la technologie ''single root input/output virtualization'' (SR-IOV) facilite le partage des périphériques PCI-Express entre plusieurs VM. Il existe aussi diverses technologies de '''virtualisation du GPU''', qui facilitent le partage d'une carte graphique entre plusieurs machines virtuelles.
Un point important est la gestion des interruptions. Les interruptions matérielles sont traitées par les routines de l'hyperviseur. Lors d'une interruption matérielle, le processeur bascule en niveau de privilège hyperviseur s'il en a un et exécute la routine adéquate de l'hyperviseur. La routine de l'hyperviseur laisse ensuite la main au système d'exploitation une fois qu'elle a finit son travail. Le système d'exploitation exécute alors sa routine d'interruption, puis communique avec le contrôleur d'interruption pour lui dire qu'il a terminé son travail. Cette communication demande d'interagir avec le contrôleur d'interruption, donc avec le matériel, ce qui déclenche une exception qui appelle l'hyperviseur. L'hyperviseur reçoit alors la communication et signale au contrôleur d'interruption que l'interruption matérielle a été traitée.
Vu que chaque machine virtuelle a son propre matériel simulé, chaque machine virtuelle a son propre vecteur d'interruption. Par exemple, si une machine virtuelle n'a pas de carte graphique son vecteur d'interruption ne pointera pas vers les routines d'interruption d'un quelconque GPU. L'hyperviseur gére les différents vecteurs d'interruption de chaque VM et traduit les interruptions recues en interruptions destinées aux VM/OS.
De plus, les interruptions matérielles ne sont pas à prendre en compte pour toutes les machines virtuelles. Par exemple, si une machine virtuelle n'a pas de carte graphique, pas besoin qu'elle prenne en compte les interruptions provenant de la carte graphique. L'assignement direct peut aussi être source de problèmes dans le genre : seule une machine virtuelle doit recevoir les interruptions du périphérique assigné, l'hyperviseur n'est pas concerné. Aussi, les contrôleurs d'interruption modernes supportent des interruptions virtuelles, à savoir qu'ils déterminent à quelles machines virtuelles sont destinées telle ou telle interruption, et peuvent leur envoyer directement, sans passer par l'hyperviseur. Grâce à cela, l'assignement direct a de très bonnes performances.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les sections critiques et le modèle mémoire
| prevText=Les sections critiques et le modèle mémoire
| next=Le matériel réseau
| nextText=Le matériel réseau
}}
</noinclude>
5k6ot0w18yk2ycv3x97dlj3r7op5i8d