l'assembleur  [livre #4602]

par  pierre maurette



L'architecture IA

Le but de ce chapitre est de présenter, au sein de l'architecture IA-32 , Intel Architecture 32  bits , quelques notions fondamentales pour le programmeur en assembleur. Les plates-formes "PC sous Windows" que nous utilisons aujourd'hui sont issues par évolutions successives d'un IBM PC doté d'un 8088 cadencé à 4,77 MHz et d'un système d'exploitation MS-DOS ou PC DOS. Cette machine originelle est encore présente plus ou moins virtuellement dans nos machines, y compris celles basées sur les plus récentes architectures comme AMD64 .

Chaque étape de l'évolution tente de respecter au mieux le principe de compatibilité ascendante . L'objectif est de permettre de conserver logiciels et périphériques lors d'un changement d'unité centrale. Cette compatibilité peut se définir à plusieurs niveaux liés, mais fondamentalement différents.

  Le microprocesseur  : un Pentium 4 ou un Athlon XP, comme il y a quelques années un 80386 ou un K6, encapsulent un antique 8086, permettant de simuler parfaitement son fonctionnement dans un mode particulier. Dans un autre mode, plusieurs 8086 peuvent être émulés simultanément, chacun possédant son environnement. C'est le domaine de l'architecture x86, appelée IA-32  par Intel, et où AMD joue un rôle de plus en plus important.

Pour respecter cette étonnante compatibilité ascendante, chaque étape ne peut que rajouter des possibilités, comme un jeu de registres et les instructions associées, dont l'utilisation n'a rien d'obligatoire. Quand c'est la taille des registres généraux qui augmente, l'ancien jeu de registres demeure à l'intérieur des nouveaux, et l'ancien code doit continuer à s’exécuter.

Courant 2002, nous vivions l'arrivée d'une nouvelle génération 64 bits, qui va encapsuler la technologie 32 bits, elle-même conservant sa propre encapsulation des modes 16 bits.

Consultez, en fin de chapitre, la section intitulée Les architectures 64 bits – AMD64 .

La compatibilité 8086 dans l'avenir n'est pas une certitude. Si les performances n'en sont pas affectées, la tâche des concepteurs se trouve compliquée. L'heure de la disparition des modes 8086 n'a pas encore sonné.

  L'ordinateur : celui que vous achetez aujourd'hui est honorablement compatible avec celui de 1980. Manquent malgré tout certains accessoires, comme un Basic en ROM ou un lecteur de cassettes. Le format des disquettes a changé, la capacité du disque dur peut poser problème.

La norme de fait compatible PC est évolutive et ouverte. Cette norme n'a pas de véritable tuteur ; elle dépend des décisions à la fois des fondeurs de microprocesseurs, des développeurs de systèmes d'exploitation et des fabricants de cartes-mère et de composants. Elle intéresse le programmeur en assembleur au plus haut point, en ce qui concerne les pilotes de périphériques par exemple.

Les circuits coupleurs d'E/S de l'IBM PC original sont de plus en plus souvent absents, Dans le cas contraire, ils ne sont présents qu'au travers de circuits à haute intégration. Vous ne trouverez pas physiquement, sur les cartes actuelles, de coupleur 8250 ou 8255. Vous pourrez en revanche les programmer comme s'ils y étaient.

  Le système d'exploitation  : sous Windows XP, certaines applications anciennes ne fonctionneront pas. Et l'évolution de la lignée Windows ne va rien arranger dans ce domaine. Le mode MS-DOS n'est désormais plus qu'une simple émulation. Le système d'exploitation va interdire normalement l'accès direct aux coupleurs d'E/S, alors que le matériel le permet, même sous Windows XP, mais dans des conditions très particulières.

Il y a eu très grossièrement trois périodes distinctes :

  La période MS-DOS, dont fait partie Windows jusqu'à 3.11. Windows n'est qu'un intégrateur graphique lancé par DOS.

  Les Windows 9X. Ce sont de véritables systèmes d'exploitation à coté desquels un vrai DOS existe toujours.

  La technologie 2000/XP. Le DOS n'est plus qu'imparfaitement émulé, dans le but d'améliorer la stabilité du système.

Citons ici une solution alternative aux divers Windows sur les mêmes machines, Linux. Cette alternative ne sera pas traitée spécifiquement dans cet ouvrage.

 

2.1 Le 8086/8088

L'importance accordée au 8088/8086 pourra être jugée excessive. Ce que nous avons voulu, dans la continuité du chapitre précédent, c'est démystifier encore un peu le fonctionnement de l'ordinateur. Nous ne pouvions pour cela baser les représentations sur les microprocesseurs actuels, beaucoup trop complexes. Il était également bon de partir sur des bases solides, pour en arriver aux architectures actuelles par évolutions successives.

Si le premier IBM PC embarquait un 8088 à 4,77 MHz, c'est bien le 8086 qui est à l'origine de toute l'histoire. Le 8088 fut développé ultérieurement, peut-être dans le but précisément de motoriser plus économiquement ce premier ordinateur personnel d'IBM.

Il y eut une préhistoire : le 4004, 4 bits et 4 Ko de mémoire adressable, 8008, 8 bits et 16 Ko de mémoire adressable. Puis, supportant le système d'exploitation CP/M, le 8080, 8 bits et 64 Ko de mémoire adressable, horloge à 500 KHz, cloné en Z80 de Zilog par un de ses concepteurs. Enfin le 8085 en 1977, juste avant le 8086.

Le 8088 est strictement un 8086, donc un microprocesseur 16 bits. Simplement, son bus externe de données était réduit à 8 bits ; un transfert sur 16 bits nécessitait deux accès mémoire. Cette particularité est absolument transparente pour le programmeur. La seule infime différence au niveau du cœur est la longueur du tampon de préchargement, qui passe de 6 dans le 8086 à 4 dans le 8088. Répétons que le code à écrire est strictement le même pour les deux modèles.

Il semble bien qu'un 8088 soit aussi cher à produire qu'un 8086, voire un peu plus. L'économie se réalisait plutôt sur la mémoire. Neuf boîtiers mono-bits suffisaient, au lieu du double pour un 8086. Il est courant, dans le domaine du microprocesseur, de vendre moins cher qu'un autre un produit plus cher à produire et ayant demandé des études supplémentaires.

Remarque

La détection d'erreurs par parité

Quand nous avons évoqué les 9 boîtiers, donc 9 bits, il ne s’agissait pas d’une coquille. Au moment de l'écriture mémoire d'un mot de 8 bits, un circuit indépendant calculait le 9 e , pour que le nombre total de bits à 1 soit impair. Cette propriété était vérifiée à la lecture par un circuit qui pouvait générer une interruption en cas d'erreur. Cette technique utilisée sur les compatibles PC jusqu'au début des années quatre-vingt-dix, qui ne concernait pas le programmeur et ne ralentissait pas la machine, explique la présence sur le marché de barrettes mémoire 9 bits.

La gamme 80186 est un peu à part. Ces processeurs possèdent le jeu d'instructions du 8086 légèrement amélioré et intègrent un certain nombre de circuits périphériques. Le 80186, dans ses versions les plus complètes, ressemble à un microcontrôleur plus qu'à un microprocesseur.

Des microprocesseurs compatibles ont très rapidement vu le jour. Comme plus tard AMD, le japonais NEC proposait les modèles V20 et V30 compatibles 8088 et 8086, et légèrement plus performants.

2.1.1 Le circuit intégré 8086/8088

Le 8086 et le 8088 se présentaient sous la forme d'un même boîtier DIL 40. Le brochage est pratiquement identique.

Brochage des 8086 et 8088
figure 2.01 Brochage des 8086 et 8088 [the .swf]

La barre au-dessus du nom d'un signal indique simplement que celui-ci est actif à l'état bas. Nous négligeons cette barre dans le texte pour des raisons typographiques.

Le 8086/8088 pouvait être positionné dans un mode parmi deux : Minimum ou Maximum. Ce choix est fait de façon statique, lors de la conception de la carte accueillant le circuit.

Le mode Maximum permet par exemple l'utilisation conjointe de plusieurs processeurs, en déléguant une partie du contrôle de bus à un circuit spécialisé de type 8288. C'est le signal en entrée MN/MX qui sélectionne le mode. L'IBM PC pouvait accueillir un coprocesseur arithmétique 8087 optionnel ; il était dessiné en mode Maximum et intégrait un 8288.

Certains signaux qui ne sont plus disponibles au niveau du 8088 en mode Maximum se retrouvent sur le 8288. Leur rôle reste le même, et nos commentaires également.

Il a déjà été signalé que le 8086/8088 possède deux instructions spécifiques ( IN et OUT ) et une patte spécialisée pour accéder à une zone E/S de 64 Ko, indépendante de la mémoire principale.

Le bus est multiplexé, c'est-à-dire qu'il est bus d'adresses puis de données, à des moments différents. Un accès mémoire ou I/O, en lecture ou en écriture, demande donc une succession d'actions relativement complexe au niveau des bus : présentation et verrouillage d'une adresse, présentation de la donnée à écrire ou acquisition de la donnée à lire, fin des opérations. La documentation divise cette chronologie en quatre phases, nommées T1, T2, T3 et T4. Pour résoudre les problèmes liés par exemple à des mémoires à temps d'accès trop long, un certain nombre de cycles TW (Wait States) vont parfois s'insérer entre T3 et T4. Dans l'IBM PC, le signal WAIT en provenance de la logique de gestion des accès transite par le circuit d'horloge de type 8284A, qui fabrique les signaux CLK, RESET et READY.

Attention

Quel que soit le modèle, la mémoire est accessible octet par octet

Il est tout à fait possible de lire (écrire) en mémoire un mot de 16 bits à une adresse impaire, même sur le 8086. En d’autres termes, l’alignement de la mémoire sur le mot n’est pas nécessaire. L’instruction : mov word ptr[adresse impaire], valeur est parfaitement valable. Elle demandera simplement deux accès mémoire, au lieu d’un pour un accès aligné sur un 8086. Voir à ce sujet le signal BHE, patte 34 dans le tableau. L’alignement des grands tableaux de données est donc un point majeur d’optimisation. Le mauvais alignement est plus ou moins pénalisant pour tous les processeurs de la famille, sauf bien entendu le 8088.

Ce point n'a pas changé depuis, et sur un 686 ou un ATHLON XP64, toute adresse à l'octet près est valable pour toute donnée en mémoire, même si elle n'est pas toujours souhaitable.

Dans les tableaux suivants, nous voyons de façon simplifiée les signaux importants entrant et sortant dans le 8088, en prenant souvent en exemple le premier IBM PC. Le premier tableau concerne les signaux présents dans les deux modes de fonctionnement, le deuxième les signaux spécifiques au mode Minimum, et enfin le troisième ceux spécifiques au mode Maximum. Rappelons à propos du deuxième tableau que beaucoup de signaux y figurant seront disponibles également en mode Maximum, car refabriqués par ailleurs, par un 8288, dans le cas de l'IBM PC.

 

Les signaux du 8086/8088 présents dans les deux modes

Nom

Patte

Sens

Description

GND

1, 20

E

GrouND, masse. Référence 0 volt de l'alimentation.

Vcc

40

E

+5 volts de l'alimentation.

CLK

19

E

CLocK : horloge de base à 4,77 MHz. C'est la période de cette horloge qui correspond au cycle.

AD0 à AD15

2 à 16, 39

E/S

Représentent, pendant T1, les 16 premiers bits du bus d'adresses, et pendant T2, T3, TW et T4, les 16 bits du bus de données. Pour le 8088, remplacer par AD0 à AD7 et A8 à A15, le bus de données étant réduit à 8 bits.

A16/S3

A18/S5

35 à 38

S

Pendant T1, complètent le bus d'adresses à 20 bits par A16..A19.

BHE/S7

34

S

Bus High Enable/Status : gère la lecture ou l'écriture d'un seul octet à la fois ou d'un mot de 16 bits. Le résultat est le suivant, en fonction du couple de valeurs BHE,A0 :

0,1 : 1 octet haut vers/depuis adresse impaire ;

1,1 : Rien.

SSO

34

S

System Status Output : uniquement sur le 8088 et en mode Minimum, différencie la lecture de code de celle de données.

RD

32

S

Read : ce signal indique que le cycle en cours (T2, T3 et TW) est une lecture mémoire ou E/S.

MN/MX

33

E

MiNimum/MaXimum : positionne le processeur en mode Maximum à 0 (GND), en mode Minimum à 1 (Vcc).

READY

22

E

Indique au microprocesseur que la mémoire ou le périphérique en cours d’accès a terminé son transfert. Élaboré (synchronisé) à partir de WAIT par le circuit d'horloge 8284A dans l'IBM PC.

INTR

18

E

INTerrupt Request : demande d'interruption externe masquable. C'est le niveau sur cette patte qui est lu, à la fin de chaque instruction effectuée par le microprocesseur.

NMI

17

E

Non Maskable Interrupt : c'est un front sur cette entrée qui envoie le processeur dans une interruption non masquable.

TEST

23

E

Cette patte est testée par l'instruction  WAIT et détermine si le processeur continue (patte à 0) ou devient effectivement inactif.

RESET

21

E

Ce signal, à l'état bas au repos, doit passer pendant au moins 4 cycles par l'état haut. C'est quand il redescend que le microprocesseur entame son cycle de (re)démarrage.

 

Les signaux du 8086/8088 spécifiques au mode Minimum

Nom

Patte

Sens

Description

HOLD

30, 31

E/S

L'entrée HOLD transmet au microprocesseur une demande de prise de possession du bus. Celui-ci répond à la sortie HLDA (HoLD Acknowledge), tout en s'isolant des différents bus, y compris de contrôle, en les mettant dans le troisième état, haute impédance. Le dialogue inverse a lieu quand le demandeur redescend le HOLD.

M/IO

28

S

Ce signal différencie un accès mémoire d'un accès E/S. En rapport direct avec les instructions assembleur IN et OUT .

WR

29

S

WRite : indique que le cycle en cours (T2, T3 et TW) est une écriture mémoire ou E/S.

INTA

24

S

INTerrupt Acknowledge : acquittement d'interruption. Il s'agit d'un signal de lecture équivalent à RD pour le traitement des interruptions.

ALE

25

S

Address Latch Enable : un front qui indique l'instant où l'adresse présente sur les 20 bits du bus est valable et peut être verrouillée.

DT/R

27

S

Data Transmit/Receive : différencie cycle de lecture et cycle d'écriture. Utile pour les amplis de bus bidirectionnels.

DEN

26

S

Data Enable : indique la validité des données sur le bus. Utile pour les amplis de bus bidirectionnels.

SSO

34

S

System Status Output : uniquement sur le 8088 ; différencie la lecture de code de celle de données.

 

Les signaux du 8086/8088 spécifiques au mode Maximum

Nom

Patte

Sens

Description

S0, S1, S2

26, 27, 28

S

3 bits à destination du 8288 qui, une fois décodés par celui-ci, indiquent quel cycle effectue le 8086/8088 et permettent au 8288 de fabriquer tous les signaux utiles de contrôle d'accès au bus.

RQ/GT0

30, 31

E/S

ReQuest/GrantT (demande/autorisation) : c'est par ces deux fils que le processeur peut négocier la possession du bus avec d'autres propriétaires possibles de celui-ci.

LOCK

29

S

Par cette sortie, le processeur signale son refus temporaire de partager l'accès au bus. Elle intéresse le programmeur, puisque c'est lui qui va décider de ce verrouillage, en préfixant telle ou telle instruction par LOCK. Utile pour traiter les sémaphores. Toujours d'actualité.

QS0

24, 25

S

Queue Status : les combinaisons de ces deux bits indiquent l'état de la queue de préchargement d'instructions du processeur. Utilisé par exemple par le 8087 pour se synchroniser.

Dans le chronogramme général de lecture et écriture suivant, nous voyons les signaux les plus importants. Il n'est pas indispensable de maîtriser ce type de document, mais il peut constituer une aide pour la lecture de ce qui précède et de ce qui suit.

Chronologie de base d'accès mémoire et E/S
figure 2.02 Chronologie de base d'accès mémoire et E/S [the .swf]

Les lignes ADRESSE/STATUS et ADRESSE/DATA représentent les deux parties du bus multiplexé, respectivement la partie supérieure (adresse et bits d'état) et la partie supérieure (adresse et données).

Notons que les signaux RD et INTA ont strictement la même fonction et le même comportement.

Rien de ce qui précède n'est absolument indispensable au programmeur. Pour faire un parallèle avec une voiture, la conduite est possible sans rien connaître du piston ni du vilebrequin. Ignorer le rôle de l'embrayage se révèle plus délicat.

Dans les parties suivantes, nous entrons dans le domaine du très utile jusqu’à l'indispensable.

2.1.2 Structure interne

Voici une représentation, symbolique, de la structure interne du 8086, qui fait suite aux représentations plus générales vues au chapitre précédent.

Structure interne du 8086
figure 2.03 Structure interne du 8086 [the .swf]

Cette représentation, qui n'est qu'une esquisse, est valable également pour le 8088, à deux différences près : la queue d'instructions sera réduite à 4 octets et le bus de données sera sorti en largeur 8 bits vers l'extérieur. C'est ce détail qui justifie notre aparté précédent au sujet du prix de revient du 8088 : il semble bien qu'il y ait un travail de multiplexage supplémentaire pour le 8088, par rapport au 8086.

Nous constatons immédiatement la structuration en deux blocs, une unité de traitement  EU  déjà en grande partie connue, et une unité d'échanges et d'approvisionnement, BIU .

Au centre de l'EU, l' ALU , unité arithmétique et logique. Elle est très vascularisée, puisqu'en contact avec tous les autres sous-ensembles, par l'intermédiaire souvent d'un bus 16 bits local. Les registres généraux accessibles au programmeur en font également partie de l'EU. Nous voyons qu'il s'agit de registres 16 bits, pouvant pour certains être vus comme deux registres 8 bits.

Le cœur de l'unité est le bloc de logique de contrôle. En fonction de l'instruction en cours de traitement, il va positionner l'ALU pour une opération particulière, une addition par exemple. Il va également programmer la logique interne pour qu'à l'entrée de l'ALU, dans deux registres tampons, se trouvent les valeurs à traiter. Quand nous aurons étudié le jeu d'instructions, nous verrons qu'un au moins de ces tampons va chercher son contenu dans les registres généraux. L'autre sera alimenté, soit par un registre général, soit via le BIU par une valeur lue en mémoire.

Une valeur immédiate est de celles qui sont fournies explicitement par le programmeur comme dans   ADD AX, 12 ; elle est donc présente dans le flux du code, en tant qu'opérande mélangé aux instructions. Quand les valeurs ne sont pas immédiates, c'est leurs adresses qui sont, sous une forme ou une autre, dans ce flux.

Le traitement d'une instruction n'est pas instantané. Certaines vont demander jusqu'à quelques dizaines de cycles. Parmi ces longues instructions, la multiplication et la division. L'instruction  AAM (ASCII Adjust for Multiplication), qui permet, comme nous le verrons, d'effectuer des multiplications sur des entiers codés en BCD, demande 83 cycles d'horloge. Elle se comporte, dans l'ALU et le bloc logique, comme un véritable sous-programme, à base de micro-instructions. Nous sommes, avec ces instructions, au cœur d'une architecture CISC.

La BIU gère tout ce qui est échanges avec la mémoire et les entrées/sorties qui sont une forme particulière de mémoire. Cela concerne donc, en premier lieu, l'élaboration de l'adresse à déposer sur le bus au temps T1.

La mémoire segmentée

Ce que nous venons de décrire relève, pour le programmeur, du niveau culturel, plus ou moins important, en revanche, les connaissances suivantes doivent absolument être maîtrisées pour la programmation assembleur.

Le microprocesseur fabrique une adresse sur 20 bits qui permettent d'adresser 1 048 576 octets, soit 1 Mo. En hexadécimal, les adresses possibles vont courir sur 5 chiffres, de 00000h à FFFFFh. Nous appellerons, pour l'instant, cette adresse complète adresse physique.

Les registres du microprocesseur sont au mieux de 16 bits de largeur, soit 4 chiffres hexadécimaux. C'est le cas du registre IP, qui pointe sur la prochaine instruction à exécuter. Ces 16 bits nous permettent de pointer quelque part dans un bloc de 65 536 octets, ou 64 Ko. Les adresses, dans ce bloc, vont courir de 0000h à FFFFh.

Dans l'espace total adressable, nous pouvons définir 16 de ces blocs, chacun correspondant à une valeur du chiffre hexadécimal le plus significatif, de 0 à F. En d'autres termes, ces 16 blocs commenceront aux adresses 00000h, 10000h, 20000h... F0000h. Par exemple, le 5 e  bloc comprendra les adresses de 40000h à 4FFFFh.

Pour pointer l'adresse 42DF3h, il faut tout d'abord choisir, d'une façon ou d'une autre, la page 4, par exemple en déposant préalablement cette valeur dans un registre de page. Il faut ensuite que le mot de 16 bits 2DF3h soit dans un registre, IP par exemple. Si nous considérons que le registre de page pointe vers un début de page, 40000h ici, alors IP représente le déplacement, offset en anglais, à effectuer à partir du début de page pour atteindre l'adresse convoitée.

Adressage par pages
figure 2.04 Adressage par pages [the .swf]

Les 16 blocs occupent tout l'espace mémoire sans se chevaucher. À une adresse physique correspondent un numéro de page et un déplacement uniques.

Cette méthode serait parfaitement viable. En effet, quand la mémoire est parcourue, le changement de page est une opération beaucoup moins fréquente que les changements de déplacement. Nous pourrions par exemple positionner le registre en début de module, l'évolution de IP ou BX suffisant ensuite pour se déplacer efficacement dans la mémoire. Nous pourrions améliorer encore le procédé en gérant simultanément plusieurs registres de page, pour accéder à des zones spécialisées de l'espace adressable : une page pour le code, une page pour la pile, une autre pour les données, etc.

En réalité, la méthode adoptée par Intel est un peu plus complexe. Néanmoins, ce procédé par pages non recouvrantes représente une bonne introduction. Dans un autre contexte, le registre de page pourrait être extérieur au microprocesseur, faire partie du circuit d'accès à la mémoire et être positionné par une opération d'entrée/sortie. Un microprocesseur pourrait ainsi accéder à un espace mémoire plus grand que celui pour lequel il a été initialement dessiné, au prix il est vrai de beaucoup de complications. Cette technique est à la base de la mémoire étendue LIM/EMS.

Venons-en maintenant à la solution réellement utilisée dans le 8086/8088, proche de celle que nous venons de décrire, troublante au premier abord mais plus efficace. Il existe effectivement dans ce processeur un certain nombre de registres, non pas de page mais de segment  ; l'adresse physique sera également toujours le résultat d'un déplacement, contenu dans un registre comme IP ou BX, à partir du début de cette zone appelée segment.

Alors que 4 bits suffisaient pour définir une page, les registres de segment sont, comme les autres registres généraux, de 16 bits de largeur. Il est donc possible de définir 65 536 segments différents.

L'adresse physique d'un début de segment s'obtient en ajoutant 0h, soit 0000b, à droite de la valeur du registre de segment considéré. En d'autres termes, nous multiplions cette valeur par 16 ou nous la décalons de 4 bits vers la gauche, en complétant par des 0. Les débuts de segments sont les adresses divisibles par 16.

L'adresse physique d'un accès mémoire se calcule toujours en additionnant l'adresse physique du début de segment au déplacement sur 16 bits.

Adressage segmenté
figure 2.05 Adressage segmenté [the .swf]

Nous pouvons placer les segments pratiquement où nous le désirons, à 16 octets près.

De cela, nous pouvons déduire trois conséquences, pas forcément agréables pour le programmeur :

  Les segments se recouvrent.

  À une adresse physique correspondent un grand nombre de couples segment/déplacement.

  Le calcul de l'adresse physique, dans le cas général, n'est pas immédiat. Le fait que deux couples segment/déplacement différents représentent la même adresse physique ne saute pas aux yeux.

Nous pouvons nous étonner de ce choix d'un si grand nombre de segments possibles. N'aurait-il pas été préférable de réduire le recouvrement intersegment pour laisser la porte ouverte à des bus d'adresses de 24, voire de 32 bits ? D'un autre côté, le fait de pouvoir placer les segments avec une telle liberté permettait d'optimiser l'utilisation de la mémoire, à une époque où celle-ci était rare et chère. Sauf erreur, les premiers IBM PC étaient proposés dans une version n'embarquant que 16 Ko (oui, 16 384 mots de 8 bits).

Nous devons maintenant trouver quelles valeurs de segment et de déplacement va utiliser la BIU pour élaborer l'adresse physique. La réponse, selon le bon sens, nous est en grande partie fournie par le modèle du programmeur et les noms que portent les divers registres (voir plus loin). Il est par exemple logique que l'adresse générée pour charger une instruction soit située au déplacement IP (Instruction Pointer), dans le segment CS (Code Segment).

Dans le 8086, quatre registres de segment sont à notre disposition. Pour chacun, il existe un registre contenant l'offset permettant l'élaboration de l'adresse physique :

  CS, Code Segment, associé à IP ;

  SS, Stack Segment, associé à SP ou BP ;

  DS, Data Segment, associé à SI ;

  ES, Extra Segment, associé à DI.

Spécificités des segments de registres

Segment

Type

Règle implicite

CS, Code Segment

Instructions

Toute référence à du code.

DS, Data Segment

Données

Cas général des références à des données.

ES, Extra Segment

Données

Destination des opérations sur les chaînes.

SS, Stack Segment

Pile

Toutes références à la pile.

Que signifie ce tableau ? Tout simplement qu'un JMP offset fabriquera l'adresse de destination à l'aide de offset et de CS, que MOV EAX, [offset] transférera dans EAX la donnée située à l'adresse fabriquée à l'aide de offset et de DS.

Ce sont des règles par défaut qui peuvent être surchargées, c'est-à-dire forcées par le programmeur. C'est ce que les documentations appellent une surcharge de segment ou segment override .

Remarque

Code relogeable

La segmentation permet d'écrire facilement du code relogeable, et même dynamiquement relogeable. Il suffit qu'un programme ne modifie pas la valeur des registres de segment, qu'il se contente d'accepter leur valeur, ce qui est normalement le cas. Le système d'exploitation peut alors placer le code et les données pour optimiser l'occupation de la mémoire, et en fonction de la quantité de mémoire installée. Il lui suffira ensuite d'initialiser de façon adéquate les registres de segment pour que le programme s’exécute sans modification du code machine à son emplacement effectif. Les éléments pour lesquels la valeur du segment importe seront cités dans des tables, dites tables de relocation, pour être initialisés au lancement du programme.

Plus précisément, sous DOS, le programme est fabriqué en supposant CS à 0000h et en exprimant les autres registres de segment par rapport à CS (travail du lieur). Au chargement par le loader de DOS, il suffit d'ajouter systématiquement la valeur de CS à tous les registres de segment.

Pour exprimer une adresse physique, dans du code source ou du texte libre, la syntaxe SSSS:DDDD est universelle. DDDD représente la valeur du segment, DDDD le déplacement. Il est rare d'utiliser la valeur du segment. La mention du registre suffit généralement, CS:DDDD. Le segment est même le plus souvent omis, quand le registre par défaut est utilisé.

Remarque

NEAR, FAR, SHORT

Dans les instructions de saut (au sens large) va apparaître la notion de distance ou de proximité. Si un saut se fait avec changement de segment, il s'agira d'un saut lointain ou FAR . Dans le cas contraire, d'un saut proche ou NEAR . Un saut NEAR qui, de plus, peut se coder sur 8 bits signés relativement à l'adresse actuelle est un saut court, ou SHORT . Ceci concerne CALL , JMP , Jcc et sera étudié en même temps que ces instructions.

D'une façon générale, ce qui est NEAR ou SHORT est immédiatement relogeable, ce qui est FAR ne l'est qu'avec l'aide d'une table de relocation.

Le sommateur situé en haut de la BIU peut maintenant être précisé.

Fabrication des 20 bits d'adresse
figure 2.06 Fabrication des 20 bits d'adresse [the .swf]

 

La queue de préchargement

Nous avons vu que les accès mémoire, par la BIU, n'étaient pas immédiats, et qu'il existait des mémoires lentes, demandant au processeur de temporiser durant un certain nombre de phases d'attente TW. D'autre part, certaines instructions traitées par l'EU peuvent être très longues.

L'architecture interne a été optimisée afin d'éviter autant que possible que les deux unités fonctionnelles BIU et EU ne passent leur temps à s'attendre mutuellement.

Le premier point à respecter a été de rendre les fonctionnements de l'EU et de la BIU aussi indépendants que possible. Il est par exemple impératif que l'EU soit autonome lors du traitement d'une instruction longue, qu'elle n'ait pas à solliciter la BIU pendant cette durée. Les deux unités sont asynchrones et ne se rencontrent que de temps en temps.

Ensuite, afin que la BIU fonctionne au maximum de ses capacités et de celles de la mémoire, et qu'elle profite au mieux du temps laissé libre par l'EU, il lui a été laissé la possibilité de s'avancer dans son travail, sous la forme d'une file d'attente ou queue d'instructions. En fait d'instructions, il s'agit de code, opérandes compris. Dans MOV EAX, 12h , la valeur numérique 12h fait partie du flux d'instructions. Cette queue est un registre de 6 (4 pour le 8088) octets, qui se remplit par une extrémité pour se vider par l'autre. Nous entendons parler parfois de pile FIFO (First In, First Out, soit premier entré, premier sorti). L'image d'une pile est erronée ; celle d'un tube ou d'un tuyau, pipe en anglais, convient mieux. Cette configuration est connue sous le nom de structure pipeline .

Nous trouvons souvent sur la documentation en langue anglaise les mots fetch et prefetch . Fetch pourrait être traduit par "aller chercher", dans le sens d'un approvisionnement. C'est le travail de la BIU. Appelons cette action accède , comme nous appelons traite l'action consistant à traiter une instruction et attend le fait d'attendre, faute de munitions généralement.

Représentons chronologiquement l'enchaînement de ces actions.

Enchaînement de tâches d'accès mémoire
figure 2.07 Enchaînement de tâches d'accès mémoire [the .swf]

Sur la première ligne, un seul bloc s'occupe de tout ; il enchaîne approvisionnements et traitements, sans attente semble-t-il.

Le deuxième schéma représente le même fonctionnement, dans lequel nous avons graphiquement séparé les fonctions BIU et EU. Nous constatons qu'à chaque instant l'une des deux unités est en attente.

Dans le troisième schéma, les deux unités ont été rendues indépendantes. Elles peuvent fonctionner simultanément et un moyen a été donné à la BIU de laisser le résultat de son travail à la disposition de l'EU, par la queue d'instructions. Cette particularité est représentée par une flèche. D'une certaine façon, la BIU anticipe les besoins de l'EU.

Nous voyons que le point le plus important est l'indépendance des deux unités BIU et EU, plus que cette fameuse queue, qui ne gagnerait pas forcément à être plus longue. Il suffit qu'à l'issue du traitement d'une instruction, tous les éléments concernant la suivante soient disponibles.

Le but est que l'EU (et non la BIU qui n'est qu'un moyen) fonctionne au mieux de ses possibilités.

Nous avons représenté un cycle attend initial comme si nous représentions le début du code ; cela n'a aucune importance. Il arrive que, dans certaines circonstances, l'EU doive subir un ou plusieurs cycles de ce type. Ces cas sont :

  Quand une donnée n'est pas dans l'instruction et ses opérandes, donc pas dans la queue. L'instruction mov ax, word ptr[122h] , par exemple, demande de charger AX par la valeur à l'adresse 122h, ou plus exactement au déplacement 122h du segment pointé par le registre DS. 122h est dans la queue, mais l'EU devra charger la BIU de faire l'emplette de la valeur contenue à cette adresse et devra se reposer pendant ce temps.

  Dans le cas d'une instruction de déroutement de programme, typiquement un saut. Même un saut non conditionnel va perturber le pipeline. Nous pouvons facilement interpréter cette particularité. La BIU charge dans la queue le code comme il vient, en se fondant très certainement sur la valeur du registre IP. Or, l'instruction de saut inconditionnel, JMP , est équivalente à une instruction de déplacement de données : mov ip, adresse . La BIU ne peut pas en connaître le résultat tant que l'EU ne l'a pas traitée. Il serait justifié de penser que c'est par une comparaison de la valeur effective de IP et de celle que la BIU attendait qu'une procédure de récupération de la queue est déclenchée : purge et fourniture de l'instruction située au bon IP. Remarquons enfin qu'une queue plus longue serait certainement un handicap dans de telles situations.

Nous sommes en 1978, très loin des algorithmes de prédiction de nos processeurs modernes. Néanmoins, la technique est astucieuse et permet de tirer un maximum de l'EU, sans être trop pénalisée par de la mémoire lente.

Les cycles d'attente dans la chronologie de la BIU, provoqués par exemple par des instructions dispendieuses dans l'EU, sont normaux, le but étant de se mettre à la disposition de l'EU.

Le pipeline du 8086 est rempli par un mot de 16 bits, quelles que soient les instructions lues. Il ne peut donc y avoir de dégradation des performances par désalignement de la mémoire du code. La notion d'alignement n'existe pas pour le code.

 

Vecteurs d'interruptions et de reset

Un certain nombre d'adresses sont réservées aux vecteurs de reset et d'interruptions.

Adresses réservées
figure 2.08 Adresses réservées [the .swf]

Le processus de démarrage, ou de reset, sur le 8086 est très proche du processus générique que nous présentions au chapitre précédent. Le processeur exécute alors du code situé à FFFF0h et jusqu'à FFFFFh. Au vu de la taille de cette zone, c'est généralement un JMP que nous trouvons à cette adresse. Les explications du chapitre intitulé Structure d'un micro-ordinateur sont suffisantes. Le démarrage normal de l'IBM PC ou XT consistait, après une séquence d'initialisations et de tests nommée POST  (Power On Self Test), à rechercher un secteur bootable sur une disquette, puis sur un disque dur quand il était présent. Cette zone était nécessairement en ROM. Cette dernière nécessité demeure aujourd'hui, au moins à la mise sous tension. En fait un microprocesseur Pentium 4, ATHLON ou ATHLON 64 démarre en mode Réel (nous y reviendrons) et donc exactement de la même façon qu'un 8086.

En bas de la mémoire, de 00000h à 003FFh, se trouvent 256 blocs de 4 octets ; chacun d’eux est (ou contient ?) un vecteur d'interruption, c'est-à-dire l'adresse où va démarrer la routine de traitement de l'interruption. Dans chacun de ces blocs, les 2 octets d'adresses inférieures contiennent le déplacement, les 2 autres renfermant le segment. C'est tout simplement l'adresse effective ou adresse physique. L'ordre dans lequel ces 4 octets sont implantés en mémoire trouve son explication dans l'Annexe C consacrée aux conventions Little-Endian et Big-Endian, dont la lecture peut sans problèmes être différée.

Les interruptions sont numérotées de type 0 à type 255, en démarrant par celle dont le bloc-vecteur est à 00000h. Nous écrirons le plus souvent : l'interruption xxh pour désigner son programme de traitement.

Tous ces vecteurs ne sont pas libres d'utilisation. Certains sont imposés par le jeu d'instructions. Ce sont les exceptions du microprocesseur, c’est-à-dire des interruptions spécifiques levées par celui-ci quand telle ou telle circonstance inattendue survient. Par exemple, une division par 0 va lever l'exception type 0, Divide error . L'utilisation des exceptions n'est pas nécessairement signe d'erreur, les circonstances peuvent ne pas être si inattendues que cela et leur traitement par exceptions peut très bien constituer la solution la plus souple.

Tout un chacun s'est un jour posé des questions sur le fonctionnement d'un débogueur, qui nous donne l'impression que notre machine nous permet de regarder tourner un programme pas à pas, tout en surveillant la valeur de la mémoire et des registres. Deux exceptions facilitent ce tour de magie :

  Type 1, ou Single Step. Quand le flag TF (Trap Flag) est allumé, chaque instruction provoque cette exception. De plus, l'extinction de TF pendant le traitement de l'exception et son rallumage au retour sont automatiques.

  Type 3, ou Breakpoint Interrupt. Cette interruption logicielle INT 3 possède la particularité de se coder sur un seul octet. Ce petit détail est très pratique puisque, ainsi, elle peut remplacer n'importe quelle instruction, en fait, être placée en début de n'importe quelle instruction. Elle sera utilisée pour placer des points d'arrêt.

Pour la gestion des interruptions externes, une logique de gestion des interruptions, en l'occurrence un  PIC (Programable Interrupt Controler) 8259 ou compatible, est pratiquement indispensable.

Quand une interruption masquable survient, le processeur termine l'instruction en cours, puis initie une séquence d'acquittement. Au cours de cet échange (avec le PIC), en deux cycles, un octet va lui être communiqué, correspondant au type de l'interruption. Le processeur va en déduire l'adresse du vecteur. Il suffit de multiplier ce nombre par 4, ou plus simplement de lui ajouter deux 0 à droite. Il va ainsi pouvoir traiter l'interruption selon son type, sans avoir à effectuer d'autres recherches pour en identifier la source.

Dans le cas d'une interruption non masquable (patte NMI), aucune procédure d'acquittement n'est lancée. Le vecteur est toujours le type 2.

Modèle du programmeur

Pour programmer en assembleur, il suffit presque toujours de ne retenir de tout ce qui précède qu'un résumé, le modèle du programmeur . Celui-ci est constitué du jeu d'instructions , que nous n'avons pas encore réellement abordé, et d'une cartographie des registres, y compris le registre d'indicateurs ou flags.

 

Modèle du programmeur du 8086/8088
figure 2.09 Modèle du programmeur du 8086/8088 [the .swf]

 

Au modèle du programmeur du microprocesseur, il faut ajouter celui de la machine, ou plutôt du couple machine + système d'exploitation, ses entrées-sorties, bien entendu, mais surtout sa mémoire. Le 8086/8088 pouvait, par ses 20 bits d'adresse, adresser 1 Mo de mémoire. À ne pas confondre avec la limite de 640 Ko de la mémoire RAM, due non pas au processeur lui-même mais à la définition de l'IBM PC et à son système d'exploitation. L'ensemble 8086/8088, DOS et IBM PC définit donc un modèle de mémoire qu'il n'est pas encore possible, malheureusement, d'oublier complètement en 2004. C'est ainsi que nous voyons la mémoire dans certains modes de fonctionnement des machines actuelles.

 

Cartographie mémoire simplifiée de l'IBM PC
figure 2.10 Cartographie mémoire simplifiée de l'IBM PC [the .swf]

 

Cette représentation est un exemple maximal. La seule contrainte était d'avoir un peu de RAM à partir de l'adresse 00000h en montant, ce qui permet d'initialiser des vecteurs d'interruption, un peu de ROM à partir de FFFFFh en descendant, à cause du RESET, et quelques kilo-octets de mémoire écran. La seule limite incontournable (et encore) du standard DOS est la RAM utilisateur inférieure ou égale à 640 Ko.

Pour des raisons de technologie, il est rapidement devenu plus économique d'équiper la carte mère de 1 Mo de mémoire vive en 1 ou 4 blocs que d'implanter 640 Ko en 10 blocs de 64 Ko, car implanter 40 blocs de 16 Ko semblait irréaliste. En effet, à 9 chips mono-bits 16 Kb par banque comme sur les premiers PC, cela représente 360 boîtiers. Avec 9 boîtiers de 1 Mb, de la RAM est présente "sous" la ROM, à la même adresse. Elle n'est tout simplement pas adressée, du moins au démarrage.

La RAM étant d'accès plus rapide que la ROM, une technique dite RAM shadow a été inventée. D'une façon que nous ne détaillerons pas, qui est propre au fabriquant de la carte mère et du BIOS, la ROM est recopiée dans la RAM située sous elle, en miroir. Une fois cette opération effectuée, c'est la ROM qui n'est plus adressée, au profit de la RAM. Eventuellement la zone de la ROM pourrait alors perdre son caractère Read Only.

L'extension mémoire EMS

À partir de la généralisation des lecteurs de disquettes, puis avec le PC-XT et son disque dur, la mémoire ROM avait pour seul rôle de gérer le boot en accord avec le matériel réellement installé sur la carte mère, cette séquence se terminant par le lancement d'un système d'exploitation sur disque ou Disk Operating System. Il est alors clair qu'il n'est plus souhaitable de figer un interpréteur BASIC en ROM, il est bien plus facile de le lancer à partir du disque. Ce qui signifie que la zone mémoire de 384 Ko entre 640 Ko et 1 024 Ko sera généralement sous-utilisée. Elle sera mise à profit pour augmenter artificiellement la mémoire vive utilisable sous DOS.

Bill Gates est souvent cité avec ironie pour avoir en 1981 affirmé à propos de MS-DOS : " 640 Ko (environ 1/2 mégabits) devraient suffire à tout le monde ". Si ce garçon était idiot, cela se saurait. Imagine-t-on sérieusement un 8088 à 4,77 MHz traiter efficacement un 1 Go de données ? Les concepteurs de machines, processeurs et systèmes d'exploitation, dont Bill Gates, savaient très bien que la limite des 640 Ko serait un jour contraignante, mais certainement pensaient-ils qu'il serait alors temps de passer à la génération suivante de matériel et d'OS. Au moment de sa présentation, une génération doit apparaître parfaite, voire définitive, puis, dès que le marché commence à s'essouffler, beaucoup de monde gagne à ce qu'elle devienne obsolète. Ce qui avait certainement été sous-évalué, c'est l'exigence de compatibilité ascendante et de conservation en l'état du logiciel existant.

En effet, il a effectivement fallu augmenter artificiellement et considérablement la mémoire adressable par le 8086, et surtout par le DOS. Pourtant, en 1985, au moment où ces extensions deviennent réellement disponibles, le 80286 existe depuis longtemps, et le PC-AT arrive sur le marché. Le 80286 adressait 16 Mo de mémoire sans artifice, mais éditeurs et utilisateurs choisiront de continuer très longtemps à utiliser DOS, donc d'une certaine façon à n'utiliser qu'un 8086 dans le 80286. La norme d'extension que nous allons aborder était donc plus une rustine à DOS qu'une évolution du 8086.

Les gens intéressés par l'augmentation de la mémoire adressable étaient les éditeurs de logiciels professionnels, parmi lesquels, outre Microsoft, Ashton Tate pour dBase et Lotus pour le tableur 1 2 3. Des éditeurs sont allés jusqu'à proposer sous leur nom des cartes d'extension mémoire. L'idée fut donc de trouver une norme matérielle et logicielle impliquant un nombre suffisant de partenaires pour en assurer le succès. Ce furent Lotus, Intel et Microsoft, d'où l'acronyme LIM , qui décrochèrent la timbale. Les termes EMS , comme Expanded Memory System et mémoire paginée sont également utilisés. D'autres propositions comme  EEMS eurent leur heure de gloire.

En revanche, ce n'est pas parce que votre machine embarque 512 Mo de mémoire que les extensions mémoire ne vous importent plus : si un programme un peu ancien, par exemple un jeu, demande un gestionnaire de mémoire étendue d'un certain type, il vous  faudra d'une façon ou d'une autre le lui fournir.

Pour étendre la mémoire dans la norme EMS, il faut une carte mémoire pouvant supporter jusqu'à 32 Mo plus quelques circuits dont certains peuvent être assimilés à des registres. Cette carte n'était pas toujours indispensable, si l'on se contentait de 1 Mo installé sur la carte mère et d'une émulation EMS. Les premiers clones asiatiques pouvaient ainsi annoncer cette quantité de mémoire.

Revenons à notre carte. Il faut dégager un bloc, de 64 Ko dans les premières versions, dans la zone de 384 Ko entre le haut de la RAM normale à 640 Ko et le bas du BIOS. Dégager signifie que rien n'est adressé dans cette zone, qui ne comporte selon MS-DOS pas de mémoire sur la carte mère. Ce bloc est lui-même constitué de pages de base de 16 Ko. La carte EMS peut éventuellement être paramétrée pour situer ce bloc un peu n'importe où dans la zone.

Le principe de base consiste à projeter des blocs de 16 Ko de la mémoire de la carte additionnelle à l'adresse de chacune des pages du bloc, c’est-à-dire que le bloc est accédé en lecture comme en écriture aux adresses de la page dégagée en mémoire haute. Cette projection, ou mapping , consiste donc à tromper le processeur par une circuiterie additionnelle.

L’intérêt, sinon tout ceci n'aurait aucun sens, réside dans le fait que le programme peut, à tout moment, en adressant les registres de la carte, modifier ce mapping et ainsi accéder à toute l'étendue mémoire de la carte EMS par combinaisons de pages.

Principe de la projection de pages
figure 2.11 Principe de la projection de pages [the .swf]

La zone des pages dans la mémoire de la CPU, vide en l'absence de carte EMS, s'appelle un cadre de page ou page frame .

Il faut savoir que les signaux disponibles sur les connecteurs d'extension, nommés slots, des PC sont très nombreux, couvrant l'ensemble des bus et signaux de contrôle. Rappelons que :

  Quand une mémoire est divisée, physiquement ou mentalement, en blocs, un certain nombre de bits de poids faible servent à accéder à un octet au sein de chaque bloc. Ce nombre de bits correspond à la taille du bloc. Les bits restants, de poids fort, servent à choisir le bloc.

  20 bits correspondent à 1 048 576 octets, soit 1 Mo. 14 bits à 16 384 octets, soit 16 Ko. 6 bits à 64, ici pages ou blocs. 25 bits correspondent à 32 Mo et 11 bits à 2 048.

Considérons le bloc page situé de  C0000 à C3FFF. En binaire :

1100 00-00 0000 0000 0000 à 1100 00-11 1111 1111 1111

Nous distinguons bien les 6 bits fixes sur la page, et les 14 bits qui sont entièrement parcourus sur l'étendue de cette page. Donc, programmer la carte EMS pour le bloc C0000-C3FFF consiste à configurer la carte pour valider le bloc souhaité quand les 6 bits de poids fort du bus d'adresses seront à 110000 . Il serait possible de traiter moins que 6 bits, par exemple il est déjà certain que le msb est toujours à 1, puisque la zone de 64 Ko est toujours au-dessus de 64 Ko. Ceci ne relève pas de l'électronique compliquée, le seul problème vient du fait que, sur une carte complète pouvant être équipée de 32 Mo, il faut adresser 2 048 pages possibles. Il n'y aura bien entendu pas un fil qui partira réellement vers chaque page, mais plutôt une boîte qui prendra 6 fils en entrée et sortira sur 11 fils.

Deux versions de la norme ont existé, la 3.2 et la 4.0. La 4.0 représente une évolution importante de la 3.2, en termes de mémoire disponible, de positionnement du bloc dans la partie haute de la mémoire et de nombre de pages. Quand un gestionnaire EMS est installé, sa gestion est possible au travers des fonctions de l'interruption 67h.

Aujourd'hui, point n'est besoin d'installer une carte pour bénéficier de l'EMS. Nos machines contiennent suffisamment de mémoire, mais accessible uniquement en mode Protégé. Cette mémoire est rendue accessible en modes Réel et V86 (les notions de modes Protégé, Réel et V86 sont abordées un peu plus loin dans ce chapitre) via le gestionnaire de périphériques  EMM386 . Il s'agit d'un émulateur EMS, accessible donc par les fonctions de l'interruption 67h. Une première partie de ces fonctions est compatible 3.2 (donc 4.0). Les suivantes sont uniquement compatibles 4.0.

La mémoire EMS, le gestionnaire EMM386 concernent la mémoire paginée. Il existe également la mémoire étendue, permettant de bénéficier sous DOS de la mémoire située au-dessus du premier Mo, dans les générations qui suivront les 8086 et 80186. Mais là, nous anticipons.

Pour résumer le 8086/8088, il s’agit d’un microprocesseur 16 bits quelque peu étrange : bus d'adresses sur 20 bits, registres en 16 bits, un accès mémoire octet par octet. L'IBM PC avait tout pour décourager le bidouilleur. Mais il a fini par imposer son architecture, peut-être à cause de la puissance d'IBM et d'Intel, mais surtout grâce à la possibilité laissée à tout le monde de proposer des machines compatibles sans royalties et à l'impact de l'industrie asiatique sur le prix de ces équipements.

2.2 Du 8086 aux Pentium 4, Athlon XP...

Voyons maintenant comment les rejetons successifs des 8086/8088 ont abouti aux modèles actuels, Pentium 4 et Athlon XP, au moment de l'arrivée sur le marché grand public des modèles 64 bits.

80186/80188

Premier nouveau processeur de la lignée, possédant quelques nouvelles instructions. Donc premier contact avec les problèmes posés par la compatibilité descendante. Une instruction nouvelle 80186/80188 n'était pas gérée par un 8086/8088 et provoquait le plantage. Dans le 80186/80188, une instruction inconnue lançait une interruption, dans le traitement de laquelle on pouvait par exemple émuler l'instruction, ce qui ne résolvait pas le problème mais préparait l'avenir. Peut-être la notion de lignée n'avait-elle pas été envisagée dès le tout début de l'aventure.

Le 80286

Le 80286 apparu en 1982 introduisit des améliorations majeures. Il fut supporté par un véritable standard de machine, le PC-AT apparu en 1985. Il avait donc tout pour imposer ses améliorations.

Mais Intel avait certainement, peut-être pour la dernière fois, sous-estimé les problèmes de compatibilité. Le logiciel disponible sous DOS était déjà très répandu. Le 286 est théoriquement pleinement compatible 8086. En fait, avec ce modèle, Intel inaugure une série de "deux en un". Dans un mode de fonctionnement dit mode Réel , c'est un 8086. C'est dans ce mode que le processeur démarre.

Il est ensuite possible de passer dans le mode le plus performant, le mode Protégé . Dans ce mode, les applications compilées pour le 8086 ne fonctionnent pas. Et malheureusement, une fois en mode Protégé, on ne pouvait facilement revenir au mode Réel. C'est le principal défaut de ce processeur.

Cela eut pour conséquence le choix des utilisateurs de conserver un OS compatible 8086 (MS-DOS ou compatible), condamnant au mode Réel. À l'exception d'OS/2 et d'un Windows 286 qui n'a pas laissé d'inoubliables souvenirs, le 286 fut donc essentiellement exploité comme un 8086 légèrement amélioré et plus rapide, mais nécessitant un gestionnaire de type EMS pour accéder à la mémoire au‑dessus de 1 Mo.

À côté des instructions système nécessaires à la gestion des modes et de la protection, propres au nouveau modèle du programmeur, le 286 a vu l'apparition de quelques instructions utilisables dans les deux modes ( BOUND , ENTER , LEAVE , INS , OUTS , PUSHA , POPA ). D'autres instructions, rotations et décalages, ont été légèrement modifiées.

Les processeurs précédents avaient un bus d'adresses de 16 bits, qui permettait d'adresser 1 Mo, un PC sous DOS ne permettant que 640 Ko de RAM. Le bus d'adresses du 286 comporte 24 bits, l'espace adressable est donc physiquement de 16 Mo. En mode Réel, le 286 n'accède comme le 8086 qu'à 1 Mo de mémoire, 4 bits du bus d'adresses ne servent à rien.

Le modèle du programmeur est très proche de celui du 8086. Voyons surtout les quelques différences.

Registres du 286
figure 2.12 Registres du 286 [the .swf]

Les registres et flags supplémentaires ne sont utiles qu'en mode Protégé, ou en mode Réel au moment du passage en mode Protégé. Trois points distinguent ce mode :

  L'espace mémoire adressable est de 16 Mo.

  L'existence de plusieurs niveaux de privilège. Certaines instructions, l'accès complet à la mémoire et à sa gestion, à certaines ressources, sont réservés au niveau le plus privilégié, qui va ensuite décider des droits des applications.

  Le registre de segment existe toujours et est utilisé pour la détermination de la partie haute de l'adresse physique. Mais son contenu n'est plus l'adresse réelle d'un début d'un segment, ni même d'une zone : c'est un sélecteur , qui permet d'accéder à un descripteur . Ce descripteur indique, outre l'adresse et la taille du segment, un certain nombre d'attributs propres à ce segment, dont le niveau de privilège.

Nous verrons pourquoi cette notion de sélecteur, toujours présente sur les processeurs les plus récents, ne doit pas effrayer le programmeur en assembleur "normal".

Un descripteur de segment sur le 286
figure 2.13 Un descripteur de segment sur le 286 [the .swf]

Le registre d'état machine MSW ne contient pour le 286 que 4 bits documentés. Trois concernent le coprocesseur arithmétique. Seul le bit 0, ou bit PE comme Protection Enable, nous intéresse, puisqu'il indique le mode de fonctionnement, Réel ou Protégé. Il est à 0 à la mise sous tension, ce qui correspond au fait que, comme nous l’avons signalé, le 286 démarre toujours en mode Réel. Il faut utiliser l'instruction LMSW (Load Machine Status Word) pour allumer le bit PE et passer en mode Protégé. Une fois dans ce mode, le repositionnement à 0 de PE devient impossible. Repasser en mode Réel ne semble pas avoir été envisagé et il faut recourir à une ruse : déclencher un RESET et faire en sorte que le BIOS ne réinitialise pas tout. Une opération lourde, qui interdit un multitâche préemptif performant si toutes les applications et le noyau ne sont pas dans le même mode.

Le 286 a donc introduit des concepts fondamentaux, encore valables aujourd'hui, dont le moindre n'est pas celui de mode. La gestion efficace du multitâche fait également son apparition, en mode Protégé et à l'aide du registre TR.

Des versions d’origine non-Intel ont existé pour le 286, mais il s’agissait généralement de fabrications sous licence, autrement dit de clones légaux. Entre-temps était apparu le PC-XT, version du PC initial supportant un disque dur (10, voire 5 Mo au début), et le marché du clone, monté ou par éléments, le plus souvent d'origine asiatique, avait explosé.

Les 386

Après le mode Protégé avec le 286, le 386 a vu une autre évolution majeure, l'apparition des registres 32 bits. La version normale, DX, avait des registres, un bus d'adresses et un bus de données tous en largeur 32 bits. La version économique DX présentait un bus de données réduit à 16 bits. Les registres 32 bits encapsulaient les anciens registres 16 bits.

Avec le 386 est également apparu le mode  V86 , ou Virtual 8086 . Dans ce mode Protégé, le processeur peut exécuter comme des tâches indépendantes des programmes écrits pour le 8086. Mieux, plusieurs applications DOS peuvent tourner "simultanément", dans plusieurs fenêtres, si le système d'exploitation propose cette possibilité bien entendu.

D'autres améliorations fondamentales, particulièrement concernant la gestion de la mémoire, sont apparues avec le 386. Elles concernent essentiellement le niveau système.

Il faut également noter quelques caractéristiques transparentes pour le programmeur qui commencent à apparaître : nombreuses unités de traitement fonctionnant en parallèle, cache inclus dans la puce.

À partir de ce modèle, le décor est planté. C'est le modèle le plus ancien qui pourrait supporter les systèmes d'exploitation actuels. Plus exactement, il en est structurellement capable, mais généralement insuffisant en termes de performances. Le modèle du programmeur est figé, aux jeux d'instructions additionnels près. La cible "386" définit souvent, dans les options des compilateurs par exemple, le niveau de compatibilité universelle des applications 32 bits.

Avec le 386 sont apparues les premières propositions de solutions compatibles alternatives : parmi d’autres, IBM, Cyrix et AMD, avec l’Am386. Cyrix semblait le plus sérieux concurrent d’Intel et répertoriait ses compatibles 386… 486 !

Les 486

À part quelques instructions supplémentaires et l'intégration du coprocesseur arithmétique, les améliorations du 80486 furent essentiellement transparentes pour le programmeur : cinq pipelines exécutaient en parallèle cinq instructions à différents stades de cette exécution.

Il est dit qu'un processeur est scalaire quand il effectue une instruction par cycle d'horloge et superscalaire quand il fait mieux. Donc le mot scalaire ne définit absolument pas une architecture, mais une performance. Certains processeurs, particulièrement RISC à architecture Harvard, sont scalaires par construction. Le 486 est scalaire, mais en boostant un moteur qui ne l'est pas intrinsèquement. En effet, beaucoup d'instructions s'exécutent en un seul cycle, aidées en cela par le quintuple pipeline, par le cache de niveau 1 de 8 Ko inclus dans la puce et les pattes prévues pour la gestion d'un cache niveau 2 externe. Les dernières versions du 486 (DX4-100) étaient réellement rapides et sont sorties postérieurement aux premières versions du successeur, le Pentium. Avec le 486 sont également apparues des versions particulières, pour des configurations multiprocesseurs, pour des ordinateurs portables, de gestion de l'énergie. Avec lui ou son successeur apparut également la gestion de la température interne du processeur.

Côté copieurs crédibles, toujours Cyrix, avec le 5x86, et la gamme AMD Am486.

La famille Pentium

Génération P5

Ce fut ensuite la sortie du premier Pentium (types P5). À partir de ce moment-là, pour Intel comme pour la concurrence, de plus en plus réduite à AMD, l'évolution devient rapide, mais ne touche pas au cœur du modèle du programmeur. Cette évolution s’est effectuée selon deux axes :

  Des structures internes créatives, de plus en plus performantes, qui de génération en génération accélèrent l'exécution, à code machine et fréquence d'horloge égaux, de façon importante. Et de plus, cette fréquence continue à croître régulièrement.

  De temps en temps, une technologie apparaît. Ce sont MMX, 3DNow!, SSE, SSE2, SSE3, qui se présentent sous la forme d'un jeu de registres et/ou d'une amélioration du jeu d'instructions correspondant. Cette évolution était initialement orientée multimédia.

L'ensemble de ces dernières améliorations, qui ne forment plus à proprement parler une norme mais un jeu d'options, a mis en exergue la nécessité d'une identification facile par logiciel des possibilités du processeur. Dès le 80386, il était possible d'obtenir famille, modèle et sous-modèle du processeur, mais uniquement lors d'un RESET. Avec le 80486 est apparue l'instruction CPUID (CPU IDentification) qui est évolutive et qui va beaucoup plus loin dans la détection des possibilités du processeur. Nous aurons l'occasion d'y revenir. De même, nous réservons un chapitre aux technologies évoluées MMX, 3DNow!, SSE.

Le Pentium, le premier modèle du nom, est sorti dans une ambiance de rumeurs et de fausses nouvelles. Existent encore sur Internet de nombreuses références au scandale du Pentium . Un bien grand mot, pour qualifier un bug, chose assez courante.

Fin 1994 éclata une polémique au sujet d'erreurs dans la FPU des Pentium. En de très rares circonstances, des instructions de division de cette FPU voyaient leur résultat entaché d'une erreur sur la cinquième à la neuvième décimale.

La polémique fut d'autant plus vive que le fondeur avait, semble-t-il, tout d'abord pensé pouvoir passer le défaut sous silence. L'annonce du bug est arrivée en terrain médiatiquement bien préparé.

Ce n'est pas le seul bug reconnu sur une série, mais bizarrement, c'est le seul dont il est possible de trouver des traces. Encore aujourd'hui, des compilateurs C++ ou Pascal proposent une option de correction du défaut Pentium FDIV. Nous pouvons tester cette option en C++ Builder sur le code :

double a, b, c;
a = M_PI;
b = 2.4;
c = b/a;

Le code désassemblé sans l'option est :

00401C4B DD45C8           fld qword ptr [ebp-0x38]
00401C4E DC75D0           fdiv qword ptr [ebp-0x30]
00401C51 DD5DC0           fstp qword ptr [ebp-0x40]

Une fois l'option validée, nous relevons le code suivant :

00401C4F DD45C8           fld qword ptr [ebp-0x38]
00401C52 DD45D0           fld qword ptr [ebp-0x30]
00401C55 803DB434400001   cmp byte ptr [0x4034B4],0x01
00401C5C 7504             jnz +0x04
00401C5E DEF9             fdivp st(1)
00401C60 EB05             jmp +0x05
00401C62 E87C090000       call _fdiv()
00401C67 DD5DC0           fstp qword ptr [ebp-0x40]

Il va sans dire que les processeurs incriminés, aujourd'hui anciens, ont été échangés par Intel et que cette option n'est pas d'une grande utilité.

Le premier intérêt de ce bug est d'être tout à fait expliqué. Pour accélérer le traitement FPU, l'algorithme de division réelle avait été modifié depuis la version 486. La nouvelle version du microcode exploite une table de valeurs numériques à 1 066 entrées, codée en dur. Or, suite à une erreur, 5 de ces valeurs n'ont pas été "claquées" dans le prototype et sont restées à 0. L'erreur étant rare et faible, elle n'a pas été détectée par les procédures de tests avant mise en production, que nous pouvons pourtant supposer drastiques. Il est permis de présumer que l'erreur s'est produite sur des composants de tests de type PLA (Programable Logic Array) et que le masque définitif de production a été fait à l'image de ce réseau.

Il reste de cette affaire quelques plaisanteries du meilleur goût. Un exemple :

Q : Pourquoi le Pentium ne s'est il pas appelé 586 ?

A : Parce que l'addition de 486 et de 100 sur le premier Pentium donnait 585.999983605.

Q : Combien de concepteurs Pentium sont nécessaires pour changer une ampoule ?

R : 0.99904274017.

Q : Comment nomme-t-on une série de FDIV sur Pentium ?

A : Un calcul par approximations successives.

Ce premier Pentium était structurellement un double 486. Deux structures de pipeline, nommées u et v, au lieu d'une, un cache de niveau 1 pour les données et un autre pour le code. Depuis, les techniques de prédiction des branchements pour l'approvisionnement des caches ont évolué. De plus, en interne, les processeurs les plus récents utilisent des bus de données de 128 et même de 256 bits de large, ainsi qu'un bus externe sur 64 bits.

Définition

Instructions SIMD

Une opération SIMD, single-instruction multiple-data , consiste à effectuer en une seule instruction la même opération sur plusieurs données, en parallèle.

Un registre de 64 bits contiendra 8 entiers de 8 bits,  4 entiers de 16 bits, 2 entiers de 32 bits, 1 entier de 64 bits, 2 réels simple précision ou 1 réel double précision.

Des données ainsi regroupées sont indépendantes : pas de report de retenue entre elles lors d'une opération par exemple. On parle en anglais de packed data .

Par exemple, deux registres MMX MM0 et MM1 de 64 bits sont chargés chacun par 4 mots de 16 bits, qui seront additionnés en une seule instruction PADDW. Le mot n de MM0 est additionné avec le mot n de MM1, et le résultat déposé dans le mot n de MM0.

Sont SIMD les technologies MMX, 3DNow!, SSE, SSE2, SSE3, et d'autres actuelles ou à venir.

Le jeu d'instructions MMX et les nouveaux registres MM0 à MM7 (en fait, les registres de la FPU) caractérisent le dernier P5, appelé Pentium MMX.

Définition

Jeu d'instructions MMX

Ce jeu d'instructions SIMD utilise 8 registres MMX 64 bits, qui sont en fait ceux de la FPU. Il propose un jeu d'instructions plutôt orienté multimédia travaillant sur des entiers de 8, 16, 32 bits en général, et sur 64 bits pour certaines.

Chez AMD, nous trouvons, peu performant et sorti relativement tard, le K5. C’était le premier processeur ayant réellement fait l’objet d’un développement original chez AMD. Ce fut un demi-échec, réservé aux configurations économiques.

Génération P6

Le premier de la série P6 fut le Pentium Pro. Trois unités au lieu de deux (3 instructions par cycle). Les instructions sont éclatées en microcode, traitées à part et en parallèle dans plusieurs unités spécialisées (entiers, floats, accès mémoire) de façon asynchrone si possible, puis replacées dans une file d'attente. L'idée est de faire le maximum de choses en parallèle, sur le code et les données préchargés, y compris les prédictions de branchement. Aux caches de niveau 1 s’ajoute un cache de niveau 2 de 256 Ko. La coopération entre les unités de traitement, les caches et la mémoire sont particulièrement travaillés. Enfin, le bus d'adresses passe à 36 bits (64 Go de mémoire accessibles).

Le Pentium Pro n'implémentait pas MMX, certainement selon l'idée que le multimédia et le Pro étaient deux choses différentes. Bien entendu, cette idée n'a plus vraiment cours aujourd'hui. Donc, le Pentium II est un Pentium Pro équipé MMX aux mémoires caches plus ou moins doublées.

Passons sur le Celeron (the Castrated One), version économique du Pentium II, ainsi que le Xeon (Pentium II on steroïds), version serveur du même Pentium II. D'autres versions, en particulier du Celeron, existent.

Le Pentium III introduit essentiellement le jeu d'instructions et les registres 128 bits SSE.

Définition

Jeu d'instructions SSE

Ce jeu d'instructions SIMD est une évolution de MMX. Il utilise 8 nouveaux registres 128 bits XMM. Il propose un jeu d'instructions travaillant en réels simple précision sur ces registres. De plus, il ajoute des instructions entières 64 bits sur les registres MMX.

AMD, peu fier certainement du succès mitigé de son K5, avait lors de sa sortie racheté NexGen, qui amenait les bases du K6. Puis ce fut le K6-2, qui proposait le jeu d’instructions 3DNow!, une amélioration de MMX.

Définition

Jeu d'instructions 3DNow!

Ce jeu d'instructions SIMD est une version AMD un peu améliorée de MMX. Elle ajoute essentiellement 21 instructions, et le type réel simple précision. Donc, la mention 3DNow! implique une compatibilité MMX.

Génération Pentium 4

Le Pentium 4 est basé sur la micro-architecture Netburst. En particulier, les unités de calcul travaillent à plusieurs fois la vitesse d'horloge. Le Pentium 4 introduit la technologie SSE2.

Définition

Jeu d'instructions SSE2

Ce jeu d'instructions SIMD est une évolution de SSE. Il ajoute essentiellement les instructions correspondants à de nouveaux types dans les registres XMM. Ces nouveaux types sont les réels double précision, et les entiers 8, 16, 32 et 64 bits.

Les fréquences d'horloge, de calcul et de bus, ne sont données qu'à titre indicatif. Chaque visite sur le site d'Intel apporte de nouvelles informations. Au moment de la rédaction, le constructeur affirme qu'une des caractéristiques du dernier cœur Pentium 4 est sa réserve de puissance en termes de fréquence, le goulet d'étranglement étant certainement l'accès mémoire. Cette réserve de puissance en interne explique sans doute la présence de pipelines à vingt étapes, soit deux fois plus que l'Athlon, le processeur pouvant effectuer un travail conséquent en parallèle sur le contenu de cette queue. Les modèles les plus récents incorporent une nouvelle extension du jeu d'instructions, SSE3.

Définition

Jeu d'instructions SSE3

Ce jeu d'instructions complémentaires ajoute 13 instructions au jeu SSE2. Elles ne concernent pas uniquement les registres XMM, puisqu'on y trouve des instructions FPU et autres.

(début 2004) Intel Pentium 4 HT Extreme Edition :

  Jeux d'instructions : MMX, SSE, SSE2 et SSE3.

  Clock : 3,4 GHz (calculs à 6,8 GHz).

  Vitesse de bus : 800 MHz.

  Cache niveau 1 : 8 Ko.

  Cache niveau 2 : 512 Ko.

  Cache niveau 3 : 2 Mo.

  Technologie : 0,13 µm.

Pendant ce temps, AMD, depuis le succès du K6-2, avait proposé une série de processeurs tout à fait intéressants, économiques et malgré tout performants, surtout si nous considérions la mémoire DDR supportée. Le dernier produit de la gamme (en oubliant pour l’instant la gamme 64 bits AMD64) est l’Athlon XP +. Ses caractéristiques, un peu en retard à l'instant t sur le Pentium :

(début 2004) AMD Athlon XP 3200 + :

  Jeux d'instructions : MMX, 3DNow! et SSE.

  Clock: 2,2 GHz.

  Vitesse de bus : 400 MHz.

  Cache niveau 1 : 128 Ko.

  Cache niveau 2 : 512 Ko.

Course à la puissance et à la sécurisation, dans le cadre d'une optimisation du multitâche, c'est ce qui a sous-tendu l'évolution que nous venons de survoler. Face à cette évolution, le programmeur en assembleur devra soigneusement déterminer sa cible, ainsi que son domaine d'intervention. Il conviendra pour le débutant de ne pas être trop gourmand.

2.3 L'architecture IA-32 actuelle

Parler de l'architecture des compatibles PC de la dernière génération cache en réalité au moins trois approches, chacune définissant un modèle pouvant représenter un environnement de travail spécifique :

Modèle matériel  : c'est ce que nous venons de toucher du doigt dans le paragraphe précédent, avec les notions de caches, pipelines, microcode. L'architecture interne d'un microprocesseur nous est masquée. Néanmoins, nous pouvons agir par certaines instructions sur, par exemple, le comportement des caches. Cette partie de l'architecture est spécifique à chaque microprocesseur, bien qu'il existe des points communs entre eux, et il faudra le cas échéant se procurer, généralement par téléchargement, de la documentation chez le constructeur sous forme de brochures ou mieux de guides d'optimisation.

Modèle du programmeur d'applications  : quand vous programmez de simples applications, par exemple sous DOS, Windows, Linux, seule une partie du microprocesseur (et de l'ensemble de la machine) est mise à votre disposition. Vous devrez alors travailler avec le modèle du programmeur d'applications correspondant au mode de fonctionnement. Ce modèle est standard, commun à tous les processeurs de la même famille, à quelques détails près, et autorise donc quelques espoirs de portabilité.

Modèle du programmeur système  : il s'agit de l'ensemble des caractéristiques du microprocesseur, utiles pour programmer au niveau système. Bien entendu, même en restant au niveau application, il n'est pas inutile d'être curieux. Ce modèle est également standard, puisqu'il doit être respecté par tous les processeurs aspirant à la compatibilité.

Seul le modèle 2 entre complètement dans le cadre de ce livre. Un chapitre sera dédié à l'optimisation, mais simplement une approche générale non spécifique. Nous en savons donc suffisamment sur l'architecture matérielle générale des machines compatibles IA-32.

La programmation système est abordée dans un autre ouvrage du même auteur chez le même éditeur.  Néanmoins, nous allons aborder le sujet en quelques lignes. Pas uniquement pour ne pas mourir idiot, mais parce que c'est nécessaire pour introduire des notions aussi importantes que le multitâche, les privilèges, les modes de fonctionnement. Et parce qu'en programmant de simples applications, nous héritons d'une environnement mis à notre disposition par le système.

Le multitâche, justement, permet par ses contraintes d'introduire de nombreux concepts avancés au niveau système. Voyons ces contraintes...

2.4 Les contraintes du multitâche

Sous DOS, certains artifices bien pratiques pouvaient donner l'illusion que plusieurs tâches pouvaient tourner simultanément. Un spooler d'imprimante, qui va utiliser des interruptions pour travailler à l'arrière plan d'une application, un programme de type TSR (Terminate and Stay Resident). D'une certaine façon, les débogueurs peuvent donner la même impression, puisqu'ils surveillent un programme en train de tourner.

Si nous voulons aller plus loin dans l'illusion, nous pouvons tenter de charger plusieurs programmes en mémoire et de les faire fonctionner à tour de rôle. La magie fonctionne mieux quand un environnement fenêtré montre les deux applications évoluer de concert.

Il suffit par exemple que chaque programme décide "de temps en temps" de donner la main à une autre application ou au système d'exploitation qui s'occupera de lancer la tâche suivante. Nous venons de définir le multitâche coopératif , qui était utilisé dans Windows 3.1 par exemple. Une tâche pourra ainsi tester en boucle un ensemble de capteurs, dont le clavier, traiter l'événement s'il y a lieu et "rendre la main" à la fin de chacune de ces boucles. Si aucune touche ou aucun capteur n'a été activé, la boucle sera très courte, et plus longue dans le cas contraire. Si le traitement est considéré comme trop long, il pourra être prévu d'autres points de commutation, au détriment de la clarté de programmation. Si, suite à une erreur de programmation, événement non prévu par exemple, la tâche plante, alors le système est lui-même "à la rue". Les autres tâches à ce moment-là en sommeil sont perdues, données comprises. Sans aller jusqu'au plantage, une tâche qui consommerait plus de temps de boucle que prévu va pourrir le fonctionnement de l'ensemble du système. Au chapitre des avantages, nous constatons que chaque tâche "sait" quand elle rend la main. Il n'y a donc pas à se préoccuper particulièrement de sauvegarder le contexte : instruction en cours, valeur des registres, etc. Accompagné d'un circuit chien de garde ou watchdog , ce peut être une bonne solution pour une carte à microcontrôleur.

Pour nous affranchir des défauts de ce mode coopératif, testons une autre méthode : programmons un noyau de gestion des tâches. Outre les fonctions de chargement et de lancement des applications, c'est là que va se décider le temps accordé à chacune d'entre elles, selon des critères variés de priorité. Imaginons que sur notre système, rien ne soit spécialement prévu pour cette gestion du découpage des tranches de temps. En revanche, nous disposons d'un timer programmable câblé pour générer une interruption non masquable (NMI). Nous allons écrire la partie découpage du temps de notre noyau ( scheduling ) dans la routine de traitement de cette NMI. Pour accorder une tranche de temps à une tâche, nous allons lancer le timer, puis sauter dans la tâche, et ainsi de suite. Chaque tâche sera interrompue par la NMI à la fin de la période allouée. Au noyau de veiller à ce qu'elle retrouve son contexte de fonctionnement quand, après un tour de table complet, il lui accordera à nouveau du temps. Bien entendu, les choses ne sont pas tout à fait aussi simples, mais il est possible de faire la manipulation, en prenant soin par exemple de récupérer, sur la pile, l'adresse de retour d'interruption et de la remplacer par l'adresse en cours sauvegardée de la tâche suivante, etc. Ce fonctionnement, dans lequel le noyau du système d'exploitation impose sa volonté, décrit le multitâche préemptif , utilisé dans les versions récentes de Windows.

Le premier gros avantage de cette méthode est qu'une application même mal écrite ne consommera sur le système que le temps prévu. Si nous pouvons renvoyer le traitement de certaines exceptions inattendues vers le noyau, c'est encore mieux. Pour "tuer une tâche", le noyau ne lui redonne pas la main, libère son espace mémoire et les ressources du noyau propres à cette tâche, par exemple les sauvegardes de contexte.

Pour fixer les idées, un programme A et un programme B fonctionnent par tranches de 100 ms, et les passages de A vers B et de B vers A prennent chacun 50 ms.

Enchaînement de tâches
figure 2.14 Enchaînement de tâches [the .swf]

Puisque chaque tâche fonctionne le tiers du temps (100 ms sur 300 ms), et si nous sommes sur un système "tournant à 600 MHz", chaque tâche verra virtuellement le même processeur, mais à 200 MHz.

En commutant les tâches plus fréquemment, peut-être l'illusion de simultanéité sera-t-elle meilleure. Malheureusement, ce n'est pas en diminuant le temps accordé à chaque tâche que le temps nécessaire à la commutation va diminuer. Le schéma devient donc :

Enchaînement de tâches
figure 2.15 Enchaînement de tâches [the .swf]

Dans ce cas, une simple règle de trois permet d'affirmer que des 600 MHz du processeur, chaque tâche ne verra que 50 MHz virtuels. 500 MHz seront utilisés par la commutation de tâches et seulement 2 fois 50 MHz pour le travail utile. Il faudra donc trouver un compromis pour déterminer la fréquence de commutation des tranches de temps. De plus, il devient clair qu'un processeur puissant est une des nécessités afin d’obtenir un multitâche performant.

Nous pourrions aller plus loin dans le jeu, mais la programmation suffirait de moins en moins. Voyons ce qu'un microprocesseur pourrait nous proposer pour améliorer notre système multitâche, particulièrement dans le sens de la sécurité. Par sécurité, entendons la possibilité de lancer une application inconnue, idéalement non écrite spécifiquement pour du multitâche, avec un minimum de risques pour la stabilité du reste du système.

2.4.1 Niveaux de privilège

Une tâche ne peut pas masquer la NMI, certes, mais elle peut en modifier le traitement. Sous 8086, il n'est pas possible d'interdire sélectivement telle ou telle instruction, ou l'accès à une zone de la mémoire ou de la périphérie. Donc, n'importe quelle tâche peut tout à fait écraser code et données du noyau.

Donc, nous aimerions pouvoir positionner un niveau de privilège, qui déterminerait les opérations possibles et celles qui ne le sont pas. Simplement avec deux niveaux, noyau et application , les possibilités sont séduisantes. La question qui vient immédiatement à l'esprit est celle du passage du niveau application au niveau noyau . Il suffirait pour que cela puisse fonctionner que la NMI, ou l'interruption spécifique qui la remplacerait, repositionne automatiquement le niveau de privilège à noyau .

2.4.2 Mémoire

Nous savons déjà en partie résoudre par les segments le problème de la relocation du code. Nous avons vu, avec la mémoire EMS, qu'une adresse issue d'une instruction, ou adresse logique , pouvait être modifiée extérieurement en intercalant sur le bus d'adresses un circuit de translation. Nous aimerions que le processeur améliore cette fonctionnalité, en gérant finement la traduction de l'adresse logique vers une adresse physique , non segmentée. A cette traduction nous ajouterons une vérification des limites de la mémoire adressable. Ainsi, chaque tâche pourrait fonctionner réellement comme si elle était seule et les applications ne pourraient physiquement pas écrire dans les zones affectées aux autres applications et surtout au noyau du système d'exploitation.

Chaque tâche pourra ainsi voir l'ensemble de la mémoire adressable, il est clair que la quantité de mémoire réellement mobilisée à un instant donné va pouvoir dépasser la mémoire physique disponible sur le système. Il serait bon que de la mémoire rarement utilisée, ou inutilisée pendant un certain laps de temps, puisse être facilement sauvegardée temporairement sur le disque. Cette technique est connue sous le nom de mémoire virtuelle  : avec des ressources suffisantes et un système d'exploitation le permettant, le programmeur pourra utiliser de façon transparente une quantité de mémoire dont il ne dispose pas physiquement.

2.4.3 Commutation des tâches

Dans ces conditions, chaque tâche peut ignorer qu'elle est interrompue de temps en temps, nous pouvons faire tourner des applications non prévues pour un mode multitâche : nous avons plusieurs ordinateurs indépendants dans la même machine.

Pour en arriver là, le travail fait par le noyau à chaque commutation est important en termes de sauvegarde et restauration du contexte et de l'espace mémoire. Il serait bon que tout soit mis en oeuvre pour le faciliter et l'accélérer. Le microprocesseur va pour cela être conçu en connaissant physiquement des entités task . Tout ce qui concerne le contexte de fonctionnement d'une telle task sera dans des tables en mémoire, accessibles au travers de pointeurs, et les tâches pourront facilement être commutées.

La réalité est beaucoup plus complexe. Nous n'avons pas, par exemple, abordé le problème des ressources partagées : il n'existe pas une imprimante ou un lecteur de CD-Rom pour chaque tâche. Donc, nous introduisons la notion de jeton ou de sémaphore. Par exemple, une case mémoire accessible depuis toutes les tâches, indiquant la disponibilité de la ressource. Mais tout cela va encore se compliquer. Pour utiliser une ressource, imaginons la séquence :

Je lis le jeton de la ressource (instruction de lecture).

Je constate qu'il est à "libre" (instruction de test).

Je le mets à "occupé" (instruction d'écriture).

Je travaille avec la ressource (plusieurs instructions).

J'ai terminé, je remets le jeton à "libre" (instruction d'écriture).

Rappelons que, quand survient une interruption, l'instruction en cours se termine. Si la commutation de tâches survient entre le début et la fin de 2, et si une autre application initie la même séquence pendant la période de sommeil, deux applications vont partager la ressource de façon erronée et sans le savoir. Le problème pourrait être résolu par une nouvelle instruction qui remplacerait 1-2-3 : "Lire jeton et changer si..." Il serait bon que cette instruction soit protégée d'une prise de possession du bus durant son déroulement, puisque cette prise de possession n'attend pas la fin de l'instruction en cours. C'est ce que réalise le préfixe LOCK (voir le chapitre sur le jeu d'instructions).

2.4.4 Le système d'exploitation

Nous allons retrouver l'ensemble de ces notions au cœur d'un microprocesseur actuel. Mais la gestion réelle du multitâche incombe au système d'exploitation. Par exemple, la représentation de la répartition des temps de travail en tranches égales n'est généralement pas réaliste du tout.

Supposons que nous avons déposé la calculatrice sur le Bureau de Windows, à toutes fins utiles. Hors utilisation de cet accessoire, le mieux serait que nous demandions au noyau de Windows de gérer lui-même les quelques événements susceptibles de réveiller la calculatrice. Nous sommes là dans le domaine de la programmation par messages et événements, nous devons retenir que multitâche préemptif ne signifie absolument pas tranches de temps égales.

Nous pouvons sous Windows consulter la charge processeur de chaque processus (  Ctrl  +  Alt  +  Suppr  ).

Processus actifs sous Windows XP
figure 2.16 Processus actifs sous Windows XP

Nous sommes sous Windows XP. Remarquons que la valeur affichée (en %) est nécessairement l'intégration de l'activité sur les quelques instants précédents, puisque l'activité à l'instant t est 100% sur une tâche. psp.exe est l'application qui capture l'écran, il est donc logique qu'elle soit active, tout comme explorer.exe et de temps en temps taskmgr.exe . wmplayer.exe est le Lecteur Windows Media jouant un fichier encodé en MP3. Nous remarquons qu'écouter un peu de musique n'est pas très coûteux. Cette valeur est valable quand la fenêtre est invisible ou quasi-statique, affichage de la liste des titres par exemple. En revanche, nous dépassons largement les 10% dès qu'une animation psychédélique est visible à l'écran.

Songeons que certains processus (Corel, Word, Acrobat Reader, Explorateur) gèrent ici un grand nombre de fenêtres ou documents, tout ceci sans ralentissement perceptible.

Programmant en assembleur, nous en voudrons à un moment ou à un autre à Windows de nous masquer tant de choses, interruptions de BIOS et du BIOS particulièrement dans le cas des versions XT et 2000. Ne faisons pas l'erreur de considérer cela comme une contrainte. C'est même la valeur ajoutée de l'OS, qui ne prétend absolument pas être "temps réel". D'autres propositions existent, chez Microsoft et ailleurs. La programmation Windows en assembleur est introduite dans l'autre ouvrage de l'auteur déjà cité.

2.5 Modèle du programmeur système

En abordant le sujet, c'est immédiatement l'image de l'iceberg qui vient à l'esprit : ce qui est visible en programmation normale est, avec la complexité du niveau système, dans le même type de rapport que les parties visibles et immergées du glaçon.

La partie invisible de la programmation
figure 2.17 La partie invisible de la programmation [the .swf]

Dans le schéma de l'architecture système IA-32, donnée pour information, qui représente de façon simplifiée les registres et structures mises en œuvre, nous avons marqué d'un astérisque les quelques éléments que reconnaît le modèle du programmeur d'applications.

L'architecture système IA-32
figure 2.18 L'architecture système IA-32 [the .swf]

Rappelons les rôles dévolus à la couche système de l'architecture :

  Gestion de la mémoire.

  Protection du système et des applications.

  Multitâche.

  Gestion des exceptions et des interruptions.

  Gestion des caches.

  Gestion du matériel et de l'énergie.

  Débogage.

  Contrôle et mesure des performances.

  Gestion du mode multiprocesseur.

C'est tout ce que nous dirons de l'architecture système, passons à ce qui en découle au niveau de la programmation d'applications.

2.6 Modèle du programmeur d'applications

Quand nous programmons de simples applications, sous Windows ou sous DOS, un mode de fonctionnement et donc un modèle du programmeur s'imposent. Dans le cadre d'une application, nous sommes dans un mode ou dans un autre, nous ne gérons en aucun cas le basculement d'un mode à un autre.

2.6.1 Modes de fonctionnement

Rappelons que nous nous situons dans un contexte DOS - Windows, même si nous ne le rappelons pas. Donc, dans ce qui suit, nous trouvons un mélange de faits liés au processeur et de spécificités comme par exemple la limitation de la mémoire accessible sous DOS.

A un instant donné, le processeur peut se trouver dans un mode de fonctionnement parmi quatre. Le basculement "soft" est possible entre chacun de ces modes, à l'exclusion du SMM :

Le mode de gestion système, ou mode SMM, system management mode

Ce mode très spécial n'est accessible que via une interruption externe spécifique : SMI. Il est réservé à des tâches particulières, la gestion de l'alimentation par exemple. Il intéresse les concepteurs de BIOS et d'OS, nous l'oublierons.

Le mode Réel

C'est celui du 8086 tel que nous le connaissons. Le processeur est dans ce mode lors d'un reset, donc au démarrage. Il n'a alors accès qu'aux mêmes possibilités qu'un 8086, limitation mémoire comprise.

En réalité, les instructions utilisables en mode Réel sont beaucoup plus nombreuses que celles du 8086, mais nous préfèrerons généralement rester compatible avec ce processeur.

La seule possibilité supplémentaire, mais importante, est de pouvoir basculer en mode Protégé ou en mode SMM.

L'adresse logique obtenue à partir d'un registre de segment et d'un déplacement, est directement appliquée au bus. Les registres 32 bits ni les mécanismes de protection n'existent pas.

Ce mode est accessible en bootant sur une disquette DOS, ou en utilisant Redémarrer en mode MS-DOS sous Windows 9x. Sous Windows XP ou 2000, rien n'est livré avec l'OS pour accéder à ce mode. Ce n'est pas le mode des fenêtres DOS ou Invite de commandes sous Windows.

Nous travaillons avec le modèle du programmeur et la cartographie mémoire, que nous connaissons, et le jeu d'instruction d'un l'IBM PC-XT sous MS-DOS.

Certains composants de Windows, au niveau le plus profond, travaillent dans ce mode.

Le mode Virtual-8086, ou V86

C'est un sous-mode du mode Protégé qui permet de mettre à la disposition d'une application 16 bits un environnement 8086 complet, comme en mode Réel, tout en bénéficiant des avantages du mode Protégé, protection et multitâche.

Sous Windows, chaque session DOS met à la disposition du programmeur un environnement DOS complet, 1 Mo de mémoire, copie des 384 Ko affectés au système, gestionnaire EMS. Et cela est possible en un certain nombre d'exemplaires. L'avantage est que le blocage de cette tâche ne met pas en cause la stabilité du système.

Le programmeur utilisera les mêmes modèles et jeu d'instruction qu'en mode Réel. C'est en cas de problème qu'il faudra faire la différence avec la mode Réel. Le matériel et les interruptions sont par exemple traités par le système et inaccessible directement par le programme.

Attention, ce point dépend de l'OS. Le port parallèle n'est définitivement pas accessible directement sous Windows XP dans ce mode, alors qu'il l'est sous Windows 98se. Disons qu'une tâche V86 est une tâche comme une autre, à laquelle le système d'exploitation peut ou non accorder des droits.

Le mode Protégé

C'est le mode normal des programmes utilisateurs, dans lequel ils tireront le meilleur du matériel et du jeu d'instructions. En réalité, dans ce mode Protégé, est possible de distinguer des sous-modes 16 et 32 bits, et il est fréquent que les deux notions soient confondues par erreur.

Voyons d'abord le modèle du programmeur, qui sera constitué tout d'abord de la cartographie des registres. Les jeux MMX et XMM sont théoriquement optionnels, mais très répandus. Placer MMX dans la FPU est peut-être discutable, mais cela ne change rien pour le programmeur, puisqu'il ne peut utiliser les deux jeux de registres simultanément. Sans MMX ni XMM, ce modèle est valable pour tous les processeurs depuis le 386, en y incorporant la FPU, c’est-à-dire l'ancien coprocesseur arithmétique. La limite de l'espace mémoire à 2 32 octets peut être portée dans certains cas à 2 36 octets.

Environnement de base en IA-32
figure 2.19 Environnement de base en IA-32 [the .swf]

Pour le jeu d'instructions, c'est le jeu complet, instructions en virgule flottante comprises, mais en excluant les instructions privilégiées . Il faut ajouter la liste des flags. Instructions et flags sont présentés au chapitre sur le jeu d'instructions. Enfin, selon votre équipement ou la machine cible, il vous faudra la documentation des divers jeux d'instructions supplémentaires utilisés, comme 3DNow !, SSE3 par exemple. Les modèles du programmeur IA-32 se composent donc d'une documentation de base et d'une série de kits, chacun dédié à une technologie particulière.

Dans le mode Protégé, pour des applications 32 bits, les choses sont simples le plus souvent. Il est en effet très improbable que nous ayons à nous préoccuper du contenu des registres de segments. Sous Windows, nous allons demander au système de nous fabriquer au minimum trois segments, code, pile et données, correspondant à CS, SS et DS. Ce travail de demande sera celui du lieur, sur la base du type de programme que nous souhaitons développer. Donc tout est transparent pour le programmeur, qui n'a même pas à se préoccuper de savoir que les registres de segments contiennent des sélecteurs. Sous DOS, c'est relativement facile, en choisissant sous MASM le modèle mémoire adéquat, mais il faudra quand même gérer des segments.

La segmentation en mode Protégé n'est plus une nécessité pour accéder à toute la mémoire, c'est une facilité dédiée à la gestion de la fiabilité d'un système, particulièrement s'il est multitâche. Un modèle non segmenté existe, mais pas sous Windows : il suffit que tous les sélecteurs pointent vers un descripteur tel qu’adresse de base égale 0, niveau de privilège égale 0 et limite égale taille maximale de la mémoire, jusqu'à 4 Go. C'est le modèle flat de la documentation Intel. Il ne faut pas le confondre avec d'autres utilisations du terme flat sous Windows, comme le modèle mémoire FLAT de MASM. Plus clairement, dire que le modèle est flat 32 bits sous Delphi ne signifie pas que le microprocesseur est dans le modèle flat.

Il faut bien dire que les confusions sont nombreuses autour de mots comme flat, mode, modèle, segment, qui peuvent prendre plusieurs sens selon le contexte. Il nous reste justement à clarifier la notion de mode 16 bits et de mode 32 bits.

16 bits - 32 bits

Ce point est important, et dépasse le cadre de la programmation système. Les notions de taille d'adresse par défaut et de taille d'opérande par défaut sont parfois mal comprises à la lecture de la documentation sur le jeu d'instructions. Par exemple, 16 bits n'est pas systématiquement associé à un mode 8086 (Réel ou Virtual), ni 32 bits au mode Protégé.

En mode Protégé, il existe deux types de modules : les modules 16 bits et les modules 32 bits. Ces deux types définissent d'une certaine façon deux sous-modes, mais attention, nous sommes toujours en mode Protégé. En fait, c'est chaque segment qui est de type 16 bits ou 32 bits, selon des attributs dans les descripteurs.

Caractéristiques des types 16 bits et 32 bits

Caractéristique

Module 16 bits

Module 32 bits

Taille d'un segment

0 à 64 Ko

4 Ko à 4 Go

Taille par défaut des opérandes

8 bits et 16 bits

8 bits et 32 bits

Taille par défaut des adresses (offset)

16 bits

32 bits

Taille par défaut du pointeur de pile

16 bits (SP)

32 bits (ESP)

Ce sont deux bits dans le descripteur de segment qui positionnent les tailles d'opérandes et d'adresses par défaut. Il est donc possible au système de mélanger, la notion de module n'étant qu'une simplification.

En mode Protégé 16 bits que la taille des segments varie de 0 à 64 Ko, avec protection. En mode Réel, elle est de 64 Ko, rien ne protégeant un segment d'un autre s'ils se recouvrent.

Il est donné deux tailles d'opérandes par défaut. Si nous regardons la documentation, nous trouvons très souvent des définitions faisant apparaître trois tailles d'opérandes :

C6 /0 MOV r/m8,imm8   Move imm8  to r/m8
C7 /0 MOV r/m16,imm16 Move imm16 to r/m16
C7 /0 MOV r/m32,imm32 Move imm32 to r/m32

Les deux dernières ont le même opcode. Le choix se fera à l'exécution, en fonction de l'attribut de taille d'opérande par défaut. Il est possible d'inverser ce choix, en préfixant l'instruction par le préfixe de surcharge de taille d'opérande (66h). Vous n'aurez à priori pas à vous préoccuper de ce préfixe, l'assembleur le gère pour vous.

Voyez par exemple cette séquence, extraite d'un fichier listing MASM, le programme étant en modèle FLAT, donc 32 bits :

00000013  B8 000001A8   mov eax, 1A8h
00000018  66| B8 01A8   mov  ax, 1A8h

Attention, le signe | suivant 66 est ajouté au listing par MASM pour justement signaler une surcharge de taille.

Répétons-nous peut-être, ce point le mérite. Dans tous les modes, les trois instructions suivantes sont possibles :

mov  al,  A8h ; 1
mov  ax, 1A8h ; 2
mov eax, 1A8h ; 3

Avec une taille d'opérande par défaut de 16 bits, 1 et 2 seront obtenues directement, 3 à l'aide de la surcharge de taille d'opérande 66h.

Avec une taille d'opérande par défaut de 32 bits, 1 et 3 seront obtenues directement, 2 à l'aide de la surcharge de taille d'opérande 66h.

Exactement de la même façon, l'attribut de taille d'adresse détermine la taille d'adresse par défaut :

FF /4 JMP r/m16    Jump near, absolute indirect, address given in r/m16
FF /4 JMP r/m32    Jump near, absolute indirect, address given in r/m32
EA cd JMP ptr16:16 Jump far, absolute, address given in operand
EA cp JMP ptr16:32 Jump far, absolute, address given in operand

Il est également possible de modifier ce choix par le préfixe de surcharge de taille d'adresse (67h). Cette surcharge est beaucoup moins fréquente que celle de la taille d'opérande. Dans le chapitre sur la pile et les sous programmes, ce point est repris au sujet des instructions PUSH et POP, qui sont intéressantes sur ce point en ce sens que à la fois la largeur du pointeur de pile et le nombre d'octets par lequel il est incrémenté et décrémenté varient en fonction des attributs.

 

2.7 Les architectures 64 bits – AMD64

Ce paragraphe doit être abordé en tenant compte de sa date de rédaction, à savoir début 2004. L'auteur n'est pas au fait des derniers potins de l'industrie du microprocesseur. Enfin, nous ne nous intéressons volontairement qu'à la suite directe de la lignée "compatible PC", et de plus en nous limitant aux offres Intel et AMD.

Depuis plusieurs années, la règle semblait être : Intel développe une nouvelle génération de processeurs dans la lignée x86 qu'AMD clone, avec de plus en plus souvent des améliorations significatives, puis se positionne au mieux sur le marché en travaillant sur les prix. Le terme clonage n'est pas péjoratif, il désigne simplement le respect des contraintes de la compatibilité.

Intel propose depuis plusieurs années une architecture  IA-64 , des processeurs Itanium , avec de bons résultats dans le domaine des serveurs, stations de travail et calcul intensif. Malheureusement, cette technologie est chère et, malgré son nom, est mal adaptée aux logiciels IA-32 non recompilés, c'est à dire à l'immense majorité de l'existant. C'est justement cet existant que AMD a choisi de favoriser.

C'est sous la forme d'une architecture initialement nommée x86-64  qu'AMD a présenté une solution de compatibilité dans la continuité "à la Intel", en poupées russes. C’est-à-dire que IA-32 est présent au sein de x86-64, comme le 8086 était encapsulé dans IA-32, avec apparition des modes de fonctionnement.

Au printemps 2003, AMD change le nom de la technologie de x86-64 en AMD64 . Puisque IA-32 est entièrement dans AMD64, nous y retrouvons un 8086 en modes Réel et Protégé. AMD64 ne fait appel à aucune émulation pour honorer les nombreux modes de fonctionnement. Il y aurait émulation si le code machine ancien était d'abord traduit dans un nouveau jeu d'instructions avant d'être exécuté, comme c'est le cas en IA-64 d'Intel.

Des machines AMD64 sont aujourd'hui proposées à la vente, qui peuvent utiliser les systèmes d'exploitation 32 bits. L'architecture est supportée par la communauté Linux et par Microsoft. Des logiciels adaptés commencent à être disponibles. Des toolkits de développement sont disponibles, qui fort logiquement sont utilisables sur nos machines actuelles.

Tout semble donc bien se présenter pour que AMD soit le temps d'une génération en position d'initiateur. Il reste à souhaiter que l'offre en support et documentation soit d'une qualité au moins équivalente à ce que propose son concurrent. Il semble que ce soit le cas. En particulier, vous trouverez sur le CD-Rom des liens pour télécharger un excellent AMD64 Architecture Programmer’s Manual en cinq volumes sous format  .pdf , ainsi qu'un Software Optimization Guide for AMD Athlon 64 and AMD Opteron Processors . Ces documents doivent être utiles en IA-32 (AMD et Intel), mais il faudrait déterminer jusqu'à quel point.

Ces processeurs sont prévus pour faire fonctionner des applications 16 bits et 32 bits sans recompilation. Ils sont, de plus, aptes, toujours sans recompilation, à supporter les systèmes d'exploitation 32 bits et même 16 bits. En survolant la documentation AMD, il apparaît que la transition pourra se faire en douceur, au niveau des applicatifs bien entendu, mais également des systèmes d'exploitation, drivers et même du BIOS.

Au niveau des modes de fonctionnement, les choses vont encore se compliquer, mais de façon logique et prévisible pour qui connaît IA-32. Cette idée de complication doit être modérée. En fait, comme en IA-32, même en assembleur, le programmeur se trouvera le plus souvent dans un mode et pourra totalement oublier les autres. Il pourra continuer à utiliser, avec son ancienne documentation, choisie en fonction du mode cible.

Comme pour tout processeur de la lignée x86, le démarrage ou RESET se fait en mode Réel, ou plutôt ici en mode Legacy, sous-mode Réel. C'est-à-dire qu'à priori, si le BIOS est conçu dans ce sens, les nouvelles machines seront capables de booter sous DOS...

Les différents modes de fonctionnement sont :

  Le mode Legacy (héritage), qui nous ramène à un processeur IA-32, qui prend donc en charge les modes Réel, V86 et Protégé. Ce mode supporte les systèmes d'exploitatation antérieurs, ce qui est un point très important. Donc, en termes de performances, il n'y a aucun gain, mais surtout aucune perte.

  Le mode Long , le système d'exploitation est obligatoirement spécifiquement 64 bits et travaille dans un sous-mode parmi deux :

  Le mode 64-Bit , qui utilise toutes les caractéristiques nouvelles de l'architecture, mais exige des programmes 64 bits, c'est-à-dire au minimum recompilés. Dans ce mode, la taille par défaut des adresses (déplacements) est de 64 bits, avec les conséquences logiques sur la taille des registres système. Celle des opérandes est de 32 bits, surchargeable par le préfixe REX en 64 bits.

  Le mode Compatibility , qui exécute les anciennes applications 16 bits et 32 bits modes V86 et Protégé (donc pas en mode Réel) non recompilées, sans utilisation bien entendu des nouveaux registres, mais avec, semble-t-il, un gain en performances.

La compatibilité des registres est assurée sans surprise. Les technologies MMX, 3DNow !, SSE et SSE2 sont présentes, des registres supplémentaires étant ajoutés. Les registres 32 bits sont étendus à 64 bits, selon la méthode déjà évoquée des poupées russes, dans la continuité du passage de 16 bits à 32 bits.

Les registres de l'architecture x86-64
figure 2.20 Les registres de l'architecture x86-64 [the .swf]

Les registres en gris ne sont disponibles qu'en mode 64-Bit. Le jeu d'instructions est naturellement un sur-ensemble de IA-32, MMX, 3DNow !, SSE et SSE2. A noter que si nous considérons AMD64 comme un standard, comme l'a été IA32 défini par le 80386, alors MMX, 3DNow !, SSE et SSE2 semblent faire partie du minimum garanti.

Envoyer un message à l'auteur

se connecter pour pouvoir écrire

Ce site est créé et maintenu (laborieusement) par Pierre Maurette
hébergé par 1 & 1