Aller au contenu

Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation

Un livre de Wikilivres.

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. On peut les voir comme une sorte de sous-système d'exploitation, de système d'exploitation pour les systèmes d'exploitation. A ce propos, les OS virtualisés sont appelés des OS invités, alors que l'hyperviseur est parfois appelé l'OS hôte.

Différence entre système d'exploitation et hyperviseur.

Les processeurs modernes intègrent des techniques pour accélérer la virtualisation. Les techniques en question sont assez variées, allant d'un niveau de privilège en plus des modes noyau/utilisateur à des modifications de la mémoire virtuelle, en passant à des modifications liées aux interruptions matérielles. Mais pour comprendre tout cela, il va falloir faire quelques explications sur la virtualisation elle-même.

La virtualisation : généralités

[modifier | modifier le wikicode]

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. Le partage de la RAM demande concrètement des modifications assez légères de la mémoire virtuelle, qu'on verra en temps voulu.

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. A ce propos, il s'agit de la même solution que les OS utilisent pour faire tourner plusieurs programmes en même temps sur un processeur/cœur unique.

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

[modifier | modifier le wikicode]

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.
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.

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.

La méthode trap and emulate basique

[modifier | modifier le wikicode]

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.

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és 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.

Virtualization - Para vs Full

La virtualisation du processeur

[modifier | modifier le wikicode]

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

[modifier | modifier le wikicode]

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.

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

[modifier | modifier le wikicode]

Les processeurs ARM de version v8 et plus incorporent un mode hyperviseur, mais pas les processeurs x86. À 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 à 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 pages. 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

[modifier | modifier le wikicode]

Avec la virtualisation, les différentes machines virtuelles, les différents OS doivent se partager la mémoire physique, en plus d'être isolés les uns des autres. 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é un espace d'adressage fictif, qui ne correspond pas à la mémoire physique. Les adresses physiques manipulées 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.

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.

Table des pages cachée.

La MMU et la virtualisation : les tables des pages emboitées

[modifier | modifier le wikicode]

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 pages, 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

[modifier | modifier le wikicode]

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 pages 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 traduites 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 pages 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 reçues du driver sont traduite avec une table des pages normale, non-emboitée. Mais elle a aussi un mode virtualisation qui utilise des tables de pages étendues.

La virtualisation des entrées-sorties

[modifier | modifier le wikicode]

Virtualiser les entrées-sorties est simple sur le principe. Un OS communique avec le matériel soit via des ports 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 logicielle des interruptions

[modifier | modifier le wikicode]

É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 matérielles, les fameuses IRQ, est quant à elle plus complexe. 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. La gestion des interruptions matérielles n'est pas la même si l'ordinateur grée des cartes virtuelles ou s'il se débrouille avec une carte physique unique.

Lors d'une interruption matérielle, le processeur exécute la routine adéquate de l'hyperviseur. Celle-ci enregistre qu'il y a eu une IRQ et fait quelques traitements préliminaires. Ensuite, elle laisse la main au système d'exploitation concerné, qui exécute alors sa routine d'interruption. Une fois la routine de l'OS terminée, l'OS dit au contrôleur d'interruption qu'il a terminé son travail. Mais cela demande d'interagir avec le contrôleur d'interruption, ce qui déclenche une exception qui appelle l'hyperviseur. L'hyperviseur signale au contrôleur d'interruption que l'interruption matérielle a été traitée. Il rend alors définitivement la main au système d'exploitation. Le processus complet demande donc plusieurs changements entre mode hyperviseur et OS, ce qui est assez couteux en performances.

Vu que le matériel simulé varie d'une machine virtuelle à l'autre, 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 reçues en interruptions destinées aux VM/OS.

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 virtualisation des périphériques avec l'affectation directe

[modifier | modifier le wikicode]

Virtualiser les entrées-sorties avec de bonnes performances est plus complexe. En pratique, cela 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 des entrées-sorties entre machines virtuelles.

La première est l'affectation directe, 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'affectation directe 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'affectation directe est certes limitée, mais elle se marie bien avec certaines de virtualisation matérielles, intégrées dans de nombreux périphériques. Il existe des périphériques qui sont capables de se virtualiser tout seuls, à savoir qu'ils peuvent se dédoubler en plusieurs périphériques virtuels. Par exemple, prenons une carte réseau avec cette propriété. Il n'y a qu'une seule carte réseau dans l'ordinateur, mais elle peut donner l'illusion qu'il y en a 8-16 d'installés dans l'ordinateur. Il faut alors faire la différence entre la carte réseau physique et les 8-16 cartes réseau virtuelles. L'idée est d'utiliser l'affectation directe, chaque machine virtuelle/OS ayant une carte réseau virtuelle d'affectée, avec affectation directe.

Virtualisation matérielle des périphériques

Pour les périphériques PCI-Express, le fait de se dupliquer en plusieurs périphériques virtuels est permis par la technologie Single-root input/output virtualization, abrévié en SRIOV. Elle est beaucoup, utilisée sur les cartes réseaux, pour plusieurs raisons. Déjà, ce sont des périphériques beaucoup utilisés sur les serveurs, qui utilisent beaucoup la virtualisation. Dupliquer des cartes réseaux et utiliser l'affectation directe rend la configuration des serveurs bien plus simple. De plus, la plupart des cartes réseaux sont sous-utilisées, même par les serveurs. Une carte réseau est souvent utilisée à environ 10% de ses capacités par une VM unique, ce qui fait qu'utiliser 10 cartes réseaux virtuelles permet d'utiliser les capacités de la carte réseau à 100%.

Il est possible de faire une analogie entre les processeurs multithreadés et les périphériques virtuels. Un processeur multithreadé est dupliqué en plusieurs processeurs virtuels, un périphérique virtualisé est dupliqué en plusieurs périphériques virtuels. L'implémentation des deux techniques est similaire sur le principe, mais les détails varient selon qu'on parle d'une carte réseau, d'une carte graphique, d'une carte son, etc. Pour gérer plusieurs périphériques virtuels, le périphérique physique contient plusieurs copies de ses registres de commande/données, plusieurs files de commandes, etc. De plus, le périphérique physique contient divers circuits d'arbitrage, qui gèrent comment le matériel est utilisé. Ils donnent accès à tour de rôle à chaque VM aux ressources non-dupliquées.

Implémentation d'une carte réseau gérant plusieurs cartes réseaux virtuelles

Dans le cas le plus simple, le matériel traite les commandes provenant des différentes VM dans l'ordre d'arrivée, une par une, il n'y a pas d'arbitrage pour éviter qu'une VM monopolise le matériel. Plus évolué, le matériel peut faire de l'affectation au tour par tour, en traitant chaque VM dans l'ordre durant un certain temps. Le matériel peut aussi utiliser des algorithmes d'ordonnancement/répartition plus complexes. Par exemple, les cartes graphiques modernes utilisent des algorithmes de répartition/ordonnancement accélérés en matériel, implémentés dans le GPU lui-même.

La virtualisation des interruptions

[modifier | modifier le wikicode]

La gestion des interruptions matérielles peut aussi être accélérée en matériel, en complément des techniques de périphériques virtuels vues plus haut. Par exemple, il est possible de gérer des exitless interrupts, qui ne passent pas du tout par l'hyperviseur. Mais cela demande d'utiliser l'affectation directe, en complément de l'usage de périphériques virtuels.

Tout périphérique virtuel émet des interruptions distinctes des autres périphérique virtuel. Pour distinguer les interruptions provenant de cartes virtuelles de celles provenant de cartes physiques, on les désigne sous le terme d'interruptions virtuelles. Une interruption virtuelle est destinée à une seule machine virtuelle : celle à laquelle est assignée la carte virtuelle. Les autres machines virtuelles ne reçoivent pas ces interruptions. Les interruptions virtuelles ne sont pas traitées par l'hyperviseur, seulement par l'OS de la machine virtuelle assignée.

Une subtilité a lieu sur les processeurs à plusieurs cœurs. Il est possible d'assigner un cœur à chaque machine virtuelle, possiblement plusieurs. Par exemple, un processeur octo-coeur peut exécuter 8 machines virtuelles simultanément. Avec l'affectation directe ou à tour de rôle, l'interruption matérielle est donc destinée à une machine virtuelle, donc à un cœur. L'IRQ doit donc être redirigée vers un cœur bien précis et ne pas être envoyée aux autres. Les contrôleurs d'interruption modernes 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'affectation directe à tour de rôle ont de bonnes performances.

En plus de ce support des interruptions virtuelles, le contrôleur d'interruption peut aussi être virtualisé, à savoir être dupliqué en plusieurs contrôleurs d'interruption virtuels. Sur les systèmes à processeur x86, le contrôleur d'interruption virtualisé est l'APIC (Advanced Programmable Interrupt Controller). Diverses technologies de vAPIC, aussi dites d'APIC virtualisé, permettent à chaque machine virtuelle d'avoir une copie virtuelle de l'APIC. Pour ce faire, tous les registres de l'APIC sont dupliqués en autant d'exemplaires que d'APIC virtuels supportés. Il existe un équivalent sur les processeurs ARM, où le contrôleur d'interruption est nommé le Generic Interrupt Controller (GIC) et peut aussi être virtualisé.

La virtualisation de l'APIC permet d'éviter d'avoir à passer par l'hyperviseur pour gérer les interruptions. Par exemple, quand un OS veut prévenir qu'il a fini de traiter une interruption, il doit communiquer avec le contrôleur d'interruption. Sans virtualisation du contrôleur d'interruption, cela demande de passer par l'intermédiaire de l'hyperviseur. Mais s'il est virtualisé, l'OS peut communiquer directement avec le contrôleur d'interruption virtuel qui lui est associé, sans que l'hyperviseur n'ait à faire quoique ce soit. De plus, la virtualisation du contrôleur d'interruption permet de gérer des interruptions inter-processeurs dites postées, qui ne font pas appel à l'hyperviseur, ainsi que des interruptions virtuelles émises par les IO-MMU.

Sur les plateformes ARM, les timers sont aussi virtualisés.

Annexe : le mode virtuel 8086 des premiers CPU Intel

[modifier | modifier le wikicode]

Les premiers processeurs x86 étaient rudimentaires. Le 8086 utilisait une forme très simple de segmentation, sans aucune forme de protection mémoire, ni même de mémoire virtuelle, dont le but était d'adresser plus de 64 kibioctets de mémoire avec un processeur 16 bits. Sur le processeur 286, la segmentation s'améliora et ajouta un support complet de la mémoire virtuelle et de la protection mémoire. L'espace d'adressage en mode protégé est passé de 24 bits sur le CPU 286, à 32 bits sur le 386 et les CPU suivants. Pour bien faire la différence, la segmentation du 8086 fut appelée le mode réel, et la nouvelle forme de segmentation fut appelée le mode protégé.

Les programmes conçus pour le mode réel ne pouvaient pas s'exécuter en mode protégé. En clair, tous les programmes conçus pour le 8086 devaient fonctionner en mode réel, qui était supporté sur le 286 et les processeurs suivant. Pour corriger les problèmes observés sur le 286, le 386 a ajouté un mode 8086 virtuel, une technique de virtualisation qui permet à des programmes de s'exécuter en mode réel dans une machine virtuelle dédiée appelée la VM V86. Notez que nous utiliserons l'abréviation V86 pour parler du mode virtuel 8086, ainsi que de tout ce qui est lié à ce mode.

La virtualisation du DOS et la mémoire étendue

[modifier | modifier le wikicode]

Utiliser le mode V86 demande d'avoir un programme 8086 à lancer, mais aussi d'utiliser un hyperviseur V86. L’hyperviseur V86 est un véritable hyperviseur, qui s’exécute en mode noyau, exécute des routines d'interruption, gère les exceptions matérielles, etc. Il réside en mémoire dans une zone non-adressable en mode réel, mais accessible en mode protégé, celui-ci permettant d'adresser plus de RAM. L'hyperviseur est forcément exécuté en mode protégé.

Le système d'exploitation DOS s'exécutait en mode réel, ce qui fait qu'il pouvait être émulé par le mode V86. Il était ainsi possible de lancer une ou plusieurs sessions DOS à partir d'un système d'exploitation multitâche comme Windows. Windows. Beaucoup de personnes nées avant les années 2000 ont sans doute profité de cette possibilité pour lancer des applications DOS sous Windows. Les applications étaient en réalité lancées dans une machine virtuelle grâce au mode V86. Windows implémentait un hyperviseur V86 de type 2, à savoir que c'était un logiciel qui s'exécutait sur un OS sous-jacent, ici

Hyperviseur

Les applications DOS dans une VM V86 ne peuvent pas adresser plus d'un mébioctet de mémoire. L'ordinateur peut cependant avoir plus de mémoire RAM, notamment pour gérer l'hyperviseur V86. Diverses techniques permettaient aux applications DOS d'utiliser la mémoire au-delà du mébioctet, appelée la mémoire étendue. Les logiciels DOS accédaient à la mémoire étendue en passant par un intermédiaire logiciel, qui lui-même communiquait avec l'hyperviseur V86. L'intermédiaire est appelé le Extended Memory Manager (EMM), et il est concrètement implémenté par un driver sur DOS (HIMEM.SYS).

Les applications DOS ne pouvaient pas adresser la mémoire étendue, mais pouvaient échanger des données avec l'EMM. Les logiciels peuvent ainsi déplacer des données dans la mémoire étendue pour les garder au chaud, puis les rapatrier dans la mémoire conventionnelle quand ils en avaient besoin. L'intermédiaire Extended Memory Manager s'occupe d’échanger des données entre mémoire conventionnelle et mémoire étendue. Pour cela, il switche entre mode réel et protégé à la demande, quand il doit lire ou écrire en mémoire étendue.

Il ne faut pas confondre mémoire étendue et expanded memory. Pour rappel, l'expanded memory est un système de commutation de banque, qui autorise un va-et-vient entre une carte d'extension et une page de 64 kibioctets mappé en mémoire haute. Elle fonctionne sans mode protégé, sans virtualisation, sans mode V86. La mémoire étendue ne gère pas de commutation de banque et demande que la RAM en plus soit installée dans l'ordinateur, pas sur une carte d'extension.

Par contre, il est possible d'émuler l'expanded memory sans carte d'extension, en utilisant la mémoire étendue. Quelques chipsets de carte mère intégraient des techniques cela. Une émulation logicielle était aussi possible. L'émulation logicielle se basait sur une réécriture de l'interruption 67h utilisée pour adresser la technologie expanded memory. L'hyperviseur V86 pouvait s'en charger, il avait juste à réécrire son allocateur de mémoire pour gérer cette interruption et quelques autres détails.

Le fonctionnement du mode virtuel 8086 de base

[modifier | modifier le wikicode]

En mode V86, la segmentation du mode protégé est désactivée, seule la segmentation du mode réel est utilisée. Il y a quelques subtilités liées à la ligne A20 du bus d'adresse, déjà abordées auparavant dans ce cours. Sur les CPU 286 et ultérieurs, le processeur peut adresser 1 mébioctet (2^20 adresses), plus 64 kibioctets qui ne sont pas adressables sur le 8086. Le tout permet donc d'adresser les adresses allant de 0 à 0x010FFEFH. Et ces adresses sont utilisées pour les programmes en mode réel. Les adresses au-delà de l'adresse 0x010FFEFH sont typiquement le lieu de résidence de l’hyperviseur en RAM.

Par contre, la pagination peut être activée par l’hyperviseur, afin d’exécuter plusieurs logiciels en mode réel simultanément. La mémoire virtuelle par pagination peut aussi être utile si l'ordinateur a peu de mémoire RAM, pas assez pour faire tourner le logiciel : l'espace d'adressage vu par le logiciel est un espace virtuel de grande taille, ce qui permet de lancer le logiciel, au prix de performances dégradées. Enfin, la gestion des entrées-sorties mappées en mémoire est aussi simplifiée. De plus, cela permettait d'adresser plus de mémoire RAM grâce aux adresses plus longues du mode protégé.

Le processeur est configuré en mode V86, le bit VM du registre d'état spécifique est mis à 1. Le processeur utilise ce bit lorsqu'une instruction utilise les registres de segments, afin de savoir comment calculer les adresses, le calcul n'étant pas le même en mode réel et en mode protégé. Quelques instructions machines dépendent aussi de la valeur de ce bit, mais cela est traité au décodage de l'instruction.

Il faut noter que dans le mode 8086 virtuel, les programmes peuvent utiliser les registres ajoutés sur le 386 et ultérieurs. Par exemple, le 8086 n'a que 4 registres de segment, alors que le 286 en a 6. Les programmes en mode 8086 virtuel peuvent utiliser les deux registres de segment supplémentaires. Il en est de même pour d'autres registres ajoutés par le 286, comme des registres de contrôle, des registres de debug, et quelques autres. Il en est de même pour les instructions ajoutées par le 286, le 386 et ultérieur, qui sont exécutables en mode virtuel 8086. Et elles sont nombreuses.

La compatibilité n'était pas parfaite, il y avait quelques petites différences entre ce mode V86 et le mode réel du 8086, idem avec le mode réel du 286. Mais la grande majorité des applications n'avait aucun problème. Les problèmes étaient concentrés sur quelques instructions précises, notamment celles avec un préfixe LOCK.

Les Virtual-8086 mode extensions

[modifier | modifier le wikicode]

A partir du processeur Pentium, les processeurs x86 ont introduit des optimisations du mode V86, afin de rendre la virtualisation plus rapide. L'ensemble de ces optimisations est regroupé sous le terme de Virtual-8086 mode extensions, abrévié en VME. Les optimisations du VMA étaient, pour certaines, utiles au-delà de la virtualisation et étaient activables indépendamment du reste du VME.

Le VME introduisait des optimisations quant au traitement des interruptions, à savoir la gestion des interruptions virtuelles. De plus, le VME modifie la gestion de l'interrupt flag du registre d'état. Pour rappel, ce bit permet d'activer ou de désactiver les interruptions masquables. Modifier le bit interrupt flag permettait de désactiver les interruptions masquables ou au contraire de les activer.

Il se trouve que ce bit était accesible par les programmes exécutés en mode réel, qui pouvaient en faire ce qu'ils voulaient. Le mode réel n'étant pas prévu pour la multi-programmation, ce n'était pas un problème. Mais en mode V86, toute modification de ce bit se répercute sur les autres VM en mode V86. Pour éviter les problèmes, le VME a ajouté de quoi virtualiser cet interrupt flag, avec une copie par machine virtuelle V86. Chaque programme modifiait sa propre copie de l'interrupt flag sans altérer celle des autres programmes exécutés en mode V86, et surtout sans déclencher une exception matérielle gérée par l'hyperviseur.

Bien qu'elles aient été introduites sur les processeurs Pentium, elles n'ont réellement été rendues publiques qu'après la sortie des processeurs de microarchitecture P6. Avant d'être rendue publique, la documentation du VME était une annexe de la documentation officielle, la fameuse annexe H. Elle était mentionnée dans la documentation officielle, mais était indisponible au grand public, seules quelques entreprises sous NDA y avait accès.