Fonctionnement d'un ordinateur/L'espace d'adressage du processeur

L'espace d'adressage du processeur est l'ensemble des adresses utilisables par le processeur. Par exemple, un processeur 16 bits peut adresser 2^16 = 65536 adresses, l'ensemble de ces adresses forme son espace d'adressage. L'espace d'adressage n'est pas toujours égal à la mémoire réellement installée. S'il n'y a pas assez de RAM installée, des adresses seront inoccupées. De plus, une partie de l'espace d'adressage peut être détourné pour communiquer avec les périphériques, comme nous le verrons plus bas. Nous verrons aussi qu'un processeur peut avoir plusieurs espaces d'adressages séparés.

L'adressage de la RAM et de la ROM

modifier

Avoir plusieurs espaces d'adressage spécialisés est quelque chose que nous avons déjà rencontré dans les chapitres précédents, mais sans le dire ouvertement. Aussi, nous allons faire quelques rappels sur les cas déjà rencontrés. En premier lieu, nous allons rappeler la différence entre architectures Von Neumann et Hardvard. La différence entre les deux tient dans l'adressage des mémoires RAM et ROM : est-ce qu'elles sont dans un seul espace d'adressage, ou dans des espaces d'adressage séparés.

L'architecture Von Neumann a un seul espace d'adressage, découpé entre la mémoire RAM d'un côté et la mémoire ROM de l'autre. Une adresse correspond soit à la mémoire RAM, soit à la mémoire ROM, mais pas aux deux. Typiquement, la mémoire ROM est placée dans les adresses hautes, les plus élevées, alors que la RAM est placée dans les adresses basses en commençant par l'adresse 0. C'est une convention qui n'est pas toujours respectée, aussi mieux vaut éviter de la tenir pour acquise.

 
Vision de la mémoire par un processeur sur une architecture Von Neumann.

L'architecture Harvard utilise des espaces d'adressage séparés pour la RAM et la ROM. Une même adresse peut correspondre soit à la ROM, soit à la RAM. Le processeur voit bien deux mémoires séparées, chacune dans son propre espace d'adressage. Les deux espaces d'adressage n'ont pas forcément la même taille. Il est possible d'avoir un plus gros espace d'adressage pour la RAM que pour la ROM. Cela implique que les adresses des instructions et des données soient de taille différentes. C'est peu pratique et c'est rarement implémenté, ce qui fait que le cas le plus courant est celui où les deux espaces d'adressages ont la même taille.

 
Vision de la mémoire par un processeur sur une architecture Harvard.

L'adressage des périphériques

modifier

Passons maintenant à l'adressage des périphériques. La communication avec les périphériques se fait par l'intermédiaire de registres d’interfaçage. Et ces registres peuvent soit avoir un espace d'adressage séparé, soit être inclus dans l'espace d'adressage des mémoires. Dans ce qui suit, nous allons supposer que l'architecture des de type Von Neumann pour simplifier les explications.

Les entrées-sorties mappées en mémoire

modifier

Une partie de l'espace d'adressage peut être détourné pour communiquer avec 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. Les adresses mémoires associées à un périphérique sont redirigées automatiquement vers les registres du périphérique en question, voire vers sa mémoire intégrée. On parle alors d'entrées-sorties mappées en mémoire.

 
IO mappées en mémoire

On remarque un défaut inhérent à cette technique : les adresses utilisées pour les périphériques ne sont plus disponibles pour la RAM. On ne peut plus adresser autant de RAM qu'avant. La perte dépend des périphériques installés et de leur gourmandise en adresses mémoires. C'est ce qui causait un problème assez connu sur les ordinateurs 32 bits, capables d'adresser 4 gibioctets de mémoire. Certaines personnes installaient 4 gigaoctets de mémoire sur leur ordinateur 32 bits et se retrouvaient avec « seulement » 3,5 à 3,8 gigaoctets de mémoire, les périphériques prenant le reste.

Il est possible que la RAM d'un périphérique soit mappée en RAM. Un exemple classique est celui des cartes graphiques qui incorporent une mémoire RAM appelée la mémoire vidéo. La mémoire vidéo est mappée en mémoire, ce qui permet au processeur d'écrire directement dedans. Toute lecture ou écriture dans les adresses associées est redirigée vers le bus PCI/AGP/PCI-Express. Nous verrons plus bas des exemples d'ordinateurs où la mémoire vidéo est mappée en mémoire, que ce soit totalement ou partiellement.

Si je dis totalement ou partiellement, c'est parce que les cartes graphiques modernes disposent de tellement de mémoire qu'on ne peut pas la mapper totalement dans l'espace d'adressage. Sur les systèmes d'avant 2008, seuls 256 mégaoctets de mémoire vidéo sont mappés en mémoire RAM. Le reste de la mémoire vidéo est invisible du point de vue du processeur, mais manipulable par le GPU à sa guise. 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.

L'espace d'adressage séparé pour les entrées-sorties

modifier

Les entrées-sorties et périphériques peuvent avoir leur propre espace d'adressage dédié, séparé de celui utilisé pour les mémoires RAM et ROM.

 
Bit IO.

Une même adresse peut donc adresser soit une entrée-sortie, soit une case mémoire. Et pour faire la différence, le processeur doit avoir des instructions séparées pour adresser les périphériques et 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. Sans cela, le processeur ne saurait pas si une adresse est destinée à un périphérique ou à la mémoire. Cela élimine aussi les problèmes avec les caches : les accès à l'espace d'adressage de la RAM passent par l'intermédiaire de la mémoire cache, alors les accès dans l'espace d'adressage des périphériques le contournent totalement.

Là encore, les deux espaces d'adressage n'ont pas forcément la même taille. Il arrive que les deux espaces d'adressage aient la même taille, le plus souvent sur des ordinateurs complexes avec beaucoup de périphériques. Mais les systèmes embarqués ont souvent des espaces d'adressage plus petits pour les périphériques que pour la ou les mémoires. L'implémentation varie grandement suivant le cas, la première méthode imposant d'avoir deux bus séparés pour les mémoires et les périphériques, l'autre permettant un certain partage du bus d'adresse. Nous reviendrons dessus plus en détail dans le chapitre sur l'adressage des périphériques.

La memory map d'un ordinateur

modifier

Les deux sections précédentes nous ont appris que l'on peut utiliser un espace d'adressage séparé pour la ROM et un autre pour les périphériques. En tout, cela donne quatre possibilités distinctes.

IO mappées en mémoire IO séparées
ROM mappée en mémoire (architecture Von Neumann) Un seul espace d'adressage Deux espaces d'adressage :
  • un pour les mémoires RAM/ROM ;
  • un pour les IO.
ROM séparée (architecture Harvard) Deux espaces d'adressage :
  • un pour la RAM et les IO ;
  • un pour la ROM.
Trois espaces d'adressages :
  • un pour la RAM ;
  • un pour la ROM ;
  • un pour les IO.

Les quatre solutions ont des avantages et inconvénients divers, mais il est intéressant de contraster un espace d'adressage unique avec plusieurs espaces d'adressages.

L'espace d'adressage unique

modifier

Avec un espace d'adressage unique, la ROM est au sommet de l'espace d'adressage, les périphériques sont juste en-dessous, la RAM commence à l'adresse 0 et prend les adresses basses. Faire simplifie grandement l'implémentation matérielle de l'adressage. Notons que d'autres composants que les périphériques ou les mémoires peuvent se trouver dans l'espace d'adressage. On peut y trouver les horloges temps réels, des timers, des senseurs de température, ou d'autres composants placés sur la carte mère. Un exemple un peu original est le suivant : la console de jeu Nintendo DS incorporait une unité de calcul spécialisée dans les divisions et racines carrées, séparée du processeur, qui était justement mappée en mémoire !

 
Espace d'adressage classique avec entrées-sorties mappées en mémoire

L'espace d'adressage unique est plus simple pour les programmeurs, malgré un désavantage dont il faut parler. Le problème est que des adresses censées être disponibles pour la RAM sont détournées vers la ROM ou les périphériques, ce qui limite la quantité de RAM qui peut être réellement adressée en pratique. C'est un défaut qui se manifeste seulement pour les petits espaces d'adressages, de 8 à 16 bits, qui ne permettent pas d'adresser beaucoup de RAM. Avec des adresses codées sur 16 bits, un espace d'adressage de 64 kibioctets de RAM est grignoté par des entrées-sorties assez rapidement. Par contre, pour des adresses de 32 à 64 bits, une bonne partie de l'espace d'adressage est inutilisé, car il n'y a pas assez de RAM. L'espace d'adressage unique n'a donc pas de désavantages.

Les vielles consoles de jeux et certains vieux ordinateurs (Commodores et Amiga), utilisaient un espace d'adressage unique. Elles n'avaient pas une variété de cartes graphiques ou de cartes sons différentes, comme sur les PCs modernes. Au contraire, elles avaient la même configuration matérielle, le matériel ne pouvait pas être changé ni upgradé. Le système d'exploitation était rudimentaire et ne contrôlait pas vraiment l'accès au matériel. Les programmeurs avaient donc totalement accès au matériel et mapper les entrées/sorties en mémoire rendait la programmation des périphériques très simple.

La commutation de banques (bank switching)

modifier

Le bank switching, aussi appelé commutation de banque, permet d'utiliser plusieurs espaces d'adressage sur un même processeur, sans attribuer chaque espace d'adressage pour une raison précise. L'espace d'adressage est présent en plusieurs exemplaires appelés des banques. Les banques sont numérotées, chaque numéro de banque permettant de l'identifier et de le sélectionner.

Le but de cette technique est d'augmenter la mémoire disponible pour l'ordinateur. Par exemple, supposons que j'ai besoin d'adresser une mémoire ROM de 4 kibioctets, une RAM de 8 kibioctets, et divers périphériques. Le processeur a un bus d'adresse de 12 bits, ce qui limite l'espace d'adressage à 4 kibioctets. Dans ce cas, je peux réserver 4 banques : une pour la ROM, une pour les périphériques, et deux banques qui contiennent chacune la moitié de la RAM. La simplicité et l'efficacité de cette technique font qu'elle est beaucoup utilisée dans l'informatique embarquée.

 
exemple de Bank switching.

Cette technique demande d'utiliser un bus d'adresse plus grand que les adresses du processeur. L'adresse réelle se calcule en concaténant le numéro de banque avec l'adresse accédée. Le numéro de la banque actuellement en cours d'utilisation est mémorisé dans un registre appelé le registre de banque. On peut changer de banque en changeant le contenu de ce registre. Le processeur dispose souvent d'instructions spécialisées qui en sont capables.

   

Le placement des programmes dans l'espace d'adressage

modifier

L'espace d'adressage inclut des périphériques, de la ROM, mais aussi une RAM. Sur les ordinateurs modernes, le système d'exploitation et les programmes sont copiés en mémoire RAM avant d'être exécutés. Ils ne sont pas exécutés depuis une mémoire ROM, cela est réservé aux systèmes embarqués simples. Et cela a une influence sur l'espace d'adressage. Les programmes et le système d'exploitation doivent se partager l'espace d'adressage. Voyons voir comment ce partage se fait.

Dans ce chapitre, nous allons volontairement mettre de côté les ordinateurs qui supportent la mémoire virtuelle. Nous en parlerons dans le prochain chapitre. Le principe est de voir comment un processeur avec un seul espace d'adressage gèrent la présence d'un ou de plusieurs programmes.

Un seul espace d'adressage, non-partagé

modifier

Le cas le plus simple est celui où il n'y a pas de système d'exploitation et où un seul programme s'exécute sur l'ordinateur. Le programme a alors accès à tout l'espace d'adressage. L'usage qu'il fait du ou des espaces d'adressage dépend de si on est sur une architecture Von Neumann ou Harvard.

Sur une architecture Von Neumann, le programme et ses données sont placées dans le même espace d'adressage. Le programme organise la mémoire en plusieurs sections, dans lesquelles le programme range des données différentes. Typiquement, on trouve quatre blocs de mémoire, appelés des segments, chacun étant spécialisé pour les données, le code, la pile, etc. Voici ces trois sections :

  • Le segment text contient le code machine du programme, de taille fixe.
  • Le segment data contient des données de taille fixe qui occupent de la mémoire de façon permanente.
  • Le segment pour la pile, de taille variable.
  • le reste est appelé le tas, de taille variable.
 
Organisation d'un espace d'adressage unique utilisé par un programme unique

Sur une architecture Harvard, le programme est placé dans un espace d'adressage à part du reste. Le problème, c'est que cet espace d'adressage ne contient pas que le code machine à exécuter. Il contient aussi des constantes, à savoir des données qui gardent la même valeur lors de l'exécution du programme. Elles peuvent être lues, mais pas modifiées durant l'exécution du programme. L'accès à ces constantes demande d'aller lire celles-ci dans l'autre espace d'adressage, pour les copier dans l'autre espace d'adressage. Pour cela, les architectures Harvard modifiées ajoutent des instructions pour copier les constantes d'un espace d'adressage à l'autre.

 
Organisation des espaces d'adressage sur une archi harvard modifiée
 
Typical computer data memory arrangement

La pile et le tas sont de taille variable, ce qui veut dire qu'ils peuvent grandir ou diminuer à volonté, contrairement au reste. Entre le tas et la pile, on trouve un espace de mémoire inutilisée, qui peut être réquisitionné selon les besoins. La pile commence généralement à l'adresse la plus haute et grandit en descendant, alors que le tas grandit en remontant vers les adresses hautes. Il s'agit là d'une convention, rien de plus. Il est possible d'inverser la pile et le tas sans problème, c'est juste que cette organisation est rentrée dans les usages.

Avant de poursuivre, précisons qu'il est possible de regrouper plusieurs programmes distincts dans un seul, afin qu'ils partagent le même morceau de mémoire. Les programmes portent alors le nom de threads. Les threads d'un même processus partagent le même espace d'adressage. Ils partagent généralement certains segments : ils se partagent le code, le tas et les données statiques. Par contre, chaque thread dispose de sa propre pile d'appel.

 
Distinction entre processus mono et multi-thread.

Il va de soi que cette vision de l'espace d'adressage ne tient pas compte des périphériques. C'est-à-dire que les schémas précédents partent du principe qu'on a un espace d'adressage séparé pour les périphériques. Dans le cas où les entrées-sorties sont mappées en mémoire, l'organisation est plus compliquée. Généralement, les adresses associées aux périphériques sont placées juste au-dessus de la pile, dans les adresses hautes.

Un seul espace d'adressage, partagé avec le système d'exploitation

modifier
 
Gestion de la mémoire sur les OS monoprogrammés.

Maintenant, étudions le cas où le programme partage la mémoire avec le système d'exploitation. Sur les systèmes d'exploitation les plus simples, on ne peut lancer qu'un seul programme à la fois. Le système d'exploitation réserve une portion de taille fixe réservée au système d'exploitation, alors que le reste de la mémoire est utilisé pour le programme à exécuter. Le programme est placé à un endroit en RAM qui est toujours le même.

Le système d'exploitation peut être soit placé dans une ROM, soit copié en mémoire RAM depuis une mémoire de masse, comme le programme à lancer. Si le système d'exploitation est copié en mémoire RAM, il est généralement placé dans les premières adresses, les adresses basses. A l'inverse, un OS en mémoire ROM est généralement placé à la fin de la mémoire, dans les adresses hautes. Mais tout cela n'est qu'une convention, et les exceptions sont monnaie courante.

Il existe aussi une organisation intermédiaire, où le système d'exploitation est chargé en RAM, mais utilise des mémoires ROM annexes, qui servent souvent pour accéder aux périphériques. On a alors un mélange des deux techniques précédentes : l'OS est situé au début de la mémoire, alors que les périphériques sont à la fin, et les programmes au milieu.

 
Méthodes d'allocation de la mémoire avec un espace d'adressage unique

Sur de tels systèmes, il faut protéger l'OS contre une erreur ou malveillance d'un programme utilisateur. Notons qu'il s'agit d'une protection en écriture, pas en lecture. Le programme peut parfaitement lire les données de l'OS sans problèmes, et c'est même nécessaire pour certaines opérations courantes, comme les appels systèmes. Par contre, il faut rendre impossible au programme d'écrire dans la portion mémoire réservée au système d'exploitation.

Si le système d'exploitation est placé dans une mémoire ROM, les écritures sont impossibles, l'OS est naturellement protégé. Mais dans le cas où le système d'exploitation est chargé en RAM, il faut prémunir l'OS contre des écritures fautives/malveillantes. Dans ce qui suit, on part du principe que l'OS est dans les adresses basses.

La solution la plus courante interdit au programme d'écrire au-delà d'une limite au-delà de laquelle se trouve le système d’exploitation. Pour cela, le processeur incorpore un registre limite, qui contient l'adresse limite au-delà de laquelle un programme peut pas écrire. Pour toute écriture en espace utilisateur, l'adresse écrite est comparée au registre limite. Si l'adresse est inférieure au registre limite, le programme cherche écrire dans la mémoire réservée à l'OS. L’accès mémoire est interdit, une exception matérielle est levée et l'OS affiche un message d'erreur.

 
Protection mémoire avec un registre limite.

Un seul espace d'adressage, partagé entre plusieurs programmes

modifier

Les systèmes d’exploitation modernes implémentent la multiprogrammation, le fait de pouvoir lancer plusieurs logiciels en même temps. Et ce même si un seul processeur est présent dans l'ordinateur : les logiciels sont alors exécutés à tour de rôle. Un point important est le partage de la RAM entre les différents programmes. Les différents programmes et leurs données sont placés en mémoire RAM, et il faut trouver un moyen pour répartir les différents programmes en RAM. Il y a plusieurs solutions pour cela, mais l'une d'entre elle est l'usage de segments mémoire.

 
Partitions mémoire

Avec elle, le système d'exploitation répartit les différents programmes dans la mémoire RAM et chaque programme se voit attribuer un ou plusieurs blocs de mémoire. Ils sont appelés des partitions mémoire, ou encore des segments.

Dans ce qui va suivre, nous allons parler de segments, pour simplifier les explications. Nous allons partir du principe qu'un programme est égal à un segment, pour simplifier les explications, mais sachez qu'un programme peut être éclaté en plusieurs segments dispersés dans la mémoire, et même être conçu pour ! Nous en reparlerons dans le chapitre sur le mémoire virtuelle.

Le système d'exploitation mémorise une liste des segments importants en mémoire RAM, appelée la table des segments. Il s'agit d'un tableau dont chaque entrée mémorise les informations pour un segment. Une entrée de ce tableau est appelée un descripteur de segment et contient son adresse de base, sa taille, et des autorisations liées à la protection mémoire. Précisément, l'entrée numéro N mémorise les informations du segment numéro N. A partir de l'adresse de base de la table des segments et du numéro de segment, on peut lire ou écrire un descripteur de segment.

 
Global Descriptor table

Toutefois, l'usage de segments amène un paquet de problèmes qu'il faut résoudre au mieux. Le premier problème est tout simplement de placer les segments dans l'espace d'adressage, mais c'est quelque chose qui est du ressort du système d'exploitation. Par contre, cela implique qu'un segment peut être placé n'importe où en RAM et sa position en RAM change à chaque exécution. En conséquence, les adresses des branchements et des données ne sont jamais les mêmes d'une exécution à l'autre. Pour résoudre ce problème, le compilateur considère que le segment commence à l'adresse zéro, puis les adresses sont corrigées avant ou pendant l'exécution du programme. Cette correction est en général réalisée par l'OS lors de la copie du programme en mémoire, mais il existe aussi des techniques matérielles, que nous verrons dans le chapitre suivant.

Il faut aussi prendre en compte le phénomène de l'allocation mémoire. Derrière ce nom barbare, se cache quelque chose de simple : les programmes peuvent déclamer de la mémoire au système d'exploitation, pour y placer des données. Les langages de programmation bas niveau supportent des fonctions comme malloc(), qui permettent de demander un bloc de mémoire de N octets à l'OS, qui doit alors accommoder la demande. S'il n'y a pas assez de mémoire, l'appel échoue et le programme doit gérer la situation. De même, un programme peut libérer de la mémoire qu'il n'utilise plus avec des fonctions comme free(). Avec des segments, cela revient à changer la taille d'un segment, plus précisément à la fin du segment. Et tout cela est géré par le système d'exploitation, aussi on laisse cela pour le prochain chapitre.

Enfin, sans protection particulière, les programmes peuvent techniquement lire ou écrire les données des autres. Si un programme pouvait modifier les données d'un autre programme, on se retrouverait rapidement avec une situation non prévue par le programmeur, avec des conséquences qui vont d'un joli plantage à des failles de sécurité dangereuses. Il faut donc introduire des mécanismes de protection mémoire, pour isoler les programmes les uns des autres, et éviter toute modification problématique. La protection mémoire regroupe plusieurs techniques assez variées, qui ont des objectifs différents. Elles ont pour point commun de faire intervenir à des niveaux divers le système d'exploitation et le processeur.

La memory map des PC IBM x86

modifier

Un autre exemple est celui des ordinateurs PC, avec des processeurs x86. L'organisation de leur espace d'adressage a évolué dans le temps et l'espace d'adressage s'est adapté. Mais il s'est adapté sans modifier l'existant, pour des raisons de compatibilité. Aussi, les processeurs x86 modernes peuvent fonctionner dans plusieurs modes, appelés mode réel, protégé, virtuel 8086, long, system management mode, et compatibilité 32 bits.

 
AMD64StateDiagram

Le system management mode a déjà été abordé dans le chapitre sur les interruptions. Pour simplifier, il ne s'agit pas d'un véritable mode au sens "adressage". Dans ce mode, le système d'exploitation est mis en pause et le BIOS ou un autre firmware s'exécute sans restriction. On y rentre grâce à une interruption dédiée, lancée par le noyau de l'OS. Le système d'exploitation exécute cette interruption afin de laisser la main au BIOS, pour lui déléguer certaines tâches. Par contre, les autres modes sont bien plus intéressant, car ils ont chacun un espace d'adressage différent. Voyons-les dans le détail.

Le mode réel : l'adressage sur 20 bits

modifier

Le tout premier mode était le mode réel, qui utilisait des adresses de 20 bits, ce qui fait 1 mébioctet de mémoire adressable. Il était utilisé sur des processeurs 16 bits, qui gérait donc des adresses de 16 bits capables d'adresser 64 kibioctets de mémoire. Pour contourner cette limitation, la mémoire en mode réel était composée de 16 blocs de 64 kibioctets. Pour générer une adresse de 20 bits, le processeur contient un registre de 4 bits qui précise quel bloc de 64 kibioctet est couramment adressé. En somme, une sorte de commutation de banque intégrée au processeur.

La réalité est plus complexe, comme on le verra dans le prochain chapitre, mais cette explication simpliste suffira pour le moment.

Le processeur démarre systématiquement en mode réel, y compris sur les ordinateurs modernes. Le BIOS s'exécute dans ce mode, avant de passer en mode protégé et de laisser la main à l'UEFI et au système d'exploitation. Autrefois, le système d'exploitation s'exécutait aussi en mode réel, c'est le cas du DOS.

En mode réel, l'espace d'adressage est peuplé par de la RAM, la ROM du BIOS et des périphériques. La RAM prenait les adresses basses, la ROM était au sommet de l'espace d'adressage, et les périphériques entre les deux. Les premiers 640 kibioctets sont réservés à la RAM et sont appelés la mémoire conventionnelle. Les octets restants forment la mémoire haute, réservée aux ROM et aux périphériques. Elle est sectionnée en deux portions. Le sommet de la mémoire haute est réservé au BIOS, alors que le bas de la mémoire haute est réservé aux périphériques. Dans cette dernière, on trouve les BIOS des périphériques (dont celui de la carte vidéo, s'il existe), qui sont nécessaires pour les initialiser et pour communiquer avec eux. De plus, on y trouve la mémoire de la carte vidéo et éventuellement la mémoire d'autres périphériques comme la carte son.

Espace d'adressage en mode réel
Numéro du bloc Contenu du bloc
1024 - 960 Kio BIOS principal (ROM)
960 Kio - 640 Kio ROM d'extension des périphériques (XT, EGA, 3270 PC)
Mémoire vidéo des cartes EGA, MDA ou CGA
640 Kio - 0 Mémoire RAM, mémoire conventionnelle

Pour détailler un peu plus, il faut tenir compte du découpage de l'espace d'adressage en blocs de 64 kibioctets. Le découpage en blocs de 64 kibioctets est particulièrement important pour la mémoire haute. Des blocs sont attribués à un périphérique spécifique. Par exemple, le 16ème bloc est réservé au BIOS, le 12ème aux ROM des périphériques, le 10ème et le 11ème à la mémoire vidéo, etc.

Espace d'adressage en mode réel
Numéro du bloc Contenu du bloc
0 Mémoire RAM, mémoire conventionnelle
1
2
3
4
5
6
7
8
9
10 Mémoire vidéo des cartes EGA
11 Mémoire vidéo des cartes MDA ou CGA
12 ROM d'extension des périphériques (XT, EGA, 3270 PC)
13 Autre, non-réservé
14 Autre, non-réservé
15 ROM du BIOS

Vous remarquerez que les blocs 13 et 14 sont non-réservés. Ils peuvent être utilisés pour mapper des périphériques en mémoire. Mais si aucun périphérique n'est mappé dedans, les blocs sont laissés libres. Les OS n'hésitaient pas à les utiliser pour adresser de la RAM. Par exemple, si l'ordinateur n'a pas de carte vidéo EGA, le bloc qui leur était réservé était réutilisé comme mémoire RAM. La mémoire conventionnelle passait alors de 640 à 704 kibioctets de RAM disponibles.

Les deux premiers kibioctets de la mémoire conventionnelle sont initialisés au démarrage de l'ordinateur. Ils sont utilisés pour stocker le vecteur d'interruption (on expliquera cela dans quelques chapitres) et servent aussi au BIOS. La portion réservée au BIOS, la BIOS Data Area, mémorise des informations en RAM. Elle commence à l'adresse 0040:0000h, a une taille de 255 octets, et est initialisée lors du démarrage de l'ordinateur. Le reste de la mémoire conventionnelle est réservée au le système d'exploitation (MS-DOS, avant sa version 5.0) et au programme en cours d’exécution.

Interlude : les technologies d'expanded memory

modifier

Déjà à l'époque, 1 mébioctet était une limite assez faible, même si cela n'a pas empêché à un grand dirigeant de l'industrie informatique de dire que "640K ought to be enough for anybody". Aussi, diverses solutions matérielles ont vu le jour. Elles étaient peu utilisées et ont concrètement servi à des applications spécifiques. La principale est l'expanded memory (EMS), une solution utilisant une carte d'extension avec plusieurs modules de RAM installés dessus. Les modules étaient accédés via la technique de la commutation de banque.

 
L'espace d'adressage avec Expanded memory. La carte d'extension et le matériel requis ne sont pas représentés.

Le terme expanded memory regroupe plusieurs technologies propriétaires semblables sur le principe mais différentes dans l'exécution. Les premières utilisaient des banques de 64 kibioctets, qui étaient mappées dans un bloc de 64 kibioctets libre en mémoire haute. L'usage de banques de 64 kibioctets collait bien avec l'adressage particulier du mode réel, où la mémoire était gérée par blocs de 64 kibioctets. Les technologies ultérieures utilisaient 2 à 4 pages de 16 kibioctets, qui pouvaient être placée n'importe où en mémoire conventionnelle.

 
Carte d'extension Expanded memory de marque Emulex Persyst, de 4 mébioctets.

L'expanded memory demandait que les programmes soient codés pour utiliser cette fonctionnalité. Pour cela, ils devaient utiliser une technique de programmation nommée Overlay programming. L'idée est de découper le programme en plusieurs blocs séparés, appelés des overlays. Certains blocs sont en permanence en RAM, mais d'autres sont soit chargés en RAM, soit stockés ailleurs. Le ailleurs est souvent le disque dur, ou dans la RAM expanded memory si elle est disponible. Le va-et-vient des overlays entre RAM et disque dur était réalisé en logiciel, par le programme lui-même.

 
Overlay Programming.

L'utilisation de l'expanded memory utilisait l'interruption logicielle 67h, une interruption peu utilisée à l'époque, qui était libre. La carte d'extension était utilisée à travers un pilote de périphérique dédié, qui était appelé via l'interruption 67h.

Le mode protégé : l'adressage sur 24 et 32 bits

modifier

Par la suite, le mode réel a été remplacé par le mode protégé. Il utilise la mémoire virtuelle, ce qui fait qu'on devrait le détailler dans le chapitre suivant. Il permettait aussi de dépasser la limite du mébioctet de 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.

Les programmes conçus pour le mode réel ne pouvaient pas s'exécuter en mode protégé. Pour corriger cela, le 286 a introduit le mode virtuel 8086, aussi appelé mode V86. Il s'agit d'une technique de virtualisation, ce qui fait qu'on en parlera en détail dans le chapitre sur la virtualisation assistée en matériel. Tout ce que l'on peut dire ici est que le mode V86 a été détourné pour que les applications DOS utilisent la mémoire au-delà du mébioctet, appelée la mémoire étendue. Les logiciels DOS accédaient à la mémoire étendue à travers un driver DOS dédié (HIMEM.SYS). Les logiciels DOS déplaçaient des données dans la mémoire étendue, puis les rapatriaient en mémoire conventionnelle quand ils en avaient besoin.

Il ne faut pas confondre mémoire étendue et expanded memory. La technique de expanded memory repose sur de la commutation de banque, demande une carte d'extension dédiée, fonctionne sans mode V86. A l'inverse, la mémoire étendue ne gère pas de commutation de banque, ne demande pas de carte d'extension, mais utilise le mode V86. Par contre, il est possible d'émuler l'expanded memory sans carte d'extension, avec la mémoire étendue. Quelques cartes mères intégraient des techniques pour, mais une émulation logicielle était aussi possible en réécrivant l'interruption 67h.

Le mode long : l'adressage 64 bits

modifier

Par la suite, les processeurs ont introduit le mode long, dans lequel les adresses font 64 bits. Le CPU en mode long a aussi un jeu d'instruction totalement différent, des registres en plus et d'autres fonctionnalités actives uniquement dans ce mode. Les programmes compilés pour le 64 bits s'exécutent nativement dans ce mode, alors que les programmes 32 et 16 bits s'exécutent dans un mode de compatibilité dédié.

Sur les systèmes 64 bits, le bus d'adresse fait seulement 48 bits afin d'économiser des interconnexions. Avec 48 bits, il manque 64 - 48 = 16 bits d'adresses, qui ne peuvent pas être utilisés pour adresser quoi que ce soit. La règle est que ces 16 bits doivent être tous égaux : soit ils valent tous 0, soient ils sont tous à 1. Les adresses qui respectent cette contrainte sont appelées des adresses canoniques.

Le résultat est que l'espace d'adressage est coupé en trois sections, comme illustré ci-dessous.

  • Les adresses canoniques basses ont leurs 16 bits de poids fort à 0 et sont situées en bas de l'espace d'adressage, dans les premières adresses.
  • Les adresses canoniques hautes ont leurs 16 bits de poids fort à 1 et sont situées au sommet de l'espace d'adressage, dans les dernières adresses.
  • Entre les deux, se trouvent des adresses non-canoniques, qui ne sont pas accessibles. Y accéder déclenche la levée d'une exception matérielle.
 
Espace d'adressage x86-64 bits, avec des adresses physiques de 48 bits.
 
Espace d'adressage x86-64 bits, avec des adresses physiques de 57 bits.
Les futurs systèmes x86 devraient passer à des adresses de 57 bits, les trois sections auront donc des frontières différentes.

Les adresses non-canoniques sont censées être inutilisables. Mais les programmeurs aimeraient bien pouvoir les utiliser pour des pointeurs tagués, à savoir des pointeurs/adresses associées à des informations. L'idée serait d'utiliser les 16 bits de poids fort pour stocker des informations liées au pointeur, seuls les 48 bits restant codant l'adresse. Les 16 bits peuvent être utilisés de manières très diverses.

Un exemple est la technique du memory tagging, qui crée une somme de contrôle au pointeur. La somme de contrôle est générée par un algorithme cryptographique, à partir de l'adresse du pointeur. Elle est vérifiée à chaque utilisation du pointeur. Si la somme de contrôle ne correspond pas au pointeur, une erreur est levée. Si jamais un virus ou un code malveillant modifie un pointeur, il ne saura pas comment calculer la somme de contrôle, la somme de contrôle a énormément de chances d'être incorrecte.

Quelques fonctionnalités des processeurs visent à autoriser l'utilisation des adresses non-canoniques. L'idée est que les 16 bits de poids fort sont ignorées lors des accès mémoire, ce qui permet d'utiliser les 16 bits de poids fort à volonté. Il s'agit des techniques Top Byte Ignore d'ARM, Upper Address Ignore d'AMD, et Linear Address Masking d'Intel.