Académique Documents
Professionnel Documents
Culture Documents
ordinateurs
Cours et exercices corrigés
Gilles G EERAERTS
1
Document mis en page à l’aide de XELATEX, en utilisant la classe KOMA script. Version du 2
août 2022.
2
À mon père, qui m’a appris le plaisir d’apprendre.
3
4
Table des matières
I. Notions de base 17
1. Leçon 1 – Introduction : Qu’est-ce qu’un ordinateur ? 19
1.1. Définitions et étymologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.2. Un premier modèle : l’architecture de VON N EUMANN . . . . . . . . . . . . . . . 21
1.3. Une vision différente : structure en niveaux et traductions . . . . . . . . . . . . . 26
5
Table des matières
6
Table des matières
7
Table des matières
8
Table des figures
1.1. Lettre de J. P ERRET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.2. Un exemple de programme écrit en langage Python. . . . . . . . . . . . . . . . . 22
1.3. L’architecture VON N EUMANN, un premier modèle d’ordinateur. . . . . . . . . . 25
1.4. L’architecture de l’i486 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.5. Les 6 couches décrivant le fonctionnement d’un ordinateur. . . . . . . . . . . . 30
9
Table des figures
10
Table des figures
11
Table des figures
12
Liste des tableaux
2.1. Comparaison des différentes représentations (sur n bits). . . . . . . . . . . . . . 46
4.1. Les 4 opérations de notre exemple d’ALU, ainsi que les entrées qui y corres-
pondent. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
5.1. Les opcodes IJVM que nous utiliserons. NB : par souci de cohérence de ces
notes, nous avons adopté des opcodes différents de ceux de [39]. . . . . . . . . . 159
13
14
Au lecteur
Ce document constitue les notes du cours INFO-F102 « Fonctionnement des Ordinateurs ».
La structure de ce cours est librement inspiré de l’ouvrage « Structured Computer Organi-
sation » [39] d’Andrew TANENBAUM, qui a un temps servi de livre de référence pour ce cours,
mais qui n’est plus édité en français. Ces notes ont donc évolué, au cours du temps, à partir
de résumés au style télégraphiques qui accompagnaient les premières version du cours.
La version que vous tenez en main est la sixième version de ces notes, valable pour l’an-
née académique 2022–2023. La nouveauté de cette version est l’inclusion d’une petite cin-
quantaine d’exercices à la fin de certains chapitres, exercices qui sont accompagnés de leurs
corrections. Ces exercices sont ceux qui seront utilisés lors des séances de travaux pratiques.
À travers toutes ces notes, les conventions typographiques suivantes ont été adoptées pour
les exemples et les concepts les plus importants :
ß
Ceci est un exemple illustrant une notion exposée par ailleurs.
Signalons enfin qu’un document décrivant en détail les objectifs pédagogiques du cours et
son organisation pratique est disponible sur l’UV. De nombreuses autres resources (comme
les fichiers des circuits du Chapitre 4) y sont également présentes, et constituent un complé-
ment indispensable à ces notes.
Remerciements J’adresse tout d’abord mes plus vifs remerciements à toutes les assistantes
et tous les assistants qui ont collaboré à cet enseignement depuis 2011, et qui ont largement
contribué à l’écriture des exercices et de leurs corrigés. J’espère n’oublier personne en citant
ici : Axel A BELS, Stéphane F ERNANDES M EDEIROS, François G ÉRARD, Markus L INDSTRÖM, Ar-
thur M ILCHIOR, Charlotte N ACHTEGAEL, Gianmarco PALDINO, Arnaud P OLLARIS, Cédric T ER-
NON , Marie VAN D EN B OGAARD , Nassim V ERSBRAGEN et Nikita V ESHCHIKOV .
En outre, je tiens à remercier toutes celles et tous ceux qui ont pris la peine de relire tout
ou une partie de ces notes et qui m’ont fait des commentaires. Je remercie à nouveau Marie
VAN D EN B OGAARD pour sa lecture très attentive et bienveillante. Je remercie aussi vivement
Noé B OURGEOIS, Arnold D ECHAMPS, Mathieu L ENG et Thierry M ASSART pour leurs commen-
taires.
Bruxelles, septembre 2022
15
16
Première partie
Notions de base
17
18
1. Leçon 1 – Introduction : Qu’est-ce qu’un
ordinateur ?
En ce qui concerne maintenant le sens du mot, Wikipedia [25] nous donne la définition
suivante :
Un ordinateur est un système de traitement de l’information programmable [. . . ]
et qui fonctionne par la lecture séquentielle d’un ensemble d’instructions, orga-
nisées en programmes, qui lui font exécuter des opérations logiques et arithmé-
tiques.
Le Petit Robert, pour sa part, nous donne la définition :
Machine électronique de traitement numérique de l’information, exécutant à grande
vitesse les instructions d’un programme enregistré.
Ces deux définitions mettent en avant plusieurs concepts essentiels :
1. L’ordinateur est une machine qui traite l’information. Il reçoit de l’information et en
produit, sur base de l’information reçue. Par exemple, un ordinateur peut être utilisé
pour calculer les racines réelles d’une équation du second degré à une inconnue sur
base des coefficients a, b et c de cette équation (c’est l’exemple que nous développons
ci-dessous) ; ou pour élaborer la fiche de paye d’un travailleur sur base de ses presta-
tions ; ou pour afficher une vidéo sur base de données reçues via Internet,. . .
2. Pour traiter l’information, l’ordinateur exécute un programme, qui est une séquence
d’instructions, indiquant chacune à traitement spécifique à exécuter. Cela signifie que
1. Philologue français né le 6 décembre 1906 et mort le 29 mars 1992, il est professeur à la Faculté de lettres de
Paris (France).
19
1. Leçon 1 – Introduction : Qu’est-ce qu’un ordinateur ?
F IGURE 1.1. – Que diriez-vous d’ordinateur ? La proposition de Jacques P ERRET pour nommer
cette nouvelle machine. . .
20
1.2. Un premier modèle : l’architecture de VON N EUMANN
l’ordinateur a été conçu de manière à être capable de réaliser certains traitements cor-
respondants à des instructions pré-définies, mais qu’on peut utiliser ces instructions
de base comme des briques pour construire des traitements plus complexes appelés
programmes.
Par exemple, supposons que nous disposons d’un ordinateur capable d’exécuter les
instructions add et idiv qui calculent respectivement la somme de deux nombres et la
division (entière) d’un nombre par un autre. Nous pouvons imaginer qu’il est possible
de construire, sur base de ces deux instructions, un programme qui calcule la (partie
entière de la) moyenne de deux nombres, en utilisant d’abord add pour faire la somme
de ces deux nombres, puis idiv pour diviser le résultat par 2. Naturellement, la somme
doit être effectuée avant la division : l’ordre (la séquence) des instructions est donc
important. Ce faisant, nous avons esquissé l’écriture d’un programme, qui calculerait
une moyenne en utilisant add et idiv.
3. Le programme de l’ordinateur peut être modifié. L’ordinateur n’a pas été conçu dans le
but d’exécuter un et un seul programme, mais son utilisateur a la faculté de modifier le
programme, soit en acquérant des programmes écrits par des tiers (comme des appli-
cations achetées et téléchargées sur Internet, par exemple), soit en écrivant lui-même
de nouveaux programmes.
4. Enfin, l’information que l’ordinateur traite à l’aide des programmes qu’il exécute est
représentée de manière numérique, et est traitée comme telle. Cela signifie que toute
information, quelque soit sa nature (nombres, texte, images, sons, vidéos,. . . ) doit être
exprimée sous forme de nombres pour pouvoir être traitée par l’ordinateur. En l’occur-
rence, tous les ordinateurs modernes représentent l’information sous forme de nombres
binaires, c’est-à-dire formés de séquences de 0 et de 1 uniquement, comme nous le ver-
rons en détails dans le Chapitre 2. Cela signifie également que les traitements exécutés
(et exprimés à l’aide des instructions connues de l’ordinateur) réalisent eux aussi des
opérations sur des nombres (ce sont des opérations logiques et arithmétiques).
Ces éléments constituent les caractéristiques généralement retenues pour définir un ordi-
nateur : (1) la possibilité d’exécuter un programme arbitraire, qui peut être modifié, et est
donc stocké dans la mémoire de l’ordinateur au même titre que les données qui sont trai-
tées 2 ; et (2) la représentation et le traitement numérique de l’information (y compris du pro-
gramme à exécuter).
2. Il doit également être possible d’écrire des programmes complexes, comprenant des boucles et des sauts.
La notion précise est la notion de machine Turing-complète, telle que définie par le mathématicien anglais Alan
T URING [42]. Nous ne développerons pas cette notion en détail ici, le lecteur intéressé peut consulter une intro-
duction à la calculabilité comme l’excellent ouvrage de Pierre W OLPER [50].
21
1. Leçon 1 – Introduction : Qu’est-ce qu’un ordinateur ?
1 """
2 Calcul des racines d ’ une équation du second degré
3 """
4 __author__ = " Thierry Massart "
5 __date__ = " 22 août 2012 "
6
7 from math import sqrt
8 a = float ( input ( " valeur de a : " ))
9 b = float ( input ( " valeur de b : " ))
10 c = float ( input ( " valeur de c : " ))
11
12 delta = b **2 - 4* a * c
13 if delta < 0:
14 print ( " pas de racines réelles " )
15 elif delta == 0:
16 print ( " une racine : x = " , -b /(2* a ))
17 else :
18 racine = sqrt ( delta )
19 print ( " deux racines : x1 = " , \
20 ( - b + racine )/(2* a ) , " x2 = " , ( - b - racine ) / (2* a ))
et qui calcule les éventuelles racines réelles d’une équation du second degré à une inconnue.
Il est donné à la Figure 1.2.
Cet exemple est fort utile car il illustre bien les différentes ressources dont l’ordinateur a
besoin pour exécuter un programme. En voici la liste :
— L’ordinateur doit disposer d’un moyen d’obtenir des données en entrée (c’est-à-dire les
données à traiter). Dans notre exempele, le mot-clé input interroge l’utilisateur qui
entre les données via le clavier. Ces données sont stockées dans des variables a, b et c.
— L’ordinateur doit être capable de stocker des données de manière temporaire, c’est-à-
dire durant la durée d’exécution d’un programme. Ces données temporaires corres-
pondent en général aux données d’entrée, ou aux résultats de calculs intermédiaires.
Dans notre exemple, les variables a, b, c et delta représentent ces stockages tempo-
raires. Notez bien que delta n’est pas une donnée d’entrée du programme, mais bien
une valeur intermédiaire du calcul.
— L’ordinateur doit pouvoir communiquer ses résultats au monde extérieur. Dans notre
exemple, le mot-clé print permet d’afficher des messages ainsi que certaines variables.
— L’ordinateur doit avoir la capacité d’effectuer certaines opérations de base, sans que
l’utilisateur n’ait besoin d’écrire un programme pour expliciter à l’ordinateur le sens de
ces opérations de base. C’est le cas, par exemple des opérations arithmétiques : elles
sont considérées comme « connues » et peuvent être utilisées dans un programme sans
22
1.2. Un premier modèle : l’architecture de VON N EUMANN
qu’il soit nécessaire d’expliciter le calcule du +, du -, etc. Dans notre exemple, ces opé-
rations interviennent dans le calcul du ∆.
L’ensemble des opérations de base connues de l’ordinateur, et qu’on peut utiliser pour
écrire un programme, composent ce qu’on appelle un langage. Le langage principal
d’un ordinateur est ce qu’on appelle le langage machine. C’est ce langage qui opère di-
rectement sur la représentation binaire des informations. Notre exemple, lui, est écrit
dans une langage différent : le langage Python. Dans ce langage (et donc dans notre
exemple), on ne voit pas la représentation binaire des informations apparaître explici-
tement 3 . Au contraire, Python permet d’exprimer les opérations sur des données plus
abstraites, et donc plus facilement compréhensible par un être humain, comme le texte
qui est affiché pour présenter le résultat. Le langage Python offre donc une facilité ac-
crue par rapport au langage machine, mais afin qu’il puisse être exécuté par l’ordina-
teur, il devra impérativement être traduit en langage machine.
— Bien que cela apparaisse de manière moins évidente sur notre exemple, l’ordinateur
doit également être capable de stocker le programme à exécuter (puisque celui-ci peut
être modifié) pour pouvoir s’y référer, et ainsi avoir la capacité d’accéder à la bonne
instruction à exécuter. Pour ce faire, il faut aussi disposer d’un moyen retenant l’avan-
cement de l’exécution dans le programme, et de modifier cet état d’avancement. Cette
modification peut consister soit à passer à l’instruction suivante dans le programme,
soit à désigner une instruction arbitraire comme étant celle à exécuter en prochain
lieu, éventuellement selon certaines conditions (c’est ce que fait l’instruction if dans
notre exemple).
Sur base de ces besoins, on peut proposer un premier mo-
dèle abstrait de ce qui est nécessaire pour concevoir une telle
machine. Notre modèle est présenté à la Figure 1.3. Il s’agit
d’une conception de l’ordinateur qui a été proposée par John
VON N EUMANN 4 en 1945 [44], et sur lequel tous les ordina-
teurs modernes se basent. Il est bien entendu que ce modèle
est une simplification (sans doute un peu grossière) de la réa-
lité, mais elle est suffisante, en première approximation, pour
expliquer les principes généraux de fonctionnement de l’ordi-
nateur. Nous raffinerons cette figure dans la suite. Notons que
ce modèle est souvent appelé architecture de von Neumann ou
John VON N EUMANN.
architecture à programme enregistré (stored program architec-
ture en anglais). On utilise ici le terme architecture, car le mo-
dèle explique quels sont les différents composants de l’ordinateur et comment ils sont utili-
sés, interconnectés, pour bâtir la machine qu’est l’ordinateur.
En analysant cette figure, nous pouvons constater :
— que l’O RDINATEUR (cadre gras) communique avec le monde extérieure à travers des
dispositifs appelés périphériques d’entrée/sortie (par exemple : le clavier, la souris, l’écran,
3. Par exemple, la valeur 2 utilisé dans la dernière ligne du programme n’est pas exprimée en binaire, sans
quoi on aurait écrit 10 (voir Chapitre 2)
4. Mathématicien hongrois, né à Budapest le 28 décembre 1903, mort à Washington, le 8 février 1957.
23
1. Leçon 1 – Introduction : Qu’est-ce qu’un ordinateur ?
etc) ;
— que l’ordinateur est composé essentiellement de deux composants : le processeur (ou
en anglais, Central Processing Unit, CPU) et la mémoire. Ce sont les deux cadres aux
bords arrondis sur la figure ; et enfin
— que le processeur est lui-même composé d’une unité de contrôle, d’une unité arithmético-
logique (en anglais Arithmetic and Logic Unit, ALU) et de registres.
Détaillons maintenant les rôles de ces différents composants, pour l’exécution d’un pro-
gramme. Tout d’abord, le programme et ses données sont stockées dans la mémoire, qui,
comme son nom l’indique, est un composant servant à mémoriser (stocker) l’information.
Comme cette mémoire est modifiable, on peut également modifier le programme ou l’ap-
pliquer à d’autres données ; ce qui est essentiel dans notre définition d’ordinateur (cela ex-
plique le nom d’architecture à programme enregistré, car le programme est enregistré dans la
mémoire).
Ensuite, le processeur a pour tâche d’exécuter le programme, instruction par instruction.
Pour ce faire, il interroge la mémoire qui lui transmet la prochaine instruction à exécuter ;
puis, il analyse et exécute celle-ci, et recommence ad infinitum. Le processeur a donc un
comportement cyclique que l’on représente par la boucle d’interprétation du processeur par-
fois appelée également fetch–decode–execute, du nom des trois étapes principales (en an-
glais) :
1. fetch : aller chercher la prochaine instruction à exécuter, en mémoire. Cela signifie,
comme nous l’avons déjà dit, que le processeur doit avoir un moyen, d’identifier cette
instruction parmi d’autres en mémoire. C’est un des rôles (mais pas le seul) des re-
gistres.
2. decode : il s’agit ici d’analyser la représentation binaire de l’instruction (en langage ma-
chine) pour en déduire l’opération à effectuer.
3. execute : exécuter l’instruction à proprement parler. Les éléments du CPU en charge de
l’exécution d’une instruction sont les registres et l’ALU. D’une part, les registres sont
des zones de mémoire de très petite capacité mais d’accès très rapide, dans lesquels les
données nécessaires à l’exécution de l’instruction, ainsi que son résultat, seront sto-
ckés. D’autre part, l’ALU est un circuit électronique du processeur qui peut appliquer
une opération arithmétique ou logique à deux données provenant des registres. Par
exemple, si l’on souhaite faire la somme de deux nombres, il faudra placer les deux va-
leurs dans des registres, transmettre ces valeurs à l’ALU qui effectuera la somme, puis
placer le résultat dans un registre. Nous décrirons ce procédé plus en détail à la Sec-
tion 3.2, et surtout au Chapitre 5.
L’effet d’une instruction peut également être d’écrire ou de lire une valeur en mémoire
(depuis ou vers les registres), ou encore de communiquer avec les périphériques.
La coordination de ces trois étapes est assurée par l’unité de contrôle du CPU. Ainsi, l’unité de
contrôle doit s’assurer que ce sont les bons registres qui transmettent leur données à l’ALU,
que l’ALU applique la bonne opération aux données (il faut choisir l’opération à effectuer
parmi une palette d’opérations disponibles), que le résultat calculé par l’ALU est placé dans
le bon registre de sortie, etc.
24
1.2. Un premier modèle : l’architecture de VON N EUMANN
O RDINATEUR
Processeur (CPU)
Unité de contrôle
Mémoire
ß
Dans ces notes de cours, nous utiliserons souvent la famille de processeurs x86
de la firme Intel comme exemples pour illustrer les notions relatives aux pro-
cesseurs. On retrouve aujourd’hui ces processeurs dans l’immense majorité des
ordinateurs personnels.
En particulier, nous considérerons le processeur i486. Il s’agit d’un processeur
qui a été commercialisé de 1989 à 2007, et il a des performances relativement
modestes si on le compare aux processeurs récents. Néanmoins, il possède la
plupart des caractéristiques des processeurs modernes, tout en restant relative-
ment simple. Il constitue donc un excellent exemple pédagogique. ../.
25
1. Leçon 1 – Introduction : Qu’est-ce qu’un ordinateur ?
L’architecture de l’i486 est montrée à la Figure 1.4. Comme on peut le voir, elle
... est bien plus complexe que notre simple modèle de la Figure 1.3 ! Mais nous
pouvons déjà reconnaître certains éléments :
— En jaune, sur la gauche de la figure, nous retrouvons l’ALU (avec le shif-
ter qui peut également être vu comme un circuit réalisant des opérations
arithmétiques), et les registres servant à contenir des données (l’ensemble
de ces registres est appelé register file). L’i486 possède au total 32 registres
qui ont des fonctions différentes. En particulier, les 4 registres appelés
eax, ebx, ecx et edx servent à stocker les données sur lesquelles opèrent
les instructions. Ce sont des registres de 32 bits. L’i486 possède également
8 registres spécialisés de 80 bits, qui permettent d’effectuer des opérations
arithmétiques sur des nombres décimaux (dont nous parlerons dans la
Section 2.2.5). Ces opérations un peu spéciales ne sont pas réalisées par
l’ALU, mais par le FPU (pour floating point unit), en orange, sur la gauche
de la figure.
— Les flèches en noires à droite de l’image représentent les communications
du processeur avec le monde extérieur, c’est-à-dire avec la mémoire (deux
flèches du haut) et les périphériques.
— On reconnait également un bloc chargé du décodage des instructions
(dans le base de la figure) et un bloc chargé de la lecture en mémoire
(fetch), appelé ici prefetcher.
Différents niveaux Partons du CPU. Celui-ci est composé de circuits électroniques que
nous appellerons circuits logiques, et que nous étudierons dans quelques leçons. C’est à l’aide
de ces circuits logiques que le CPU doit donc interpréter le langage machine. Comme nous
le verrons plus tard, l’interprétation de chaque instruction en langage machine n’est pas im-
médiate : le langage machine est d’abord traduit dans une langage plus simple encore, le
microlangage, lequel est enfin exécuté à l’aide des circuits logiques. Tout comme un pro-
gramme (en langage machine) se compose d’une série d’instructions (appelées instructions
machine), chaque instruction machine est traduite 5 en une série de micro-instructions (les
26
Intel 80486DX2 Architecture 64 Bit Interunit Transfer Bus
Core
32 Bit Data Bus Clock
Clock Clock
32 Bit Data Bus Multiplier
128 Bit
PLOCK#, BOFF#, A20M#,
BREQ, HOLD, HLDA, RESET,
Displacement Bus SRESET, INTR, NMI, SMI#,
SMIACT#, FERR#, IGNNE#,
Micro-Instruction 32 Bit Prefetcher Request STPCLK#
Sequencer
Boundary
Scan TCK, TMS, TDI, TD0
Control
F IGURE 1.4. – L’architecture de l’i486 (sous sa variante 80486DX2), telle que publiée par Intel [12].
27
1.3. Une vision différente : structure en niveaux et traductions
1. Leçon 1 – Introduction : Qu’est-ce qu’un ordinateur ?
instructions du micro-langage). Nous avons donc identifié les trois « couches » les plus basses
de l’ordinateur, à savoir (et en partant de la couche la plus basse) :
0. les circuits logiques (qui constituent le véritable matériel constitutif de l’ordinateur) ;
1. le micro-langage ; et
2. le langage machine, qui est le langage du CPU.
Durant ce cours, nous nous concentrerons essentiellement sur ces trois couches.
Mais tout qui a un jour utilisé un ordinateur sait pertinemment bien que celui-ci peut être
utilisé sans interagir directement avec le processeur, à l’aide du langage machine. L’exemple
de la Figure 1.2 est écrit, comme nous l’avons déjà dit, en Python, un langage plus abstrait
(on dit aussi : « de plus haut niveau ») que le langage machine, ce qui est une facilité non-
négligeable. En effet, un même programme en langage Python peut être exécuté par des or-
dinateurs différents qui ont potentiellement des caractéristiques (nombre de registres, lan-
gage machine) différentes. Cela n’a pas d’importance pour le programmeur qui a écrit son
programme en Python, car ce langage ne demande pas et ne permet pas d’interagir direc-
tement avec les composants matériels de base (ALU, registres) de la machine. Au contraire,
si on souhaite exécuter un programme en Python sur un ordinateur donné, il faut disposer
d’un interpréteur, qui est lui-même un programme qui traduit le programme en langage Py-
thon vers une séquence d’instruction machine propre à l’ordinateur sur lequel le programme
Python doit être exécuté.
Par ailleurs, tout ordinateur personnel, tout smartphone est aujourd’hui livré avec un pro-
gramme de base, permettant d’interagir facilement avec lui, et appelé « système d’exploita-
tion », ou operating system, ou encore OS (par exemple, Windows, Linux, MacOS, Android,
iOS, etc). Tous ces programmes sont in fine exécutés par le processeur et doivent donc être
traduits en langage machine. Au final, nous avons 3 couches supplémentaires qui s’empilent
au-dessus du langage machine :
3. le système d’exploitation, dont nous parlerons brièvement à la fin du cours, mais qui
fera surtout l’objet d’un cours de deuxième année ;
4. l’assembleur, qui est un langage intermédiaire entre le langage machine et les pro-
grammes écrits dans un langage de haut niveau. L’assembleur sera étudié dans le cours
de langages de programmation, au second quadrimestre ;
5. les langages de haut niveau, qui sont ceux à l’aide desquels on écrit les applications
que l’on utilise quotidiennement (traitement de texte, navigateur web, etc). Le cours de
programmation vous enseigne un de ces langages (python) et le cours de langages de
programmation (second quadrimestre) vous en enseignera d’autres.
De bout en bout, l’exécution d’un programme de haut niveau peut donc se résumer comme
suit : le programme de haut niveau (niveau 5) est traduit en assembleur (niveau 4). L’assem-
bleur est traduit en un langage qui est essentiellement du langage machine augmenté d’ap-
pels aux services prodigués par le système d’exploitation (niveau 3). Ces « appels systèmes »
sont eux-mêmes traduits en langage machine, et on obtient alors, au niveau 2, un programme
entièrement en langage machine. L’exécution de ce programme machine par le CPU consiste
en une traduction vers le micro-langage interne au CPU (niveau 1), dont le résultat est fina-
lement exécuté à l’aide de circuits logiques (niveau 0).
28
1.3. Une vision différente : structure en niveaux et traductions
— soit un programme d’un certain niveau est traduit vers le niveau inférieur, instruction
par instruction, au moment de l’exécution. On parle alors d’interprétation. C’est ce qui
se passe, par exemple, quand on traduit le langage machine en micro-code, ou quand
le processeur exécute le langage machine.
De manière générale, un interpréteur est un programme écrit dans un langage de ni-
veau i et qui interprète un programme écrit dans un langage de niveau j > i , instruc-
tion par instruction, c’est-à-dire en exécutant continuellement une boucle qui consiste
à : (1) lire la prochaine instruction à exécuter ; (2) analyser et exécuter cette instruction ;
(3) passer à l’instruction suivante. Le CPU peut donc être vu comme la réalisation d’un
interpréteur pour le langage machine, réalise à l’aide de micro-code.
La hiérarchie de niveaux que nous venons de décrire, ainsi que les différents types de tra-
ductions entre ces niveaux, sont présentés à la Figure 1.5.
Matériel et logiciel Notons finalement que chacun des 6 niveaux peut théoriquement être
réalisé soit à l’aide de matériel soit à l’aide de logiciel. Élucidons ces deux termes :
— le matériel (hardware en anglais) est l’ensemble des dispositifs physiques qui consti-
tuent l’ordinateur ; tandis que
En pratique, les niveaux 0,1 et 2 sont réalisés à l’aide de matériel, et les niveaux 3, 4 et 5 à
l’aide de logiciel. Le logiciel et le matériel ont chacun leurs avantages et leurs inconvénients.
Le logiciel est plus flexible, dans le sens où on peut le modifier et le remplacer, mais plus lent.
Le matériel est plus rapide, mais n’est pas modifiable, en général. Il est par exemple possible
de changer d’interpréteur Python (niveau 5) si jamais une nouvelle version de ce langage
venait à être proposée. Par contre, le processeur, qui exécute le langage machine, doit être
très rapide, et on ne peut pas modifier la traduction du langage machine en micro-langage
(niveau 2 vers niveau 1).
29
1. Leçon 1 – Introduction : Qu’est-ce qu’un ordinateur ?
Niveau 5 :
les langages de haut niveau (Python, C, C++,. . . )
Niveau 4 :
Logiciel
l’assembleur
Compilation (assemblage)
Niveau 3 :
le système d’exploitation (Linux, Windows, MacOS. . . )
Compilation
Niveau 2 :
le langage machine, langage du CPU
Interprétation
Matériel Niveau 1 :
(dans le CPU) le micro-code
Interprétation
Niveau 0 :
les portes logiques
M8N
30
2. Leçons 2 à 4 – Représentation de
l’information
Avant de pouvoir étudier la manière dont l’ordinateur traite l’information, nous devons
fixer la manière dont cette information est représentée. L’objet de ces leçons est d’étudier
la représentation binaire, qui est aujourd’hui 1 la représentation universellement utilisée en
informatique pour toutes les informations que les ordinateurs manipulent.
Dans ce chapitre, nous développerons des techniques permettant de représenter tout type
d’information (nombres, texte, images,. . . ) à l’aide d’un représentation binaire, et de manipu-
ler cette représentation. Nous verrons également comment concevoir des techniques de dé-
tection et de correction d’erreur sur cette représentation. Mais avant d’aborder cette matière,
il est important de bien comprendre la différence entre une information, et sa représentation.
L’exemple suivant illustre cette différence.
ß
Considérons la quantité 17, nous pouvons en trouver plusieurs représentations :
— la représentation usuelle, dite « en base 10 » : 17,
— en base 2 : 10001 (nous aborderons les les notions de base et de change-
ment de base en détail dans la suite),
— en chiffres romains : XVII,
— en utilisant des marques de dénombrement : HHH
IIII HH
IIII H II,
IIII
— ...
On voit donc qu’une même information, qu’un même concept admet plusieurs repré-
sentations. Le choix de la représentation binaire comme représentation universelle en in-
formatique s’explique par le fait qu’on peut aisément représenter deux valeurs différentes à
l’aide d’états différents de composants électriques comme les transistors. Ces même transis-
tors, assemblés en portes logiques, permettent également de manipuler l’information binaire,
1. Il y a eu quelques expériences utilisant des représentations ternaires. On peut citer les ordinateurs sovié-
tiques Setun (1959–1965) [6].
31
2. Leçons 2 à 4 – Représentation de l’information
Unité Symbole
bit b
octet o
byte B
La définition de ces préfixes est relativement récente (elle date du début des années 2000).
En pratique, on est souvent confronté à l’utilisation des préfixes traditionnels kilo-, méga-,
tera-, etc du système SI, qui sont, pour rappel :
On voit donc qu’un mégaoctet est un petit peu plus petit qu’un mébioctet. . . Force est de re-
connaître qu’une certaine confusion règne ! Cette confusion a d’ailleurs donné lieu à des pro-
cès retentissants, notamment aux États-Unis, où des consommateurs ont entamé une class
32
2.2. Écriture des nombres dans différentes bases
action contre plusieurs fabricants de disques durs et mémoires, les accusant de tromperie sur
la capacité de stockage des produits 2 .
ß
Par exemple, la représentation :
1234, 56
On note également que la base donne le nombre de chiffres que l’on peut utiliser pour
représenter les nombres. En base 10, on peut utiliser 10 chiffres différents, à savoir les chiffres
de 0 à 9 inclus.
Ces concepts se généralisent dans n’importe quelle base. Fixons à partir de maintenant une
base b ≥ 2. Dans cette base, le nombre :
d n · · · d 0 , d −1 · · · d −k
2. Voir par exemple : https://en.wikipedia.org/wiki/Binary_prefix#Inconsistent_use_of_units.
33
2. Leçons 2 à 4 – Représentation de l’information
représente la valeur :
−100, 1
en base 2. Notons au passage que cette dernière représentation n’est pas stricto
sensu une représentation binaire, étant donné que les symboles , (virgule) et −
(moins unaire) sont utilisés, en plus du 0 et du 1. Nous verrons plus tard com-
ment une telle valeur peut être représentée en utilisant des 0 et des 1 unique-
ment.
Afin d’éviter toute confusion dans la suite, nous noterons, quand c’est nécessaire, la base
en indice des nombres. Par exemple, 1012 signifie que le nombre 101 doit être interprété
comme étant en base 2 (c’est-à-dire 510 ). Notons que dans certains langages de program-
mation, comme le C [3], on utilise d’autres conventions pour expliciter certaines bases : les
nombres en base 16 son préfixés par 0x, et les nombres en base 2 par 0b. Enfin, notons qu’en
base 2, nous grouperons souvent les bits par paquets de 4, en introduisant un petit espace, et
ce, uniquement dans un souci de lisibilité. Nous écrivons ainsi 1011 1001 au lieu de 10111001.
En informatique, les bases usuelles sont la base 2, la base 8 (on parle d’octal) et la base 16
(on parle d’hexadécimal). Pour la base 16, on est confronté au problème suivant : les chiffres
utilisés devraient être 0, 1,. . . 10, 11, 12, 13, 14 et 15. Mais les « chiffres » de 10 à 15 demandent
deux symboles pour être représentés, ce qui risque de porter à confusion. Par exemple, doit-
on interpréter 1016 comme le chiffre 10 (et alors 1016 = 1010 ) ou comme les chiffres 1 et 0 (et
donc 1016 = 1 × 16 + 0 × 1 = 1610 ) ? Pour éviter ce problème, on remplace les « chiffres » de 10
à 15 par les lettres de a à f, selon la correspondance suivante :
Lettre : a b c d e f
Valeur : 10 11 12 13 14 15
34
2.2. Écriture des nombres dans différentes bases
ß
En base 16 :
Sur base de ces définitions, nous pouvons déjà faire quelques remarques utiles.
Ajouts de zéros Dans toutes les bases, et à condition d’utiliser la représentation position-
nelle usuelle (avec le chiffre de poids fort à gauche), on peut toujours ajouter des 0 à gauche
de la partie entière ou à droite de la partie décimale, sans modifier la valeur du nombre re-
présentée. Par exemple, 13, 510 = 0013, 500010 . Par contre, on ne peut pas ajouter de zéros à
d’autres endroits sans modifier la valeur du nombre. Par exemple, 1012 6= 1010002 (voir aussi
la remarque suivante).
Multiplier ou diviser par la base Dans toutes les bases, on peut aisément multiplier ou
diviser par la base en déplaçant la position de la virgule :
Nous utiliserons les opérateurs ÷ et mod pour noter respectivement la division entière et
le reste de la division. Voici quatre exemples qui illustrent ces remarques :
35
2. Leçons 2 à 4 – Représentation de l’information
Notons que quand nous souhaiterons représenter des nombres négatifs de manière pure-
ment binaire (donc, sans utiliser le symbole -), nous aurons besoin d’utiliser certaines de ces
représentations pour « encoder » des nombres négatifs.
Il y a donc 2n nombres binaires sur n bits. Par exemple, il y a 232 = 4 294 967 29610
ß (soit à peu près 4 milliards de) nombres binaires différents sur 32 bits.
Cas des nombres entiers Nous allons commencer par supposer que N est un nombre en-
tier positif. Observons d’abord que si N < b, on exprime le nombre à l’aide du chiffre corres-
pondant dans la nouvelle base 3 . Autrement, divisons N par b (il s’agit de la division entière),
3. Par exemple, 1010 est exprimé par a en base 16.
36
2.2. Écriture des nombres dans différentes bases
N = q0 × b + r 0 . (2.3)
Comme nous avons supposé que N ≥ b, nous savons que q 0 6= 0. Recommençons l’opéra-
tion en divisant q 0 par b ; nous obtenons à nouveau un quotient que nous appelons q 1 et un
reste que nous appelons r 1 . Nous avons donc la relation :
q0 = q1 × b + r 1 ,
N = (q 1 × b + r 1 ) × b + r 0
= q1 × b 2 + r 1 × b + r 0 .
N = (q 2 × b + r 2 ) × b 2 + r 1 × b + r 0
= (q 2 × b 3 ) + r 2 × b 2 + r 1 × b + r 0 .
N = (q k × b k+1 ) + r k × b k + · · · + r 2 × b 2 + r 1 × b + r 0 .
Supposons que nous avons continué ce développement jusqu’à ce que q k = 0 (ce qui finira
toujours par arriver étant donné qu’on est parti d’un N fixé). On a alors :
N = (q k × b k+1 ) + r k × b k + · · · + r 2 × b 2 + r 1 × b + r 0
= (q k × b k+1 ) + r k × b k + · · · + r 2 × b 2 + r 1 × b 1 + r 0 × b 0
X
k
= (q k × b k+1 ) + ri × bi
i =0
X
k
= (0 × b k+1 ) + ri × bi
i =0
X
k
= ri × bi . (2.4)
i =0
On peut maintenant comparer cette dernière équation (2.4) à l’équation (2.2), et constater
que les restes r i que nous avons calculés en effectuant des divisions successives par b corres-
pondent aux d i que nous cherchons pour représenter le nombre N en base b. Attention tout
de même que le premier reste calculé (r 0 ) est le chiffre de poids faible ! Pour résumer :
37
2. Leçons 2 à 4 – Représentation de l’information
Nombres réels Nous pouvons maintenant généraliser ce principe aux nombres réels 4 . Com-
mençons par observer qu’un nombre rationnel qui possède une expression finie dans une
base ne possède pas forcément une expression finie dans toutes les bases. Prenons par exemple
le nombre 13 . Nous savons qu’il n’est possible de l’exprimer de manière finie en base 10, mais
bien de manière infinie périodique :
1
= 0, 333310 . . .
3
Nous notons cette représentation infinie périodique en soulignant la partie qui se répète in-
finiment souvent :
1
= 0, 3.
3
Par contre, comme 1
3 = 3−1 , on peut exprimer 1
3 de manière finie en base 3 :
1
= 0, 13 .
3
Voyons maintenant comment représenter un nombre N < 1 dans une base b arbitraire.
Notons que nous supposons que N < 1 car on peut traiter séparément la partie entière et
la partie fractionnelle d’un nombre. Pour exprimer N en base b, nous allons procéder par
multiplication successive par b. Supposons que N est de la forme :
N = 0, α0
N × b = β1 , α1
où β1 est la partie entière, et α1 la partie décimale. Comme nous avons supposé que N < 1,
nous savons que 0 ≤ β1 ≤ b − 1. Considérons le nouveau nombre 0, α1 , et appelons-le N1 .
Nous avons donc :
N1 = 0, α1
β1 N 1
N= + . (2.5)
b b
4. En pratique, sur un ordinateur, nous ne pourrons représenter et manipuler de manière précise que les
nombres qui possèdent
p une représentation finie en base 2, ce qui exclut d’office tous les nombres irrationnels,
comme π ou 2, par exemple. Mais cela n’empêche que même les nombres irrationnels possèdent aussi une
représentation (infinie non-périodique) dans d’autres bases que la base 10.
38
2.2. Écriture des nombres dans différentes bases
N1 × b = β2 , α2
N2 = 0, α2
β2 N 2
N1 = + . (2.6)
b b
β
N2
b + b
2
β1
N= +
b b
β1 β2 N 2
= + 2+ 2
b b b
= β1 × b + β2 × b −2 + N2 × b −2 .
−1
N = β1 × b −1 + β2 × b −2 + · · · βk × b −k + Nk × b −k
X
k
= βi b −i + Nk × b −k . (2.7)
i =1
En comparant (2.7) et (2.2), on voit que la séquence des βi ainsi calculée correspond à la
séquence des d −i nécessaires pour exprimer N en base b. Ce développement sera arrêté soit
lorsque Ni = 0, soit dès qu’on détecte deux valeurs N j , Ni (i 6= j ) telles que N j = Ni , ce qui
signifie que l’expression de N en base b est infinie périodique.
i qi ri
0 12 0
1 6 0
2 3 0
3 1 1
4 0 1
39
2. Leçons 2 à 4 – Représentation de l’information
i βi Ni i βi Ni i βi Ni
1 0 0, 84 8 1 0, 52 15 0 0, 56
2 1 0, 68 9 1 0, 04 16 1 0, 12
3 1 0, 36 10 0 0, 08 17 0 0, 24
4 0 0, 72 11 0 0, 16 18 0 0, 48
5 1 0, 44 12 0 0, 32 19 0 0, 96
6 0 0, 88 13 0 0, 64 20 1 0, 92
7 1 0, 76 14 1 0, 28 21 1 0, 84
et finalement :
ß 1 1
1 0 1 1
1 1
+ 1 1 0 1
1 1 0 0 0
40
2.2. Écriture des nombres dans différentes bases
Dans le chapitre 4, nous utiliserons ces concepts pour construire un circuit qui réalise ces
additions.
Opérations logiques En base 2, on peut utiliser une série d’opérations qui n’ont pas d’équi-
valent naturel dans les autres bases. Ce sont les opérations « logiques », que l’on peut appli-
quer en considérant que le 0 représente la valeur logique « faux », et que le 1 représente la
valeur logique « vrai ».
On définit les opérateurs binaires 5 et (noté ∧), ou (noté ∨), et ou exclusif (noté XOR) ;
ainsi que l’opérateur unaire non (noté ¬). Pour définir leur effet sur deux nombres binaires,
on commence par les définir sur deux bits (ou un bit dans le cas du ¬). Pour ce faire, on utilise
une table de vérité, qui donne, pour chaque valeur possible des opérandes (de l’opérande), la
valeur renvoyée par l’opérateur. Voici les tables de vérité de ces opérateurs :
On remarquera que ces opérateurs expriment l’idée intuitive qu’on peut s’en faire à la lec-
ture de leur nom. L’opération x ∧ y vaut 1 si et seulement x et y sont vraies (soit x = 1 et
y = 1). L’opération x ∨ y vaut 1 si et seulement si x ou y est vraie. L’opération x XOR y vaut 1 si
et seulement si soit x est vraie soit y est vraie mais pas les deux en même temps (si x est vraie,
cela exclut donc le fait que y soit vraie et vice-versa). Finalement, ¬x remplace la valeur de
vérité de x par son opposé (vrai par faux et faux par vrai).
On peut maintenant généraliser ces opérations à n’importe quel nombre binaire, en ap-
pliquant les opérations bit à bit : étant donné deux nombres sur n bits A = a n−1 a n−2 · · · a 0
et B = b n−1 b n−2 · · · b 0 , on applique l’opération sur chaque paire de bits a i , b i pour obtenir le
bit c i du résultat. Par exemple, A ∧ B est le nombre C = c n−1 c n−2 · · · c 0 , où c n−1 = a n−1 ∧ b n−1 ,
c n−2 = a n−2 ∧ b n−2 ,. . . et c 0 = a 0 ∧ b 0 . Et ainsi de suite pour les autres opérations.
Masques Ces opérations logiques permettent de réaliser certaines manipulations des va-
leurs binaires, appelées masques ou masquages. On observe que x ∧0 = 0 et x ∧1 = x, quelque
soit la valeur de x (sur un bit). De ce fait, le ∧ peut être utilisé pour masquer certains bit d’un
nombre, c’est-à-dire remplacer ces bits par des 0 tout en conservant les autres. Il suffit de
prendre la conjonction de ce nombre avec une valeur binaire comprenant des 0 à toutes les
positions qu’on veut masquer, et des 1 partout ailleurs.
5. Attention ! Ici, « binaire » signifie que l’opérateur porte sur deux opérandes, comme l’addition par exemple.
Au contraire, un opérateur unaire porte sur une seule opérande (comme le moins dans −5 par exemple).
41
2. Leçons 2 à 4 – Représentation de l’information
ß
Supposons qu’on souhaite masquer les 4 bits de poids fort d’un nombre N de 8
bits. On peut faire : N ∧ 0000 1111. Si N = 1010 1010, on a bien N ∧ 0000 1111 =
0000 1010.
Les masques ont plusieurs utilités, nous en verrons certaines dans ce cours, notamment
dans le Chapitre 9 consacré à la gestion de la mémoire à l’aide de la pagination. On peut
déjà observer que les masques permettent de calculer le reste d’une division entière par une
puissance de 2. En effet, nous avons déjà vu plus haut que le reste de la division entière de
N par 2k est le nombre composé des k bits de poids faible de N . On peut donc masquer les
autres bits pour ne retenir que ces k bits.
ß
Par exemple :
Décalages, division et multiplications Une autre opération utile (mais qui n’est pas ré-
servée aux nombres binaires) est l’opération de décalage de n positions (où n est un naturel).
Il y a deux types de décalage :
— le décalage vers la gauche de n positions, d’un nombre b est noté b << n, et consiste à
ajouter n zéros à la droite (chiffres de poids faible) du nombre (quitte à supprimer les
n chiffres de poids forts pour garder le même nombre de bits). Les chiffres constituant
le nombre initial sont donc bien décalés vers la gauche.
— le décalage vers la droite de n positions, d’un nombre b est noté b >> n, et consiste à
supprimer les n chiffres de poids faibles du nombre n (quitte à ajouter des 0 à gauche
pour garder le même nombre de bits)
Par exemple, en binaire : 0010 1101 >> 3 = 0000 0101. De même 0010 1101 <<
ß 2 = 1011 0100.
Comme nous l’avons déjà vu plus haut, ces opérations permettent d’effectuer des multi-
plications (décalage vers la gauche de k positions) et des divisions (décalage vers la droite de
k positions) par 2k (ou, de manière générale, par b k dans n’importe quelle base b).
42
2.2. Écriture des nombres dans différentes bases
ß
Par exemple :
— 4510 × 1010 = 4510 × 10110 = 4510 << 1 = 45010
— 53110 ÷ 1010 = 531 >> 1 = 5310
— 1000102 ÷ 410 = 100010 ÷ 22 = 100010 >> 2 = 10002
Bit de signe La première technique est la plus simple. Elle n’a que peu d’intérêt pratique,
mais nous l’étudions quand même car elle sans doute la première idée à laquelle on pourrait
songer, et qu’il est dès lors utile de la comparer à celles qui sont utilisées en pratique. Elle
consiste à réserver le bit de poids fort pour représenter le signe du nombre (1 signifiant que
le nombre est négatif), qui sera représenté en valeur absolue sur les n − 1 bits de poids faible.
Avec le bit de signe sur n = 8 bits, le nombre 5 est représenté par : 0000 0101. Le
ß nombre −5 est représenté par 1000 0101.
6. On se souviendra qu’un nombre positif est un nombre > 0 et qu’un nombre négatif est un nombre < 0. Les
nombres ≥ 0 sont donc les nombres « non-négatifs ».
43
2. Leçons 2 à 4 – Représentation de l’information
— il est difficile d’effectuer des opérations sur cette représentation. En particulier, effec-
tuer la somme (selon la procédure usuelle) d’un nombre positif et d’un nombre négatif
ne donne pas le résultat attendu. . .
Les valeurs représentables à l’aide du bit de signe, et sur n bits vont de −2n−1 +1 (représen-
tée par 1 · · · 1) à 2n−1 − 1 (représentée par 01 · · · 1). Cela fait au total 2n − 1 valeurs différentes,
alors qu’il existe 2n représentations. La différence provient du fait que le zéro a deux repré-
sentations distinctes.
L’avantage du complément à 1 sur le bit de signe est qu’il permet de faire des additions de
manière relativement naturelle : on peut faire la somme usuelle de deux nombres (positifs ou
négatifs) en complément à 1 et obtenir la réponse correcte, à condition d’ajouter le dernier
report au résultat.
ß
Nous donnons deux exemples sur 4 bits.
Considérons 3 − 2 = 3 + (−2). En binaire, avec le complément à 1, on obtient
0011 + 1101. En faisant la somme euclidienne, on obtient 1 0000, soit, sur 4 bits,
0000 avec un dernier report de 1. On ajoute ce report aux 4 bits de poids faible,
et on obtient : 0001.
Considérons 2 − 4 = 2 + (−4). En binaire, avec le complément à 1, on obtient
0010+1011. En faisant la somme euclidienne, on obtient 1101 (le dernier report
est égal à 0), ce qui est bien la représentation, en complément à 1, de −2.
44
2.2. Écriture des nombres dans différentes bases
Complément à 2 : Cette représentation est celle qui est utilisée en pratique pour les nombres
entiers sur les processeurs modernes.
2
Le complément à 2 d’un nombre N est le nombre N = 2n − N (où n est toujours le nombre
de bits de la représentation). La représentation des nombres en complément à 2 suit le même
principe que le complément à 1 : les nombres non-négatifs sont représentés par leur enco-
dage binaire usuel ; et les nombres négatifs sont représentés par le complément à 2 de leur
valeur absolue.
Par ailleurs, comme zéro n’a plus qu’une seule représentation, on peut maintenant repré-
senter 2n valeurs différentes sur n bits. La plus petite valeur représentable est maintenant
−2n−1 (représentée par 10 · · · 0), et la plus grande est 2n−1 − 1 (représentée par 01 · · · 1). On a
donc gagné une valeur dans les négatifs par rapport au complément à 1.
2
Enfin, remarquons que N peut être calculé plus facilement grâce à N , le complément à 1.
En effet :
2
Théorème 1 Pour tout N : N = N + 1.
Preuve. Nous considérons une représentation des nombres sur n bits. Nous savons que la
somme d’un nombre avec son complément à 1 donne :
N + N = 1···1
= 2n − 1.
N + N + 1 = 2n − 1 + 1
= 2n .
45
2. Leçons 2 à 4 – Représentation de l’information
2n − N = N + N + 1 − N
= N + 1.
2 2
Or, 2n − N = N , par définition. La dernière équation prouve donc que N = N + 1. □
Excès à K La technique de l’excès à K est à nouveau une idée simple : elle consiste à fixer
une valeur K (appelée biais) suffisamment grande, et à représenter tous les nombres N (po-
sitifs ou négatifs) par la représentation binaire de N + K (nécessairement positif). De ce fait,
sur n bits, toutes les valeurs entre −K (représentée par 0 · · · 0) et 2n − 1 − K (représentée par
1 · · · 1) sont représentables. On choisit souvent K égal à 2n−1 de manière à répartir les valeurs
positives et négatives représentables de manière équitable, mais ce n’est pas obligatoire (par
exemple, dans la norme IEEE754 que nous verrons plus tard, ce n’est pas le cas).
Comparaison des différentes représentation Afin de bien comprendre comment les dif-
férentes techniques de représentation fonctionnent, il peut être utile de les comparer. C’est
l’objet de la Table 2.1 : elle montre comment une même représentation binaire sur n bits
(colonne de gauche) représente des valeurs différentes en fonction de la convention utilisée
(voir aussi, à ce sujet, la discussion à la fin du présent chapitre).
46
2.2. Écriture des nombres dans différentes bases
Nous allons maintenant étudier des techniques permettant de représenter des nombres
qui ne sont pas entiers. Comme nous l’avons déjà observé, il est impossible, en toute géné-
ralité, de représenter tous les nombres réels à l’aide d’un ordinateur. En effet, les nombres
irrationnels 7 , comme le nombre π, ont une partie décimale infinie non-périodique 8 . Or tout
ordinateur ne dispose jamais que d’une quantité finie de mémoire, et, la représentation ex-
plicite d’un nombre réel irrationnel (dans n’importe quelle base naturelle) demanderait donc
une mémoire infinie.
Les nombres que nous allons être en mesure de représenter sont tous des nombres ra-
tionnels, c’est-à-dire des nombres que nous exprimerons (dans une base fixée) sous la forme
α, β : à l’aide d’une partie entière α et d’une partie fractionnaire 0, β, toutes les deux finies.
Par exemple, pour le nombre 42, 625, nous avons α = 42 et β = 625.
Une première technique : la virgule fixe Comme nous l’avons déjà remarqué, si nous
pouvons exprimer séparément α et 0, β en binaire, nous ne pouvons pas exprimer explicite-
ment la virgule, et nous devons donc trouver une manière de contourner ce problème. Une
première technique, qui est simple mais qui a ses limites, consiste à fixer arbitrairement la
position de la virgule au sein d’une représentation de taille fixée. Par exemple, si on consi-
dère des représentations sur n = 32 bits, on pourrait décider que les 16 bits de poids fort
représentent la partie entière α, et que les 16 bits de poids faible représentent la partie frac-
tionnaire de 0, β.
ß
Avec la convention ci-dessus, le nombre 42, 625 est représenté par :
En effet, 42, 62510 = 10 1010, 1012 . On remarque que des zéros on été ajoutés
pour compléter les 32 bits : à gauche de la partie entière et à droite de la partie
décimale.
Le problème de cette technique est qu’elle limite de manière excessive les nombres qu’on
peut représenter, comme le montre l’exemple suivant :
47
2. Leçons 2 à 4 – Représentation de l’information
En suivant la convention donnée ci-dessus, le nombre 2−17 ne peut pas être re-
ß présenté. En effet :
2−17 = 0, 0000 0000 0000 0000 12 ,
et donc, la partie β n’est pas représentable sur les 16 bits alloués dans notre
représentation. On a affaire à un nombre trop petit, on parle d’underflow.
De même, le nombre 216 ne peut pas être représenté car :
Ces deux exemples sont un peu frustrants. On a en effet une représentation sur 32 bits, qui
permettrait aisément de représenter tant 2−17 que 216 , si on avait l’opportunité de déplacer
la virgule. En effet, tant la partie entière de 2−17 que la partie décimale de 216 se réduisent à
un seul 0, et ne nécessitent donc certainement pas 16 bits.
La virgule flottante : IEEE754 Pour remédier à ce problème, on peut utiliser une tech-
nique dite de virgule flottante, où la position de la virgule n’est pas spécifiée a priori, mais où
la représentation binaire du nombre (tant sa partie entière que sa partie décimale) s’accom-
pagne d’une information qui indique où positionner la virgule. Une telle technique s’inspire
de la notation scientifique des nombres, qui consiste à exprimer tous les nombres (en base
10) sous la forme :
0, f × 10x .
ß
Voici trois nombres et leur représentations « scientifiques » respectives :
On voit bien sur ces exemples que l’exposant x indique la position de la virgule, où, pour
être plus précis, le nombre de décalages (vers la droite pour un exposant positif, vers la gauche
pour un exposant négatif) de la virgule qu’il faut affecter au nombre 0, . . . pour retrouver le
nombre d’origine.
Ce principe se retrouve dans le standard industriel IEEE754-2008 9 , dont nous allons main-
tenant étudier une partie, à titre exemplatif [16]. Nous allons nous concentrer sur la repré-
9. L’IEEE est l’Institute of Electrical and Electronics Engineers, une association à but non-lucratif américaine
regroupant des centaines de milliers de professionnels de l’électronique et de l’informatique. Elle conçoit et pu-
blie des normes qui peuvent ensuite être adoptées et mises en pratique par l’industrie. La norme IEEE754-2008
est la révision en 2008 de la norme 754 qui définit une représentation en virgule flottante adaptée aux ordina-
teurs. Le groupe de travail qui conçoit et publie cette norme possède une page web qu’on peut consulter pour
plus d’information : https://standards.ieee.org/develop/wg/754.html.
48
2.2. Écriture des nombres dans différentes bases
sentation des nombres sur 32 bits. Dans cette norme 10 , les 32 bits sont répartis entre :
(−1)s × 1, f × 2e .
On voit donc que le bit s se comporte bien comme un bit de signe (le nombre représenté
est négatif si et seulement si s = 1). On suppose qu’on a préalablement exprimé le nombre
sous la forme 1, f , et seuls les bits de f sont effectivement stockés dans la représentation, ce
qui permet d’économiser un bit. Enfin, l’exposant e est une puissance de 2 et non pas de 10
comme dans la représentation scientifique, ce qui est logique étant donné que nous utilisons
une représentation binaire. L’exposant indique donc bien un décalage à affecter à la virgule.
ß
Considérons à nouveau le nombre 42, 62510 . Pour trouver sa représentation
IEEE754, nous commençons par l’exprime en binaire :
À noter que l’exposant est positif pour maintenir l’égalité. Nous pouvons main-
tenant trouver aisément les différents composants de la représentation :
— le signe s = 0, car le nombre est positif ;
— l’exposant e = 5, que nous devons représenter en excès à 127 sur 8 bits.
Cela revient à représenter 5 + 127 = 132 en binaire, soit 1000 0100 ;
— enfin, le signifiant est la partie après la virgule : f = 01010101.
Nous avons donc la représentation :
Remarquons que les 0 ont été ajoutés dans les bits de poids faible du signifiant,
afin de ne pas changer sa valeur (ajouter des zéros dans les bits de poids forts
reviendrait à insérer des zéros juste à droite de la virgule). ../.
49
2. Leçons 2 à 4 – Représentation de l’information
Comme ces nombres sont relativement longs à écrire, il est souvent pratique
... d’utiliser une représentation en hexadécimal pour la totalité de l’encodage bi-
naire :
0100 0010 0010 1010 1000 0000 0000 0000
=
4 2 2 a 8 0 0 0
soit : 422a8000.
ß
Sur le processeur Intel 486 [12], il est possible de manipuler des données en vir-
gule flottante selon la norme IEEE754 (originelle), sur 32, 64 ou même 80 bits.
Ces données doivent être chargées dans des registres spéciaux de 80 bits appe-
lés st0,. . . st7. Des instructions dédiées comme fadd, fsub, fdiv, etc implé-
mentent les opérations arithmétiques sur ces registres. Ces opérations ne sont
pas réalisées par l’ALU, mais par un circuit dédié du processeur : le FPU (floa-
ting point unit), qui était un circuit séparé sur les processeurs Intel précédent
le 486 (par exemple, pour le 386, il fallait acheter séparément un co-processeur
appelé 387 pour disposer d’un FPU).
50
2.3. Représentation des caractères
Le code ASCII L’évolution la plus marquante des développements historiques que nous
venons de présenter est le code ASCII (American Standard Code for Information Interchange,
présenté en 1967), qui est encore en usage aujourd’hui. Il comprend 128 caractères encodés
sur 7 bits, et est présenté à la Figure 2.2.
ß
Avec le code ASCII la chaîne de caractères INFO-F-102 est représentée par (en
hexadécimal) :
49 4E 46 4F 2D 46 2D 31 30 32,
100 1001 100 1110 100 0110 100 1111 010 1101 100 0110 010 1101
011 0001 011 0000 011 0010.
Ce code, bien adapté à la langue anglaise, ne permet pas de représenter les caractères ac-
centués. C’est pourquoi plusieurs extensions ont vu le jour, sur 8 bits, où les 128 caractères
supplémentaires permettaient d’encoder les caractères propres à une langue choisie. Par
exemple sur l’IBM PC, ces extensions étaient appelées code pages. En voici deux exemples :
51
2. Leçons 2 à 4 – Représentation de l’information
F IGURE 2.1. – Le code Baudot, un système historique de représentation binaire des carac-
tères (on peut associer le + à 1 et le − à 0), breveté en 1882 en France. On voit
ici un extrait du brevet américain (1888).
52
2.4. Représentation d’images
ASCII Code Chart
0 1 2 3 4 5 6 7 8 9 A B C D E F
0 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
2 ! # $ % & ( ) * + , - . /
" '
3 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
4 @ A B C D E F G H I J K L M N O
5 P Q R S T U V W X Y Z [ \ ] ^ _
6 ` a b c d e f g h i j k l m n o
7 p q r s t u v w x y z { | } ~ DEL
F IGURE 2.2. – Le code ASCII. Chaque caractère est représenté par une valeur hexadécimale
sur 2 chiffres : le chiffre de poids fort est donné par la ligne, le chiffre de poids
faible par la colonne.
— le code page 437 : le jeu de caractère ASCII standard auxquels on a ajouté les caractères
accentués latin ;
— le code page 737 : le code ASCII standard plus les caractères grecs, voir Figure 2.3.
L’utilisation de ces code pages supposait que l’utilisateur connaissaient le code qui avait été
utilisé pour représenter le texte source, et qu’il disposait des moyens logiciels et matériels
pour afficher les caractères correspondants. En pratique, cela se révélait souvent fastidieux,
et donnait lieu à des surprises si on se trompait de code page. . .
Unicode Plus récemment, le projet Unicode 12 a vu le jour, et s’est donné pour objectif de
créer une norme reprenant la plupart des systèmes d’écriture utilisés dans le monde, et de
leur attribuer un encodage. La version en cours d’Unicode est la version 11 et elle comprend
137 439 caractères. La norme Unicode associe plusieurs encodages à chaque caractère. L’en-
codage le plus courant est appelé UTF-8 : il s’agit d’un encodage à longueur variable car
chaque caractère est encodé sur 1, 2, 3 ou 4 octets. Tous les caractères encodés sur 1 octet
sont compatibles avec le standard ASCII. Les encodages sur 2, 3 et 4 octets sont utilisés pour
représenter d’autres caractères, aussi exotiques soient-ils. . . Par exemple, la Figure 2.4 pré-
sente l’encodage Unicode de l’alphabet Tagbanwa, en usage aux Philippines [37, 36].
La norme Unicode est devenue aujourd’hui bien adoptée, notamment par les sites et navi-
gateurs web.
53
Code page 737 (CP 737, IBM 737, OEM 737) is a code page to be used under MS-DOS to write
Greek language. It was much more popular than CP869.
Code page layout
Only the upper half (128–255) of the table is shown, the lower half (0–127) being plain ASCII.
–0 –1 –2 –3 –4 –5 –6 –7 –8 –9 –A –B –C –D –E –F
8! ! " # $ % & ' ( ) * + , - . / 0
391 392 393 394 395 396 397 398 399 39A 39B 39C 39D 39E 39F 3A0
9! 1 2 3 4 5 6 7 8 9 : ; < = > ? @
3A1 3A3 3A4 3A5 3A6 3A7 3A8 3A9 3B1 3B2 3B3 3B4 3B5 3B6 3B7 3B8
A! A B C D E F G H I J K L M N O P
3B9 3BA 3BB 3BC 3BD 3BE 3BF 3C0 3C1 3C3 3C2 3C4 3C5 3C6 3C7 3C8
B! ! " # $ % & ' ( ) * + , - . / 0
2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255D 255C 255B 2510
2. Leçons 2 à 4 – Représentation de l’information
C! 1 2 3 4 5 6 7 8 9 : ; < = > ? @
2514 2534 252C 251C 2500 253C 255E 255F 255A 2554 2569 2566 2560 2550 256C 2567
D! A B C D E F G H I J K L M N O P
2568 2564 2565 2559 2558 2552 2553 256B 256A 2518 250C 2588 2584 258C 2590 2580
E! Q R S T U V W X Y Z [ \ ] ^ _ `
3C9 3AC 3AD 3AE 3CA 3AF 3CC 3CD 3CB 3CE 386 388 389 38A 38C 38E
F! a b c d e f g h i j j k ! l ! m
38F B1 2265 2264 3AA 3AB F7 2248 B0 2219 B7 221A B2 25A0 A0
207F
Retrieved from "http://en.wikipedia.org/wiki/Code_page_737"
Categories: DOS code pages
F IGURE 2.3. – Le Code Page 737 : une extension du code ASCII pour obtenir les caractères grecs. [7]
54
This page was last modified on 16 December 2009 at 18:55.
Text is available under the Creative Commons Attribution-ShareAlike License; additional
terms may apply. See Terms of Use for details.
Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit
organization.
Unused 14 reserved code points
Unicode version history
3.2 18 (+18)
Note: [1][2]
2.4. Représentation d’images
Tagbanwa[1][2]
Official Unicode Consortium code chart (http://www.unicode.org/charts/PDF/U1760.pdf) (PDF)
0 1 2 3 4 5 6 7 8 9 A B C D E F
U+176x ᝠ ᝡ ᝢ ᝣ ᝤ ᝥ ᝦ ᝧ ᝨ ᝩ ᝪ ᝫ ᝬ ᝮ ᝯ
U+177x ᝰ ◌ᝲ ◌͕
Notes
1.^ As of F IGURE
Unicode 2.4.
version – Extrait du standard
10.0 Unicode, alphabet Tagbanwa [37].
2.^ Grey areas indicate non-assigned code points
History
des formats historiques comme le GIF ou le BMP. . . ) Ces formats sont utilisés par les sites
The following
webs, Unicode-related
par les documents
appareils photos record the purpose
numériques, and process
etc. Tous of definingreprésentent
ces formats specific characters
desin images
the Tagbanwa
de block:
16
type bitmap , et suivent le même principe de base. Une image y est vue comme une matrice
de points, appelés pixels (contraction de l’anglais picture elements), qui sont indivisibles, et
monochromatiques. Ce sont les éléments de base constitutifs d’une image. La couleur de
chaque pixel peut (comme on l’a fait pour les caractères) alors être représentée par un na-
turel selon un encodage fixé a priori. Par exemple, sur 8 bits on pourra avoir une palette de
256 couleurs, ou 256 niveaux de gris. . . On peut alors représenter l’image comme la suite des
encodages binaires de chacun des pixels, la matrice étant lue ligne par ligne (par exemple).
Un fichier contenant une image contiendra en général d’autres informations utiles (commes
les dimensions de l’image).
ß
Un format de fichier d’image élucidant clairement ces concepts est le format
PGM. Il s’agit en fait d’un fichier texte, contenant plusieurs valeurs numériques,
stockées en ASCII. La Figure 2.5 présente un exemple de fichier PGM, avec
l’image représentée.
Le fichier commence par « P2 », qui indique, par convention, que les couleurs
représentées sont en fait des niveaux de gris. Ensuite, viennent les dimensions
de l’image : 45 pixels de large, par 7 de haut. Puis, vient le nombre de couleur
utilisées : ici, on pourra utiliser les nombres de 0 à 14 pour représenter des ni-
veaux de gris qui s’échelonnent du noir (0) au blanc (14). Enfin, vient la suite des
valeurs numériques décrivant chaque pixel, énumérés ligne par ligne.
Les formats d’images utilisés en pratique comme JPEG ne se contentent pas de stocker
la suite des pixels de l’image, mais appliquent aussi des techniques de compression pour
réduire l’espace nécessaire pour stocker cette information [19]. Notons enfin qu’une vidéo
n’est jamais qu’une séquence d’image fixes. Les concepts développés ici s’appliquent donc
aux différents formats (MPEG, AVI, etc) utilisés pour encoder des images en mouvement.
16. Notons qu’il existe d’autres formats, comme le SVG, qui représentent les images sous formes de points
connectés par des lignes ou des courbes décrites de manière mathématique. Ces formats ont l’avantage de per-
mettre des agrandissements sans perte de qualité.
55
P2
45 7
15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 3 0 4 0 0 0 4 0 5 5 5 5 0 6 6 6 6 0 0 0 0 0 8 8 8 8 0 0 0 0 0 10 0 11 11 11 11 11 0 12 12 12 12 0
0 3 0 4 4 0 0 4 0 5 0 0 0 0 6 0 0 6 0 0 0 0 0 8 0 0 0 0 0 0 0 0 10 0 11 11 0 0 11 0 0 0 0 12 0
0 3 0 4 0 4 0 4 0 5 5 5 0 0 6 0 0 6 0 7 7 7 0 8 8 8 0 0 9 9 9 0 10 0 11 0 11 0 11 0 12 12 12 12 0
0 3 0 4 0 0 4 4 0 5 0 0 0 0 6 0 0 6 0 0 0 0 0 8 0 0 0 0 0 0 0 0 10 0 11 0 0 11 11 0 12 0 0 0 0
0 3 0 4 0 0 0 4 0 5 0 0 0 0 6 6 6 6 0 0 0 0 0 8 0 0 0 0 0 0 0 0 10 0 11 11 11 11 11 0 12 12 12 12 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2. Leçons 2 à 4 – Représentation de l’information
F IGURE 2.5. – Un exemple de contenu de fichier PGM avec l’image qu’il représente. . .
56
2.5. Représentation des instructions
ß
Voici deux exemples d’instructions de l’i486 :
1. L’instruction hlt (halt) qui arrête le processeur est représentée par 8 bits :
1111 0100. Cette instruction très simple ne comprend donc qu’un opcode
(1111 0100) et ne nécessite pas d’opérande.
2. Pour faire la somme de deux registres de 32 bits, on utilise l’instruction
add qui tient sur 16 bits et attend deux opérandes : les numéros des re-
gistres à sommer ; et qui place son résultat dans le premier de ces deux re-
gistres. Cette instruction est représentée de la façon suivante (sur 2 octets,
avec l’octet de poids fort à gauche, les numéros des bits étant indiqués
au-dessus) :
15 8 7 0
0000 0001 11 r eg 1 r eg 2
où r eg 1 et r eg 2 représentent les registres à sommer, sur 3 bits (par
exemple, eax est représenté par 000 et ebx par 001). Dans cet exemple,
l’opérande tient sur dix bits (0000 0001 11).
17. Notez que les mots opcode et opérande sont tous deux masculins.
57
2. Leçons 2 à 4 – Représentation de l’information
b n−1 · · · b 0 ,
on lui associé le bit de parité (ou bit de contrôle) b c calculé de la manière suivante :
Fixons n = 7. Si l’information considérée est 011 1111 (ce qui correspond par
ß exemple au caractère ASCII ?, voir Figure 2.2), on a le bit de contrôle b c = 0. On
obtient alors l’octet 0011 1111, en supposant que le bit de contrôle est le bit de
poids fort.
Supposons maintenant que la donnée soit modifiée en 0011 0111 (le bit b 3 a
été inversé), suite à une erreur. On peut calculer la parité de l’information reçue
0 XOR 1 XOR 1 XOR 0 XOR 1 XOR 1 XOR 1 = 1, et constater que ce n’est pas le bit de
contrôle reçu. On a donc détecté une erreur. Si, par exemple, l’information a été
transmise sur un réseau, on peut demander une retransmission.
Supposons maintenant qu’une erreur ait lieu sur le bit de parité : la valeur
1011 1111 est reçue. À nouveau, une erreur est détectée, même si l’information
à proprement parler est correcte.
Supposons maintenant que deux erreurs ont lieu : 0011 0011. On voit que le bit
de contrôle calculé est 0, ces deux erreurs ne seront donc pas détectées.
58
2.6. Détection et correction d’erreurs
Dans le code de H AMMING, le bit de contrôle C i est un bit de parité pour tous
les bits de données S j , tels que le bit de poids i est à 1 dans l’expression binaire
de j .
Quand un bit C i est un bit de parité pour un bit de donnée D j , nous dirons que « C i vérifie
D j ».
Élucidons cette définition dans le cas où n = 7. Les représentations binaires de numéros
des bits de donnée sont les suivants 011 = 310 , 101 = 510 , 110 = 610 et 111 = 710 . Donc, le bit
de donnée 3 est vérifié par les bits de contrôle 2 et 1 (car en effet 1 + 2 = 3) ; le bit de donnée 5
est vérifié par 4 et 1 (4 + 1 = 5) ; le bit de donnée 6 est vérifié par 4 et 2 (4 + 2 = 6) ; et le bit
de donnée 7 est vérifié par 4, 2 et 1 (4 + 2 + 1 = 7). On peut donc observer les deux propriétés
suivantes, qui découlent de la définition des bits de contrôle et qui seront cruciales dans la
suite pour corriger erreurs :
1. quand on fait la somme des numéros des bits de contrôle qui vérifient un bit de don-
née D j , on retrouve cette valeur j ; et
2. chaque bit de donnée est vérifié par au moins 2 bits de contrôle. Cela provient du fait
que les numéros de bits de données ne sont pas des puissances de 2, leur représenta-
tion en binaire comprend donc au moins deux bits à 1.
18. Né le 11 février 1915 et mort le 7 janvier 1998 , Richard H AMMING est un mathématicien américain, réci-
piendaire du prix Turing en 1968, qui a travaillé entre autres pour le projet Manhattan et aux laboratoires des Bell
Labs.
59
2. Leçons 2 à 4 – Représentation de l’information
On peut ré-exprimer la relation entre les bits de contrôle et les bits de données de la façon
suivante, ce qui nous permet de calculer directement les bits de contrôle :
— le bit de contrôle C 1 vérifie les bits de données D 3 , D 5 et D 7 ;
— le bit de contrôle C 2 vérifie les bits D 3 , D 6 et D 7 ; et
— le bit de contrôle C 4 vérifie les bits D 5 , D 6 et D 7 .
Par exemple le bit de contrôle C 2 est un bit de parité pour D 3 , D 6 et D 7 , donc
C 2 = D 3 XOR D 6 XOR D 7 .
ß
Supposons que nous ayons la donnée 1011 :
C1 C2 D3 C4 D5 D6 D7
1 0 1 1
Cette technique de codage permet non seulement de détecter une erreur mais également
de la corriger. Nous pouvons aisément nous convaincre du fait qu’on peut détecter une erreur
(c’est-à-dire le fait qu’un seul bit soit inversé), en constatant que chaque bit de donnée est
vérifié par au moins un bit de parité parmi C 1 , C 2 , C 3 . Or, nous savons déjà que la technique
du bit de parité permet de détecter tout changement d’un seul bit.
En ce qui concerne la correction de l’erreur, supposons que celle-ci est unique. Nous avons
alors deux cas à considérer :
— soit cette erreur a lieu sur un bit de donnée, D j . Dans ce cas, tous les bits de contrôle
qui vérifient D j auront une valeur qui ne correspond pas à la donnée vérifiée. Il suffit
dès lors de faire la somme des numéros de ces bits de vérification pour retrouver la
valeur j (cfr. la première des deux propriétés énoncés ci-dessus). ;
— soit cette erreur a lieu sur un bit de contrôle C i . Dans ce cas, ce bit sera le seul a poser
problème, et nous saurons donc qu’il n’y a pas d’erreur dans les données (sans quoi
il y aurait au moins deux bits de contrôle problématiques, selon la seconde propriété
énoncée ci-dessus).
60
2.6. Détection et correction d’erreurs
ß
Continuons l’exemple ci-dessus et imaginons qu’il y ait une erreur sur le bit D 3 ,
soit :
C1 C2 D3 C4 D5 D6 D7
0 1 0 0 0 1 1
Re-faisons le calcul de chacun des bits de contrôle :
Bit C 1 : le calcul de D 3 XOR D 5 XOR D 7 donne 1, au lieu de 0 ;
Bit C 2 : le calcul de D 3 XOR D 6 XOR D 7 donne 0 au lieu de 1 ; et enfin
Bit C 4 : le calcul de D 5 XOR D 6 XOR D 7 donne 0, ce qui est bien la bonne valeur.
Il y a donc des problèmes avec les bits de contrôle C 1 et C 2 . On en déduit que le
bit erroné est le bit D j avec j = 1 + 2 = 3. On peut donc re-constituer la donnée
d’origine en inversant le bit D 3 .
Continuons toujours le même exemple et supposons qu’il y ait une erreur sur le
bit de contrôle C 2 , soit :
C1 C2 D3 C4 D5 D6 D7
0 0 1 0 0 1 1
61
2. Leçons 2 à 4 – Représentation de l’information
F IGURE 2.6. – Un exemple de code code QR : à gauche, le code d’origine, à droite le code
dont une partie a été masquée par Mozilla. . . Il est pourtant toujours possible
de lire le message, car les QR codes utilisent les codes de R EED et S ALOMON
pour détecter et corriger les erreurs.
un exemple) qui ont été inventés dans les années 1990 au Japon pour simplifier la logis-
tique de pièces automobiles, et ont depuis été standardisés [17]. À nouveau, les condi-
tions de lecture de ces codes ne sont pas toujours idéales, et de nombreuses erreurs de
lecture ont lieu (c’était d’ailleurs déjà le cas avec les code à barre « classiques » unidi-
mensionnels que l’on retrouve aujourd’hui sur tous les emballages de produits manu-
facturés).
Ces deux exemples ont en commun la technique utilisée pour détecter et corriger les erreurs,
à savoir les codes de R EED 19 et S ALOMON 20 , introduits en 1960 par ces deux auteurs [30].
Nous n’allons pas expliquer ici comment ils fonctionnent, cela nous emmènerait trop loin. . .
mais ces deux exemples montrent que les codes détecteurs et correcteurs d’erreur restent
importants. Ils font d’ailleurs encore l’objet d’une recherche active.
ß
La représentation binaire 1000 0001 s’interprète comme 12910 si on considère
des nombres non-signés ; comme −110 si on considère le bit de signe ; comme
−12710 si on considère le complément à deux ; comme le caractère « ü » si on
considère qu’on a affaire à un caractère ASCII étendu. . .
19. Irving R EED, né le 12 novembre 1923, et mort le 11 septembre 2012, est un mathématicien et ingénieur
américain, qui a enseigné à l’University of Southern California, É-U d’Amérique.
20. Gustave S OLOMON, né le 27 octobre 1930 et mort le 31 janvier 1996 est un mathématicien et ingénieur
américain, qui a travaillé pour la Hughes Aircraft Company.
62
2.7. Conclusion : sémantique d’une représentation binaire
La réponse à cette question est que rien ne permet, a priori de décider quelle interpréta-
tion donner à une séquence binaire particulière. C’est le contexte, et en particulier le pro-
gramme qui est exécuté sur ces données, qui leur donne un sens. Nous le prouvons à l’aide
du programme C++ de la Figure 2.7. Il consiste à placer la même représentation binaire (sur
32 bits) dans trois variables x, y et z différentes, qui ont des types différents. Cela signifie que
les instructions machines qui seront in fine exécutées pour afficher ces variables (instruc-
tions std::cout << à la fin du programme) seront différentes. Dans le cas la variable x, la
représentation binaire sera interprétée comme un entier signé en complément à deux ; dans
le cas de la variable y, la représentation binaire sera interprétée comme un entier non-signé ;
et dans le cas de la variable z, la représentation binaire sera interprétée comme un caractère
(les 24 bits de poids fort seront donc ignorés).
La sortie du programme confirme cela :
1 -2147483601
2 2147483695
3 /
M8N
63
2. Leçons 2 à 4 – Représentation de l’information
F IGURE 2.7. – Un programme C++ qui démontre qu’une même représentation binaire est in-
terprétée de façon différente par différentes instructions.
64
2.8. Exercices
2.8. Exercices
2.8.1. Changements de base
Ex. 1 Effectuez les changements de base suivants :
— Exprimez 101012 en base 10,
— Exprimez 111112 en base 10,
— Exprimez 728 en base 10,
— Exprimez 4E 16 en base 10,
— Exprimez 22710 en base 2 et 16,
— Exprimez 45410 en base 2 et 16,
— Exprimez 1910 en base 2, 8 et 16.
Ex. 2 Combien de nombres binaires différents peut-on représenter sur 3 bits ? sur 4 bits ? sur n
bits ? Justifiez.
Ex. 4 Effectuez les opérations suivantes (où a mod b dénote le reste de la division de a par b,
a ÷ b denote la division entière de a par b, et a >> n représente le décalage de n positions vers
la droite du nombre a). Donnez tous les résultats sur 8 bits. Exprimez ensuite le résultat des
opérations ÷ et mod en terme de décalage et/ou de masque.
— 0101 1101 ∧ 0011 1100,
— 0101 1101 ∨ 0011 1100,
— 0101 1101 XOR 0011 1100,
— ¬0101 1101,
— 0101 1101 XOR 1111 1111
— 0011 00112 >> 310
— 0010 11012 ÷ 1002 ,
— 0010 11012 mod 1002 ,
65
2. Leçons 2 à 4 – Représentation de l’information
Ex. 6 Représentez, en binaire, sur 8 bits, le nombre −7810 , en utilisant les 4 encodages vus au
cours, à savoir :
1. le bit de signe
2. le complément à 1
3. le complément à 2
4. le excès à 128.
Ex. 7 Pour cet exercice, on supposera qu’on manipule des représentations binaires sur 16 bits,
et qu’on utilise le complément à 2. Donnez la représentation en binaire de :
— +103210 .
— −72110 .
⋆ Ex. 8 On considère une représentation sur 8 bits. Pour les quatre représentations des nombres
négatifs vus au cours (bit de signe, complément à 1, complement à 2, excès à 128), déterminez
quel est le plus petit et le plus grand nombre représentable.
Ex. 10 On considère une représentation des nombres flottants selon la norme IEEE 754 simple
précision (sur 32 bits, comme étudiée à la Section 2.2.5). Quelle est la valeur décimale des re-
présentations suivantes ?
— C 657000016 ,
— 42E F 910016 .
Ex. 11 On considère une représentation des nombres flottants selon la norme IEEE 754 simple
précision. Quelle est la représentation des nombres suivants ? Exprimez les résultats finaux en
base 16.
66
2.8. Exercices
— +1432, 4510 ,
— −721, 2510 .
⋆ Ex. 12 La norme IEEE 754 admet deux nombres spéciaux, à savoir NaN (pour not a num-
ber : utilisé en général en cas de division par 0) et ∞ 21 . Pour savoir quel est le résultat d’une
opération dont une des deux opérandes est NaN ou ∞, on a recours à un tableau, par exemple
pour la division :
⋆ Ex. 13 Démontrez que, à part ∞ et NaN, seuls des nombres rationnels peuvent être représen-
tés à l’aide de la norme IEEE 754.
2.8.4. Corrections
Correction de l’exercice 1
1. 101012 = 2110
2. 111112 = 3110
3. 728 = 5810
4. 4E 16 = 7810
5. 22710 = 111000112 = E 316
6. 45410 = 1110001102 = 1C 616
7. 1910 = 100112 = 238 = 1316
Correction de l’exercice 2 De façon générale, on peut représenter 2n nombres binaires différents sur n bits. Pour
n = 3 et n = 4, on a donc respectivement 8 et 16 nombres différents.
On peut expliquer le 2n de la façon suivante : il n’y a clairement que deux nombres de 1 bit, à savoir 0 et 1. Si
on veut construire un nombre de n bits, il faut choisir une valeur (soit 0 soit 1) pour chacun des chiffres (bits),
comme pour un nombre d’un seul bit. Pour un nombre de 2 bits, il faut donc faire un choix parmi deux pour le
premier bit, et un choix parmi deux pour le second, soit au total 2 × 2 = 4 choix possibles. Pour un nombre de 3
bits, on a donc 2 × 2 × 2 = 8 choix, etc. Pour un nombre n bits, on a donc 2 × · × 2} = 2n choix possibles.
| × 2{z
n
Correction de l’exercice 3
1.
1 1 1
1 0 1 1
+ 1 1 1 0
1 1 0 0 1
21. Remarque : en pratique, il existe un infini positif et un infini négatif. Nous ne considérerons qu’un seul
infini dans ces exercices, par souci de simplification.
67
2. Leçons 2 à 4 – Représentation de l’information
2.
1
1 A 7
+ 9 0 9
A B 0
3. 4338 − 1368 = 2758
Correction de l’exercice 4
1. 0101 1101 ∧ 0011 1100 = 0001 1100,
2. 0101 1101 ∨ 0011 1100 = 0111 1101,
3. 0101 1101 XOR 0011 1100 = 0110 0001,
4. ¬0101 1101 = 1010 0010,
5. 0101 1101 XOR 1111 1111 = 1010 0010. Remarquez que cette valeur est également ¬0101 1101,
6. 0011 00112 >> 310 = 0000 0110,
7. 0010 11012 ÷ 1002 = 0010 11012 ÷ 22 = 0000 10112 . De plus, 0010 11012 ÷ 1002 = 0010 11012 >> 2
8. 0010 11012 mod 1002 = 0010 11012 mod 22 = 0000 00012 . De plus, 0010 11012 mod 1002 = 0010 11012 ∧
0000 0011.
Correction de l’exercice 5
BS
1. 0000 11012 ≡ 1310
BS
2. 1100 00002 ≡ −6410
C1
3. 1011 11012 ≡ −6610
C1
4. 0111 10012 ≡ 12110
C2
5. 0001 10012 ≡ 2510
C2
6. 1100 11012 ≡ −5110
E 128
7. 0011 00112 ≡ −7710
E 128
8. 1000 10102 ≡ 1010
Correction de l’exercice 6 On commence par exprimer la valeur absolue du nombre en base 2, à savoir : 7810 =
0100 11102 . Ensuite :
1. avec bit de signe : −7810 ≡ 1100 11102
2. en complément à 1 : −7810 ≡ 1011 00012
3. en complément à 2 : −7810 ≡ 1011 00102
4. en excès à 128 : −7810 ≡ 0011 00102
Correction de l’exercice 7
1. +103210 ≡ 0000 0100 0000 10002 .
2. −72110 ≡ 1111 1101 0010 11112
Correction de l’exercice 8
68
2.8. Exercices
Correction de l’exercice 9
— 12,510 = 12 + 12 = 1100,12
— 12,12510 = 12 + 18 = 1100,0012
— 12,62510 = 12 + 12 + 18 = 1100,1012
— 1432,4510 = (101 1001 1000, 011100 . . .)2
Correction de l’exercice 10
1. C 657000016 = −13760
2. 42E F 910016 = 119,783203125
Correction de l’exercice 11
1. +1432, 4510 = 44B 30E 6616 ,
2. −721, 2510 = C 434500016 .
Correction de l’exercice 12
Correction de l’exercice 13 Considérons le cas général où l’exposant e n’a pas de valeur spéciale (c’est-à-dire
e 6= 0 et e 6= 255). Dans ce cas, le signifiant peut représenter les entiers entre 223 et 224 − 1 compris. L’exposant
peut quant à lui prendre les valeurs entières entre −126 (e = 1) et 127 (e = 254). On ne peut donc représenter
(modulo le signe) que des nombres de la forme :
³ ´
entier
signifiant × 2 ∈Q
| {z } | {z }
∈N ∈Q
Un raisonnement analogue peut être appliqué pour le cas où e = 0. Enfin, dans le cas où e = 255, c’est ∞ et
NaN qui sont représentés.
M8N
69
70
3. Leçon 5 & 6 – Organisation de
l’ordinateur
Maintenant que la manière dont l’ordinateur représente et manipule les informations nous
est connue, nous pouvons repartir du modèle de l’architecture de VON N EUMANN (Figure 1.3)
et étudier ses différents composants plus en détail. On se souviendra que les composants
principaux de l’ordinateur sont :
1. le processeur ;
Tous ces composants sont connectés entre eux de manière à pouvoir communiquer. Les ca-
naux de communication entre ces composants, ainsi qu’entre les éléments constitutifs du
CPU sont appelés des bus. Durant ce chapitre, nous allons donc raisonner sur la Figure 3.1,
qui schématise cette organisation des composants de l’ordinateur.
Mémoire
CPU Périph. 1 Périph. 2
primaire
Bus
71
3. Leçon 5 & 6 – Organisation de l’ordinateur
Le contenu d’une mémoire est structuré en cellules ou cases de taille fixée (typi-
quement 8 bits) qui ont chacune une adresse unique. La mémoire permet deux
types d’opération :
1. la lecture : on transmet à la mémoire une adresse, et la mémoire renvoie
le mot stocké en mémoire à partir de cette adresse ; et
2. l’écriture : on transmet à la mémoire une adresse et une donnée (un mot),
et la mémoire stocke la donnée dans les cellules à partir de l’adresse don-
née.
ß
Les mots du processeur i486 sont de 4 octets : c’est un processeur 32 bits. Dans
la documentation technique de ce processeur, ces mots sont en fait appelés
« double mots » (Dwords) car l’i486 est compatible avec les premiers proces-
seurs Intel qui avaient des mots de 16 bits. L’appellation « mot » (word) a donc
été conservée pour désigner les données de 16 bits.
1. En plaçant l’octet de poids fort soit dans la cellule 20 soit dans la cellule 23, cfr. la discussion sur l’ordre des
octets dans les mots donnée plus loin.
72
3.1. Mémoire primaire
Adresse Contenu
.. ..
. .
00a4 5b11 d1
1b3 2011 d2
.. ..
. .
Dans ce cas, la consultation de l’adresse 00a4 5b11 donnera un cache hit, et on accèdera di-
rectement à la donnée d 1 . La consultation d’une adresse n’apparaissant pas dans la première
colonne, donne lieu à un cache miss, suite à quoi la donnée lue en mémoire est stockée dans
la cache.
Ce mécanisme est efficace si de nombreux cache hits ont lieu. Or, la taille de la mémoire
cache est forcément beaucoup plus petite que la mémoire source, il faut donc bien sélection-
ner les informations qu’on prélève dans la mémoire source pour les stocker en cache. Par
ailleurs, quand la cache est pleine (toutes les lignes du tableau sont occupées) et qu’on veut
y stocker une nouvelle information provenant de la mémoire source, il faut décider quelle
information sacrifier.
En pratique ces mécanismes fonctionnent bien car les accès à la mémoire sont souvent
localisés :
— quand on exécute une instruction d’un programme, il y a de fortes chances pour que
l’instruction à exécuter ensuite soit l’instruction suivante en mémoire ;
— beaucoup de programmes exécutent des boucles. Dans ce cas, les mêmes instructions
sont répétées, et la cache améliorera grandement les performances si on arrive à garder
les instructions de la boucle en cache tout au long de son exécution ;
2. Ce principe peut se généraliser à tous les cas où on souhaite consommer des données depuis une source
plus lente.
73
3. Leçon 5 & 6 – Organisation de l’ordinateur
ß
Le processeur i486 possède un seul niveau de cache (L1) de 8 kibioctets, présent
directement sur le circuit intégré du processeur. Il peut contenir tant des don-
nées que des instructions. On voit cette cache sur la Figure 1.4, connectée aux
composants servant à communiquer avec la mémoire primaire.
74
3.1. Mémoire primaire
Petit boutiste
entier 1 524 852
01110100 01000100 00010111 00000000
Chaîne ABCD
A B C D
01000001 01000010 01000011 01000100
Gros boutiste
entier 1 524 852
00000000 00010111 01000100 01110100
Chaîne ABCD
A B C D
01000001 01000010 01000011 01000100
pas question d’inverser l’ordre des octets qui contiennent des caractères, alors que ce sera
nécessaire pour les mots qui contiennent des nombres. La Figure 3.2 illustre cela.
ß
Le processeur i486 est un processeur petit boutiste. Quand on indique une
adresse a (pour la lecture ou l’écriture d’un mot en mémoire), on indique donc
l’adresse de l’octet de poids faible, et les octets de poids plus fort sont stockés
en a + 1, a + 2,. . .
Notons que les termes « petit boutiste » (little endian) et « gros boutiste » (big endian) pro-
viennent des célèbres Voyages de Gulliver [35] de Jonathan Swift (1667 – † 1745). Ce livre est
une satire sociale, dans lequel deux peuples s’affrontent dans des guerres sanglantes, sous
prétexte de savoir s’il convient de manger les œufs à la coque en les brisant du petit ou du
gros côté [35, Chapitre IV] :
[. . . ] two mighty powers have, as I was going to tell you, been engaged in a most
obstinate war for six-and-thirty moons past. It began upon the following occa-
sion : It is allowed on all hands, that the primitive way of breaking eggs, before we
eat them, was upon the larger end ; but his present majesty’s grandfather, while he
was a boy, going to eat an egg, and breaking it according to the ancient practice,
happened to cut one of his fingers. Whereupon the emperor, his father, published
an edict, commanding all his subjects, upon great penalties, to break the smaller
end of their eggs. The people so highly resented this law, that our histories tell us,
there have been six rebellions raised on that account, wherein one emperor lost
his life, and another his crown. [. . . ] It is computed, that eleven thousand persons
have, at several times, suffered death, rather than submit to break their eggs at the
smaller end. Many hundred large volumes have been published upon this contro-
versy, but the books of the Big-endians have been long forbidden, and the whole
party rendered incapable, by law, of holding employments. [. . . ]
75
3. Leçon 5 & 6 – Organisation de l’ordinateur
Au cours de l’histoire plusieurs solutions techniques ont été proposées pour réaliser des
mémoires primaires. La solution actuelle utilise des transistors intégrés pour réaliser les cir-
cuits logiques que nous décrirons dans le Chapitre 4, mais ce n’a pas toujours été le cas. Voici
quelques-unes des solutions « historiques » :
Les tubes de Williams Ce sont des dispositifs (voir Figure 3.3) qui ressemblent aux écrans
des anciennes télévision (type CRT) : une couche de phosphore est placée au fond d’un tube
cathodique, qu’un canon à électrons vient balayer. Les données sont stockées comme des
charges électrostatiques sur la couche de phosphore : en traçant, comme sur un écran de
télévision, des points et des traits sur le fond du tube. Ces données restent présentes sur le
tube une fraction de seconde, ce qui permet de tracer d’autres points. Les données sont lues
à l’aide d’une plaque métallique placée face au fond du tube. Comme les données ne restent
en place qu’une fraction de seconde, le dispositif doit continuellement lire les données et les
ré-inscrire (éventuellement avec une modification en cas d’écriture).
76
3.2. Le processeur
Les mémoires à lignes à délai Dans ces systèmes, les informations étaient représen-
tées sous forme de pulsations électriques qu’on émettait à un bout d’un tube de mercure
ou d’un très long câble, et qu’on lisait à l’autre bout. Comme la pulsation électrique mettait
un certain temps à travers le medium, on pouvait envoyer plusieurs pulsations différentes
(et donc une séquence de bits) l’une à la suite de l’autre. Une fois arrivée au bout du tube, la
pulsation était lue, amplifiée, et réinjectée au début. L’information circulait donc en boucle.
Pour lire un bit, il suffisait d’attendre que la pulsation correspondante arrive au bout du tube.
Pour écrire, il suffisait d’injecter la nouvelle information au lieu de recopier l’ancienne. La
Figure 3.4 montre deux types de mémoires à lignes à délai.
3.2. Le processeur
Maintenant que nous avons étudié la mémoire, nous pouvons nous intéresser au compo-
sant principal de l’ordinateur : bien évidemment, le processeur.
77
3. Leçon 5 & 6 – Organisation de l’ordinateur
F IGURE 3.4. – Deux types de mémoires à ligne à délai. Au-dessus : ligne de délai à mercure
de l’Univac 1, 1951. Dimensions approximatives : 4m × 2m × 2m. Contient 18
canaux de mercure pouvant contenir chacun 10 mots de 12 bits, soit 270 oc-
tets. En-dessous : ligne à délai en fil de nickel du Ferranti Sirrius au Computing
Museum de l’Université Monash (Melbourne, Australie). Le rayon du cercle fait
approximativement 50cm. Capacité de stockage : 50 mots, soit environ 125 oc-
tets.
78
3.2. Le processeur
79
3. Leçon 5 & 6 – Organisation de l’ordinateur
80
3.2. Le processeur
ß
Comme nous l’avons déjà dit, le processeur i486 est un processeur 32 bits, qui
possède 32 registres [12]. Ils se répartissent comme suit :
— les 8 registres de travail de 32 bits eax, ebx, ecx, edx, esi, edi, ebp et esp ;
— 1 registre d’état de 32 bits appelé eip (pour instruction pointer) qui fait
office de registre PC ;
— 1 registre d’état de 32 bits (dont seuls 19 bits sont utilisés), appelé EFlags,
dont les bits individuels (appelés flags) permettent d’avoir de l’informa-
tion sur l’état du processeur. Par exemple, le huitième bit, appelé S in-
dique si le résultat de la dernière opération effectuée par le CPU est une
valeur négative ;
— 8 registres de 80 bits appelés st0,. . . st7 pour réaliser des calculs sur des
nombres en virgule flottante selon la norme IEEE754 (voir Section 2.2.5),
à l’aide du FPU. Les mots du FPU de l’i486 sont donc de 80 bits ;
— 4 registres de contrôle (dont seuls 3 sont utilisés), utilisés pour la gestion
de la mémoire (voir Chapitre 9) ; et enfin
— 10 registres supplémentaires qui sont utilisés pour la gestion de la mé-
moire segmentée (nous n’entrerons pas dans les détails ici).
ß
Voici un exemple de programme machine pour l’i486, il comporte 3 instruc-
tions, toutes les trois données en binaire. Les deux premières tiennent sur 40
bits :
1011 1000 0000 0000 0000 0000 0000 0000 0010 1010
1011 1001 0000 0000 0000 0000 0000 0000 0000 0111
0000 0001 1100 0001
La première instruction commence par l’opcode 10111 qui indique de placer
une valeur de 32 bit dans un registre. Le registre est spécifié par les trois bits sui-
vants : c’est eax (code binaire 000). Ensuite, viennent les 32 bits qui spécifient la
valeur 42. La seconde instruction fait de même en plaçant la valeur 7 dans ecx
(code binaire 001). Enfin, la troisième indique au processeur de faire la somme
(opcode 0000 0001 11), du registre eax (donné en binaire par 000) et du re-
gistre ecx (donné en binaire par 001). L’instruction de somme place le résultat
dans le premier des deux registres, à savoir eax.
Ces instructions sont assez difficiles à lire telles quelles, c’est pourquoi nous
adopterons une syntaxe plus lisible (qui est en fait celle de l’assembleur), plus
proche d’un langage de programmation usuelle : voir Figure 3.6.
Notons qu’au lieu de charger des valeurs fixées dans les registres, on aurait
tout aussi bien pu charger des valeurs depuis la mémoire, en spécifiant leurs
adresses.
81
3. Leçon 5 & 6 – Organisation de l’ordinateur
Nous allons maintenant nous concentrer sur l’ALU et les registres qui permettent l’exécu-
tion des instructions machine.
À l’intérieur d’un CPU, l’ALU est connectée aux registres via des bus car ce sont eux qui
l’alimentent en données et qui recueillent ses résultats. Les données se propagent donc dans
le CPU de manière cyclique : elle partent des registres, sont traitées dans l’ALU, et le résultat
retourne aux registres.
Le chemin des données est symbolisé à la Figure 3.7. On observe en particulier que l’ALU
possède ses propres registres d’entrée et de sortie qui sont alimentés et alimentent les re-
gistres de travail du CPU.
Comme nous l’avons déjà expliqué dans le Chapitre 1, le CPU exécute continuellement la
boucle appelée fetch–decode–execute, que nous pouvons maintenant donner de façon plus
détaillée 5 , en expliquant quels sont les registres qui interviennent dans cette boucle et com-
ment ils sont utilisés.
5. On pourra utiliser avec bonheur le simulateur de Dave Reed pour illustrer et manipuler ces notions de
manière un peu plus pratique. On y accède à l’adresse : http://www.dave-reed.com/book/Chapter14/.
82
3.2. Le processeur
A+B
Registres
B
(CPU)
A
Registres d’entrée
A B de l’ALU
ALU
Registre de sortie
A+B de l’ALU
F IGURE 3.7. – Le chemin des données. Les flèches grises symbolisent les bus (internes au
CPU).
La ligne 4 peut être vue comme une traduction de l’instruction machine (de niveau 2) en
une instruction ou une série d’instructions du niveau inférieur (micro-instructions, voir Fi-
gure 1.5). La boucle d’interprétation du CPU est schématisée à la Figure 3.8.
83
3. Leçon 5 & 6 – Organisation de l’ordinateur
IR ← M[PC ]
PC ← PC +1
néanmoins un modèle différent, dont nous reparlerons plus largement dans le Chapitre 5 :
celui de la machine à pile.
Une machine à pile ne possède pas une telle série de registres 6 servant à stocker les don-
nées qui vont être traitées par le langage machine 7 . Les données à traiter ainsi que les résul-
tats des opérations proviennent tous d’une pile, qui est une structure de données particulière.
6. Ce qui ne signifie pas pour autant qu’il n’y a pas de registre dans une machine à pile, mais ceux-ci sont
internes au processeur et ne sont pas accessibles par le langage machine. Nous en verrons un exemple concret
dans le Chapitre 5.
7. Par exemple, sur l’i486, les données à additionner lors d’une instruction add peuvent être dans n’importe
quel registre général : eax, ebx,. . .
8. Nous avons toutes et tous essayé de prendre une assiette au milieu d’une pile ; et nous savons quels sont les
risques, n’est-ce pas ?
84
3.2. Le processeur
13
15 15 15
Push(15) Push(13) Pop()
42 −−−−−−→ 42 −−−−−−→ 42 −−−−→ 42
27 27 27 27
64 64 64 64
milieu »), via l’opération appelée Push, et la suppression de données se fait en prélevant la
donnée au sommet, via l’opération Pop.
ß
La Figure 3.9 montre un exemple de pile avec trois manipulations. Dans son
premier état, la pile contient trois données : 64, 27 et 42 (de bas en haut). L’appel
à Top sur cette pile donnerait donc 42. Ensuite, un Push de la donnée 15, ajoute
celle-ci au sommet de la pile (et on a donc Top = 15 dans ce cas). Un second
Push, de la valeur 13, étend encore le contenu de la pile. Finalement, le dernier
Pop supprime la dernière valeur insérée.
Une pile est parfois appelée un LIFO pour last in, first out 9 , car c’est toujours la dernière
donnée insérée qui est lue en premier.
Utilisation dans le langage machine Comment la pile est-elle utilisée par les instructions
machines ? Il suffit de faire des Push des opérandes sur la pile, puis d’exécuter l’instruction
(en général, sans opérande), qui placera le résultat sur la pile également.
Par exemple, pour une opération d’addition, on pourrait : (1) faire un Push des deux valeurs
à sommer sur la pile ; puis (2) appeler l’instruction d’addition, ce qui aura pour effet de faire
un Pop des deux opérandes, puis un Push de leur somme.
9. À opposer à FIFO, signifiant first in, first out, qui définit une file d’attente comme à la caisse des magasins.
85
3. Leçon 5 & 6 – Organisation de l’ordinateur
ß
Bien que l’i486 soit essentiellement une machine à registres comme nous l’avons
décrit jusqu’à présent, ce processeur se comporte comme une machine à pile
quand on effectue des opérations en virgule flottante, à l’aide du FPU.
L’i486 possède 8 registres de 80 bits utilisés pour contenir la pile sur laquelle
agissent les instructions en virgule flottante. Le FPU de l’i486 possède aussi un
registre appelé STATUS, de 16 bits, dont les bits 13, 12 et 11 contiennent, en bi-
naire, le numéro du registre qui constitue le sommet du stack. En pratique, on se
réfère au registre qui est au sommet de la pile par st0, a celui juste en-dessous
par st1, etc.
La Figure 3.10 présente un exemple de traitement qu’on peut réaliser à l’aide
d’instructions en virgule flottante sur l’i486 et qui illustre l’usage de la pile.
D’autres processeurs « historiques », par contre étaient intégralement des ma-
chines à pile. Par exemple, la série des ordinateurs HP 3000, introduits en
1972. . .
86
3.2. Le processeur
son lointain ancêtre l’Intel 8086 (datant de. . . 1977 !) Cela introduit donc une contrainte forte
sur le jeu d’instructions et sur la conception des processeurs comme le choix des registres 10 .
Ces contraintes qui sont communes à une famille de processeurs et qui assurent un certain
niveau de comptabilité au cours de temps sont appelés une architecture, nous en reparlerons
dans le Chapitre 6. Dans le cas des processeurs Intel, par exemple, on parle de l’architec-
ture x86.
L’introduction de la notion de micro-code vers 1951 par Maurice W ILKES 11 [48, 49] a gran-
dement simplifié la conception des microprocesseurs, et a eu pour effet un peu inattendu
l’explosion des jeux d’instructions des machines. Dans les années ’70, on rencontrait des
machines avec des centaines d’instructions 12 . . . Dans les années 1980, plusieurs chercheurs
dont David PATERSON 13 [27] ont estimé que ces processeurs devenaient trop complexes, et
on commencé à plaider pour une simplification des processeurs, permettant des architec-
tures plus simples 14 . Ces architectures sont désignées sous le nom de RISC pour Reduced Ins-
truction Set Computing, par apposition aux CISC (Complex Instruction Set Computing). Les
concepteurs de RISC plaident pour des processeurs sans micro-code, où le nombre d’exécu-
tions d’instructions par seconde est maximisé (au lieu de tenter d’optimiser chaque instruc-
tion), et avec de nombreux registres.
Parmi les processeurs RISC, on trouve :
— Les instructions arithmétiques, Source : Unknown – University of Cambridge Computer Laboratory Archive (https:
//commons.wikimedia.org/wiki/File:Maurice_Vincent_Wilkes_1980_(3)
qui opèrent sur les registres uni- .jpg), « Maurice Vincent Wilkes 1980 (3) », https://creativecommons.org/lice
nses/by/2.0/uk/deed.en
quement (pas d’accès mémoire).
10. Ainsi les 2 octets de poids faible des registres eax, ebx, ecx et edx peuvent être vus comme des registres de
16 bits appelés ax,. . . dx, ce qui correspond aux noms des registres 16 bits originels du 8086. Le e dans les noms
eax, etc est en fait l’abréviation d’extended.
11. Né le 26 juin 1913 et mort le 29 novembre 2010, informaticien anglais, professeur à l’Université de Cam-
bridge, et récipiendaire du Prix Turing, il a largement contribué à la conception de l’EDSAC, un des premiers
ordinateurs.
12. Le VAX, par exemple. Voir [43] pour un guide de référence de l’architecture.
13. Né le 16 novembre 1947, informaticien américain, professeur à l’Université de Berkeley (Californie, É.-U.
d’Amérique) et récipiendaire du Prix Turing en 2017.
14. L’instruction INDEX du VAX, par exemple, était plus lente qu’une implémentation « à la main » du même
traitement. . .
87
3. Leçon 5 & 6 – Organisation de l’ordinateur
88
3.2. Le processeur
Lire l’instruction
en mémoire
(instruction fetch)
F IGURE 3.13. – Un pipeline superscalaire à cinq étages. L’étage « instruction fetch », qui est
typiquement très rapide, est unique. Les instructions récupérées en mémoire
par cet étage sont réparties sur deux pipelines, qui peuvent exécuter deux ins-
tructions en parallèle.
il est impératif que la première instruction se soit entièrement exécutée pour que la seconde
s’exécute correctement, car cette dernière dépend de la valeur calculée lors de la première.
Cela peut être résolu par le compilateur, ou bien détecté à l’exécution.
16. Né le 28 septembre 1925 et mort le 5 octobre 1996, informaticien américain connu pour être le « père des
super-ordinateurs ». Il a fondé plusieurs entreprises ayant donné naissance à Cray Inc. qui conçoit et commer-
cialise encore aujourd’hui des super-ordinateurs.
89
3. Leçon 5 & 6 – Organisation de l’ordinateur
90
3.2. Le processeur
Processeurs vectoriels ou SIMD Ces processeurs mettent en œuvre une technique diffé-
rente, bien que toujours basée sur l’idée de paralléliser les calculs nécessaires à la bonne exé-
cution d’un programme. Dans de nombreuses applications, il est nécessaire d’appliquer la
même opération à tous les éléments d’un tableau. Pour ce faire, le programmeur écrit en gé-
néral une boucle, qui parcourt chaque élément du tableau, et applique l’opération à chaque
case séquentiellement. Les processeurs vectoriels sont des processeurs spécialisés dans le trai-
tement des vecteurs (un autre nom pour « tableau ») : il appliquent en parallèle la même
instruction à une grande quantité de données. En pratique, plusieurs (jusqu’à 256) unités de
calcul sont mises en parallèle, mais la partie qui contrôle les instructions est commune. Les
ordinateurs ILLIAC IV (voir Figure 3.15) et la série des ordinateurs Cray (Figure 3.16) sont des
exemples historiques.
Aujourd’hui, ces types de processeurs sont appelés SIMD pour Single Instruction Multiple
Data (application de la même instruction à de multiples données). On retrouve des proces-
seurs vectoriels dans la plupart des cartes graphiques optimisées pour les jeux, car la mani-
pulation des images en 3D se prête bien à ce type de traitements. La plupart des processeurs
récents possèdent des instructions de ce type 17 .
Systèmes multi-cœurs Afin d’éviter de surcharger le bus, on peut regrouper plusieurs pro-
cesseurs sur un même circuit intégré. Chaque processeur est alors appelé cœur et le circuit
intégré est appelé processeur multicœur. Un processeur multicœur ressemble donc très fort à
un système multiprocesseur, à la différence que les communications entre cœurs ne doivent
pas passer par le bus et sont donc plus rapides. Les différents cœurs partagent par contre une
interface d’accès au bus (externe) unique, et souvent la mémoire cache de niveau L2.
La plupart des processeurs présents aujourd’hui dans les ordinateurs personnels sont multi-
cœurs, c’est la solution qui a été privilégiée par les fabricants depuis environ 2005 pour conti-
nuer à faire croître les performances des processeurs sans augmenter leur fréquence.
17. Par exemple, l’instruction mulps du jeu d’instructions Intel SSE, qui permet d’effectuer plusieurs multipli-
cations en virgule flottante en parallèle.
91
3. Leçon 5 & 6 – Organisation de l’ordinateur
F IGURE 3.15. – Une vue de l’ILLIAC IV, ordinateur développé à l’Université de l’Illinois, et
construit par la firme Burroughs, il fut finalement installé en 1972 au centre
de recherche Ames de la NASA. Il fut le premier super-ordinateur connecté à
ARPANet, l’ancêtre d’Internet.
Source : Steve Jurvetson from Menlo Park, USA (https://commons.wikimedia.org/wiki/File:ILLIAC_4_parallel_computer.jpg), « ILLIAC 4 parallel
computer », https://creativecommons.org/licenses/by/2.0/legalcode.
92
3.2. Le processeur
F IGURE 3.16. – Une Cray 1 au Deutschen Museum de Munich. Il s’agit du premier modèle de
super-ordinateur commercialisé par la firme fondée par Seymour C RAY.
93
3. Leçon 5 & 6 – Organisation de l’ordinateur
Mémoire
CPU 1 CPU 2 Périph. 1 Périph. 2
principale
Bus
crivent dans la lignée de celles que nous venons de décrire, car elles visent toutes à augmenter
la capacité du système informatique à traiter plusieurs informations en même temps (en pa-
rallèle). On peut dès lors connecter plusieurs unités de calcul individuelles, appelées nœuds,
entre elles, et répartir la charge de calcul sur l’ensemble de ces ordinateurs, en les faisant
travailler en parallèle.
Ces nœuds doivent naturellement communiquer et se synchroniser, et doivent donc être
connectés entre eux. On distingue :
— les grappes de calcul (ou clusters en anglais), où tous les nœuds sont réunis en un même
endroit, et communiquent entre eux de manière rapide et fiable à travers un réseau
local ; et
— les fermes de calcul (ou grid en anglais), où les différents nœuds (qui peuvent être eux-
mêmes des clusters) sont connectés entre eux par un réseau de grande dimension,
comme l’Internet.
Ce qui est important, dans le deux cas, est que l’utilisateur n’a pas à se soucier de la répartition
de la charge de calcul sur les différents nœuds : il accède au cluster ou au grid à travers un
point d’entrée unique, et un logiciel spécialisé (parfois intégré dans le système d’exploitation)
se charge de répartir le travail sur les nœuds disponibles.
ß
L’ULB et la VUB possèdent un cluster, appelé Hydra, qui se trouve au Shared ICT
Services Center, et qui était composé en 2018 d’environ 160 nœuds multiproces-
seurs multicœur (entre 16 et 24 cœurs par nœud), voir https://hpc.ulb.be/.
Un exemple de grid est composé par le projet SeTI@home (Search for extraTer-
restrial Intelligence at home, voir : http://setiathome.ssl.berkeley.edu/),
auquel tout le monde peut participer en y connectant son ordinateur individuel.
Le but de ce projet est d’analyser de grandes quantités de données provenant
de radio-téléscopes pour tenter d’y détecter le signal d’une intelligence extra-
terrestre.
94
3.3. Les périphériques
communiquer avec le monde extérieur. Parmi les périphériques, nous commençons par dis-
tinguer la mémoire secondaire.
Les cartes perforées L’information est stockée physiquement à l’aide de trous perforés
dans une carte en carton (voir Figure 3.18). Chaque position sur la carte correspond à un bit,
et la présence ou non d’un trou indique la valeur du bit. Les cartes typiques stockaient 80
caractères 18 , encodés sur 8 à 10 bits en fonction du type de carte.
Les cartes perforées datent de bien avant les premiers ordinateurs. Elles étaient déjà uti-
lisées au dix-huitième siècle par J ACQUARD 19 pour représenter des motifs à broder sur des
métiers automatisés. Au début du vingtième siècle, elles étaient en usage pour stocker et trai-
ter de l’information de manière automatique, à l’aide de machines appelées tabulatrices 20
Les Rubans perforés Ce medium applique le même principe que les cartes perforées, sauf
que les perforations sont faites dans ruban de papier (voir Figure 3.19).
Les bandes magnétiques Avec ce medium, l’information est stockée de manière séquen-
tielle sur une bande magnétisée. Les informations sont encodées par des variations du champ
magnétique de la bande. Les bandes sont apparues dans les années 50, et sont encore en
18. Ce qui explique pourquoi certains programmes de courrier électronique formattent encore les messages
sur 80 colonnes).
19. Joseph Marie J ACQUARD, né le 7 juillet 1752 à Lyon et mort le 7 août 1834 à Oullins. Inventeur français ayant
perfectionné des travaux antérieurs pour créer un métier à tisser programmable à l’aide de cartes perforées, fort
célèbre et répandu.
20. Ces machines, qui ont fait la fortune d’IBM, appliquaient, à toutes les cartes d’un paquet, un traitement
choisi en modifiant des connexions câblées (un peu comme dans les anciennes centrales téléphoniques, du
temps des opératrices). On ne peut donc pas vraiment parler d’ordinateur programmable. . .
95
3. Leçon 5 & 6 – Organisation de l’ordinateur
F IGURE 3.18. – Une carte perforée telles qu’elles étaient utilisées dans les supermarchés
belges Colruyt durant les années 1980 (avant l’utilisation systématique des
codes-barre). Les clients devaient collecter une carte perforée pour chaque
produit acheté, et le caissier pouvait ensuite imprimer un ticket de caisse dé-
taillé sur base de ces cartes, qui donnaient accès à toute l’information néces-
saire sur les produits.
96
3.3. Les périphériques
Les disques magnétiques Les disques magnétiques existent sous plusieurs formes :
— Les disques durs : il s’agit d’un ensemble de disques d’aluminium recouverts d’une
surface magnétique. Les disques sont divisés logiquement en secteurs qui enregistrent
chacun, une quantité fixée d’information. Ces disques sont maintenus en rotation en
permanence. L’information est inscrite et lue à l’aide de têtes de lecture mobiles. L’accès
à une zone particulière du disque requiert donc de positionner la tête et d’attendre que
la bonne portion du disque passe sous la tête, comme on peut le voir sur la Figure 3.21.
Un disque dur comporte généralement un circuit qui gère la position des têtes en fonc-
tion des demandes qui sont faites, appelé contrôleur de disque.
Certains systèmes, comme le système RAID, répartissent l’information sur plusieurs
disques physiques, qui apparaissent alors à l’utilisateur comme un seul disque. Cela
permet, selon les cas, de paralléliser le traitement de l’information, voire de récupérer
l’information en cas de panne d’un des disques, si l’information est stockée de manière
redondante sur les différents disques.
— Les disquettes : les disquettes fonctionnent sur le même principe que les disques durs,
mais ne comprennent qu’un seul disque (qui peut être inscrit en simple ou double face
selon les modèles). Les disquettes sont amovibles alors que les disques durs ne le sont
généralement pas. Remarque : les premiers ordinateurs utilisaient de gros disques ma-
gnétiques, semblables à nos disques durs modernes, mais amovibles (comme les dis-
quettes). La Figure 3.20 présente des exemples de disquettes.
97
3. Leçon 5 & 6 – Organisation de l’ordinateur
Les disques optiques tels que les CD-ROMS ou DVD-ROMS, enregistrent les informations
à l’aide de trous dans une surface réfléchissante, qui peut ensuite être lue optiquement (par
un faisceau laser).
La mémoire Flash / SSD Ce type de mémoire est apparu de manière plus récente et a
d’abord été disponible en faible capacité (quelques centaines de kilo-octets, quelques méga-
octets), principalement dans des appareils comme les appareils photo numériques. Le dé-
veloppement des technologies permet aujourd’hui de les intégrer dans les ordinateurs per-
sonnels, en remplacement des disques durs classiques. Il existe plusieurs technologies dif-
férentes, mais toutes stockent l’information de manière électronique, comme la mémoire
primaire (à la différence que la mémoire Flash/SSD n’est pas volatile).
98
3.3. Les périphériques
Accès direct à la mémoire Comme nous l’avons dit, la plupart des périphériques servent
à échanger de l’information entre l’ordinateur et le monde extérieur. Au sein de l’ordinateur,
cette information est stockée dans la mémoire primaire (afin que le processeur puisse la trai-
ter). Les périphériques doivent donc dialoguer en permanence avec la mémoire primaire.
Voyons maintenant comment nous pouvons réaliser cela de manière efficace.
Le schéma de base permettant d’acquérir de l’information sur un périphérique, et de la
stocker en mémoire fonctionne comme suit :
1. le CPU envoie un ordre au contrôleur du périphérique ;
2. le contrôleur envoie la commande correspondante au périphérique ;
3. le périphérique répond au contrôleur ;
4. le contrôleur émet l’information sur le bus, adressée au CPU ;
5. le CPU récupère l’information et la transmet à la mémoire, via le bus.
Si le but final de l’opération est de stocker l’information dans la mémoire, ce schéma est peu
efficace. Il n’est en effet pas nécessaire de passer par le CPU, qui pourrait utiliser le temps
ainsi économisé à d’autres tâches. Sur les ordinateurs modernes, certains périphériques ont
donc la possibilité d’écrire directement en mémoire à un endroit bien précis. On appelle cette
technique l’accès direct à la mémoire, ou Direct memory access (DMA). En pratique, chaque
périphérique se voit attribuer une plage d’adresses en mémoire primaire où il peut stocker
directement de l’information sans la supervision du CPU. Ce mécanisme est chapeauté par
un contrôleur DMA, sorte de processeur dédié à cette seule tâche, qui peut donc fonctionner
en parallèle du CPU. Quand l’information est prête en mémoire, le contrôleur prévient le
CPU via un mécanisme d’interruption (que nous décrirons dans le Chapitre 7).
99
3. Leçon 5 & 6 – Organisation de l’ordinateur
Bus dédiés La Figure 3.1 nous présente un modèle de l’ordinateur avec un bus unique, sur
lequel transitent tous les échanges d’informations entre le CPU, la mémoire et les périphé-
riques. En pratique, cela peut poser plusieurs problèmes :
— le CPU, la mémoire et les périphériques ne fonctionnent pas à la même vitesse. Un bus
lent est amplement suffisant pour des périphériques comme la souris et le clavier, mais
pas pour la communication entre le CPU et la mémoire (n’oublions pas que le CPU doit
charger chaque instruction exécutée depuis la mémoire !) ;
— tous les composants connectés au bus doivent communiquer de la même manière
(encodage de l’information, etc). Par ailleurs, les périphériques d’un ordinateur sont
par nature amovibles, et leur raccordement au bus doit donc se faire via des connec-
teurs, qui sont naturellement standardisés et dépendent souvent du type de bus. Un
bus unique implique donc que tous les périphériques doivent pouvoir être connectés
physiquement au bus de la même manière ;
— le changement d’un composant essentiel, comme le CPU ou la mémoire, risque de de-
mander de changer également le bus, et donc, potentiellement, tous les périphériques,
alors que pour des raisons économiques évidentes, on souhaite pouvoir réutiliser ses
anciens périphériques sur son nouvel ordinateur.
Afin de contourner ce problème, l’industrie a développé une série de bus dédiés, qui spé-
cifient : un protocole de communication de l’information sur ce bus ; et une norme pour les
connecteurs, assurant que tous les dispositifs qui respectent cette norme puissent être facile-
ment branchés sur le bus. Ces bus spécialisés accueillent alors une famille de périphériques,
et sont connectés au bus principal via un contrôleur dédié, souvent appelé pont (ou bridge).
Par exemple : le bus USB 21 existe en plusieurs versions selon des normes bien définies,
spécifiant tant le protocole de communication (vitesse, encodage des données, etc) que les
formes des connecteurs USB. Il est bien adapté pour connecter des petits périphériques
comme le clavier, la souris, etc. Parmi les autres exemples de bus courants on peut citer :
1. le bus ISA, qui était le standard pour les cartes d’extension (carte graphique, carte son)
sur les premiers PC ;
2. le bus PCI, une évolution d’ISA lui-même récemment remplacé par PCI express ;
3. le bus AGP, dédié aux cartes graphiques.
M8N
21. USB signifie d’ailleurs Universal Serial Bus, ou bus série universel
100
Deuxième partie
101
102
4. Leçons 7 à 9 – Niveau 0 : portes
logiques
Dans ce chapitre, nous allons aborder l’explication du fonctionnement d’un ordinateur
(alors que les chapitres précédents étaient d’avantage descriptifs). Pour ce faire, nous allons
considérer le niveau le plus bas de la Figure 1.5, c’est-à-dire le niveau matériel : les portes
logiques.
Les portes logiques sont les éléments de calcul les plus simples que nous considérerons
dans ce cours. Elles permettent de calculer des opérations très élémentaires, appelées opéra-
tions logiques. Elles peuvent être réalisées à l’aide de transistors, ce qui, étant donné la tech-
nologie actuelle, permet d’en intégrer un nombre considérable (des millions, des milliards)
dans des circuits électroniques de petite dimension (environ la surface de l’ongle d’un pouce
pour un processeur moderne).
Néanmoins, ces opérations très simples que sont les opérations logiques sont suffisantes
pour réaliser des opérations plus complexes. Ainsi, dans ce chapitre, nous verrons comment
réaliser un circuit qui effectue l’addition de deux nombres (exprimés en binaire, voir Cha-
pitre 2), comment réaliser une ALU, comment réaliser une mémoire. . . uniquement à l’aide
de portes logiques.
103
4. Leçons 7 à 9 – Niveau 0 : portes logiques
expressions de l’algèbre Booléenne ne pourront prendre qu’une de ces deux valeurs (contrai-
rement à l’algèbre à laquelle nous avons été habituées depuis l’école secondaire, ou les va-
riables prennent des valeurs entières, réelles. . . selon le contexte).
Les opérateurs qui permettent de combiner ces valeurs sont aussi propres à l’algèbre Boo-
léenne, et ils reflètent les connecteurs logiques que nous utilisons dans les langues naturelles :
et, ou, non,. . .
Tables de vérité Afin d’expliquer de manière non-ambiguë quel est le sens de chacun des
opérateurs (ainsi que des expressions Booléennes plus complexes), on utilise des tables de vé-
rité. Ces tables indiquent, pour chacune des combinaisons des valeurs des variables, quelle
est la valeur de l’opérateur ou de l’expression considérée. De ce fait, une table de vérité pour
une expression qui a n variables d’entrée aura nécessairement 2n lignes (le nombre de com-
binaisons de valeurs Booléennes pour n variables).
ß
Voici un exemple de table de vérité, pour deux variables a et b d’entrée, et qui
nous indique la valeur d’une fonction f (a, b) de ces deux variables. Cette table
a bien 22 = 4 lignes.
a b f (a, b)
0 0 0
0 1 1
1 0 0
1 1 1
Cette table nous indique que, quand a = 0 et b = 1, par exemple, la sortie doit
être 1 ; alors qu’elle doit être 0 si a = b = 0, etc.
a b a ∧b
0 0 0
0 1 0
1 0 0
1 1 1
3. C’est-à-dire qu’il s’applique à deux valeurs, comme les +, par exemple. Ces deux valeurs sont appelées les
opérandes
104
4.1. L’algèbre Booléenne
a b a ∨b
0 0 0
0 1 1
1 0 1
1 1 1
a b a XOR b
0 0 0
0 1 1
1 0 1
1 1 0
L’opérateur non Enfin, l’opérateur de négation est un opérateur unaire 4 qui inverse la
valeur de son opérande. On le note ¬ :
a ¬a
0 1
1 0
Autres opérateurs Sur base de ces opérateurs, on peut en construire d’autres, notamment
le NOR et le NAND que l’on définit ainsi :
a NOR b = ¬(a ∨ b)
a NAND b = ¬(a ∧ b)
Un NOR est donc bien la négation d’un ou. Prenons garde à ne pas confondre cela avec la
disjonction de la négation des variables, en d’autres termes :
4. C’est-à-dire qu’il n’a qu’un seul opérande comme le − dans −(x + y), par exemple).
105
4. Leçons 7 à 9 – Niveau 0 : portes logiques
a NOR b = 1 NOR 0
= ¬(1 ∨ 0)
= ¬1
= 0,
alors que :
¬a ∨ ¬b = (¬1) ∨ (¬0)
= 0∨1
= 1.
De même pour le NAND. On peut par contre appliquer les lois de D E M ORGAN, dont nous
parlerons plus tard aux équations (4.15) and (4.16).
Priorité des opérateurs Comme dans l’algèbre « classique », on peut utiliser des paren-
thèses pour fixer l’ordre dans lequel les opérateurs doivent être évalués, et il existe une prio-
rité fixe des opérateurs, à savoir, du moins au plus prioritaire :
∨
, ∧ , ¬.
XOR
ß
Par exemple, l’expression :
x ∨ ¬y ∧ z
se comprend et s’évalue comme :
¡ ¢
x ∨ (¬y) ∧ z
x ∧1 = x (4.8)
x ∧0 = 0 (4.9)
x ∨1 = 1 (4.10)
x ∨ 0 = x. (4.11)
En d’autres termes, 1 est neutre pour l’opérateur ∧ ; 0 est absorbant pour l’opérateur ∧ ; 1 est
absorbant pour l’opérateur ∨ ; et 0 est neutre pour l’opérateur ∨.
106
4.1. L’algèbre Booléenne
x ∧ ¬x = 0 (4.12)
x ∨ ¬x = 1. (4.13)
Intuitivement, la partie gauche de l’équation (4.12) demande que x soit à la fois vraie (va-
leur 1) et fausse (valeur 0), ce qui n’est pas possible (le résultat est donc 0). De même, la partie
gauche de l’équation (4.13) demande que x soit soit vraie soit fausse, ce qui est toujours vrai
(le résultat est donc 1).
De plus, l’algèbre Booléenne jouit également d’une loi de distributivité. Ainsi, pour toutes
expressions φ1 , φ2 et φ3 , on a :
Des expressions Booléennes aux tables de vérité Il est parfois utile, étant donné une ex-
pression Booléenne, de donner sa table de vérité in extenso. Pour ce faire, on peut construire
une table de vérité qui contient une colonne pour chaque étape du calcul de l’expression, en
suivant l’ordre de priorité des opérateurs.
5. D’après le mathématicien britannique Augustus D E M ORGAN (27 juin 1806 à Madurai (Tamil Nadu) – 18
mars 1871).
107
4. Leçons 7 à 9 – Niveau 0 : portes logiques
ß
Par exemple, considérons à nouveau l’expression
x ∨ ¬y ∧ z.
Des tables de vérité aux expressions Booléennes Dans bien des cas, il est utile de pou-
voir traduire une table de vérité en expression Booléenne. C’est ce que nous ferons de ma-
nière régulière dans ce chapitre quand nous voudrons concevoir un circuit logique. Nous
commencerons par écrire la table de vérité du circuit, qui spécifiera ce qu’on attend de lui,
puis nous transformerons cette table en expression qu’il sera enfin facile de traduire en cir-
cuit logique. Pour ce faire, il existe une technique systématique. Commençons par regarder
un exemple :
ß
Considérons la table de vérité :
x y z s
0 0 0 0
0 0 1 1
0 1 0 0
0 1 1 0
1 0 0 0
1 0 1 1
1 1 0 0
1 1 1 1
../.
108
4.1. L’algèbre Booléenne
Nous voyons que la sortie s est vraie (elle vaut 1) dans exactement trois cas :
... 1. soit quand x = 0 et y = 0 et z = 1 ;
2. soit quand x = 1 et y = 0 et z = 1 ;
3. soit quand x = 1 et y = 1 et z = 1.
Cette formulation suggère une expression Booléenne obtenue comme suit. Tout
d’abord, on traite chacun des trois cas ci-dessus séparément, en écrivant, pour
chacune des valuations des trois variables, une expression qui n’est vraie que
pour ces valuations :
1. l’expression ¬x ∧ ¬y ∧ z n’est vraie que si x = 0, y = 0 et z = 1 ;
2. l’expression x ∧ ¬y ∧ z n’est vraie que si x = 1, y = 0 et z = 1 ;
3. l’expression x ∧ y ∧ z n’est vraie que si x = 1, y = 1 et z = 1.
Ensuite, comme la formule doit être vraie dans un de ces trois cas, et unique-
ment dans un de ces trois cas, on peut prendre la disjonction des trois formules :
¡ ¢ ¡ ¢ ¡
s = ¬x ∧ ¬y ∧ z ∨ x ∧ ¬y ∧ z ∨ x ∧ y ∧ z).
On peut vérifier que cette formule correspond à la table de vérité : pour chaque
valuation d’x, y et z, la formule vaut 0 dans tous les cas où s = 0 selon la table, et
la formule vaut 1 dans tous les cas où s = 1 selon la table.
La méthode systématique pour extraire une expression d’une table de vérité (sur
les variables x 1 , x 2 , . . . x n ) peut donc être résumée comme ceci :
1. Pour chacune des lignes i où le résultat de la table est 1, on construit une
formule φi comme suit :
a) Pour chaque variable x, on construit l’expression αix , qui vaut x si la
valeur de x dans la ligne i est 1 ; et qui vaut ¬x sinon.
b) On construit alors φi = αix1 ∧ αix2 ∧ · · · ∧ αixn .
2. On prend la disjonction ( ou) de toutes les formules φi correspondant aux
lignes où la sortie vaut 1.
ß
En continuant l’exemple ci-dessus, on a donc :
1. pour la ligne 2 : α2x = ¬x, α2y = ¬y et α2z = z. Donc, φ2 = ¬x ∧ ¬y ∧ z ;
2. pour la ligne 6 : α6x = x, α6y = ¬y et α6z = z. Donc, φ6 = x ∧ ¬y ∧ z ;
3. pour la ligne 8 : α8x = x, α8y = y et α8 − z = z. Donc φ8 = x ∧ y ∧ z. ../.
109
4. Leçons 7 à 9 – Niveau 0 : portes logiques
(¬x ∧ ¬y ∧ z) ∨ (x ∧ ¬y ∧ z) ∨ (x ∧ y ∧ z)
¡ ¢
= (¬x ∨ x) ∧ (¬y ∧ z) ∨ (x ∧ y ∧ z) Par (4.14),
¡ ¢
= 1 ∧ (¬y ∧ z) ∨ (x ∧ y ∧ z) Par (4.13),
= (¬y ∧ z) ∨ (x ∧ y ∧ z) Par (4.8).
110
4.2. Les circuits logiques
différentes formes (voir Figure 4.2), selon la fonction qu’ils doivent réaliser, mais on est au-
jourd’hui capable de les miniaturiser énormément (les processeurs actuels ont de l’ordre de
plusieurs milliards de transistor par mm2 ).
En combinant deux transistors, on peut réaliser les portes logiques NAND et NOR. Ces
portes peuvent ensuite être utilisées pour réaliser d’autres portes logiques. En effet, on peut
observer que :
¬x = x NAND x,
x ∧ y = ¬(x NAND y)
= (x NAND y) NAND(x NAND y),
¡ ¢
x ∨ y = ¬ (¬x) ∧ (¬y) par (4.15),
= (¬x) NAND(¬y)
= (x NAND x) NAND(y NAND y).
On peut donc exprimer les portes logiques « habituelles » en termes de NAND uniquement.
Un raisonnement similaire permet la même conclusion pour le NOR. On peut légitimement
se demander s’il est vraiment utile de remplacer une seule porte ou par trois portes NAND,
comme suggéré ci-dessus. Cette façon de faire est motivée par des considérations pratiques :
en ramenant tout à des portes NAND (ou NOR), on simplifie la fabrication des circuits in-
tégrés. Les machines qui les fabriquent ne doivent réaliser qu’un seul type de porte logique,
toutes les autres sont obtenues par assemblage.
Aspects historiques Notons pour terminer que dans les premiers ordinateurs (avant les
années 1950), les transistors étaient remplacés par des tubes à vide, comme sur la Figure 4.3.
Le principe étaient grosso modo le même, mais les tubes avaient le désavantage de chauffer
(ils devaient d’ailleurs être suffisamment chauds pour fonctionner correctement), de consom-
mer beaucoup de courant, d’être fragiles et d’occuper énormément de place. La fiabilité et la
consommation électrique des tubes à vide étaient un véritable problème sur les premiers or-
dinateurs. Par exemple, l’ENIAC utilisait 17 466 tubes à vide, et tombait en panne en raison
d’un tube défectueux en moyenne tous les deux jours. Cet ordinateur consommait 150 kW 6 ,
dont 80 étaient utilisés uniquement pour chauffer les tubes [28].
Les transistors ont été inventés en 1947 par John B ARDEEN 7 , Walter Houser B RATTAIN 8 ,
et William S HOCKLEY 9 aux Bell Labs 10 . Ils ont reçu le prix N OBEL de physique pour leurs tra-
6. Par comparaison, un ordinateur de bureau moderne consomme typiquement quelques centaines de Watts.
L’alimentation électrique de l’ordinateur portable utilisé pour écrire ces notes consomme 87 W. Tout cela pour
une puissance de calcul largement supérieure à celle de l’ENIAC
7. Physicien américain, né le 23 mai 1908 et mort le 30 janvier 1991.
8. Physicien américain, né le 10 février 1902 et mort le 13 octobre 1987.
9. Physicien américain, né le 13 février 1910 et mort le 12 août 1989.
10. Entreprise américaine (faisant aujourd’hui partie de Nokia) de recherche et de développement industriel
dans les domaines des télécommunication et de l’informatique. Elle a été fondée en 1925 et a eu une influence
considérable en développant une série de technologies comme le LASER, les capteurs CCD, le système d’exploi-
tation Unix, les langages de programmation C et C++, etc.
111
4. Leçons 7 à 9 – Niveau 0 : portes logiques
Source : Shieldforyoureyes Dave Fischer / Retro-Computing Society of Rhode Island Native, Established 1994. Website http://rcsri.org. Authority control : Q18857750
(https://commons.wikimedia.org/wiki/File:Ibm-tube.jpg), « Ibm-tube », https://creativecommons.org/licenses/by-sa/3.0/legalcode.
vaux, en 1972. Si les premiers transistors représentaient un progrès considérables par rapport
aux tubes à vide (du point de vue de la consommation électrique et de la taille) ils restaient
malgré tout relativement encombrants car un seul transistor occupait typiquement un vo-
lume d’une centaine de mm3 . Ce n’est qu’au début des années 1960 que les premiers circuits
intégrés ont pu être réalisés. L’histoire retient la date du 27 septembre 1960 pour la réalisation
du premier circuit intégré à base de semi-conducteurs, par Fairchilds Semiconductors, une
entreprise américaine. Cette réalisation est basée sur les idées et techniques développées par
plusieurs chercheurs et ingénieurs américains dans les années qui précèdent, notamment
Jack K ILBY 11 , Kurt L EHOVEC 12 Robert N OYCE 13 et Jean Amédée H OERNI 14 .
11. Né le 8 novembre 1923, mort le 20 juin 2005, ingénieur américain. Il a travaillé pour Texas Instruments et
est le récipiendaire du prix Nobel de physique en 2000 pour ses travaux sur les circuits intégrés.
12. Né le 12 juin 2918 à Ledvice en République Tchèque, et mort le 17 février 2012 en Californie, aux États-
Unis d’Amérique. Il a été professeur à l’Université de Californie du Sud, à Los Angeles, et a travaillé pour Sprague
Electric.
13. Né le 12 décembre 1927, mort le 3 juin 1990, physicien américain. Il a co-fondé Fairchild Semiconductors
et Intel Corporation.
14. Né le 26 septembre 1924 à Génève, en Suisse et mort le 12 janvier 1997 à Washington, aux États-Unis d’Amé-
rique. Deux fois docteur en physique (Université de Genève et de Cambridge, Royaume-Uni). Il est un des fonda-
teurs de Fairchild Semiconductors.
15. Pour rappel : 1nm vaut 1 × 10−9 m ou un millionième de mm.
112
4.2. Les circuits logiques
riteraient un voir plusieurs cours à eux seuls), nous pouvons citer deux grandes techniques
contemporaines de réalisation des circuits (logiques) intégrés :
1. L’Intégration à Très Grande Échelle, Very Large Scale Integration ou VLSI. Ce terme dé-
signe la technologie qui permet de concevoir et de produire des circuits intégrés conte-
nant de plusieurs milliers à plusieurs milliards de composants électroniques dans une
seule puce, sur base du placement des unités de base que sont les transistors. Le concep-
teur d’un circuit VLSI doit donc décider (à l’aide d’outils logiciels) du placement de
chaque transistor individuel et de leurs connexions. C’est une tâche fort complexe et
longue, car il faut tenir compte de nombreux aspects, notamment des questions de
dissipation de chaleur. Une fois ce travail effectué, un logiciel produit une série de
masques, qui permettent ensuite « d’imprimer » les circuits de manière très efficace.
Il s’agit donc d’une technologie où le temps à la conception du circuit est considérable,
mais permettant de produire les circuits en grande quantité et à très faible coût.
2. Les Réseaux Logiques Programmables, Field-Programmable Gate Arrays ou FPGA. Un
FPGA est un circuit contenant un nombre important de portes logiques diverses (voire
de circuits logiques de base, comme les additionneurs que nous allons voir plus tard)
que l’on peut configurer après sa production (d’où le terme « on the field », autrement
dit : « sur le terrain »). En changeant la configuration (qui est typiquement stockée dans
une mémoire modifiable du circuit), on change donc la fonction logique que le circuit
calcule.
Jusqu’il y a peu, les FPGA étaient essentiellement des outils de prototypage très pra-
tiques : ils permettent d’obtenir facilement un circuit logique fonctionnel pour les tes-
ter (mais celui-ci sera probablement bien moins efficace en terme de temps de réaction
ou de consommation d’énergie qu’un VLSI qui a été soigneusement conçu). Depuis
peu, les FPGA ont vu un regain d’intérêt, et sont maintenant intégrés à l’intérieur de
certains microprocesseurs 16 [2]. L’intérêt de cette approche réside sur le fait que le hard-
ware est, en règle générale, plus rapide que le software : pour réaliser une certaine tâche
de calcul, il vaut mieux disposer d’un circuit logique qui réalise cette tâche (mais ne réa-
lise alors que celle-là), plutôt que de faire exécuter par un CPU un programme qui réa-
lise le même calcul. Ceci est vrai typiquement pour des calculs complexes qu’on trouve
dans certaines applications cryptographiques 17 , ou pour le traitement d’images. Avec
ces systèmes hybrides CPU-FPGA, on pourrait imaginer que le processeur puisse se
décharger régulièrement sur le FPGA de tâches de calcul complexe, tâches qui peuvent
être modifiées en re-configurant le FPGA en fonction de l’application exécutée.
113
4. Leçons 7 à 9 – Niveau 0 : portes logiques
Maintenant que nous avons à notre disposition une série d’outils pour manipuler les for-
mules Booléeennes et les tables de vérité, nous allons mettre ces connaissances en pratique
et concevoir des circuits qui sont nécessaires à la réalisation d’un ordinateur. Nous commen-
cerons par des circuits qui permettent de manipuler l’information (en binaire) et d’effectuer
des calculs ; ils apparaîtront dans les processeurs. Ensuite, nous considérerons des circuits qui
permettent de réaliser des mémoires.
Durant le cours, nous utiliserons régulièrement l’outil LogiSim-evolution, un simulateur de
circuits logiques, distribué comme logiciel libre, et compatible tant avec les PC sous Windows
et Linux qu’avec les Macs. On peut le télécharger à l’adresse : https://github.com/logis
im-evolution/logisim-evolution.
114
4.3. Circuits pour réaliser l’arithmétique binaire
nombre n de bits pour représenter ces nombres, et réaliser des circuits qui ont, au moins k ×n
entrées (s’il y a k opérandes à l’opération, par exemple k = 2 pour l’addition) et au moins ℓ×n
sorties (si l’opération produit ℓ résultats, par exemple ℓ = 1 pour l’addition)
115
4. Leçons 7 à 9 – Niveau 0 : portes logiques
a
s
r
b
4.3.2. Demi-additionneur
Un demi-additionneur réalise la somme de deux bits a et b et produit deux sorties : la
somme s (bit de poids faible) et le report r (bit de poids fort). La table de vérité est la suivante,
elle représente l’effet de l’addition :
a b r s
0 0 0 0
0 1 0 1
1 0 0 1
1 1 1 0
Partant de cette table, on peut facilement reconnaître que les sorties s et r correspondent à
des portes logiques de base, à savoir :
— s = a XOR b ; et
— r = a ∧ b.
On en déduit le circuit de la Figure 4.4.
Dans nos circuits, nous ajouterons des • quand deux traits sont connectés (l’ab-
sence de • indique donc qu’il n’y a pas de connexion logique, il s’agit simple-
ment d’un croisement sur le schéma.
Additionneur Comme expliqué ci-dessus, nous avons également besoin d’un circuit qui
réalise la somme de 3 bits (au lieu de 2 dans le cas du demi-additionneur). Il aura donc trois
entrées : a, b, r pr ec (le report provenant de la colonne précédente) et on a toujours deux
sorties : s (bit de poids faible de la somme des trois entrées), et r (la valeur à reporter pour
l’addition suivante, c’est-à-dire le bit de poids fort de la somme des trois entrées).
Ce circuit est donné à la Figure 4.5. Pour l’obtenir, on peut observer que faire la somme
de trois bits revient à faire la somme de deux d’entre eux (disons, a et b), puis à faire la
somme du résultat avec le troisième (r pr ec ). Ces deux sommes peuvent être réalisées à l’aide
de deux demi-additionneurs : les deux entrées du premier demi-additionneur sont les en-
trées a et b ; et les deux entrées du second demi-additionneur sont : la sortie du premier
demi-additionneur et le bit r pr ec .
116
4.3. Circuits pour réaliser l’arithmétique binaire
a
s
r1 r2
b
r pr ec
r
Il reste à expliquer comment combiner les deux reports (notés r 1 et r 2 sur la figure) qui
émanent de ces deux sommes en un seul (puisque notre additionneur complet n’a qu’un
seul report en sortie). Il est clair que si r 1 = r 2 = 0, nous aurons également r = 0. Si r 1 = 1 et
r 2 = 0 ou bien si r 1 = 0 et r 2 = 1, le report r sera égal à 1 (c’est la somme des deux reports). Le
cas r 1 = r 2 = 1 semble plus problématique car la somme de ces deux reports tient maintenant
sur 2 bits. Néanmoins, on peut facilement se convaincre que ce cas ne se présentera jamais.
En effet, en consultant la table du demi-additionneur ci-dessus, on voit que r 1 = 1 ne peut
avoir lieu que si a = b = 1, auquel cas la sortie du premier XOR est égale à a XOR b = 0. De ce
fait, r 2 = 0 également, car r 2 = (a XOR b)∧r pr ec = 0∧r pr ec = 0. On conclut qu’il faut combiner
r 1 et r 2 à l’aide d’une porte qui renvoie 0 si r 1 = r 2 = 0, et 1 si un des deux reports vaut 1. Un
ou correspond à cette spécification (un XOR également).
Ce circuit peut également être obtenu en suivant une méthode plus « classique » : com-
mencer par construire la table de vérité, puis en extraire les formules pour s et r qu’on peut
simplifier pour obtenir le même résultat. La table est la suivante :
a b r pr ec r s
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 0 1
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1
117
4. Leçons 7 à 9 – Niveau 0 : portes logiques
a3 a2 a1 a0
b3 b2 b1 b0
+ + + +
r r pr ec r r pr ec r r pr ec r r pr ec
0
s4 s3 s2 s1 s0
On a donc :
s = (¬a ∧ ¬b ∧ r pr ec ) ∨ (¬a ∧ b ∧ ¬r pr ec ) ∨ (a ∧ ¬b ∧ ¬r pr ec ) ∨ (a ∧ b ∧ r pr ec )
³¡ ¢ ´ ³¡ ¢ ´
= (¬a ∧ ¬b) ∨ (a ∧ b) ∧ r pr ec ∨ (¬a ∧ b) ∨ (a ∧ ¬b) ∧ ¬r pr ec
³¡ ¢ ´ ³ ´
= (¬a ∧ ¬b) ∨ (a ∧ b) ∧ r pr ec ∨ a XOR b ∧ ¬r pr ec
³ ¡ ¢ ´ ³ ´
= ¬ a XOR b ∧ r pr ec ∨ a XOR b ∧ ¬r pr ec
= a XOR b XOR r pr ec .
Pour r on a :
r = (¬a ∧ b ∧ r pr ec ) ∨ (a ∧ ¬b ∧ r pr ec ) ∨ (a ∧ b ∧ ¬r pr ec ) ∨ (a ∧ b ∧ r pr ec )
¡ ¢
= (¬a ∧ b ∧ r pr ec ) ∨ (a ∧ ¬b ∧ r pr ec ) ∨ (a ∧ b ∧ (r pr ec ∨ ¬r pr ec )
= (¬a ∧ b ∧ r pr ec ) ∨ (a ∧ ¬b ∧ r pr ec ) ∨ (a ∧ b)
³ ¡ ¢´
= r pr ec ∧ (¬a ∧ b) ∨ (a ∧ ¬b) ∨ (a ∧ b)
= (r pr ec ∧ a XOR b) ∨ (a ∧ b)
118
4.3. Circuits pour réaliser l’arithmétique binaire
4.3.3. Décalage
Le circuit de décalage doit réaliser un décalage d’un bit soit vers la droite soit vers la gauche,
pour obtenir respectivement une division par 2 ou une multiplication par 2. Pour l’exemple,
nous nous contenterons de nombres de 3 bits. Ce circuit aura donc 4 entrées :
— les entrées D 2 , D 1 , D 0 qui sont les trois bits du nombre à décaler. D 2 est le bit de poids
fort et donc « le plus à gauche » ; et
— une entrée C qui indique dans quel sens effectuer le décalage : vers la droite si C vaut 1,
vers la gauche si C vaut 0.
Ce circuit aura 3 sorties, à savoir les 3 bits S 0 , S 1 , S 2 du nombre en sortie (à nouveau, S 2 est le
bit de poids fort et donc « le plus à gauche ». Si le décalage a lieu vers la droite, on aura S 2 = 0 ;
et si le décalage a lieu vers la gauche, on aura S 0 = 0.
En d’autres termes, les sorties du circuit de décalage doivent se comporter comme suit :
On voit donc que les sorties ne dépendent pas toutes des 4 entrées. Plus précisément :
C D1 S0
0 0 0
0 1 0
1 0 0
1 1 1
C D1 S2
0 0 0
0 1 1
1 0 0
1 1 0
— Finalement, le cas de S 1 est un peu plus complexe, puisqu’elle doit recopier soit la va-
leur de D 0 , soit la valeur de D 2 , en fonction de C . On a donc la table de vérité suivante :
119
4. Leçons 7 à 9 – Niveau 0 : portes logiques
D2 D1 D0
S2 S1 S0
C D0 D2 S1
0 0 0 0
0 0 1 0
0 1 0 1
0 1 1 1
1 0 0 0
1 0 1 1
1 1 0 0
1 1 1 1
S 1 = (¬C ∧ D 0 ∧ ¬D 2 ) ∨ (¬C ∧ D 0 ∧ D 2 ) ∨ (C ∧ ¬D 0 ∧ D 2 ) ∨ (C ∧ D 0 ∧ D 2 )
¡ ¢ ¡ ¢
= ¬C ∧ D 0 ∧ (¬D 2 ∨ D 2 ) ∨ C ∧ D 2 ∧ (D 0 ∨ ¬D 0 )
= (¬C ∧ D 0 ) ∨ (C ∧ D 2 )
On voit bien que C sert à sélectionner la valeur d’entrée qui détermine la valeur de S 1 .
Si C = 0, la parenthèse (C ∧ D 2 ) vaudra 0 (cfr. équation (4.9)) et la parenthèse (¬C ∧ D 0 )
vaudra (1 ∧ D 0 ) = D 0 (cfr. équation (4.8)). L’expression vaudra dès lors 0 ∨ D 0 = D 0 (cfr.
équation (4.11)). Symétriquement, quand C = 1, l’expression ci-dessus vaut D 2 .
Le circuit est donné à la Figure 4.7.
Extensions Sur base de ce circuit, on peut aisément généraliser à des entrées de plus de 3
bits, et à un décalage de plus d’une position. Si on a maintenant n bits en entrée et en sortie,
120
4.3. Circuits pour réaliser l’arithmétique binaire
S0 = C ∧ D1
S n−1 = ¬C ∧ D n−2
S i = (¬C ∧ D i −1 ) ∨ (C ∧ D i +1 ) pour 1 ≤ i ≤ n − 2
S i = C ∧ D i +K pour 0 ≤ i ≤ K − 1
S i = ¬C ∧ D i −K pour n − K ≤ i ≤ n − 1
S i = (¬C ∧ D i −K ) ∨ (C ∧ D i +K ) pour K ≤ i ≤ n − K − 1
4.3.4. Décodeur
Un décodeur n bits est un circuit qui possède n entrées E 0 , . . . E n−1 et 2n sorties S 0 , . . . S 2n −1 ,
et qui met la sortie S i à 1 si et seulement si i est la valeur E n−1 · · · E 0 représentée en binaire
sur les entrées. En ce sens, le décodeur « décode » l’information donnée en binaire sur ses
entrées, et permet donc de transmettre un choix parmi 2n en n’utilisant que n entrées. Un tel
circuit a de nombreuses applications, par exemple :
1. pour sélectionner une case dans une mémoire de 4 Go (soit 232 cases d’un octet), on
peut se contenter de transmettre à la mémoire l’adresse de cette case, sur 32 bits 20 .
2. Un affichage sept segments, comme on en trouve, par exemple, sur les réveils électro-
niques (voir Figure 4.9) permet d’afficher 10 chiffres différents (de 0 à 9). Ces circuits
ont généralement 4 entrées, car 4 bits sont suffisants pour représenter les chiffres de
0 à 9 : la valeur à afficher est transmise au circuit en binaire, la valeur passe à travers
un décodeur, et chaque sortie S i de celui-ci est connectée aux segments permettant
d’afficher la valeur i .
La table de vérité d’un décodeur 4 bits est la suivante :
E1 E0 S0 S1 S2 S3
0 0 1 0 0 0
0 1 0 1 0 0
1 0 0 0 1 0
1 1 0 0 0 1
S 0 = ¬E 1 ∧ ¬E 0
S 1 = ¬E 1 ∧ E 0
S 2 = E 1 ∧ ¬E 0
S3 = E1 ∧ E0
121
4. Leçons 7 à 9 – Niveau 0 : portes logiques
E0 S0
E1 S1
S2
S3
F IGURE 4.9. – La valeur 102 sur trois afficheurs 7 segments : on peut se contenter d’avoir 4
bits en entrée de chaque chiffre.
où :
(
Ej Si le j ème bit de la valeur i en binaire vaut 1.
αj =
¬E j Sinon.
4.3.5. Le « sélecteur »
Ce que nous appelons sélecteur n’est pas vraiment un circuit en soi, mais plutôt une construc-
tion qui sera utile pour la suite. Le problème posé est le suivant : on souhaite un (morceau
de) circuit qui possède :
1. n entrées appelées I n−1 ,. . . , I 0 ; et
20. De toute manière, il ne serait pas possible, physiquement, d’avoir plus de 4 milliards d’entrées sur un circuit
de mémoire !
122
4.3. Circuits pour réaliser l’arithmétique binaire
Le circuit du sélecteur est donné à la Figure 4.10. Comme on le voit, on commence par
utiliser un décodeur (voir Figure 4.8) pour décoder les entrées I 0 , I 1 ,. . . , I n−1 . Il y a donc, à la
sortie du décodeur, une sortie que nous appelons S i correspondant à chaque entrée E i (pour
0 ≤ i ≤ 2n −1). N’oublions pas qu’une et une seule de ces sorties S i sera mise à 1 ! On combine
ensuite, à l’aide d’une porte et, chacune des S i avec l’entrée E i correspondante. Supposons
que c’est la valeur k qui est donnée en binaire sur I n−1 · · · I 0 , on a alors le phénomène suivant :
— S k = 1 (c’est une propriété du décodeur), et la sortie de la porte et qui combine S k et
E k vaut donc :
Sk ∧ Ek = 1 ∧ Ek
= Ek par (4.8) ;
Sj ∧Ej = 0∧Ej
=0 par (4.9).
On voit donc que toutes les portes et ont leur sortie à 0, sauf potentiellement celle qui calcule
S k ∧ E k = E k . La sortie de la porte ou, qui donne sa valeur à la sortie du circuit, sera donc :
(S 0 ∧ E 0 ) ∨ (S 1 ∧ S 1 ) ∨ · · · (S k ∧ E k ) ∨ · · · ∨ (S n−1 ∧ E n−1 ) = 0 ∨ · · · ∨ E k ∨ · · · ∨ 0
= Ek
Le circuit présenté à la Figure 4.10 réalise donc bien ce qui était attendu.
123
4. Leçons 7 à 9 – Niveau 0 : portes logiques
E0
E1
S
···
E 2n −1
I0
I1
Décodeur
.. n bits
.
I n−1
124
4.3. Circuits pour réaliser l’arithmétique binaire
TABLE 4.1. – Les 4 opérations de notre exemple d’ALU, ainsi que les entrées qui y corres-
pondent.
F1 F0 opération
0 0 et
0 1 ou
1 0 négation de B
1 1 somme de A, B et du report
que dans le cas où c’est « non B » qui est choisie comme opération, l’entrée A n’aura pas
d’influence sur la sortie, et que l’entrée r pr ec n’est utilisée que pour l’addition (avec les autres
opérations, elle n’aura, elle non plus, aucune influence sur la sortie). Notre ALU aura donc 5
bits d’entrée :
— 3 bits de données, à savoir : 1 bit pour l’entrée A, 1 bit pour l’entrée B et 1 bit de report
précédent r pr ec (nécessaire pour l’opération d’addition) ; ainsi que
— les deux bits F 0 et F 1 pour spécifier l’opération à appliquer sur A, B et, le cas échéant,
r pr ec .
Notre ALU aura également 2 bits de sortie :
— la valeur calculée S ; et
— le nouveau report r sui v , dans le cas où l’opération choisie est l’addition (autrement, la
valeur sur cette sortie ne sera pas significative).
Pour obtenir l’ALU, nous repartons du circuit du sélecteur (Figure 4.10) que nous modifions
comme suit :
1. nous l’équipons d’un décodeur 2 bits dont les entrées sont F 0 et F 1 ;
2. nous ajoutons une porte et, une porte ou, une porte non et un additionneur (Fi-
gure 4.5) pour calculer les 4 opérations demandées ; et
3. nous remplaçons les entrées E 0 ,. . . E 3 du sélecteur par les sorties respectives de ces
portes et de ce circuit (selon la table 4.1).
Le circuit qui en résulte est donné à la Figure 4.11.
125
4. Leçons 7 à 9 – Niveau 0 : portes logiques
r pr ec
S
B
r sui v
F0
F1
126
4.3. Circuits pour réaliser l’arithmétique binaire
A B
F0
F1
r pr ec ALU r sui v
Nous pouvons maintenant expliquer comment réaliser une ALU n bits à l’aide de n ALU
1 bit (telles que décrites à la Figure 4.11). L’idée sera similaire à celle que nous avons exploitée
pour réaliser un additionneur n bits sur base de n additionneurs 1 bit (voir Figure 4.6). Com-
mençons par fixer une représentation pour une ALU 1 bit : nous utiliserons la Figure 4.12, où
on retrouve les différentes entrées et sorties de la Figure 4.11.
En utilisant cette convention, le schéma de l’ALU 4 bits (qui peut être généralisé à toute
taille n des entrées) est donnée à la Figure 4.13. Elle consiste en 4 ALUs 1 bit ALU0 , ALU1 , ALU2
et ALU3 disposées en série. Attention, il faut bien noter que les bits de poids faibles sont cette
21. Nous continuons à considérer une ALU à 4 opérations. Il est bien entendu possible d’étendre le nombre
d’opérations possibles : cela demandera potentiellement plus de bits d’entrée et un décodeur adapté.
127
4. Leçons 7 à 9 – Niveau 0 : portes logiques
B0 B1 B2 B3
A0 A1 A2 A3
F0
F1
S0 S1 S2 S3 r sui v
F IGURE 4.13. – Une ALU 4 bits réalisée sur base de 4 ALUs 1 bit. Notez que le bit de poids
faible est à gauche et non pas à droite.
fois-ci à gauche du schéma et non pas à droite comme d’habitude. Pour tout i , l’ALU numéro
i se charge de calculer S i (et r sui v quand i = 3) sur base de A i et B i , et du report précédent
calculé par l’ALU numéro i − 1 (ou r pr ec si i = 0). À noter que toutes les ALUs appliquent la
même opération sur les bits d’entrée car les entrées F 0 et F 1 sont communes.
Remarque concernant les reports On peut se demander quel est l’usage des reports d’en-
trée et de sortie (r pr ec et r sui v ) dans une ALU, à partir du moment où les registres ont une
taille fixe. Par exemple, sur le processeur i486, les registres ont une taille de 32 bits, ce qui sera
également la taille des entrées A et B de l’ALU. Dans ce cas, si la somme des deux nombres
données en entrée requiert 33 bits, nous ne serons de toute manière pas en mesure de la
stocker dans un des registres du CPU ; quel sera dès lors le sort du bit r sui v ?
Une pratique courante (et c’est le cas du processeur i486) consiste à stocker ce bit dans le
registre des flags du processeur, où il est appelé carry flag. Il pourra ainsi être réutilisé pour
une addition suivante (comme valeur de r pr ec ).
Dans le cas du i486, par exemple, il existe 2 opérations d’addition :
— l’instruction add qui ajoute le contenu d’un registre source à un registre destination ; et
— l’instruction adc qui ajoute le contenu d’un registre source et le contenu du carry flag
à un registre destination.
Cela permet, par exemple, de réaliser en deux opérations la somme de deux nombres de 64
bits, bien que l’i486 soit un processeur 32 bits. Supposons que les deux nombres à additionner
sont dans les registres eax :ebx et ecx :edx, c’est-à-dire que les 32 bits de poids forts sont
respectivement dans eax et ecx, et les 32 bits de poids faibles dans ebx et edx. On commence
par faire la somme des bits de poids faibles à l’aide de l’instruction add, par exemple :
128
4.4. Circuits pour réaliser des mémoires
Le résultat de l’addition sur 64 bits se trouve donc dans eax :ebx (avec un éventuel 65e bit de
poids fort dans le carry flag).
Q = 1, Q = 0
Q = 0, Q = 1.
22. À vrai dire, le seul état où a = 1 et b = 1 dans ce circuit est un état où s = 0 et r = 1. Dans un circuit sans
boucle, il n’y aura jamais qu’un seul état qui corresponde à des entrées données.
129
4. Leçons 7 à 9 – Niveau 0 : portes logiques
Q Q
F IGURE 4.14. – Une boucle de portes non qui permet de stocker une valeur binaire.
Q = ¬Q
Q = ¬Q,
et les deux états ci-dessus sont les deux seules solutions qui satisfont ces équations (on peut
facilement vérifier que ni Q = Q = 0, ni Q = Q = 1 ne respectent ces égalités). Nous avons
donc réussi à créer un circuit qui peut être dans deux états différents, et qui peut donc sto-
cker une valeur parmi deux, soit une valeur binaire (même si nous ne pouvons pas encore
modifier cette valeur stockée). On prendra comme convention que la sortie Q indique la va-
leur stockée. Autrement dit, quand Q = 1 et Q = 0, le circuit stocke la valeur 1 ; alors que Q = 0
et Q = 1 correspond au cas où le circuit stocke la valeur 0.
4.4.2. Bascules
Bascule simple Voyons maintenant comment nous pouvons étendre les idées de la sec-
tion précédente pour obtenir un circuit de mémoire fonctionnel, c’est-à-dire dont on puisse
également modifier le contenu. Pour ce faire, nous allons ajouter deux entrées au circuit :
— une entrée S (pour set, en anglais), qui devra forcer l’état du circuit à contenir le bit 1
(autrement dit, qui écrira la valeur 1 dans la mémoire) ; et
— une entrée R (pour reset, en anglais), qui écrira la valeur 0 dans la mémoire.
Il est bien entendu qu’il n’y aura pas de sens à mettre ces deux entrées simultanément à 1. On
peut donc résumer les 4 possibilités comme suit :
S R Effet
0 0 Maintenir la dernière valeur enregistrée
0 1 Écrire 0
1 0 Écrire 1
1 1 Interdit !
Nous garderons les deux sorties Q et Q, qui seront toujours l’inverse l’une de l’autres. Afin
de concevoir ce circuit, nous pouvons nous poser la question suivante :
Dans quel cas la sortie Q doit-elle prendre la valeur 1 ?
130
4.4. Circuits pour réaliser des mémoires
R
Q
Q
S
Étant donné la discussion ci-dessus, on voit que la sortie Q doit avoir la valeur 1 si :
1. Q vaut 0 (car cela signifie que la valeur enregistrée par la mémoire était 1) ; et
2. l’entrée R vaut 0 (car autrement, il faut forcer Q à 0).
On remarquera qu’il n’est pas obligatoire d’avoir S = 1 pour avoir Q = 1. Heureusement ! Au-
trement, cela signifierait qu’il faut toujours maintenir en entrée la valeur à stocker dans la
mémoire, et la mémoire ne « retiendrait » en fait rien, elle se contenterait de recopier sur sa
sortie la valeur qu’on lui indique en entrée. Formellement, nous avons donc :
Q = ¬Q ∧ ¬R
= ¬(Q ∨ R) Par (4.15)
= Q NOR R.
Q = Q NOR R, (4.17)
Q = Q NOR S. (4.18)
Q = Q NOR 0
= ¬(Q ∨ 0)
= ¬Q Par (4.11),
et :
Q = Q NOR 1
= ¬(Q ∨ 1)
= ¬(1) Par (4.10)
= 0.
131
4. Leçons 7 à 9 – Niveau 0 : portes logiques
Q = ¬Q (4.19)
Q = Q NOR 0
= ¬(Q ∨ 0)
= ¬Q,
ce qui est la même équation que (4.19). Il y a deux paires de valeurs pour Q et Q qui
satisfont cette équation, et il y a donc deux états 24 qui correspondent au cas S = R = 0 :
celui où Q = 0 et Q = 1 (la mémoire stocke 0), et celui où Q = 0 et Q = 1 (la mémoire
stocke 1). C’est bien l’effet attendu : quand S = R = 0, il y a deux valeurs possibles pour
les sorties, valeurs qui dépendent du passé. La mémoire stocke donc bien la dernière
valeur.
Utilité d’une horloge Un des inconvénients du circuit que nous venons d’étudier est que
toute modification des entrées S et R a immédiatement un effet sur les valeurs stockées
dans la mémoire. En pratique, cela peut être problématique, car les valeurs injectées aux en-
trées S et R pourraient être le résultat d’un calcul effectué par un autre circuit (une ALU, par
exemple). Or, la propagation des valeurs dans un circuit logique n’est, en réalité, pas instanta-
née. Il se pourrait donc que, durant une fraction de seconde, la valeur entrée de la bascule ne
soit pas correcte, ce qui pourrait mener à l’effacement ou la corruption de la donnée stockée.
Autrement dit, nous devons trouver un mécanisme qui permette d’indiquer à la mémoire à
quel moment une nouvelle donnée doit être enregistrée. Les trois circuits que nous nous ap-
prêtons à présenter sont essentiellement un raffinement de la bascule, auquel nous ajoutons
une entrée appelée horloge (indiquée par ), qui signale, d’une manière ou d’une autre, le
ou les moments où la mémoire doit mémoriser une nouvelle donnée.
Bascule avec horloge La première idée consiste à faire en sorte que la bascule n’enre-
gistre le changement d’état qu’aux seuls moments où l’horloge est à 1. Ainsi, on peut avoir le
schéma de fonctionnement suivant :
23. À condition que R = 0, bien entendu.
24. Ce qui explique le nom de « bistable » : quand S = R = 0, il y a deux états admissibles, c’est-à-dire stables
pour le circuit. Il en va de même avec une bascule (c’est-à-dire cette sorte de balançoire constituée d’une grande
planche en bois placée en équilibre en son centre, et sur laquelle on s’assied de part et d’autre).
132
4.4. Circuits pour réaliser des mémoires
R
Q
Q
S
— pendant que l’horloge est à l’état 25 0, les entrées de la bascule peuvent changer (en
fonction des calculs qui sont effectués par un circuit en amont), sans que cela n’influe
le contenu de la bascule ; puis
— les entrées de la bascule finissent par se stabiliser ; ensuite
— l’horloge passe à l’état 1, la bascule enregistre l’information ; et finalement
— l’horloge repasse à l’état 0 : l’information est « gelée » durant toute cette période.
Ce fonctionnement est obtenu simplement en connectant les deux entrées, via deux portes
et, à l’horloge. Ainsi, quand l’horloge est à l’état bas, les entrées sont annihilées pour la bas-
cule. Ce nouveau circuit est présenté à la Figure 4.16.
Bascule D Sur le circuit de la Figure 4.16, on observe que, si l’horloge est à l’état bas (elle
vaut 0), les entrées des deux portes NOR sont à 0, et donc, la bascule mémorise la dernière
information enregistrée. Il n’y a donc plus d’utilité à permettre S = R = 0, et on peut se per-
mettre d’avoir uniquement les deux paires d’entrées suivantes : S = 1 et R = 0 d’une part ; et
S = 0 et R = 1 d’autre part. Ainsi :
— si on souhaite que la mémoire mémorise la dernière valeur inscrite, on mettra l’horloge
à 0 (et les valeurs de S et R n’importent pas) ;
— si on souhaite enregistrer un 1, on mettra S = 1, R = 0 et l’horloge à 1 ;
— si on souhaite enregistrer un 0, on mettre S = 0, R = 1 et l’horloge à 1.
Mais comme maintenant l’entrée S est toujours l’inverse de l’entrée R, on peut se contenter
d’une seule entrée D (pour data, en anglais), telle que : S = D et R = ¬D. Ce circuit est donné
à la Figure 4.17.
4.4.3. Flip-flops
Bien que la bascule D permette d’isoler dans le temps une période où la mémoire va enre-
gistrer la donnée (celle où l’horloge est à 1), il faut encore maintenir une entrée stable pen-
dant tout ce laps de temps. En pratique, cela peut s’avérer difficile, et on souhaiterait avoir
25. Pour les états de l’horloge, on parle souvent de l’état « bas » et de l’état « haut » qui correspondent à 0 et 1
respectivement.
133
4. Leçons 7 à 9 – Niveau 0 : portes logiques
D
Q
S
E
I
I
E
S
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
un circuit de mémoire dans lequel l’information serait mémorisée à un instant ponctuel (et
non plus durant une période relativement longue). C’est l’objectif des flip-flops, qui sont des
bascules de type D à horloge, mais dont l’état change (en fonction de D), au moment précis
où l’horloge change d’état. Ainsi, tant que l’horloge reste à une valeur stable (que ce soit 0 ou
1), la mémoire maintient son état, quels que soient les changements de D. Quand l’horloge
change d’état, l’ordre d’écriture indiqué sur D est pris en compte.
Générateur de pulsation Pour réaliser une flip-flop, on peut utiliser une bascule et in-
tercaler, entre l’horloge et la bascule, un générateur de pulsation. Il s’agit d’un dispositif qui
génère une pulsation brève lors de certains changements d’états de l’horloge. La sortie de ce
générateur va donc se comporter comme une horloge qui passe très rapidement de 0 à 1 et
de 1 à 0 lorsque son entrée change.
Dans ce cours, nous allons considérer un générateur de pulsation qui s’active sur les flancs
montants de l’horloge, c’est-à-dire quand l’horloge passe de 0 à 1. Concrètement, cela peut
être réalisé comme montré à la Figure 4.18. Ce circuit semble avoir peu de sens, car une des
deux entrées de la porte et aura toujours la valeur 0, et donc la sortie du et semble être tou-
jours nulle. Cette conclusion est vraie dans une situation stable (c’est-à-dire quand l’entrée
du circuit n’a plus changé depuis un certain temps). Par contre, au moment où l’entrée du
circuit change, on passe par un régime transitoire qui s’explique par le fait que la porte non
et la porte et (comme toutes les portes logiques) prennent en réalité un certain temps (très
court) pour produire leur effet.
Dans notre cas, on supposera que les portes logiques prennent une unité de temps (voir le
134
4.4. Circuits pour réaliser des mémoires
D
Q
D D Q Q
Q Q
F IGURE 4.20. – Une flip-flop, avec ses deux entrées D et horloge, et ses deux sorties Q et Q.
diagramme sur la droite de le Figure 4.18) pour produire leur effet. On voit donc que l’entrée E
est à 0 jusqu’au temps 5, moment où elle passe à 1. En tenant compte du délai de propagation,
la sortie de la porte non (connectée à l’entrée I de la porte et) est à 1 jusqu’au temps 6,
moment où elle passe à 0. Les deux entrées du et sont donc à 1 entre le temps 5 et le temps
6. La porte et (qui souffre du même délai) produit donc une sortie à 1 du temps 6 au temps
7, ce qui constitue la pulsation désirée 26 .
Flip-flop Le circuit obtenu est donné à la Figure 4.19. En pratique, on utilisera la représen-
tation montrée à la Figure 4.20 pour désigner une flip-flop ; où on reconnaît les 2 entrées D
et horloge, et les deux sorties Q et Q.
26. Remarquons que ce phénomène ne peut pas être simulé dans Logisim, mais qu’il existe des flip-flop « tout
faits » dans Logisim. Remarquons enfin que dans Logisim, les flip-flops sont soit sur flanc montant soit sur flanc
descendant : c’est le passage de 0 à 1 ou de 1 à 0 respectivement qui active la mémorisation.
135
4. Leçons 7 à 9 – Niveau 0 : portes logiques
— une entrée RD que l’on fera passer de 1 à 0 pour déclencher l’écriture de la donnée
I 2 I 1 I 0 à l’adresse A 1 A 0 ; et
— une entrée C S (pour Chip Select) qui permet d’inhiber complètement l’écriture quand
elle est mise à 0. Cette entrée est utile quand l’ensemble la mémoire est répartie sur plu-
sieurs circuits présents physiquement sur plusieurs circuits intégrés (chips en anglais).
On active alors l’écriture dans le circuit qui contient l’adresse désirée uniquement.
On souhaite également 3 sorties O 2 , O 1 et O 0 qui donneront, à tout moment le contenu de la
case mémoire d’adresse A 1 A 0 , permettant ainsi la lecture.
Pour construire ce circuit, on commence par observer qu’il s’agit d’une mémoire de 12 bits
(3 × 4) au total. Chaque bit sera stocké dans une flip-flop, nous avons donc au total 12 flip-
flops dans notre circuit. Pour faciliter la lecture du circuit, nous étiquèterons chaque flip-flop
par (x, a), ce qui signifie que cette flip-flop stocke le bit numéro x de la case à l’adresse a. Par
exemple, la flip-flop numéro (0, 2) est le bit de poids faible de la case à l’adresse 2.
On peut ensuite concevoir séparément les parties du circuit qui s’occupent de la lecture
et l’écriture. Pour la lecture, on a affaire à une nouvelle variation sur le schéma du sélecteur
(Figure 4.10) : pour chaque bit 0 ≤ i ≤ 2, la sortie O i est obtenue en sélectionnant le contenu
d’un des flip-flops (i , 0), (i , 1), (i , 2), ou (i , 3) en fonction de l’adresse qui sera préalablement
décodée. La Figure 4.21 présente le principe général de fonctionnement de la lecture. On
reproduira ce principe pour chaque bit i (i ∈ {0, 1, 2}) du contenu d’un case mémoire.
La partie du circuit en charge de l’écriture est très similaire. On commence par réaliser
un circuit calculant C S ∧ ¬RD. Il y aura donc un flanc montant à la sortie de ce circuit si et
seulement si : (i) C S = 1 ; et (ii) il y a un flanc descendant sur RD. C’est donc la sortie de ce
circuit qui commandera l’écriture dans toutes les flip-flops de la case mémoire sélectionnée
par l’adresse A 1 A 0 . On opère la sélection de la bonne case mémoire comme dans le cas du
sélecteur : en prenant la conjonction de C S ∧ ¬RD avec chacune des sorties du décodeur.
Cela crée dans le circuit quatre « lignes » servant à activer l’écriture, une pour chaque case de
la mémoire, que l’on connectera aux entrées horloge des flip-flops correspondantes. Ainsi, le
flanc montant en sortie du circuit C S ∧ ¬RD ne sera propagé que vers les flip-flops de la case
d’adresse A 1 A 0 . On n’oubliera pas de connecter l’entrée I i aux entrées D de toutes les flip-
flops (i , j ) pour 0 ≤ j ≤ 3. À noter qu’il n’y a pas de problème à connecter la donnée à toutes les
flip-flops puisque c’est l’entrée horloge qui commande l’écriture. Le principe général d’écri-
ture du bit i est illustré à la Figure 4.22. Comme dans le cas de l’écriture, on reproduira ce
même principe pour chaque bit à écrire.
En combinant ces idées, on a le circuit de mémoire à 4 cases de 3 bits de la Figure 4.23.
M8N
136
4.4. Circuits pour réaliser des mémoires
D Q
(i , 0)
Q
D Q
(i , 1)
Q
A0
D Q
(i , 2)
Q
A1
D Q
(i , 3)
Q
Oi
137
4. Leçons 7 à 9 – Niveau 0 : portes logiques
Ii
RD
D Q
(i , 0)
CS
Q
D Q
(i , 1)
Q
D Q
(i , 2)
Q
D Q
(i , 3)
Q
138
Écriture à
I2 I1 I0
l’adresse 0
RD
D Q D Q D Q
(2, 0) (1, 0) (0, 0)
CS
Q Q Q
Lecture à
l’adresse 0
D Q D Q D Q
(2, 1) (1, 1) (0, 1)
Q Q Q
A0
D Q D Q D Q
(2, 2) (1, 2) (0, 2)
Q Q Q
A1
D Q D Q D Q
(2, 3) (1, 3) (0, 3)
Q Q Q
O2 O1 O0
139
4.4. Circuits pour réaliser des mémoires
4.5. Exercices
4.5.1. Tables de vérité et formules logiques
Ex. 14 Voici une table de vérité :
x1 x2 x3 Sortie
0 0 0 0
0 0 1 0
0 1 0 1
0 1 1 0
1 0 0 1
1 0 1 0
1 1 0 1
1 1 1 1
Indiquez, pour chacune des formules suivantes, si elles correspondent à cette table :
¡ ¢ ¡ ¢ ¡ ¢ ¡ ¢
1. x 2 ∨ x 1 ∨ x 1 ∧ x 2 ∨ x 1 ∧ x 2 ∧ x 3 .
¡ ¢ ¡ ¢ ¡ ¢ ¡ ¢
2. ¬x 1 ∧ x 2 ∧ ¬x 3 ∨ x 1 ∧ ¬x 2 ∧ ¬x 3 ∨ x 1 ∧ x 2 ∧ ¬x 3 ∨ x 1 ∧ x 2 ∧ x 3 .
¡ ¢ ¡ ¢ ¡
3. ¬x 1 ∧ x 2 ∧ ¬x 3 ∨ x 1 ∧ ¬x 2 ∧ ¬x 3 ∨ x 1 ∧ x 2 ).
¡ ¢ ¡
4. (x 1 xor x 2 ) ∧ ¬x 3 ∨ x 1 ∧ x 2 ).
¡ ¢
5. x 1 ∧ x 2 .
Ex. 15 Énoncé de l’interrogation de novembre 2014. Donnez la table de vérité du circuit ci-
dessous. Notez qu’on a utilisé les conventions habituelles de Logisim : les entrées (A, B et C) sont
des carrés et les sorties des cercles.
Ex. 16 Énoncé de l’interrogation de novembre 2014. Donnez un circuit qui calcule la fonction
dont la table de vérité est donnée ci-dessous (où x 1 , x 2 et x 3 sont les entrées). Il est fortement
recommandé d’utiliser la méthode systématique vue au cours et au TP.
140
4.5. Exercices
x1 x2 x3 Sortie
0 0 0 0
0 0 1 0
0 1 0 1
0 1 1 0
1 0 0 1
1 0 1 1
1 1 0 1
1 1 1 0
Ex. 17 Un comparateur n bits prend en entrée deux nombres de n bits (donc 2n bits au total)
et possède une sortie unique qui doit être 1 si les deux nombres fournis sont égaux, 0 sinon.
Construisez un circuit logique qui implémente un comparateur 3 bits.
Ex. 18 Un décodeur n bits prend en entrée un nombre de n bits et possède 2n bits de sortie que
nous noterons b 0 , b 1 , . . . , b 2n −1 . Le rôle du décodeur est de faire en sorte que si les n bits d’entrée
représentent un nombre N , alors le bit de sortie b N est à 1 et tous les autres sont à 0. Construisez
un circuit logique qui implémente un décodeur 2 bits.
Ex. 19 Le NOR est un opérateur logique binaire qui représente simplement la négation de la
disjonction logique ( ou). Montrez que les quatre opérateurs logiques « usuels » ( et, ou, non et
ou exclusif) peuvent être construits à l’aide de ce seul opérateur. Implantez ensuite ces quatre
opérateurs sous forme de circuits logiques.
141
4. Leçons 7 à 9 – Niveau 0 : portes logiques
on écrit, comme dans l’exercice précédent), 1 pour la valeur à écrire (un bit) et 1 pour
l’horloge (pour rappel, l’information n’est stockée dans le flip flop que sur les flancs mon-
tants). Il n’y a pas de sortie. Quand on a un flanc montant de l’horloge, il faut que le
flip-flop du mot sélectionné enregistre l’information donnée en entrée.
3. À l’aide des deux exercices précédents, une mémoire de quatre mots d’un bit à l’aide de
flip-flops dont les adresses sont codées chacune par une ligne d’entrée et dans laquelle on
peut lire et écrire. Cette mémoire a donc 7 entrées : 4 pour les adresses, 1 qui désigne la
valeur à écrire (en cas d’écriture), 1 pour l’horloge et enfin 1 qui indique si on veut lire ou
écrire. Il n’y a qu’une seule sortie. Si l’entrée « lecture/écriture » indique qu’il faut lire, il
faut que le contenu du flip-flop sélectionné apparaisse en sortie. Si elle indique qu’il faut
écrire, la valeur indiquée en entrée doit être stockée dans le bon flip-flop au moment du
flanc montant.
4. Complétez la mémoire précédente en y intégrant un décodeur (2 bits) pour les adresses.
Votre mémoire n’aura donc plus que 2 entrées pour sélectionner le mot à lire : son adresse
sera donnée en binaire sur ces deux entrées.
Ex. 21 Construisez le circuit logique d’une unité arithméticologique (ALU) offrant deux opé-
rations : et et ou. Le circuit aura trois entrées : deux bits de données a et b ainsi qu’un bit de
commande f ; il aura un seul bit de sortie s. Concrètement, si f = 0, votre circuit doit faire en
sorte que s ↔ a ∧ b. Dans le cas où f = 1, votre circuit doit par contre garantir que s ↔ a ∨ b.
Un demi-additionneur est un circuit logique qui prend en entrée deux bits a et b à addi-
tionner et fournit en sortie un bit de résultat s de leur somme ainsi qu’un éventuel bit r de
report.
Ex. 22 Donnez la table de vérité d’un demi-additonneur ayant pour entrée a et b, et les deux
sorties s et r . Donnez les formules logiques qui permettent de calculer s et r en fonction d’a et
b. Enfin, construisez le circuit logique correspondant.
Ex. 25 Construisez un circuit logique simulant une ALU offrant quatre opérations : et, ou,
non et l’addition. Le circuit aura cinq bits d’entrée :
— deux bits de données a et b ;
— un bit de report c (ne doit servir que pour l’addition) ;
— deux bits de commande f 1 et f 2 .
Il aura deux bits de sortie : s et r . Votre circuit doit assurer le fonctionnement suivant selon
les bits de commande f 1 et f 2 :
142
4.5. Exercices
f1 f2 Opération s r
0 0 et a ∧b 0
0 1 ou a ∨b 0
1 0 non ¬b 0
1 1 + somme(a, b, c) report(a, b, c)
4.5.4. Corrections
Correction de l’exercice 14
1. Non. La formule est vraie chaque fois que la table renvoie 1, mais la formule est aussi vraie dans des cas
où la formule renvoie 0. Par exemple, si x 1 = 0 et x 2 = x 3 = 1, la formule est vraie (elle est vraie dès que x 2
vaut 1 en raison de la première parenthèse), mais la table renvoie 0.
2. Oui. C’est la formule qu’on obtient en utilisant la « méthode systématique ».
3. Oui, on peut obtenir cette formule en simplifiant la précédente.
4. Oui, on obtient cette formule en introduisant un « xor » pour simplifier les deux premières parenthèse de
la formule précédente.
5. Non. La table est vraie chaque fois que la formule est vraie, mais il y a des cas où la table est vraie, et pas la
formule. Par exemple, si x 1 = x 3 = 0 et x 2 = 1.
Correction de l’exercice 15 Avant de donner la table de vérité, on peut tenter d’analyser le circuit de façon intui-
tive. Si au moins deux entrées sont à 1, il y aura un des trois « et » qui calculera 1 également. Dans ce cas, la sortie
du « ou » sera à 1 et la sortie du circuit sera à 0 en raison de la porte « non ». Dans les autres cas, la sortie du circuit
sera à 1. Le circuit renvoie donc 1 si et seulement si il y a exactement 0 ou 1 entrée à 1.
A B C X = A ∧B Y = B ∧C Z = A ∧C X ∨Y ∨ Z Sortie
0 0 0 0 0 0 0 1
0 0 1 0 0 0 0 1
0 1 0 0 0 0 0 1
0 1 1 0 1 0 1 0
1 0 0 0 0 0 0 1
1 0 1 0 0 1 1 0
1 1 0 1 0 0 1 0
1 1 1 1 1 1 1 0
Correction de l’exercice 16 On commence par identifier toutes les lignes où la sortie est à 1 et on traduit chaque
ligne en une conjonction des trois variables (avec des « non » pour les variables qui sont à 0) :
x1 x2 x3 Sortie Formule
0 1 0 1 ¬x 1 ∧ x 2 ∧ ¬x 3
1 0 0 1 x 1 ∧ ¬x 2 ∧ ¬x 3
1 0 1 1 x 1 ∧ ¬x 2 ∧ x 3
1 1 0 1 x 1 ∧ x 2 ∧ ¬x 3
143
4. Leçons 7 à 9 – Niveau 0 : portes logiques
Correction de l’exercice 19 On commence par observer qu’on peut réaliser une négation à l’aide d’un NOR. En
effet :
À l’aide de cette égalité, on peut maintenant définir le ∨ en terme de NOR, en utilisant la double négation :
¡ ¢
a ∨ b = ¬ ¬(a ∨ b) Double négation
= ¬(a NOR b) Définition du NOR
= (a NOR b) NOR(a NOR b) Car x NOR x = ¬x comme on l’a vu au-dessus.
Ensuite, pour obtenir le ∧, on utilise les lois de De Morgan (cfr. équations (4.15) et (4.16) dans la Section 4.1)
pour transformer un ∧ en une combinaison de ∨ et ¬ qu’on sait déjà exprimer sous forme de NOR :
Enfin, le XOR peut s’exprimer sous forme de ∨, ∧ et ¬. On peut alors utiliser les égalités ci-dessus pour obtenir
une expression du XOR sous forme de NOR.
Posons maintenant :
A = (a NOR a) NOR b
B = a NOR(b NOR b)
Bien que ce résultat puisse sembler long, les expression entre crochets sont égales ( A NOR B ) et peuvent donc
être calculées par le même circuit. Calculer A requiert 2 portes NOR, tout comme B . A NOR B requiert donc 5
portes, et donc l’expression finale demande 6 portes NOR.
M8N
144
Troisième partie
Le micro-langage
145
146
5. Leçons 10 à 12 – La microarchitecture
Le niveau qualifié de « microarchitecture » est le niveau intermédiaire entre les circuits
électroniques digitaux et le langage machine, qui est l’ensemble d’instructions que le proces-
seur peut exécuter. Dans le chapitre précédent, nous avons vu comment construire, à l’aide
de portes logiques, les briques de base d’un processeur, en particulier :
1. des mémoires, contenant soit une seule valeur (pour réaliser des registres), ou bien
plusieurs valeurs accessibles à l’aide d’une adresse (pour la mémoire centrale) ; et
2. une ALU, c’est-à-dire un circuit permettant d’effectuer des opérations arithmétiques et
logiques simples sur deux opérandes.
Bien que ces éléments permettent de réaliser les instructions machine les plus simples des
microprocesseurs, ils ne sont pas suffisants, tels quels, pour réaliser des traitement plus com-
pliqués, comme on en trouve sur les processeurs de type CISC. Pensons par exemple à des
instructions de recherche dans une chaîne de caractères, ou même des opérations arithmé-
tiques plus complexes comme la multiplication.
Afin de réaliser ces opérations plus complexes, il convient de les traduire en des opéra-
tions de bases qu’on peut exécuter à l’aide de registres, d’une ALU et d’une mémoire. . . C’est
l’objectif de la micro-architecture, qui est donc un niveau d’abstraction intermédiaire entre
les portes logiques et le langage machine. En un sens, la micro-architecture peut être com-
prise comme la description de l’implémentation de chaque instruction machine, sur base
des composants du micro-processeur (registres, ALU, etc). Cette couche intermédiaire est im-
portante pour simplifier la conception des microprocesseurs : sans elle, il faudrait, pour ainsi
dire, avoir un circuit dédié à chaque instruction machine dans le processeur, ce qui serait évi-
demment impossible avec les jeux d’instructions complexes dont disposent nos processeurs
actuels.
Brève perspective historique Comme nous l’avons déjà expliqué dans l’introduction, les
notions de microarchitecture et de microprogramme ont été introduites en 1951 par Sir Mau-
rice W ILKES (voir Figure 3.11) dans son article The best way to design an automatic calcula-
ting machine [48, 49]. Cette contribution a été présentée lors de la conférence inaugurale de
l’ordinateur F ERRANTI 1 Mark 1 à l’Université de Manchester, qui était un des tous premiers
ordinateurs au monde, sur lequel, notamment, Alan T URING a travaillé (voir Figure 5.1). Les
idées présentées par Wilkes en 1951 ont finalement pris corps en 1964 avec la conception
1. F ERRANTI International plc était une firme britannique, fondée en 1885 et dissoute suite à une banqueroute
en 1993. Cette firme a produit de nombreux appareils électroniques, et s’est investie, dès les débuts de l’informa-
tique, dans la construction d’ordinateurs. Leur premier ordinateur était le Ferranti Mark 1, livré à l’Université de
Manchester en février 1951 [40]. La Figure 3.4 montre la mémoire d’une autre machine de la même firme : un
Ferranti Sirrius.
147
5. Leçons 10 à 12 – La microarchitecture
du System/360 d’IBM [49, 15], voir Figure 5.1. Depuis, le principe de la microarchitecture est
en usage dans tous les processeurs CISC modernes (en particulier la famille des processeurs
Intel x86, dont l’i486 qui nous sert d’exemple).
Contenu du chapitre Au-delà du principe général que nous venons d’expliquer, il est as-
sez difficile de généraliser les idées des microarchitectures présentes sur les nombreux pro-
cesseurs du marché 2 . C’est pourquoi, ce chapitre sera essentiellement dédié à l’étude d’un
exemple de microarchitecture. L’exemple que nous allons étudier est totalement fictif, mais
parfaitement réaliste. Il est inspiré de l’ouvrage d’Andrew TANENBAUM [39].
Plus précisément, nous allons décrire dans ce chapitre :
1. Les composants hardware du chemin des données 3 d’un microprocesseur très simpli-
fié, que nous appelons le Mic-1. Tous ces composants peuvent être construits sur base
de portes logiques, en suivant les idées présentées dans le Chapitre 4. À la fin du cha-
pitre, nous décrirons également l’unité de contrôle qu’on peut ajouter à ce chemin des
données pour obtenir un microprocesseur complet. Nous décrirons également le fonc-
tionnement de ce chemin des données.
2. Un langage machine, appelé IJVM, dont nous sélectionnerons certaines instructions.
Nous expliquerons comment réaliser ces instructions sur le Mic-1. Ce langage machine
est un peu particulier, dans la mesure où c’est une langage qui s’exécute sur une ma-
chine à pile (voir Section 3.2.4 pour une explication détaillée). Au niveau du langage
machine, nous ne manipulerons donc pas de registres : les données utilisées par les
instructions machines se trouveront sur la pile, qui est stockée en mémoire principale.
Attention, ce la ne signifie par pour autant que notre processeur n’aura pas de registre !
Ceux-ci seront bien présents au sein de la microarchitecture pour réaliser les instruc-
tions machine, mais ils ne seront pas directement accessible au niveau du langage ma-
chine.
Nous commençons par décrire le Mic-1.
148
5.1. Le datapath du MIC-1
F IGURE 5.1. – Image du haut : le Ferranti Mark 1 (armoires sur la gauche et la droite, et
console au milieu de l’image) de l’Université de Manchester, avec Alan T URING
à droite de la console. Image du bas : un IBM System/360 en usage dans une
usine VolksWagen
149
5. Leçons 10 à 12 – La microarchitecture
MAR
MDR
Mémoire
Principale
PC
IR
SP
Bus B
TOS
Bus C
OPC
Contrôle Bus A N
ALU
de l’ALU Z
(6 bits)
Contrôle
Shifter du shifter
(2 bits)
F IGURE 5.2. – Le chemin des données du Mic1. La mémoire principale ne fait pas partie du
chemin des données (elle est en-dehors du processeur).
150
5.1. Le datapath du MIC-1
signification, qui sera élucidée plus en détails par la suite. Néanmoins, nous pouvons déjà
commenter les abréviations :
— MAR signifie Memory Address Register. Ce registre sera donc utilisé pour stocker des
adresses lors des communications (lecture et écriture de données) avec la mémoire cen-
trale. On voit d’ailleurs sur le schéma que MAR peut envoyer son contenu à la mémoire
centrale (mais pas le contraire)
— MDR signifie Memory Data Register. Ce registre sera, comme MAR, utilisé pour la com-
munication avec la mémoire centrale de manière bidirectionnelle : il contiendra les
données à écrire ou à lire.
— PC est le pointeur d’instruction (Program Counter) que nous avons décrit précédem-
ment. Avant chaque instruction machine, il contient donc l’adresse en mémoire de la
prochaine instruction (machine) à exécuter.
— De même, le registre IR 5 est l’instruction register et a pour tâche de stocker (en tout ou
en partie) les instructions machines à exécuter. Dans le cas du Mic-1, ce registre fait 8
bits. Nous verrons plus tard que c’est la taille des opcodes de l’IJVM.
— SP est le Stack Pointer. Il stockera à tout moment l’adresse mémoire de la case au som-
met de la pile, pour pouvoir y accéder.
— TOS signifie Top of Stack. Il contiendra, à tout moment, une copie de la valeur au som-
met de la pile, pour faciliter l’accès à celle-ci (voir la discussion sur la lenteur de l’accès
mémoire ci-dessous).
— OPC est l’abréviation d’Opcode et servira principalement à sauvegarder l’adresse d’un
opcode dans les instructions de saut (et, accessoirement, de registre de stockage tem-
poraire quand c’est nécessaire).
— Enfin, H est un registre qui servira à alimenter l’entrée A de l’ALU.
Comme on peut le voir sur la figure, tous ces registres sont connectés aux bus A, B et C (qui
sont tous les trois des bus de 32 bits), mais pas tous de la même manière. Ainsi, MAR n’est pas
connecté au bus B, car son seul but est de transmettre de l’information à la mémoire centrale,
pas à l’ALU. IR possède deux connections au bus B. Cela s’explique par le fait qu’IR contient
des données des 8 bits, alors que les bus sont de 32 bits. Il y a donc deux manière différentes
d’écrire une valeur de 8 bits dans 32 bits. Dans tous les cas, on écrira les 8 bits d’IR dans les 8
bits de poids faible du bus. Ensuite :
1. soit on complète les 24 bits restants par des zéros. Cette opération ne modifie pas la
valeur, à condition que celle-ci soit positive. Par contre, si la valeur stockée dans IR est
une valeur signée négative 6 , ajouter des zéros « à gauche » va rendre le nombre positif.
2. soit on complète les 24 bits restants par la valeur du bit de poids fort d’IR, afin de
conserver le signe. Ce deuxième comportement est le comportement par défaut.
5. À noter que dans l’exemple original de TANENBAUM, ce registre est appelé MBR ou Memory Byte Register.
Nous avons choisi de le renommer IR par souci de cohérence.
6. Les valeurs entières sont représentées en complément à deux.
151
5. Leçons 10 à 12 – La microarchitecture
ß
Supposons qu’IR contient la valeur 1000 0010. S’il s’agit d’une valeur non-
signée, il faut l’interpréter comme 13010 , et sa représentation sur 32 bits
doit donc être 0000000 00000000 00000000 1000 0010 (il faut ajouter des
zéros dans les bits de poids fort). Par contre, s’il s’agit d’un entier si-
gné (en complément à 2, soit −12610 ), sa représentation sur 32 bits est
1111111 11111111 11111111 1000 0010, on a donc bien « étendu » le bit de poids
fort d’IR sur 32 bits.
Enfin, notons également que le bus C ne peut pas alimenter IR (qui ne peut recevoir des
données que de la mémoire primaire), et que le registre H est le seul à alimenter le bus A (et
non pas le bus B).
ALU et shifter L’ALU du Mic-1 possède deux entrées principales A et B, toutes deux sur
32 bits, alimentées par les bus éponymes. Ces entrées reçoivent les données sur lesquelles
appliquer l’opération demandée. Cette dernière est spécifiée sur 6 bits, ce qui permet à l’ALU
de réaliser de multiples opérations. Nous en utiliserons 5 en particulier :
1. L’ALU renvoie la valeur A et ignore la valeur B ;
2. L’ALU renvoie la valeur B et ignore la valeur A ;
3. L’ALU renvoie A + B ;
4. L’ALU renvoie A − B ;
5. L’ALU renvoie A ∨ B (disjonction bit à bit).
La valeur calculée sera, comme toujours, disponible à la sortie de l’ALU, et alimentera un cir-
cuit de décalage. Ce dernier est un perfectionnement du circuit étudié au Chapitre 4. Il est
commandé par 2 bits et permet d’effectuer une opération de décalage parmi les trois sui-
vantes :
1. Ne pas modifier la valeur passée en entrée, et la recopier telle quelle sur sa sortie.
2. Décaler l’entrée d’un octet vers la gauche en remplissant les bits « perdus » (ceux de
poids faible) par des 0 (opération SLL8 : Shift Left Logical 8 bits)
3. Décaler l’entrée d’un bit vers la droite en gardant le bit le plus significatif inchangé
(opérations SRA1 : Shift Right Arithmetic 1 bit).
Enfin, notons que l’ALU possède deux bits de sortie supplémentaires : N et Z. Ceux-ci sont
mis à 1 si et seulement si la valeur calculée est négative (N) ou nulle (Z pour « zéro »), res-
pectivement. On peut également tester si la valeur d’un registre est nulle ou négative par le
même mécanisme.
152
5.1. Le datapath du MIC-1
l’ALU. (2) Ensuite, l’ALU applique une opération aux valeurs présentes sur ces entrées, suivie
par le shifter. (3) Puis, le résultat à la sortie du shifter est recopié dans un ou plusieurs re-
gistres connectés au bus C. (4) Enfin, le Mic-1 communique avec la mémoire. Regardons ces
différentes étapes plus en détail.
Les quatre étapes de l’exécution d’un cycle du Mic-1 sont les suivantes :
1. Écriture sur le bus B Tous les registres qui sont connectés au bus B
peuvent l’alimenter (en particulier, nous avons vu qu’il y a deux façons
différentes de recopier le contenu d’IR sur le bus). Par contre, un et seul
registre peut être choisi par cycle pour alimenter le bus B.
2. ALU et shifter En fonction des commandes qu’ils reçoivent 7 , l’ALU et le
shifter calculent une nouvelle valeur. L’entrée A de l’ALU est alimentée
nécessairement par le registre H, l’entrée B par le registre sélectionné à
l’étape précédente. La valeur calculée est placée sur le bus C.
3. Écriture du résultat Le résultat calculé par le shifter peut être recopié
dans un ou plusieurs registres connectés au bus C (dans le cas de l’écri-
ture, on peut écrire dans plusieurs registres sans problème).
4. Communication avec la mémoire Finalement, le Mic-1 peut communi-
quer avec la mémoire de plusieurs façons différentes que nous détaillons
maintenant. Le Mic-1 peut envoyer trois ordres différents à la mémoire
(principale) :
— Read : cela a pour effet de transmettre l’adresse qui est dans MAR à la
mémoire et lui faire lire l’information à cette adresse. Cette informa-
tion sera retournée au Mic-1 dans le registre MDR ;
— Write : symétriquement au Read, cette opération demande à la mé-
moire de stocker, à l’adresse MAR, le contenu de MDR ;
— Fetch : cette opération de lecture est utilisé exclusivement pour lire
des instructions (alors que le Read sert à lire des données). Dans le
cas d’un Fetch, le mémoire lit l’octet à l’adresse donnée par PC et
renvoie le résultat dans le registre IR.
Notons que, par souci de réalisme, toutes ces opérations sont assorties
d’un délai : tant l’écriture que les deux formes de lecture demandent
l’équivalent d’un cycle complet du chemin des données pour s’effectuer.
En particulier, comme l’ordre de lecture est envoyé à la mémoire en fin
de cycle, cela signifie que les données lues ne seront disponibles dans les
registres qu’à la fin du cycle suivant, ce qui est trop tard pour les exploiter
durant ce cycle (étant donné que les données alimentent l’ALU en début
de cycle). Donc, si un ordre de lecture est passé au cycle i , les données ne
seront exploitable dans les registres qu’au cycle i + 2 !
153
5. Leçons 10 à 12 – La microarchitecture
ß
Supposons que nous ayons les valeurs suivantes dans les registres : MDR contient
10, MAR contient 1024, H contient 5, et TOS contient 20.
Supposons que l’on désire effectuer les opérations suivantes :
1. faire la somme des valeurs contenues dans MDR et H ;
2. stocker cette somme dans TOS ;
3. écrire cette somme en mémoire à l’adresse 1024 (déjà dans MAR).
Tout cela peut être effectué « en un cycle », dans le sens ou toutes les opérations
sur les registres peuvent être faites en un cycle, et l’ordre d’écriture peut être
envoyée à la mémoire durant le même cycle (même si un cycle supplémentaire
sera nécessaire pour terminer l’écriture).
Voyons cela plus en détail :
1. Écriture sur le bus B Nous choisissons le registre MDR pour écrire sur le
bus B, qui convoie donc la valeur 10.
2. ALU et Shifter Nous donnons à l’ALU l’ordre de faire la somme de ses en-
trées. L’ALU calcule donc la somme des valeurs venant des bus B et A, soit
10 + 5 = 15 (la valeur sur le bus A provient toujours du registre H. Cette va-
leur 15 est transmise au Shifter, auquel nous donnons l’ordre de recopier
cette valeur sans la modifier. Le Shifter alimente donc le bus C avec cette
valeur 15.
3. Écriture du résultat Nous choisissons d’écrire le résultat à la fois dans le
registre TOS et dans le registre MDR (afin d’en permettre l’écriture en mé-
moire). À ce point, TOS et MDR contiennent donc la valeur 15 ;
4. Communication avec la mémoire Nous donnons à la mémoire un ordre
d’écriture Write. Le Mic-1 va donc transmettre la donnée 15 (depuis MDR)
et l’adresse 1024 (depuis MAR) à la mémoire. La donnée sera effectivement
écrite au bout d’un cycle.
5.1.3. Micro-instructions
Comme on peut le voir, de nombreux traitements différents peuvent être réalisés à chaque
cycle du chemin des données. Afin de rendre cela plus clair, nous allons décrire les traite-
ments à effectuer à chaque cycle sous la forme d’un langage (de programmation) plutôt pri-
mitif, appelé « microlangage ». Chaque instruction de ce langage, appelée « microinstruc-
tion » détaillera le comportement d’un et un seul cycle du chemin des données. Pour conce-
voir notre processeur, nous pourrons donc écrire un « microprogramme » soit un programme
en microlangage qui décrit comment les instructions machine sont exécutées sur le Mic-1.
154
5.1. Le datapath du MIC-1
Nous avons déjà largement commenté les quatre premiers items de cette définition, mais
pas le dernier. Comme les programmes machines, les microprogrammes sont constitués d’une
séquence d’instructions (de microinstructions), qui ont chacune une adresse. Après avoir
exécuté une microinstruction, il faut passer à la suivante, qui peut être : soit l’instruction
physiquement suivante (à l’adresse qui suit la microinstruction actuelle) ; soit l’instruction
logiquement suivante (qui peut être à n’importe quelle adresse dans le microprogramme).
Dans ce dernier cas, nous pouvons utiliser un goto pour préciser l’adresse de la microins-
truction suivante à exécuter. Ce goto peut être précédé d’une condition pour effectuer un
saut conditionnel, comme nous allons le voir maintenant.
Syntaxe pour le microlangage Afin d’avoir une notation compacte et lisible pour le mi-
crolangage, nous allons fixer une syntaxe pour les Micro-instructions. Chaque microinstruc-
tion sera divisée en trois parties, séparées par des points-virgules :
où :
— « opération registres » est une opération sur les registres qui peut être réalisée en un
cycle. Il s’agit des points 1 à 3 de la définition ci-dessus : quel est le registre qui alimente
le bus B, quelles sont les opérations à appliquer dans l’ALU et le Shifter, et quels sont
les registres qui recueillent le résultat ?
— « mémoire » est une des trois opérations possibles sur la mémoire, à savoir : rd pour
Read ; wr pour Write ou fetch ;
— « saut » indique quelle est la prochaine microinstruction à réaliser. Si rien n’est in-
diqué, ce sera l’instruction physiquement suivante. Sinon, on peut utiliser goto ou
if(...) goto(...); else goto(...). Notons que, comme dans le cas du langage
machine, nous utiliserons en général des étiquettes pour remplacer les adresses, afin
de rendre les microprogrammes plus lisibles.
155
5. Leçons 10 à 12 – La microarchitecture
ß
La microinstruction de l’exemple précédent peut être notée :
La première partie se lit, comme en python, ou comme dans les autres langages
impératifs usuels, de droite à gauche : on calcule la valeur MDR+H que l’on as-
signe à TOS et MDR. La seconde partie demande de lancer l’écriture. La troisième
partie est vide, on exécutera donc la microinstruction qui suit. Remarquons que
quand c’est le cas, on évitera en général d’écrire le dernier point-virgule pour
simplifier la lecture : MDR = TOS = MDR+H ; wr.
Nous expliquerons plus en détail comment les microinstructions sont stockées et exécu-
tées par le microprocesseur, à la fin de ce chapitre. Mais pour le moment, nous pouvons sup-
poser que nous sommes capables d’écrire et de faire exécuter des microprogrammes. Tour-
nons nous maintenant vers le langage machine IJVM qui sera celui de notre processeur.
8. Nous notons en passant que le nom IJVM est un acronyme d’Integer Java Virtual Machine, car A. TANEN -
BAUM s’est inspiré [39], pour définir ce langage, du langage intermédiaire utilisé par la machine virtuelle du lan-
gage de programmation Java, restreint aux seuls entiers (alors que la JVM supporte également des valeurs en
virgule flottante), voir [22] pour plus d’information.
9. Le décalage s’appelle o car décalage se dit offset en anglais.
10. On utilisera, comme toujours, le complément à deux.
156
5.2. Le langage machine IJVM
1 BIPUSH 10
2 BIPUSH 20
3 BIPUSH 12
4 IADD
5 IADD
12
BIPUSH 10 BIPUSH 20 BIPUSH 12
IADD IADD
−−−−−−−→ −−−−−−−→ 20 −−−−−−−→ 20 −−−→ 32 −−−→
10 10 10 10 42
F IGURE 5.3. – Un exemple de programme en IJVM, qui calcule la somme de trois nombres, et
son effet sur la pile.
vers une instruction qui précède l’instruction en cours (afin de réaliser une boucle, par
exemple).
— IFEQ o. Cette instruction pop la valeur au sommet de la pile et réalise un saut de o
positions dans le code si et seulement si cette valeur vaut 0. Le décalage o est une valeur
sur 2 octets, et peut lui aussi être négatif.
ß
La Figure 5.3 présente un premier exemple de programme IJVM, et illustre son
effet sur la pile. Ce programme réalise la somme des trois entiers 10, 20 et 12.
Il commence par faire un push de ces trois valeurs sur la pile, puis réalise deux
IADD successifs pour obtenir la somme au sommet de la pile.
ß
La Figure 5.4 présente un second exemple de programme IJVM, où les sauts sont
illustrés. On a aussi utilisé des commentaires, qui sont marqués par le carac-
tère « ; ». Ce programme fait l’hypothèse que la pile contient au moins deux
valeurs. Il commence par calculer la différence des deux valeurs au sommet, ce
qui remplace ces valeurs par 0 si et seulement si ces deux valeurs sont égales.
On peut ensuite utiliser l’instruction IFEQ pour remplacer la valeur au sommet
de la pile par une valeur Booléenne indiquant si les deux valeurs initialement
au sommet étaient égales. Si le test du IFEQ est vrai (donc si le sommet vaut 0,
c’est-à-dire si les valeurs étaient égales), on saute vers la ligne equ:, où on pop le
0, et on push un 1. Sinon, on continue l’exécution à la ligne qui suit IFEQ equ,
en faisant un pop de la valeur au sommet, un push de 0, puis en sautant à la
fin du programme (avec un GOTO fin, pour éviter de faire un push supplémen-
taire du 1). Le programme se termine sur une instruction NOP afin d’avoir une
instruction dans la dernière ligne.
157
5. Leçons 10 à 12 – La microarchitecture
1 ISUB
2 IFEQ equ ; Saut vers equ si le sommet de la pile = 0
3 POP
4 BIPUSH 0
5 GOTO fin ; Pour terminer le programme
6 equ : POP
7 BIPUSH 1
8 fin : NOP
Une exécution où le saut vers equ: est exécuté :
42
ISUB IFEQ equ POP BIPUSH 1 NOP
42 −−−→ 0 −−−−−−−→ 0 −−→ −−−−−−−→ 1 −−→ 1
... ... ... ... ... ...
F IGURE 5.4. – Un exemple de programme en IJVM, qui teste si les deux nombres au sommet
de la pile sont égaux, et push un 1 si c’est le cas ; un 0 dans le cas contraire.
— Un code d’un octet 11 qui identifie de manière unique l’instruction. Comme nous l’avons
déjà dit (Section 2.5) ce code s’appelle un opcode. La liste des opcodes IJVM est donnée
à la Table 5.1 (les opcodes y sont donnés en hexadécimal). Notez que les opcodes que
nous utilisons dans ce document diffèrent de ceux utilisés dans l’exemple initial de TA -
NENBAUM [39]. Cela provient du fait que nous avons simplifié l’exemple. Néanmoins,
certains outils utilisés dans le cadre de ce cours peuvent encore utiliser les opcodes de
[39]. Il ne faut pas s’en inquiéter : la valeur exacte des opcodes n’a pas vraiment d’im-
portance, tant que celles-ci identifient de manière unique chaque instruction machine,
et qu’elles sont cohérentes avec le microcode.
— Zéro, un ou deux octets supplémentaires pour le paramètre, qui suivent immédiate-
ment l’opcode. Dans le cas du BIPUSH, le paramètre tient sur un octet, tandis qu’il tient
sur deux octets pour GOTO et IFEQ. Il n’y pas de paramètre pour les autres instructions.
158
5.2. Le langage machine IJVM
TABLE 5.1. – Les opcodes IJVM que nous utiliserons. NB : par souci de cohérence de ces notes,
nous avons adopté des opcodes différents de ceux de [39].
Opcode (hexa.) Instruction Sémantique
01 NOP N’a aucun effet
0B BIPUSH v Push v sur la pile
08 POP Pop le stack
02 IADD Pop deux valeurs et Push leur somme
05 ISUB Pope deux valeurs et Push leur différence
14 IFEQ o Pop le stack et saute de o positions si la valeur est nulle
0E GOTO o Saut de o positions : ajoute o à PC
ß
Le programme de la Figure 5.3 est stocké en mémoire sur 8 octets : chacun des
trois BIPUSH requiert deux octets (un pour l’opcode, et un pour le paramètre),
et chaque IADD requiert un octet. La séquence des valeurs, en hexadécimal est
donc :
0B 0A 0B 14 0B 0C 02 02
BIPUSH IADD
ß
Pour obtenir la représentation binaire de l’exemple de la Figure 5.4, il faut déter-
miner les offsets des instructions GOTO et IFEQ. Ces décalages sont des valeurs
qu’il faudra ajouter à PC pour se retrouver sur la bonne instruction, ils doivent
donc être exprimés en terme de cases dans la représentation binaire. Commen-
çons par indiquer tous les opcodes et les paramètres des BIPUSH, tout en ré-
servant des cases pour les paramètres de GOTO et IFEQ (en supposant que le
programme commence à l’adresse 0, les adresses sont indiquées au-dessus des
cases) :
0 1 2 3 4 5 6 7 8 9 10 11 12 13
05 14 08 0B 00 0E 08 0B 01 01
IFEQ o GOTO o
equ: POP fin: NOP
../.
159
5. Leçons 10 à 12 – La microarchitecture
On voit alors :
... — que l’opcode de l’instruction equ: POP se trouve 10−1 = 9 octets plus loin
que l’opcode du IFEQ ; et
— que l’opcode de l’instruction fin: NOP se trouve 13−7 = 6 octets plus loin
que l’opcode du GOTO.
On dispose donc maintenant des décalages (qui sont relatifs aux adresses des
opcodes), et la représentation du programme de la Figure 5.4 (en hexadécimal)
est donc :
0 1 2 3 4 5 6 7 8 9 10 11 12 13
05 14 00 09 08 0B 00 0E 00 06 08 0B 01 01
IFEQ 9 GOTO 6
equ: POP fin: NOP
160
5.3. Implémentation de l’ IJVM sur le MIC1
161
5. Leçons 10 à 12 – La microarchitecture
Mémoire
SP
a +2
42
a a +1 a +2
7 →
−20 7 42 TOS
−20
42
Observons bien que la valeur qui est dans IR au moment du goto est bien l’opcode (qui était
déjà présente dans IR, par hypothèse), et non pas la valeur lue par le fetch.
Avant de pouvoir aborder les instructions qui manipulent la pile, nous devons expliquer
comment celle-ci est stockée en mémoire.
Les valeurs qui constituent la pile seront stockées en mémoire à partir d’une
adresse fixe a. Le fond de la pile est donc à l’adresse a, l’élément au-dessus à
l’adresse a + 1, etc. À tout moment, le registre SP contient l’adresse (qui est va-
riable) de l’élément au sommet de la pile. Le registre TOS contient lui, une copie
de cette valeur.
La Figure 5.6 illustre cette implémentation de la pile. On peut donc constater plusieurs
choses :
— le contenu de la pile est en fait constitué de toutes les cases mémoires contenues entre
les adresses a et SP (comprises). Cela signifie que pour faire un Pop, il suffira de décré-
menter SP. La valeur qui était au sommet de la pile restera présente en mémoire, mais
ne sera plus considérée comme faisant partie de la pile (sur l’exemple de la Figure 5.6,
il y a des données 12 dans la case d’adresse a + 3, données qui étaient peut-être sur la
pile auparavant).
— il faudra que le microcode des instructions qui modifient la pile gardent cohérents les
registres SP et TOS : cela ne se fait pas de manière automatique !
12. N’oublions pas que, comme une case mémoire est composée de flip-flops qui sont chacune toujours soit
dans l’état 1, soit dans l’état 0, une case mémoire n’est jamais « vide ». . .
162
5.3. Implémentation de l’ IJVM sur le MIC1
Instruction NOP Cette instruction ne fait rien, son microcode est donc constitué d’une seule
microinstruction qui revient au début de l’interpréteur. Notons tout de même que ce cycle
est nécessaire pour permettre au fetch lancé à la ligne Main1: de se terminer, afin que le
prochain opcode soit chargé dans IR.
Instructions IADD et ISUB Ces deux instructions sont très proches. Commençons par re-
garder le fonctionnement de l’IADD. Voici ce que nous devons faire :
— la valeur du sommet de la pile étant dans TOS, il nous faut lire la valeur juste au-dessous. ;
— pour obtenir cette valeur, il faut d’abord décrémenter SP, et lancer une lecture (rd) à
cette adresse. Cela signifie que la valeur décrémentée de SP doit être copiée dans MAR ;
— une fois le délai d’un cycle écoulé pour la lecture, on récupère la valeur lue dans MDR,
il faut l’ajouter à TOS. Cela n’est malheureusement pas possible immédiatement, car
l’ALU reçoit d’office un de ses deux paramètres du registre H. On doit donc d’abord
copier TOS dans H, ce qui permet d’occuper le cycle durant lequel on attend que la
lecture se termine ;
— enfin, on peut effectuer la somme de MDR et de H. Ce résultat doit être écrit dans deux
emplacements : dans TOS, et en mémoire à l’adresse pointée par SP (qui a déjà été dé-
crémenté). On termine donc en plaçant le résultat dans MDR, et en lançant une écriture
(wr) : celle-ci se fera au bon endroit étant donné que MAR n’a pas été modifié.
On peut maintenant se convaincre que c’est ce que fait le microcode à la Figure 5.7. Le mi-
crocode pour ISUB est similaire : seule la ligne 7, ou l’opération est effectivement calculée,
diffère (on y fait une différence au lieu d’une somme).
Instruction POP Pour effectuer un POP, il faut décrémenter SP et mettre à jour TOS. Passons
en revue le trois cycles du microcode :
163
5. Leçons 10 à 12 – La microarchitecture
164
5.3. Implémentation de l’ IJVM sur le MIC1
Instruction BIPUSH Pour effectuer un BIPUSH, il faut récupérer en mémoire la valeur à pla-
cer sur la pile, incrémenter le pointeur SP, et placer la valeur à cette nouvelle adresse, ainsi
que dans TOS.
Pour rappel, une instruction BIPUSH tient sur deux octets en mémoire : le premier contient
l’opcode et le second la valeur à placer sur la pile. Cela signifie qu’au moment où l’on com-
mence à exécuter les micro-instructions qui effectuent BIPUSH, son opcode se trouve dans
IR, mais PC pointe déjà vers la case mémoire contenant l’opérande, et la lecture de cet opé-
rande est en cours (à cause du fetch de la ligne Main1:. Elle ne sera disponible dans IR qu’au
début du second cycle (ligne 12). Il faudra donc penser à incrémenter PC de façon à ce qu’il
pointe vers l’opcode de l’instruction machine qui suit le BIPUSH, et à charger cet opcode dans
IR. Les microinstructions sont donc :
— SP = MAR = SP + 1. Cette microinstruction calcule la nouvelle adresse de sommet
de la pile, et la place dans SP et dans MAR.
— PC = PC + 1; fetch. Lance la lecture de l’opcode de l’instruction IJVM suivante.
— MDR = TOS = IR; wr; goto Main1. La lecture lancée à la microinstruction précé-
dente (ligne 12) n’est pas encore effectuée, et IR contient donc encore la valeur de
l’opérande au début du cycle. C’est donc la valeur de l’opérande qui est copiée dans
TOS (ce sera la nouvelle valeur au sommet de la pile) et dans MDR. L’écriture en mémoire
est lancée (l’adresse d’écriture avait été mise dans MAR lors de la première microinstruc-
tion). À la fin du cycle de la ligne 13 la lecture en mémoire lancée à la microinstruction
précédente (ligne 12) est terminée, et l’opcode de la prochaine instruction est donc
bien dans IR au moment où l’on revient au début de l’interpréteur.
o = o1 o2
8 bits 8 bits
Notons que quand on atteint la ligne goto1:, la lecture de o 1 est en cours, mais cette valeur
n’est pas encore dans IR (qui contient à ce moment toujours l’opcode du GOTO, comme nous
l’avons dit).
Voyons maintenant le microcode instruction par instruction :
— OPC = PC - 1. Cette microinstruction sauve dans OPC l’adresse de l’opcode du GOTO
que l’on s’apprête à exécuter (la valeur a dans la discussion ci-dessus).
165
5. Leçons 10 à 12 – La microarchitecture
L’instruction IFEQ Cette instruction combine les idées vues dans le POP (la valeur au som-
met de la pile doit être supprimée de celle-ci) et dans le GOTO. La valeur au sommet de la pile
doit être comparée à 0, ce qui est fait à la ligne 23 (à ce moment OPC contient cette valeur) :
l’assignation Z = OPC met à jour le bit de sortie Z de l’ALU avec la valeur 1 si et seulement
si la valeur d’OPC est nulle. Ce bit peut alors être utilisé pour effectuer un saut conditionnel
(ligne 23) dans le microcode :
— Si Z vaut 1, le sommet de la pile était nul et le saut doit être effectué. Le microcode
saute alors vers la ligne T:. Étant donné que les microinstructions pour effectuer un
saut (mettre à jour PC en fonction d’un offset de 16 bits qu’il faut reconstruire à travers
deux fetch) ont déjà été écrites pour le GOTO, on fait un saut vers le microcode de cette
instruction (spécifiquement vers goto2:).
— Si, au contraire, Z vaut 0, le sommet de la pile était non-nul, et le microcode saute à la
ligne F:. Dans ce cas, le saut ne doit pas être effectué : il faut seulement incrémenter
PC à deux reprises (pour « sauter » l’offset dans le code IJVM) et effectuer un fetch pour
être certain que l’opcode de la prochaine instruction IJVM est bien chargée dans IR
quand on revient au début de l’interpréteur.
166
5.3. Implémentation de l’ IJVM sur le MIC1
MAR
Calcul de
l’adresse suivante
MDR
Mémoire
Principale
PC MPC
IR
Control
SP Store
TOS
MIR
OPC
ALU
Shifter
F IGURE 5.8. – Le Mic1 complet, avec le control store, le séquenceur (calcul de l’adresse sui-
vante) et les registres MIR (registre de microinstruction) et MPC (pointeur
de microinstruction). Pour rendre la figure plus lisible, nous n’avons indiqué
qu’une seule connexion entre MIR et chacun des registres du chemin des don-
nées. En réalité, il existe plusieurs connexions, car le contenu de MIR peut indi-
quer (en fonction des registres) si le registre écrit sur le bus B (cette information
doit d’ailleurs être décodée, mais nous n’avons pas indiqué le décodeur) et s’il
reçoit l’information du bus C.
167
5. Leçons 10 à 12 – La microarchitecture
13. À nouveau, ces explications diffèrent quelque peu de la présentation de TANENBAUM, en raison de notre
simplification. En particulier, notre Mic-1 comporte moins de registres, et les microinstructions tiennent donc
sur moins de bits que dans [39].
14. À nouveau, il est possible d’étendre ces choix, quitte à utiliser plus de bits, voir [39].
168
5.4. Pour aller plus loin. . .
27 25 24 18 17 10 9 8 7 5 2 1 0
3 bits 7 bits 8 bits 2 bits 1 bit 5 bits 2 bits
Bus B Bus C ALU+Shifter MAR/MDR PC/IR Adresse Saut
On a donc une représentation binaire des microinstructions sur 28 bits, comme montré à la
Figure 5.9. L’ensemble du microprogramme est alors stocké, sous ce format, dans le control
store (dont les cases font 28 bits).
5.3.7. Le séquenceur
Finalement, le processeur est équipé d’un séquenceur dont la tâche est d’exécuter les mi-
croinstructions les unes après les autres. Pour ce faire, le séquenceur dispose de deux registres
(voir Figure 5.8) : MPC et MIR. Étant donné que le microprogramme est lui-même un pro-
gramme, son exécution se fera de manière très similaire à celle d’un programme machine.
Le registre MPC est le compteur de microprogramme : il indique l’adresse (dans le control
store) de la prochaine microinstruction à exécuter. Cette microinstruction sera recopiée dans
le registre de microinstruction MIR. Les différents bits qui composent ce registre peuvent im-
médiatement être connectés à l’ALU, au circuit de décalage, à la mémoire principale (pour
lancer les ordres de lecture et écriture), et aux registres pour en activer la lecture ou l’écriture,
en utilisant un décodeur (non présent sur la figure) pour la partie qui indique quel registre est
connecté au bus B. Les bits 0 à 5 de la microinstruction qui indiquent quelle est la prochaine
instruction à calculer sont, eux, fournis en entrée à un simple circuit logique (que nous ne
détaillons pas ici) pour calculer la prochaine adresse à placer dans MPC (ce circuit tient aussi
compte des sorties N et Z de l’ALU pour les sauts conditionnels).
Nous insistons sur le fait que la présentation que nous avons fait du microcode dans ce
chapitre n’est qu’un exemple, et il ne faudrait pas croire que les techniques présentées ici
sont implémentées telles quelles dans tous les processeurs microprogrammés.
169
5. Leçons 10 à 12 – La microarchitecture
La microarchitecture d’un microprocesseur moderne est bien plus complexe. C’est en effet
au niveau de la microarchitecture que sont implémentées toute une série d’optimisations
dont nous avons déjà parlé, comme le cache pour accélérer l’accès à la mémoire primaire, ou
encore les pipelines. D’autres optimisations existent encore, comme les techniques de pré-
diction qui tentent de prédire quelles seront les prochaines instructions exécutées, afin de les
charger de manière anticipée dans le cache. L’ouvrage de TANENBAUM [39], dans sa dernière
édition, développe l’exemple du Mic-1 et commente la micro-architecture de processeurs
modernes comme les processeurs Intel Core.
M8N
170
5.5. Exercices
5.5. Exercices
Pour ces exercices, nous utilisons le simulateur de Mic-1 disponible à l’adresse suivante :
http://di.ulb.ac.be/verif/ggeeraer/info-f-102/Sim/.
Considérons le programme suivant :
1 BIPUSH 7
2 BIPUSH 5
3 DUP
4 IADD
5 DUP
6 IADD
7 IADD
Remarque : l’instruction DUP n’a pas été étudiée au cours théorique. Elle a pour effet de du-
pliquer la valeur au sommet de la pile.
Ex. 26 Quel est l’effet du programme ci-dessus ? À la fin de l’exécution, quelle sera la valeur
stockée au sommet du stack ?
Ce programme n’est évidemment pas celui qui est chargé en mémoire et exécuté, car il faut
remplacer les noms des instructions (lisibles par les humains) par les opcodes adéquats, qui
tiennent chacun sur un octet.
Ex. 27 À l’aide de la Table 5.1, donnez la séquence d’octets qui encode le programme ci-dessus.
Notez que l’opcode de DUP est 1C
Ex. 28 Vérifiez votre réponse à l’exercice précédent en utilisant le simulateur Mic-1. Allez dans
l’onglet « Program Builder » pour entrer le programme ligne par ligne, il s’affiche alors en hexa-
décimal.
Ex. 29 Après avoir entré votre programme, cliquez sur « Done », et exécutez le programme cycle
par cycle. Vérifiez que la réponse que vous aviez donnée à l’exercice 26 était bien correcte.
171
5. Leçons 10 à 12 – La microarchitecture
— Le fetch permet de lire un octet à l’adresse donnée par PC. Une fois le fetch
exécuté, la valeur lue apparaît dans IR après deux cycles.
— Le read permet de lire 32 bits à l’adresse donnée par 4 × MAR (l’adresse donnée
dans MAR est l’adresse d’un mot de 32 bits, qu’il faut donc multiplier par 4 pour
obtenir l’adresse physique, la mémoire étant adressée en octets). Une fois le read
exécuté, la valeur lue apparaît dans MDR après deux cycles.
— Le write permet d’écrire les 32 bits présents dans MDR à l’adresse donnée par
4 × MAR (même remarque). Une fois le write exécuté, la valeur est écrite après
deux cycles.
5. Spécifier un goto pour connaître la prochaine instruction à exécuter.
Supposons la situation initiale suivante (la colonne de droite représente le contenu du stack
avec les adresses de chacune des valeurs 32 bits données à gauche, les registres qui ne sont
pas représentés ne sont pas nécessaires ici) :
2004c : 5
5 8013 ?? ?? 0
20048 : 5
···
On constate que le registre TOS est utilisé pour garder une copie « locale » du sommet du
stack, et que le registre SP contient l’adresse du mot de 32 bits qui est au somme du stack :
pour obtenir son adresse physique, il faut multiplier cette valeur par 4 (4 × 801316 = 2004c 16 ).
1 MAR = SP = SP - 1; rd
2 H = TOS
3 MDR = TOS = MDR + H ; wr
Ex. 32 Cet exercice vous permet de vérifier votre réponse à l’exercice précédent. Créez un nou-
veau programme IJVM contenant le code suivant :
1 BIPUSH 5
2 BIPUSH 5
3 IADD
Chargez le dans le simulateur comme indiqué à l’exercice 28.
Avancez dans le programme jusqu’à ce que l’instruction IADD soit sélectionnée. Observez
alors attentivement les microinstructions qui sont exécutées pour réaliser IADD : ce sont celles
de la séquence donnée ci-dessus. Comparez vos résultats à ce qu’affiche le simulateur.
172
5.5. Exercices
2004c : 5
1 5 8013 ?? ?? 0 20048 : 5
···
:
2 :
···
:
3 :
···
:
4 :
···
173
5. Leçons 10 à 12 – La microarchitecture
Ex. 33 Reprenez le code IJVM de l’exercice 26 et donnez la suite des lignes de code que l’interpré-
teur va exécuter, en utilisant le micro-code de l’interpréteur qui est donné dans le simulateur
(car celui-ci contient le micro-code pour DUP).
174
5.5. Exercices
5.5.3. Corrections
Correction de l’exercice 26 Le programme multiplie la valeur 5 par 4 à l’aide d’une série de DUP et de IADD. On
obtient donc 20 et 7 au sommet du stack. On finit par additionner ces deux valeurs, la valeur au sommet du stack
est donc 27.
Correction de l’exercice 30
2004c : 5
1 5 8013 ?? ?? 0 20048 : 5
···
20048 : 5
2 5 8012 8012 ?? 0 20044 : ···
···
20048 : 5
3 5 8012 8012 5 5 20044 : ···
···
20048 : 5
4 10 8012 8012 10 5 20044 : ···
···
Remarque la valeur 10, calculée dans TOS ne sera placée en mémoire à l’adresse 20048 (sommet du stack)
qu’à la fin du cycle suivant.
Correction de l’exercice 33
1. Main1
2. Bipush1 à Bipush3
3. Main1
4. Bipush1 à Bipush3
5. Main1
6. Dup1, Dup2
7. Main1
8. Iadd1 à Iadd3
9. Main1
10. Dup1, Dup2
11. Main1
12. Iadd1 à Iadd3
175
5. Leçons 10 à 12 – La microarchitecture
13. Main1
14. Iadd1 à Iadd3
15. Main1
M8N
176
Quatrième partie
Le langage Machine
177
178
6. Leçon 13 – Langage machine
Le prochain niveau que nous allons étudier est celui du langage machine, qui est souvent
appelé, en anglais Instruction Set Architecture, ou ISA 1 . Ce niveau est important car il marque,
en général la séparation entre le matériel et le logiciel (voir Figure 1.5) :
Le langage machine est le langage qui est directement exécuté par le proces-
seur. C’est le langage le plus simple (de plus bas niveau) qui est accessible aux
programmeurs. Les langages de plus haut niveau (P YTHON, C, J AVA, etc) sont
traduits vers le langage machine à l’aide de compilateurs ou d’interpréteurs.
Il est tout à fait possible, même si c’est fastidieux, de programmer un ordinateur directe-
ment en langage machine. C’était d’ailleurs le seul langage qu’acceptaient les premiers ordi-
nateurs : les instructions en langage machine devaient être introduites, l’une après l’autre, en
binaire, à l’aide d’une série d’interrupteurs, ou via des cartes ou du ruban perforé. Il n’est en
général par contre pas possible de modifier ni le microprogramme (cela permettrait de chan-
ger la sémantique des instructions machine, ce qui est assez inattendu), ni les portes logiques
du processeur 2 .
Chaque processeur, ou chaque famille de processeurs possède son propre langage ma-
chine. Nous allons donc, dans ce chapitre, dégager des idées générales que l’on peut appli-
quer de manière large.
1. À ne pas confondre avec l’ancien bus ISA des premiers PC, qui signifie, lui, Industry Standard Architecture
[20].
2. Ceci doit être relativisé à l’aune de l’émergence des processeurs hybrides avec FPGA, voir l’introduction de
la Section 4.
179
6. Leçon 13 – Langage machine
Tous les exemples de cette section seront notés en utilisant cette syntaxe, mais il faut bien
garder en tête que c’est une simplification de la notation.
Comme on peut le voir dans cette définition, la notion d’architecture recouvre tous les élé-
ments qui sont importants et visibles au niveau du langage machine : le langage machine
manipule directement les registres, la mémoire, etc. Par contre, les détails de la réalisation
du processeur qui permettent l’exécution du langage machine n’ont pas d’importance ici
(microarchitecture, processeur superscalaire, etc). C’est pour cela qu’on parle de machine
abstraite.
ß
Reprenons l’exemple du processeur i486 : à peu près 4 ans après la mise sur la
marché de ce processeur, une autre firme, Advanced Micro Devices, Inc (AMD)
introduit l’Am486, un microprocesseur équivalent, et capable d’exécuter les
mêmes programmes que l’i486. Les deux processeurs appartiennent à la même
architecture, mais sont réalisés de manière différente (notamment au niveau du
microcode ou du cache disponible). Cela se vérifie d’ailleurs dans les résultats
des tests de performance, qui diffèrent entre les deux processeurs.
3. https://gcc.gnu.org/
4. https://clang.llvm.org/
180
6.1. Notion d’architecture
Passons en revue ces différentes caractéristiques. Pour ce faire, nous allons prendre en
exemple, comme d’habitude, le processeur i486, que nous allons le comparer à ses descen-
dants actuels dans la famille x86 (qui sont plus complexes). Nous allons également considérer
un autre processeur, beaucoup plus simple, le MOS Technology 6502, dont nous n’avons pas
encore parlé dans ces notes
Le 6502 est un des premiers microprocesseurs grand public, introduit en 1975 par MOS
Technology 5 . À cette époque, c’était le processeur le moins cher du marché, et il a eu un
succès considérable, étant au cœur de plusieurs ordinateurs grands publics très largement
diffusés comme l’Apple II (introduit en 1977, 6 millions d’exemplaires vendus), l’Atari 800
(introduit en 1979, 4 millions d’exemplaires vendus), le Commodore 64 (introduit en 1982, 17
millions d’exemplaires vendus), etc. Le 6502 est un processeur 8 bits très simple :
— Ses adresses tiennent sur 16 bits (cfr. taille de PC), ce qui lui permet d’accéder à 64 ko
de mémoire.
— Ses instructions tiennent sur un, deux ou trois octets, dont le premier octet forme tou-
jours l’opcode. Les éventuels deuxième et troisième octets servent à spécifier un opé-
rande (par exemple, une adresse sur deux octets).
Modèle mémoire Le modèle mémoire décrit comment la mémoire est vue par la langage
machine, et donc, comment on y accède à travers le langage machine. Ce modèle peut être
très simples, comme dans le cas du 6502 :
ß
Dans le 6502 [23], les adresses manipulées par le langage machine corres-
pondent essentiellement aux adresses physique : une adresse seize bits dans
le langage machine est exactement le « numéro de case » mémoire corres-
pondant. La seule exception est que les cases dont l’adresse ne tient que sur
huit bits (celle d’adresse 0 à 255) peuvent être adressées directement avec une
adresse de huit bits : cela évite au processeur de devoir lire les huit bits de
poids fort de l’adresse qui seront de toute manière égaux à zéro, ce qui éco-
nomise un cycle. Par exemple, l’adresse 0101 1111 n’existe pas stricto sensu,
puisque les adresses tiennent sur seize bits, mais le processeur l’interprète
comme 0000 0000 0101 1111.
Le modèle mémoire est plus complexe sur l’i486, qui illustre bien les mécanismes qu’on
peut trouver sur les processeurs modernes :
181
6. Leçon 13 – Langage machine
ß
L’i486 peut fonctionner selon deux modes : le mode réel (qui existe essentiel-
lement pour des raisons de compatibilité avec les précédents processeurs de
l’architecture x86) et le mode protégé.
Dans le mode réel, le processeur ne peut accéder qu’à 1 Mo de mémoire. La
mémoire est vue comme un ensemble de segments qui sont des portions de mé-
moire de 64 ko, et un segment peut commencer à n’importe quelle adresse qui
est un multiple de 16. Les adresses que le processeur manipule sont donc don-
nées en deux parties :
— un numéro de segment S sur 16 bits ; et
— une adresse dans le segment (décalage ou offset) O sur 16 bits (ce qui
permet donc de choisir une adresse dans le segment, étant donné que
216 o = 64ko). Ce sont ces adresses qui sont manipulées par les instruc-
tions.
L’adresse réelle est alors calculée comme suit par le processeur :
adresse réelle = S × 24 + O.
Le numéro de segment est donc décalé de 4 bits vers la gauche (cela explique
pourquoi les segments commencent toujours à une adresse qui est un multiple
de 24 = 16) et ajouté au décalage. Par exemple, l’adresse 1111 0000 1010 1010
dans le segment 0000 0001 1111 0000 sera notée en base 16 : 01F0 : F0AA (nu-
méro de segment suivi du décalage), et correspondra à l’adresse physique :
182
6.1. Notion d’architecture
ß
L’i486 possède quatre registres de travail 32 bits : eax, ebx, ecx et edx, et 8 re-
gistres de travail 80 bits pour les nombres en virgule flottante. En outre, il pos-
sède quatre registres d’index esi, edi, ebp et esp, qui servent à effectuer des
calculs d’adresse (voir la discussion plus loin sur l’adressage) ; le registre eip qui
fait office de pointeur de programme ; six registres 16 bits qui permettent de sé-
lectionner des segments : cs, ds, es, fs, fs, gs et ss ; et enfin 1 registres de flags.
Afin d’être compatible avec ses ancêtres 16 bits, l’i486 permet également de
n’accéder qu’à une portion de ses registres 32 bits. Par exemple, les 8 bits de
poids faible d’eax sont appelés al, et les bits de numéro 8 à 15 de ce même eax
sont appelés ah. Ensemble, al et ah forment un registre de 16 bits qui corres-
pond au registre ax des processeurs 16 bits de l’architecture x86 (le 8086 par
exemple) : ah forme les bits de poids fort (h signifie high) et al les bits de poids
faible (l signifie low).
ß
Rappelons rapidement les registres du 6502. Le registre A du 6502 est le seul re-
gistre de travail du processeur. Les autres registres sont des registres de contrôle :
les registres X et Y sont des registres d’index permettant le calcul d’adresses ; SP
est le pointeur de pile, et P ne contient que des flags donnant de l’information
sur le statu du processeur et le résultat des opérations (il y a par exemple un flag
N pour indiquer que le résultat de la dernière opération était négatif).
Types de données Dans les langages de programmation de haut niveau, il est habituel-
lement possible de manipuler des données qui ont des types complexes. Par exemple, en
P YTHON, on peut manipuler des chaînes de caractères, et le langage donne accès à des ins-
tructions et dédiées à ce type de données. Par exemple, le code suivant :
183
6. Leçon 13 – Langage machine
Affichera Hello world! (avec une majuscule à la première lettre), et ce à l’aide de la mé-
thode capitalize() qui fait intrinsèquement partie du langage. De même, les entiers en
Python ne sont pas, a priori, limités en taille. On peut parfaitement demander à Python de
calculer la valeur de 2238 , par exemple, ce qui n’est généralement pas possible en une seule
instruction avec un processeur ayant des registres de 32 ou 64 bits.
Typiquement, le langage machine permettra de manipuler :
— des données Booléennes. Par exemple, à l’aide d’instructions réalisant des conjonction
ou disjonction bit à bit sur les registres.
— parfois, mais plus rarement, des données non-numériques comme des chaînes de ca-
ractères.
ß
Le 6502 étant un processeur très primitif, il ne supporte que deux types de don-
nées, sur 1 octet : les Booléens et les entiers. Il permet de réaliser des opérations
Booléennes comme le and et le or entre une valeur (8 bits) en mémoire et le
contenu du registre A. Ce sont les instructions and et ora.
Il permet également de faire la somme ou la différence de deux nombres entiers
en complément à deux et sur 8 bit (l’un en mémoire et l’autre dans le registre
A), avec un report ou une retenue, et ce à l’aide des instructions adc et sbc. Il
possède également des instructions pour faire des décalages, des incréments et
décréments et des comparaisons d’entiers (toujours sur 8 bits). Il n’a, par contre,
pas d’opération pour la multiplication, qui doit être implémentée par le pro-
grammeur !
ß
L’i486 peut manipuler des Booléens, et des entiers signés ou non-signés sur 32
bits en complément à deux (les 4 opérations élémentaires sont supportées, plus
les décalages), ainsi que les entiers en BCD. Quelques opérations simples sur les
chaînes de caractères sont supportées : on peut les déplacer, les comparer ou y
faire une recherche en une seule instruction machine. Les chaînes de caractères
sont supposées stockées en ASCII 8 bits.
184
6.2. Instructions machine typiques
ß
L’ensemble du jeu d’instructions du 6502 est montré à la Figure 6.1. Il ne com-
porte que 56 instructions (avec des variantes, qui ont chacun un opcode diffé-
rent comme par exemple pour l’addition). Le jeu d’instructions de l’i486 com-
porte lui plus de 150 instructions (en ne comptant pas les différentes variantes
d’instructions comme l’addition, etc). On peut consulter, par exemple http:
//ref.x86asm.net/geek64.html, pour apprécier la complexité des jeux
d’instructions de l’architecture Intel 64 bits (x86-64), qui est celle de leurs pro-
cesseurs actuels.
Chaque architecture possède son propre jeu d’instructions, mais on peut les classifier grosso
modo comme suit :
ß
Sur l’i486, l’instruction principale pour déplacer des données est l’instruction
mov d,s où d est la destination et s est la source. Il peut sembler étonnant de
préciser la destination avant la source, mais cette syntaxe de l’assembleur est
choisie pour ressembler aux assignations dans les langages de haut niveau : a=b.
La Figure 6.6 présente des exemples d’utilisation de cette instruction.
Instructions arithmétiques, logiques, etc Comme nous en avons déjà discuté à plusieurs
reprises, les langages machines possèdent toute une série d’instructions permettant d’effec-
tuer des opérations arithmétiques et logiques qui sont dyadiques (elles combinent deux va-
leurs, comme l’addition) ou monadiques (elles s’appliquent à une seule valeur, comme le
décalage).
Le code de la Figure 6.2 calcule, dans ebx la valeur b 2 − 4ac, en supposant que
ß les valeurs a, b et c sont placées dans eax, ebx et ecx respectivement.
185
6. Leçon 13 – Langage machine
F IGURE 6.1. – Le jeu d’instructions du 6502, tel que publié par les Beagle Brothers. . .
186
6.2. Instructions machine typiques
ß
L’exemple de la Figure 6.3 montre un if en P YTHON qui a été traduit en lan-
gage machine. On suppose que la valeur x est placée dans eax. L’instruction
cmp eax, 5 compare la valeur d’eax à 5. Concrètement, le processeur soustrait
5 à eax (sans stocker le résultat dans eax) et met les flags du registre eflags à
jour en fonction du résultat.
Ensuite, l’instruction jge else effectue un saut à l’étiquette else: si et seule-
ment si eax est plus grand ou égal à 5. Concrètement, le saut a lieu : soit si le flag
ZF vaut 1, indiquant que eax = 5 ; soit si les flags SF et OF sont égaux, indiquant
que eax > 5.
Si le saut n’a pas eu lieu, l’exécution continue avec les instructions à partir de
la ligne 4, qui effectuent le contenu de la branche if. Celle-ci se termine par
un saut vers end:, sans quoi la branche else serait elle aussi exécutée après la
branche if.
187
6. Leçon 13 – Langage machine
P YTHON :
1 if x < 5:
2 # ... branche ‘‘ if ’’
3 else :
4 # ... branche ‘‘ else ’’
Langage machine :
1 mov eax , ... ; placer la valeur x à tester dans eax
2 cmp eax , 5
3 jge else ; saut si eax >= 5
4 ... ; branche ‘‘ if ’’, atteinte si eax < 5
5 ...
6 jmp end ; fin de la branche ‘‘ if ’’
7 else :
8 ... ; branche ‘‘ else ’’
9 ...
10 end :
à de simples sauts, car, une fois la fonction terminée, il faut revenir au point d’appel pour
continuer à exécuter la fonction appelante, comme montré dans l’exemple suivant.
ß
Considérons l’extrait de code P YTHON au sommet de la Figure 6.4. On y définit
une fonction f(x) qui retourne x+1. Dans le code machine, il serait simple de
sauter vers les début de la fonction f: (ligne 9) avec un jmp, mais comment
réaliser le return, étant donné que son effet dépend de l’endroit où f() a été
appelée (dans le premier cas, il faut sauter à la ligne 3 du code machine, dans le
second cas à la ligne 6) ?
On voit qu’il faut, à chaque appel, disposer d’une instruction spéciale qui va sauvegarder
l’adresse de retour (c’est-à-dire l’adresse de la ligne qui suit l’appel) à chaque appel, et d’une
autre instruction spéciale pour simuler le return, qui va sauter vers l’adresse sauvegardée.
On pourrait penser effectuer cette sauvegarde dans une registre, malheureusement, ce ne
sera pas suffisant en général. En effet, une fonction f() peut appeler une fonction g() qui
appelle elle-même une fonction h(). En général, il faut donc retenir la séquence des adresse
de retour : une fois h() terminée, il faudra revenir à al bonne adresse dans g(), tout en re-
tenant l’adresse de retour dans h() pour terminer g(). On voit donc qu’il faut conserver les
adresses de retour successives sur une pile :
— à chaque appel de procédure, on sauvegarde l’adresse de la ligne qui suit en faisant
un Push sur la pile et on saute dans la procédure. Les appels imbriqués successifs vont
donc accumuler les adresses de retour au sommet de la pile, en gardant l’appel le plus
188
6.2. Instructions machine typiques
P YTHON :
1 def f ( x ):
2 return x +1
3
4 x = 5
5 print ( f ( x ))
6 x = 42
7 print ( f ( x ))
Langage machine :
1 mov eax , 5 ; la valeur du paramètre
2 call f
3 ... ; afficher le résultat qui est dans eax
4 mov eax , 42 ; nouveau paramètre
5 call f
6 ... ; afficher le résultat qui est dans eax
7 jmp end ; fin du programme
8
9 f:
10 inc eax
11 ret ; revient à la ligne qui suit le dernier appel
189
6. Leçon 13 – Langage machine
1 mov eax , 0
2 mov ecx , 10 ; initialisation du compteur
3 debut :
4 add eax , 50 ; sera exécutée 10 fois
5 loop debut
F IGURE 6.5. – Un exemple de boucle en langage machine i486, exploitant l’instruction dédiée
loop, qui utilise ecx comme compteur.
ß
Le bas de la Figure 6.4 montre une manière d’effectuer les appels de fonction en
langage machine i486 (on a utilisé le registre eax pour transmettre le paramètre
et la valeur de retour, bien que ce ne soit pas toujours comme cela que l’on pro-
cède, cfr. infra). Pour que cela fonctionne, une pile est maintenue en mémoire,
et le registre esp indique son sommet. Cette pile ne doit pas être confondue avec
la pile que nous avons utilisée dans l’IJVM (voir Chapitre 5). En effet :
— la pile de l’i486 sert essentiellement à gérer les appels de fonctions et non
pas à stocker les valeurs des opérandes des instructions ;
— la pile de l’i486 croît « vers le bas >> : on décrémente le pointeur de pile
pour ajouter des éléments.
Chaque call fait un Push de l’adresse suivante dans le code au sommet de la
pile. Ainsi, le call de la ligne 2 fait un Push de l’adresse de la ligne 3, et celui de
la ligne 5 fait un Push de l’adresse de la ligne 6. L’instruction ret rétablit eip à
l’adresse au sommet de la pile, et fait un Pop de cette dernière.
Boucles Certains processeurs possèdent des instructions dédiées aux boucles, dans un souci
d’optimisation, mais on peut réaliser ces dernières à l’aide de sauts et de comparaisons.
ß
La Figure 6.5 montre un exemple de l’utilisation de l’instruction loop de l’i486
pour réaliser une boucle. Cette instruction utilise le registre ecx (qui ne peut
donc pas être modifié dans le corps de la boucle sans quoi cela interférerait sur
son comportement) comme compteur. Le registre est initialisé au nombre d’ité-
rations désiré de la boucle (ici, 10), puis l’instruction loop debut décrémente
ecx saute à l’étiquette debut: si ecx n’est pas nul.
190
6.3. Mécanismes de protection
Ce modèle simple sera suffisant pour la suite de nos explications. Néanmoins, la réalité est
souvent plus complexe sur de vrais processeurs :
191
6. Leçon 13 – Langage machine
ß
Les instructions du processeur i486 sont réparties en trois classes [12, Chapitre
6] :
1. les instructions privilégiées, qui sont celles qui affectent le mécanisme de
protection (par exemple, les instructions qui permettent de changer le
statut des tâches ou des segments) ;
2. les instructions sensibles, qui sont essentiellement les instructions d’en-
trée/sortie ;
3. les autres instructions.
Les instructions appelées privilégiées et sensibles sur l’i486 correspondent donc
à ce que nous avons appelé les instructions privilégiées dans notre modèle
simple.
Le processeur i486 possède quatre modes d’exécution, numérotés de 0 à 3, le
mode 0 étant le plus privilégié. Il s’agit donc d’un raffinement de notre modèle
dans lequel il n’existe que deux modes.
Le système de protection primaire de l’i486 fonctionne au niveau des segments
de mémoire (voir l’explication du modèle mémoire de l’i486 dans l’exemple de
la page 182). Chaque segment possède un niveau de privilège (lui aussi de 0 à
3, stocké dans le descripteur de segment qui est chargé dans un registre comme
cs), qui indique quelles instructions peut exécuter le programme qui s’y trouve :
1. les instructions privilégiées ne peuvent être exécutées qu’à partir de seg-
ments de niveau 0. Typiquement, ce sera uniquement le système d’exploi-
tation (Voir Partie V) qui s’exécutera à ce niveau-là.
2. les instructions sensibles ne peuvent être exécutées qu’à partir de seg-
ments dont le niveau de privilège est inférieur ou égal au niveau indi-
qué dans le champ IOPL du registre d’état eflags. Cela permet donc de
contrôler de manière fine quels programmes peuvent exécuter ces ins-
tructions.
Par ailleurs, l’i486 possède également un autre mécanisme de gestion de la mé-
moire, appelé pagination, que nous étudierons en détail à la Section 9. Avec
cette technique, le programme est également découpé en portions qui s’ap-
pelle des pages, et chaque page possède un niveau de privilège. Pour les pages,
il n’existe par contre que deux niveaux de privilège : le mode superviseur (qui
correspond aux modes 0, 1 et 2 des segments, et qu’on réserve pour les pages
du système d’exploitation) et le mode utilisateur (qui correspond au privilège le
plus bas des segments, soit le mode 3).
À chaque fois qu’une instruction machine doit être exécutée par le CPU, celui-ci
effectue toutes les vérifications nécessaires (à vrai dire, le mécanisme est encore
plus complexe [12, Chapitre 6 sqq.]) afin de s’assurer que l’instruction peut être
exécutée. L’architecture de l’i486 présentée à la Figure 1.4 présente d’ailleurs un
module appelé Control & Protection Test Unit. Si l’instruction ne peut pas être
exécutée, un mécanisme de report d’erreur sera déclenché : nous l’étudierons
en détail dans le Chapitre 7.
192
6.4. Représentation des instructions et des opérandes
Comme on le voit, aucune situation n’est idéale, et il vaudrait mieux disposer d’une taille
variable pour les opcodes : on choisira des opcodes courts pour les opérations ayant besoin
de nombreux ou de longs opérandes, et on réservera les opcodes longs pour les instructions
avec peu ou pas d’opérandes. Mais cela doit être fait avec précautions, comme le montre
l’exemple suivant.
193
6. Leçon 13 – Langage machine
31 24 23 16 15 8 7 0
1010 1010 1111 1111 ... ...
Une solution pour contourner ce problème est de réserver une valeur sur huit bits qui :
(1) ne sera jamais utilisé comme opcode sur 8 bits ; et (2) sera toujours le préfixe (les 8 bits de
poids fort) de tous les opcodes sur 16 bits. Cette solution résout le problème si on souhaite
des opcodes sur 8 et 16 bits uniquement, elle peut s’adapter à d’autre cas. La seule chose qui
compte est d’éviter les ambiguïtés comme celle présentée ci-dessus.
ß
Nous résolvons le problème de l’exemple précédent en réservant la valeur
1111 1111 comme préfixe des opcodes 16 bits. Nous avons ainsi :
— 28 − 1 opcodes sur 8 bits : de 0000 0000 à 1111 1110 ; et
— 28 opcodes sur 16 bits : de 1111 1111 0000 0000 à 1111 1111 1111 1111.
Soit un total de 29 − 1 = 511 opcodes. On voit que tous les opcodes sur 16 bits
commencent bien par la valeur réservée 1111 1111.
ß
Sur l’i486, les opcodes à taille variable sont utilisés, mais la taille des instructions
n’est pas fixe pour autant. Par exemple, l’instruction qui incrémente un des huit
registres eax, ebx, ecx, edx, esp, ebp, esi, edi tient sur un octet : 0100 0r eg
où l’opcode est donc 01000, et r eg est une valeur sur trois bits permettant de
choisir le registre. L’instruction ret, qui permet de revenir d’un appel de fonc-
tion (cfr. infra) tient également sur un octet mais n’a pas de paramètre. Les huit
bits sont donc utilisés pour l’opcode : 1100 0011. Enfin, l’instruction qui charge
le contenu d’un des quatre registres de travail vers le registre de test tr3 (utilisé
à des fins de déboggage) tient, elle sur 3 octets, avec un opcode de 21 bits :
23 16 15 8 7 3 20
0000 1111 0010 0110 11 011 r eg
6.4.2. Adressage
Voyons maintenant quelles sont les différentes techniques qui permettent au processeur
d’obtenir les valeurs des opérandes. En effet, ces valeurs peuvent se trouver soit dans les re-
194
6.4. Représentation des instructions et des opérandes
gistres, soit en mémoire (quand elles ne sont pas données directement dans l’instruction elle-
même). Ces techniques sont connues sous le nom d’adressage 6 .
Adressage immédiat Dans ce cas, la valeur de l’opérande est spécifiée directement dans
l’instruction. Cette valeur est donc forcément une constante et est de taille limitée (par le
nombre de bits réservés pour l’opérande). Si la taille des instructions est fixe, cette technique
peut se révéler très efficace car le processeur n’a plus besoin de faire de lecture supplémen-
taire en mémoire pour accéder à l’opérande.
Adressage direct Dans ce cas, c’est l’adresse mémoire où se trouve la valeur de l’opérande
qui est donnée dans l’instruction. Le processeur doit donc effectuer une lecture en mémoire
pour obtenir l’opérande. Sur certaines architecture, cette technique peut être impossible à
appliquer en raison de la taille des adresses (qui sont égales à la taille des instructions).
Adressage par registre Avec cette technique, la valeur de ou des opérande(s) se trouve(nt)
dans un ou des registre(s), et l’instruction spécifie dans quel(s) registre(s) se trouve(nt) la ou
les valeurs. Le programme doit donc inscrire les valeurs à manipuler dans les registres avant
d’appeler l’instruction.
Adressage par registre avec indirection Dans ce cas, la valeur de l’opérande se trouve
en mémoire, et son adresse (qui est, en général, trop longue pour tenir dans une instruction)
se trouve dans un registre. L’instruction spécifie quel registre contient l’adresse. L’exécution
de l’instruction sera donc ralentie par l’accès mémoire, car le processeur doit lire la valeur
en mémoire avant de pouvoir exécuter l’instruction. Beaucoup d’architectures limitent le
nombre d’opérandes que l’on peut accéder ainsi, afin de limiter le nombre d’accès mémoire
durant une instruction.
ß
Illustrons ces quatre techniques avant de passer à la suite. La Figure 6.6 illustre
les modes d’adressage immédiat, direct, par registre, et par registre avec indi-
rection, dans le langage machine de l’i486 7 . Grosso modo, l’instruction machine
mov copie le second opérande dans la première. Dans ces quatre exemples, on
assigne donc une valeur au registre eax. L’utilisation des crochets [] signifie que
la valeur entre crochets doit être interprétée comme une adresse.
Adressage indexé Il s’agit d’un raffinement de la technique précédente : l’adresse est ob-
tenue en combinant deux informations :
1. une adresse mémoire constante de base A, donnée dans l’instruction ;
2. un décalage O par rapport à A, stockée dans un registre donné ;
L’opérande est alors lu à l’adresse A + O.
6. Ce nom nous semble choisi de façon maladroite, car certaines techniques d’adressage n’utilisent pas
d’adresse. . .
195
6. Leçon 13 – Langage machine
F IGURE 6.6. – Illustration de quatre modes d’adressage par le langage machine de l’i486.
Adressage indexé avec base Cette technique fonctionne comme la précédente, sauf que
l’adresse de base A est, elle aussi, donnée dans un registre.
Ces deux derniers modes d’adressage sont surtout utiles pour accéder aux différents élé-
ments d’un tableau. Les tableaux sont une structure de données très communes dans les
langages de programmation 8 de haut niveau, contenant une séquence d’éléments de même
type (par exemple, 10 entiers consécutifs). Ces éléments sont disposés en mémoire à partir
d’une adresse A, de manière contiguë. Ainsi, si un élément occupe par exemple 4 octets en
mémoire, le premier élément (appelons le T[0]) se trouve à l’adresse A, le second (T[1]) à
l’adresse A + 1 × 4 = 1 + 4, le troisième à l’adresse A + 2 × 4 = A + 8,. . . , le i e (T[i ]) à l’adresse
A + i × 4. . .
ß
L’i486 supporte un mode d’adressage qui recouvre à la fois l’adressage indexé
et l’adressage indexé avec base (et même d’avantage). On peut spécifier une
adresse sous la forme :
R 1 + f × R 2 +C
M8N
196
6.5. Exercices
6.5. Exercices
Pour ces exercices nous allons nous baser sur la version 32 bits du jeu d’instructions x86,
que l’on retrouve notamment sur l’Intel 486DX étudié au cours théorique (bien qu’il ait im-
plémenté pour la première fois sur le processeur 80386 d’Intel en 1985). Pour ce faire, nous
allons utiliser l’outil SASM (SimpleASM) qui est à la fois un outil de développement en lan-
gage machine/assembleur x86 et un simulateur de processeur permettant d’observer l’effet
du langage machine sur les registres du processeur. SASM est un logiciel libre qu’on peut
télécharger à l’adresse https://dman95.github.io/SASM/english.html.
Registres Les registres et les adresses font 32 bits. Les principaux registres s’énumèrent
comme suit :
— Les 4 registres principaux : eax (accumulateur), ebx, ecx et edx ; item Le registre eip :
Instruction Pointer, équivalent du registre PC ;
— Le registre EFlags : bits de contrôle.
Notez que pour tous ces registres (sauf EFlags), on peut accéder aux 16 bits de poids faible
en enlevant le préfixe e, par exemple, ax représente les 16 bits de poids faible d’eax.
Flags Le registre EFlags contient différents bits (ou flags) qui sont modifiés par certaines
instructions afin d’apporter plus d’informations sur le résultat d’une opération. En voici 4 qui
nous intéresserons dans ces exercices :
1. Le carry flag (C) : Lors d’une somme ou d’un soustraction, ce flag prend la valeur du
dernier report ou du dernier emprunt.
2. Le parity flag (P) : celui-ci est mis à 1 si le résultat d’une opération arithmétique ou
logique est composé d’un nombre pair de 1. Il est mis à 0 sinon.
3. Le zero flag (Z) : si le résultat d’une opération arithmétique ou logique est nul, alors ce
flag est mis à 1. Il est mis à 0 sinon.
4. Le sign flag (S) : si le résultat d’une opération arithmétique est négative (c’est-à-dire
que le bit de poids fort est 1), ce bit est mis à 1. Il est mis à 0 sinon.
Ces flags seront utilisés pour effectuer des branchements à l’aide des différentes instruc-
tions de saut.
Registres étendus Certaines instructions (par exemple mul) permettent d’utiliser deux re-
gistres de 32 bits comme un seul grand registre de 64 bits. Dans la suite de ce document, nous
dénoterons par edx:eax le registre étendu de 64 bits formé en concaténant les bits des re-
gistres edx et eax (en d’autres termes, si edx:eax représente un nombre de 64 bits, ses 32
bits de poids fort sont stockés dans le registre edx tandis que les 32 bits de poids faible sont
stockés dans le registre eax).
197
6. Leçon 13 – Langage machine
Données Dans ces exercices nous manipulerons uniquement des données booléennes, et
des données entières (sur 32 bits ou sur 64 bits si on utilise les registres étendus), qui sont
stockés en complément à deux. Dans SASM, les entiers peuvent être indiqués soit en base 10
soit en base 16. Pour indiquer qu’on utilise des nombres en base 16, on a deux possibilités :
1. soit on ajoute un h à la fin du nombre, qui doit obligatoirement commencer par un
chiffre (on peut ajouter un zéro) ;
2. soit on ajoute 0x au début du nombre.
Par exemple, 25510 = ff16 se note soit 0ffh soit 0xff.
Langage machine La Table 6.1 donne une liste d’instructions x86 32 bits qui seront utiles
pour ces exercices. Il s’agit bien entendu d’un extrait de ce langage machine.
1 if ebx == ecx :
2 edx = ecx
3 else :
4 ecx = edx
Voici son équivalent en assembleur x86 :
Ex. 35 Recopiez le code ci-dessous dans SASM et observez-en l’effet. Utilisez le mode debug de
SASM afin d’exécuter votre code instruction par instruction. Dans le menu debug, demandez
l’affichage des registres. Observez leur contenu pendant l’exécution afin de vérifier le bon fonc-
tionnement de votre programme. Essayez de placer différentes valeurs dans les registres abx et
ecx pour observer la façon dont le branchement s’opère.
9. En réalité, les instructions de saut ont besoin d’une adresse sur 32 bits. Toutefois, les langages d’assemblage
proposent de définir des étiquettes qui sont converties en de vraies adresses par l’assembleur au moment de
produire le code machine correspondant au programme. C’est d’ailleurs la différence essentielle entre langage
machine et assembleur, différence que nous avons assez bien éludée dans le cours.
198
6.5. Exercices
Instruction Description
mov R1, R2 Copie la valeur du registre R2 dans R1
mov R, $n$ Écrit la valeur n (32 bits) dans le registre R
xchg R1, R2 Échange le contenu des registres R1 et R2
jmp LAB Saut inconditionnel à l’étiquette LAB:
jnc/jc LAB Saut si le carry flag est à 0 (pas de report ou emprunt) / 1 (report ou emprunt)
jnz/jz LAB Saut si le zero flag est à 0 (résultat non nul) / 1 (résultat nul)
jns/js LAB Saut si le sign flag est à 0 (résultat positif) / 1 (résultat négatif)
jpo/jpe LAB Saut si le parity flag est à 0 (nombre impair de 1) / 1 (nombre pair de 1)
jecxz LAB Saut si le registre ecx est à 0
inc/dec R Incrémente / décrémente le registre R
add R1, R2 Ajoute le registre R2 au registre R1
adc R1, R2 Ajoute le registre R2 et le carry flag au registre R1
add R, $n$ Ajoute la valeur n au registre R
adc R, $n$ Ajoute la valeur n et le carry flag au registre R
sub R1, R2 Soustrait le registre R2 du registre R1
sbb R1, R2 Soustrait le registre R2 et le carry flag du registre R1
sub R, $n$ Soustrait la valeur n du registre R
sbb R, $n$ Soustrait la valeur n et le carry flag du registre R
and R1, R2 Met dans R1 le résultat de R1 ∧ R2
and R, $n$ Met dans R le résultat de R ∧ n
or R1, R2 Met dans R1 le résultat de R1 ∨ R2
or R, $n$ Met dans R le résultat de R ∨ n
xor R1, R2 Met dans R1 le résultat de R1 XORR2
xor R, $n$ Met dans R le résultat de R XORn
cmp R1, R2 Met à jour les flags selon le résultat de R1 − R2
cmp R, $n$ Met à jour les flags selon le résultat de R − n
199
6. Leçon 13 – Langage machine
Ex. 36 Comme pour l’exemple précédent, recopiez le code ci-dessous dans SASM et observez-en
l’effet.
Ex. 38 Écrivez un programme qui place une valeur (au choix) dans le registre ebx, une autre
(au choix) dans le registre ecx et finalement leur somme dans le registre eax.
Ex. 39 Écrivez un programme qui calcule dans eax la valeur du contenu du registre ebx puis-
sance 8, ceci en utilisant seulement 3 multiplications.
Ex. 40 Écrivez un programme qui remplace un entier non signé situé dans eax par 1 s’il est
pair et par 0 s’il est impair.
Ex. 41 Écrivez un code assembleur qui prend un entier non signé (présent dans le registre eax)
et stocke celui-ci modulo 8 dans le registre ebx.
Ex. 42 Supposons que trois nombres sont stockés dans les registres eax, ebx et ecx. Écrivez un
programme qui trie ces trois nombres, c’est-à-dire qu’après exécution, le plus grand sera stocké
dans eax et le plus petit dans ecx.
200
6.5. Exercices
Ex. 43 Écrivez un programme qui calcule la différence de deux nombres de 64 bits en utilisant
la technique des registres étendus (cfr. supra). On supposera que le premier nombre se trouve
dans la paire de registres edx:eax et le second dans la paire ebx:ecx. Le résultat devra être
stocké dans la paire edx:eax.
6.5.5. Corrections
Correction de l’exercice 37
1 mov eax , 4
2 mov ebx , 5
3 add eax , ebx
4 ret
Correction de l’exercice 38
1 mov ebx , 4
2 mov ecx , 5
3 mov eax , ebx
4 add eax , ecx
5 ret
Correction de l’exercice 39
1 mov ebx , 2
2 mov eax , ebx
3 mul ebx
4 mov ebx , eax
5 mul ebx
6 mov ebx , eax
7 mul ebx
8 ret
201
6. Leçon 13 – Langage machine
Correction de l’exercice 40
Correction de l’exercice 41
Correction de l’exercice 42
Correction de l’exercice 43
Correction de l’exercice 44 Le code passera 4 fois par la ligne label1: avec les valeurs suivantes dans les re-
gistres :
202
6.5. Exercices
M8N
203
204
7. Leçon 14 – Le mécanisme d’interruption
Jusqu’à présent, nous avons donné dans ces notes une description de l’ordinateur qui pré-
suppose une machine assez éloignée de ce que nous connaissons bien : un ordinateur qui
n’exécute qu’un seul programme à la fois, et dont on doit attendre la fin de l’exécution pour
en obtenir les résultats. C’est à peu près ce que permettaient les ordinateurs jusqu’au milieu
des années 1960. . . Aujourd’hui, nous sommes familiers des ordinateurs qui sont capables
d’exécuter plusieurs programmes en même temps, et avec lesquels il est possible d’interagir,
à travers des périphériques comme un clavier ou une souris.
Posons nous un instant la question suivante : « comment implémenter un système qui est
capable de répondre aux demandes du clavier ou de la souris tout en effectuant un autre
traitement ? » Avec ce que nous avons décrit jusqu’à présent, la seule solution est que le pro-
gramme qui est exécuté (le traitement dans notre question) interroge de manière régulière
le contrôleur du clavier pour vérifier si une touche a été pressée (et, ajouterons-nous, assez
régulièrement de préférence, afin d’être capable de répondre promptement à la demande de
l’utilisateur qui a pressé la touche et qui est certainement déjà occupé à s’impatienter. . . )
On peut aisément concevoir qu’une telle solution n’est pas pratique : elle obligerait tous les
programmeurs à ajouter dans leur code ces vérifications, qui, par ailleurs, consomment du
temps processeur. Au contraire, il serait plus commode que le fait qu’une touche soit pressée
au clavier soit signalé au processeur, qui pourrait alors s’interrompre temporairement dans
son traitement en cours pour traiter la demande émanant du clavier (ou d’un autre périphé-
rique). C’est exactement l’objet du mécanisme d’interruption, qui est supporté par la plupart
des processeurs modernes. Nous allons en exposer les principes généraux dans ce chapitre.
205
7. Leçon 14 – Le mécanisme d’interruption
3
Mémoire
CPU Périph
primaire
Bus
2
1
Mémoire
CPU Périph
primaire
2
Bus
2’
À l’issue de ces étapes, le processeur peut alors commencer à traiter les données depuis la
mémoire. Cette solution est malheureusement très inefficace et consomme beaucoup de
temps processeur car le périphérique est « plus lent » que le processeur : il faut en général
plus de temps au périphérique pour fournir un octet de données qu’il n’en faut au processeur
pour le traiter. Par ailleurs, le processeur consacre beaucoup de temps à copier les données
en mémoire.
Pour résoudre le second problème, on peut utiliser le mécanisme d’accès direct en mémoire,
que nous avons vu au début du cours (voir Section 3.1). On a alors le comportement suivant :
1. le processeur envoie au périphérique un ordre de lecture pour l’ensemble des données
à lire ;
2. le périphérique lit les données et les écrit en mémoire. Pendant ce temps le processeur
attend, en interrogeant régulièrement (2’) le périphérique pour savoir quand la lecture
est terminée.
Une fois ce travail terminé, les données sont déjà en mémoire et le processeur peut traiter les
données.
Néanmoins, le processeur doit encore attendre le périphérique. Dans le scénario que nous
avons décrit ci-dessus, on parle d’attente active, car le processeur interroge de manière répé-
tée le périphérique pour savoir quand il a terminé. Il serait plus pratique que le processeur
puisse lancer l’ordre de lecture au périphérique, et continuer à exécuter un autre traitement
durant le temps de lecture, comme illustré à la Figure 7.2 : après l’envoi de l’ordre de lecture
(1) et la fin de la lecture (2) à proprement parler, le périphérique signale au processeur (3)
206
7.2. Déroulement d’une interruption
Mémoire 2
CPU Périph
primaire
3 Bus
qu’il a fini son traitement, pour interrompre le processeur dans son travail en cours. Le pro-
cesseur devra alors exécuter une routine de traitement pour exploiter l’information produite
par le périphérique, avant de revenir au point où il avait été interrompu. Ce comportement
est rendu possible par le mécanisme d’interruption que nous allons décrire maintenant.
207
7. Leçon 14 – Le mécanisme d’interruption
Dans notre modèle, la machine n’est interruptible qu’en mode esclave. Les de-
mandes d’interruption reçues pendant que la machine est en mode maître sont
déferrées.
208
7.2. Déroulement d’une interruption
est mis à 1 dès qu’une demande d’interruption lui est envoyée 1 . Voici un commentaire de
cette boucle :
— Le nœud en forme de losange est un test. La branche du bas sera prise si le test est faux,
et celle de droite si le test est vrai.
— La branche du bas, qui correspond à l’interprétation d’une instruction machine (comme
sur la Figure 3.8) est donc prise quand il n’y a pas de demande, ou quand la machine
n’est pas interruptible (et ce, même s’il y a une demande).
— Si, par contre, il y a une demande (IRQ=1) et que la machine est interruptible (mode
esclave), c’est la partie droite de la boucle qui est prise avant l’exécution de l’instruc-
tion machine. Les cinq nœuds à droite de la figure consistent donc à déterminer le
numéro d’IRQ (appelé i ), remettre le flag IRQ à 0, sauver PC, passer en mode maître
(non-interruptible), et à placer le vecteur d’interruption adéquat dans PC. Ensuite, la
boucle continuera « normalement » : on chargera dans IR l’instruction pointée par PC,
c’est-à-dire le vecteur d’interruption (la première adresse du gestionnaire) qui vient
d’être sélectionné, ce qui va démarrer l’exécution du gestionnaire d’interruption.
Il est important de noter que le début de l’interruption est réalise de façon ma-
térielle : les nœuds de la Figure 7.2 ne représentent pas des instructions (ce n’est
donc pas du logiciel), mais bien le fonctionnement interne du CPU (ces traite-
ments sont donc réalisés au niveau du micro-code).
Il est également important de noter que le test visant à savoir si une demande
d’interruption est présente (et donc le déclenchement de l’exécution du ges-
tionnaire) prend toujours place entre deux instructions machine. On n’inter-
rompt donc pas la machine au moment précis où la demande est formulée, cer-
tainement pas au milieu de l’exécution d’une instruction machine.
1. Nous ne traitons donc dans ce schéma que des demandes qui proviennent de l’extérieur du processeur.
Nous verrons dans l’exemple de l’i486DX que c’est aussi une simplification.
2. Nous verrons plus tard qu’il est parfois souhaitable que le gestionnaire influence l’exécution future du pro-
gramme interrompu, en lui transmettant, par exemple, une valeur calculée. Mais ce ne sera pas toujours dési-
rable, et nous devons donc concevoir le mécanisme d’interruption de telle manière qu’un retour transparent au
programme interrompu soit possible. Qui peut le plus, peut le moins. . .
209
7. Leçon 14 – Le mécanisme d’interruption
IRQ=1 et
OUI Déterminer le numéro d’IRQ i
interruptible ?
IRQ ← 0
Passer en mode
maître / non-interruptible
PC ← PC+1
210
7.2. Déroulement d’une interruption
7.2.4. Exemple
Regardons maintenant un exemple détaillé de déroulement d’une interruption. Les Fi-
gure 7.4 à 7.7 présentent un tel exemple. Tout au long de cet exemple, nous supposons que
nous avons une machine dont le processeur possède deux registres R 1 et R 2 (rectangle à coins
arrondis dans le haut des figures). Le processeur possède un registre appelé IRQ qui est mis
à 1 par le bus quand une demande d’interruption a lieu. Nous représentons le contenu de la
3. On parle d’instruction atomique, car les deux actions ne peuvent pas être dissociées.
211
7. Leçon 14 – Le mécanisme d’interruption
mémoire (rectangle à coins arrondis sous le processeur) qui est pertinent pour notre exemple,
à savoir : quelques instructions du programme en cours d’exécution (à partir de l’adresse
256) ; les premières et dernières instructions de deux gestionnaires d’interruption ; le début de
la table des vecteurs d’interruption (dont l’adresse n’est pas montrée explicitement mais est
supposée connue du processeur) ; et la pile (à nouveau, accessible par le processeur). Nous
avons mis en évidence en bleu et en vert les cases mémoires qui contiennent respectivement
l’ensemble des instructions du gestionnaire numéro et numéro 1.
ß
Notre exemple commence à la Figure 7.4 : le processeur vient d’exécuter l’ins-
truction ADD R 1 , R 2 à l’adresse 256 (elle est présente dans IR et PC a déjà été
incrémenté). Le processeur est toujours en mode esclave. Une demande d’in-
terruption a eu lieu, et le processeur la détecte. Le processeur réalise donc le
comportement dans la branche à droite de la Figure 7.3. Il communique avec les
périphériques sur le bus pour réaliser que le numéro de demande d’interruption
est i = 1 ; passe en mode maître ; sauvegarde la valeur de PC sur la pile ; et recopie
dans PC adresse trouvée dans la case i = 1 de la table des vecteurs d’interrup-
tion, soit 2048. Nous nous retrouvons alors à la situation de la Figure 7.5. À noter
que le registre IRQ est aussi repassé à 0, car le processeur traite maintenant la
requête (cela permet à d’autres requêtes d’être enregistrées).
Parmi les premières instructions du gestionnaire d’interruption, il est fort pro-
bable que nous trouvions des sauvegardes des registres de travail R 1 et R 2 . En
effet, le gestionnaire d’interruption étant lui-même un programme en langage
machine, celui-ci est tout à fait susceptible d’utiliser les registres de travail.
Or, en règle générale, le programme qui a été interrompu a besoin de retrou-
ver, après l’interruption, les valeurs qui s’y trouvaient avant. Sur notre exemple,
l’instruction à l’adresse 257 (qui est celle qu’on s’apprêtait à exécuter quand le
programme utilisateur a été interrompu) contient d’ailleurs une instruction qui
utilise les valeurs de R 1 et de R 2 . La situation après l’exécution de l’instruction à
l’adresse 2048 est montrée à la Figure 7.6.
Ensuite, le gestionnaire d’interruption à proprement parler s’exécute. . . Au bout
d’un certain temps, nous nous retrouverons dans la situation où on vient d’exé-
cuter l’instruction qui précède (logiquement) celle à l’adresse 2051, et où on
s’apprête à exécuter cette dernière. Cette situation est montrée à la Figure 7.7.
On a ici supposé que le gestionnaire d’interruption à mis les valeurs dans R 1
et R 2 .
Après l’exécution de l’instruction de restauration des registres de travail à
l’adresse 2051 (et donc avant l’exécution de l’IRET à l’adresse 2052), nous nous
retrouvons dans la situation de la Figure 7.8. Notons qu’à ce point, les registres
R 1 et R 2 ont été restaurés, mais la machine est toujours en mode maître et PC
n’a pas encore été restauré, pour sa part.
212
7.2. Déroulement d’une interruption
Processeur
IRQ Mode
1 Esclave
R1 R2 PC IR
3 4 257 ADD R 1 , R 2
Mémoire Principale
..
Table des vecteurs d’interruption : .
256 : ADD R 1 , R 2
0 1 ··· 257 : MUL R 1 , R 2
1024 2048 ··· ..
.
1024 : ···
..
Pile : .
1042 : IRET
..
.
2048 : sauver R 1 , R 2
..
.
2051 : restaurer R 1 , R 2
2052 : IRET
..
.
F IGURE 7.4. – Exemple d’interruption (1). Situation initiale : on vient d’exécuter l’instruction
à l’adresse 256, et on s’apprête à prendre en compte la demande d’interruption.
L’interruption aura donc lieu entre les instructions aux adresses 256 et 257.
213
7. Leçon 14 – Le mécanisme d’interruption
Processeur
IRQ Mode
0 Maître
R1 R2 PC IR
3 4 2048 ADD R 1 , R 2
Mémoire Principale
..
Table des vecteurs d’interruption : .
256 : ADD R 1 , R 2
0 1 ··· 257 : MUL R 1 , R 2
1024 2048 ··· ..
.
1024 : ···
..
Pile : .
1042 : IRET
..
.
2048 : sauver R 1 , R 2
..
.
PC = 257 2051 : restaurer R 1 , R 2
2052 : IRET
..
.
F IGURE 7.5. – Exemple d’interruption (2). Situation juste avant d’exécuter le gestionnaire
d’interruption.
214
7.2. Déroulement d’une interruption
Processeur
IRQ Mode
0 Maître
R1 R2 PC IR
3 4 2049 sauver R 1 , R 2
Mémoire Principale
..
Table des vecteurs d’interruption : .
256 : ADD R 1 , R 2
0 1 ··· 257 : MUL R 1 , R 2
1024 2048 ··· ..
.
1024 : ···
..
Pile : .
1042 : IRET
..
.
2048 : sauver R 1 , R 2
R2 = 4
..
R1 = 3 .
PC = 257 2051 : restaurer R 1 , R 2
2052 : IRET
..
.
215
7. Leçon 14 – Le mécanisme d’interruption
Processeur
IRQ Mode
0 Maître
R1 R2 PC IR
42 27 2051 ???
Mémoire Principale
..
Table des vecteurs d’interruption : .
256 : ADD R 1 , R 2
0 1 ··· 257 : MUL R 1 , R 2
1024 2048 ··· ..
.
1024 : ···
..
Pile : .
1042 : IRET
..
.
2048 : sauver R 1 , R 2
R2 = 4
..
R1 = 3 .
PC = 257 2051 : restaurer R 1 , R 2
2052 : IRET
..
.
F IGURE 7.7. – Exemple d’interruption (4). Situation juste avant d’exécuter l’instruction à
l’adresse 2051.
216
7.2. Déroulement d’une interruption
Processeur
IRQ Mode
0 Maître
R1 R2 PC IR
3 4 2052 Restaure R 1 , R 2
Mémoire Principale
..
Table des vecteurs d’interruption : .
256 : ADD R 1 , R 2
0 1 ··· 257 : MUL R 1 , R 2
1024 2048 ··· ..
.
1024 : ···
..
Pile : .
1042 : IRET
..
.
2048 : sauver R 1 , R 2
..
.
PC = 257 2051 : restaurer R 1 , R 2
2052 : IRET
..
.
F IGURE 7.8. – Exemple d’interruption (5). Situation juste après l’exécution de l’instruction de
restauration à l’adresse 2051, avant l’exécution de l’IRET à l’adresse 2052.
217
7. Leçon 14 – Le mécanisme d’interruption
Processeur
IRQ Mode
0 Esclave
R1 R2 PC IR
3 4 257 IRET
Mémoire Principale
..
Table des vecteurs d’interruption : .
256 : ADD R 1 , R 2
0 1 ··· 257 : MUL R 1 , R 2
1024 2048 ··· ..
.
1024 : ···
..
Pile : .
1042 : IRET
..
.
2048 : sauver R 1 , R 2
..
.
2051 : restaurer R 1 , R 2
2052 : IRET
..
.
F IGURE 7.9. – Exemple d’interruption (6). Situation après l’exécution de l’IRET : le pro-
gramme interrompu reprend son exécution normalement.
218
7.3. Applications du mécanisme d’interruption
Interruptions interruptibles Nous avons fait en sorte que le processeur ne soit plus inter-
ruptible à partir du moment où il exécute un gestionnaire d’interruption (en mode maître),
afin de simplifier les concepts exposés. Mais en pratique, cela peut se révéler limitatif. Le mé-
canisme d’interruption est utilisé, notamment, pour permettre à des périphériques d’entrée
comme le clavier ou la souris de signaler au processeur qu’une touche ou un bouton ont été
pressés. En interdisant qu’un gestionnaire soit interrompu, on se prive donc de la possibi-
lité qu’il interagisse avec un de ces périphériques. On se prive aussi de la possibilité d’avoir,
par exemple, une combinaison de touches que l’utilisateur puisse presser pour arrêter un
gestionnaire d’interruption qui « bogue ».
On peut dès lors envisager une mécanisme d’interruptions interruptibles avec un système
de priorités. On aura alors, par exemple, qu’une demande d’interruption de type i peut in-
terrompre un gestionnaire de type j avec j ≥ i , mais pas ceux de type k avec k < i . Si on a
n types d’interruptions possibles, on peut alors garder une table M de taille n de masques bi-
naires : on mettre M [i ] à 1 si et seulement si une interruption de type i peut avoir lieu. Quand
le programme s’exécute en mode esclave, toutes les cases de M seront à 1. Si on exécute un
219
7. Leçon 14 – Le mécanisme d’interruption
gestionnaire de type i , on met à 0 toutes les cases de numéro i + 1, i + 2,. . . n. On doit adapter
le test en début de boucle (Figure 7.3) pour tenir compte de ce masque. Enfin, il faut égale-
ment stocker toutes les adresses de retour (sauvegardes de PC), dans une pile, afin que celle
qui est soit au sommet soit la plus récente.
Restauration des registres Nous verrons dans le chapitre sur les systèmes d’exploitation
(Chapitre 8) que le mécanisme d’interruption peut être utilisé à des fins de communication
entre les programmes utilisateurs et le système d’exploitation. Dans ce cas, il sera parfois
nécessaire que le gestionnaire d’interruption renvoie une information au programme inter-
rompu. L’interruption ne sera alors plus transparente, mais cela peut s’avérer nécessaire, no-
tamment quand c’est le programme lui-même qui fait la demande d’interruption.
Afin d’obtenir ce résultat, il y a plusieurs possibilités. Soit, on évite de restaurer (tous) les re-
gistres depuis leur sauvegarde. Soit, on installe les nouvelles valeurs dans la sauvegarde, afin
que ce soit celles-ci qui soient restaurées. Cette dernière solution peut paraître inutilement
compliquée mais se justifie par le fait que sur certains processeurs, il existe une instruction
spécialisée qui restaure tous les registres en un seul coup.
Interruptions L’i486DX possède deux entrées qui correspondent à l’IRQ de notre modèle
(voir Figure 1.4, où ces entrées sont connectées au Request Sequencer) :
1. l’entrée INTR permet à un périphérique de signaler une demande d’interruption, qui
peut être masquée si le flag IF (interrupt-enable flag) est mis à 0. Il s’agit donc des in-
terruptions masquables. L’i486DX possède les instructions machines sti et cli qui
mettent respectivement le flag IF à 0 et à 1. Il s’agit d’instructions sensible (voir Cha-
pitre 6. Pour rappel, cela signifie qu’il y a des conditions sur le niveau de privilège du
programme qui tente d’exécuter ces instructions).
220
7.5. Cas de l’Intel 486DX
2. l’entrée NMI, qui signifie non-maskable interrupt. Comme son nom l’indique, ces in-
terruptions ne seront pas masquées. Les interruptions déclenchées par ce biais ne sont
pas interruptibles : on n’exécutera pas de nouveau gestionnaire tant qu’on n’est pas
revenu du gestionnaire précédent.
Sur l’i486DX, les interruptions masquables possèdent des vecteurs numérotés de 32 à 255.
En général, ce processeur est associé à un autre circuit externe, l’Intel 8259 [1]. Ce circuit
agit comme un médiateur entre les périphériques, qui peuvent déclencher des IRQ, et le pro-
cesseur. Le 8259 reçoit les demandes des périphériques, et transmet, via le bus, le numéro
d’interruption à l’i486DX.
Exceptions Les exceptions sont typiquement utilisées pour prendre en compte des situa-
tions inattendues, que le programme (en mode esclave) ne peut pas gérer. Les trois types
d’exceptions (fault, trap et abort) changent la manière dont l’exception est signalée, et la ma-
nière dont le programme reprend (le cas échéant) son exécution après le gestionnaire :
— dans le cas d’une fault, l’exception est détectée avant l’exécution de l’instruction, et
le processeur fait pointer le registre eip vers l’instruction qui a déclenché la fault. Des
exemples typiques sont : la division par zéro (qui possède le numéro de vecteur 0), la
détection d’un opcode invalide (vecteur numéro 6), ou le page fault (numéro 14) dont
nous parlerons en détail au Chapitre 9. Dans le cas d’une fault, le processeur ajoute
généralement un code d’erreur sur la pile, permettant au gestionnaire d’interruption
de diagnostiquer le problème est de le traite correctement.
— dans le cas des traps, l’exception est détectée juste après l’exécution de l’instruction qui
la cause. C’est typiquement ce qui se passe en cas d’« interruption logicielle », c’est-à-
dire quand le programme exécute l’instruction machine int, qui déclenche une inter-
ruption pour permettre au programme (en mode esclave) de faire un « appel supervi-
seur » ; soit de demander au système d’exploitation d’exécuter des instructions privi-
légiées à sa place. Dans le cas des traps, l’adresse de retour sauvegardée sur la pile ne
pointe pas vers l’instruction qui a déclenché l’interruption.
— enfin les aborts sont utilisés pour des problèmes graves, comme une défaillance maté-
rielle, et rien ne permet de garantir qu’on pourra reprendre l’exécution du programme
après le gestionnaire.
Table des vecteurs d’interruption L’i486DX possède un registre appelé idtr qui pointe
vers l’Interupt Descriptor Table (IDT). L’IDT correspond à la table des vecteurs d’interruption
dans notre modèle : elle associe un vecteur d’interruption à chaque numéro.
Passage en mode maître Pour assurer le passage en mode maître dans le gestionnaire, le
code qui contient ce gestionnaire doit être placé dans un segment de privilège 0 (voir Cha-
pitre 6 pour une discussion des modes de protection de l’i486DX). On a ainsi accès aux ins-
tructions privilégiées.
221
7. Leçon 14 – Le mécanisme d’interruption
Retour d’interruption Le langage machine de l’i486DX possède l’instruction iret qui ef-
fectue le retour d’interruption.
M8N
222
7.6. Exercices
7.6. Exercices
Supposons une machine avec deux registres de travail R1 et R2 qui contiennent initiale-
ment les valeurs 10 et 20. Supposons que la mémoire contienne les instructions machines
suivantes (les numéros de ligne indiqués sont les adresses) :
Comme on le voit, la mémoire contient un programme principal à l’adresse 74, et les ges-
tionnaires des interruptions numéro 4 et 2 sont respectivement aux adresses 136 et 270. Les
instructions ont leur sémantique habituelle.
Ex. 46 En supposant :
1. que la valeur initiale de PC est 74, où le CPU est en mode Esclave / Interruptible
2. qu’une IRQ numéro 4 intervient pendant qu’on exécute l’instruction d’adresse 74 ;
3. qu’une IRQ numéro 2 intervient pendant qu’on exécute l’instruction d’adresse 136 ;
donnez la séquence d’instructions exécutée. Pour chacune de ces instructions, indiquez le contenu
du stack et le contenu des registres R1, R2 au début de l’exécution de l’instruction, ainsi le mode
dans lequel elles est exécutée. on suppose que le stack est vide, initialement.
Ex. 47 Même exercice, mais en supposant que le gestionnaire d’interruption numéro 4 est in-
terruptible (le gestionnaire 2 reste non-interruptible).
7.6.1. Corrections
Correction de l’exercice 46
PC R1 R2 Stack Mode
74 10 20 Vide Esclave / Interruptible
223
7. Leçon 14 – Le mécanisme d’interruption
Le passage de l’instruction 138 à l’instruction 270 mérite un commentaire. L’exécution de l’instruction 138 (iret)
a pour effet de restaurer les registres. À la fin de l’instruction 138, on a donc la situation suivante :
PC R1 R2 Stack Mode
75 10 20 Vide Esclave / Interruptible
Néanmoins, une IRQ a eu lieu durant l’exécution de l’instruction d’adresse 136. Comme la machine n’était pas
interruptible à ce moment, cette IRQ n’a pas été traitée. Quand le processeur revient au début de la boucle d’in-
terprétation, il découvre cette IRQ et recommence immédiatement le processus d’interruption sans avoir exécuté
l’instruction d’adresse 75. Cette valeur de PC ainsi que les valeurs des registres sont à nouveau sauvées sur le stack,
et le CPU saute dans le gestionnaire d’interruption numéro 2, à l’adresse 270.
Correction de l’exercice 47 Contrairement, à l’exercice précédent, l’IRQ qui a lieu durant l’instruction d’adresse
136 est prise en compte avant d’exécuter l’instruction 137. On empile sur le stack la séquence des adresses de
retour avec les valeurs des registres associées.
PC R1 R2 Stack Mode
74 10 20 Vide Esclave / Interruptible
M8N
224
Cinquième partie
Le système d’exploitation
225
226
8. Leçon 15 – Le système d’exploitation
227
8. Leçon 15 – Le système d’exploitation
À la lumière de ces constatations, nous comprenons que, si nous souhaitons étendre notre
système pour qu’il puisse exécuter plusieurs programmes appartenant à plusieurs utilisa-
teurs, nous devrons trouver des réponses à toute une série de questions, dont les plus im-
portantes sont :
— Comment faire pour qu’un ordinateur avec un seul processeur puisse exécuter plu-
sieurs programmes « en même temps 1 » ? Et d’ailleurs, que signifie précisément « en
même temps » ? Et comment faire pour éviter que ces multiples programmes n’utilisent
les mêmes zones de la mémoire primaire, afin d’éviter qu’un programme ne corrompe
les données d’un autre ?
— Comment faire pour que l’accès à la mémoire secondaire soit plus structurée (que sa
simple structure en cases), et partagée par tous les programmes (avec les mécanismes
de protection des données qui s’imposent) ?
Cette structure est illustrée par le fragment de code Python à la Figure 8.1, qui montre la
manière typique avec laquelle on accède à un fichier dans un langage de haut niveau.
La fonction open permet d’ouvrir un fichier en précisant son nom (hello.txt) et le
répertoire qui le contient (/home/alanturing/src). Le fichier est créé, le cas échéant.
Ensuite, les fonctions write, read permettent respectivement d’écrire et de lire dans
le fichier, etc. Notons bien que, lors de la création du fichier, aucune adresse de la mé-
moire secondaire n’est précisée. De même, lors de l’écriture, les données sont ajou-
tées à la fin du fichier, mais nous ne précisons pas (et ne connaissons pas) la ou les
adresses des cases de mémoire secondaire qui contiennent ces informations. Enfin,
notons qu’il n’y a pas de garantie que la demande soit exécutée. En effet, il se pourrait
que l’utilisateur du programme en question n’ait pas le droit d’écrire dans le répertoire
/home/alanturing/src par exemple.
Pour répondre au premier item ci-dessus, nous allons introduire deux techniques : le mul-
titâche 2 (et en particulier le temps (CPU) partagé), et la mémoire (primaire) paginée. Pour
répondre au second, nous devons expliquer comment implémenter un système de fichiers.
Toutes ces tâches, et d’autres, sont dévolues au système d’exploitation, dont l’intérêt devrait
maintenant être clair pour le lecteur.
1. Nous avons vu dans les chapitres d’introduction, qu’il existait des systèmes possédant plusieurs proces-
seurs, ou encore un seul processeur mais avec plusieurs cœurs. Néanmoins, cela ne résout pas tous les problèmes
car il y aura, en général, plus de programmes à exécuter que de cœurs ou de processeurs disponibles.
2. Ou multiprogrammation.
228
8.2. Principe général de fonctionnement
F IGURE 8.1. – Un exemple de programme python qui accède à la mémoire secondaire en uti-
lisant la structure du système de fichiers. Observez qu’aucune adresse de la
mémoire secondaire n’est mentionnée.
ß
Sur les ordinateurs personnels modernes, les principaux systèmes d’exploita-
tion sont Microsoft Windows, Apple macOS et les différentes versions de GNU/-
Linux. Mais d’autres types de machines, qui sont essentiellement des ordina-
teurs, utilisent également un OS : Android est une OS dérivé de GNU/Linux et
développé par Google pour les smartphones et tablettes ; iOS est son pendant
développé par Apple pour ses appareils, etc.
229
8. Leçon 15 – Le système d’exploitation
Mode esclave
Appel Système Réponse Mode maître
Requête Réponse
F IGURE 8.2. – Une illustration du principe général de fonctionnement d’un système d’exploi-
tation. Les instructions privilégiées sont soulignées.
ß
Considérons à nouveau le cas d’un logiciel qui souhaite écrire des informations
dans un fichier, sur la mémoire secondaire (qui fait office de matériel dans notre
cas). Voici le scénario en détail :
1. Le logiciel envoie au système d’exploitation une demande d’écrire cer-
taines données à une certaine position dans un certain fichier (typique-
ment, le fichier sera identifié par son nom et le répertoire qui le contient,
comme /home/alanturing/src/hello.txt). C’est l’appel système.
2. L’OS reçoit cette demande et procède à une analyse pour déterminer si
le fichier existe, si le logiciel a le droit d’y écrire, etc. Si toutes les vérifi-
cations sont concluantes, l’OS peut traduire la position où écrire les don-
nées en une adresse (numéro de case) sur la mémoire secondaire. Pour ce
faire, il se base généralement sur une série de tables qui lui permettent de
savoir où les fichiers sont stockés en mémoire secondaire (typiquement
dans plusieurs cases). L’OS exécute alors les instructions privilégiées né-
cessaires pour obtenir cette écriture. ../.
230
8.2. Principe général de fonctionnement
À travers cet exemple, on voit bien, comme nous l’avions annoncé, que l’OS a un rôle
d’arbitre dans le système informatique, et qu’il peut être utilisé pour assurer la protection
des données (en bloquant les requêtes qui pourraient compromettre ces mêmes données), et
pour assurer un accès équitable au matériel (en répartissant les ressources matérielles entre
les différents programmes qui en font la demande).
231
8. Leçon 15 – Le système d’exploitation
F IGURE 8.3. – Un appel système sous le système d’exploitation GNU/Linux, qui affiche sur la
console un message de dix octets (donné en ASCII)
ß
La Figure 8.3 donne un exemple d’appel système pour le système d’exploitation
GNU/Linux, donné en langage machine Intel.
Pour réaliser un appel système sous GNU/Linux, les conditions suivantes
doivent être remplies :
1. Placer dans le registre eax le numéro de l’appel système qui correspond
à la fonctionnalité qu’on attend de l’OS. Ici, on a choisi l’appel système
appelé sys_write, qui porte le numéro 4 et qui sert à afficher un message
à l’écran (entre autres choses). ../.
232
8.2. Principe général de fonctionnement
... 2. Placer dans les registres appropriés (en fonction de l’appel système choisi)
les paramètres nécessaires. Dans le cas de sys_write, on doit placer :
a) Le message (en ASCII) dans la mémoire primaire, et un pointeur vers
le début de ce message dans ecx. Dans notre exemple, nous avons
désigné la valeur de ce pointeur par msg.
b) La longueur (nombre de caractères) du message dans edx.
c) L’identifiant de la sortie où écrire le message dans ebx. Ici, on a
choisi 1 qui correspond à la sortie standard : affichage à l’écran dans
le terminal où le programme est exécuté.
3. Déclencher la demande d’interruption 8016 . Avec la syntaxe de l’assem-
bleur, cette valeur se note 0x80, le préfixe 0x servant à indiquer que la
valeur est donnée en hexadécimal.
Une fois cet appel système exécuté, une valeur de retour indiquant la présence
d’une éventuelle erreur est placée dans le registre eax, ce qui permet au pro-
gramme appelant de vérifier que tout s’est bien passé.
Cet exemple illustre bien ce qui se passe au niveau du langage machine, mais
naturellement les appels systèmes sont aussi accessibles aux langages de plus
haut niveau. Sous GNU/Linux, par exemple, la bibliothèque LibC offre une série
de fonctions qu’on peut utiliser dans un programme C pour accéder aux fonc-
tions des appels systèmes. Dans le cas de notre exemple ci-dessus, nous utilise-
rions la fonction write(), qui a le même effet.
Cet exemple illustre également l’avantage, pour le programme utilisateur, du
mécanisme d’appel système. Pour afficher un message à l’écran en communi-
quant directement avec le matériel (la carte graphique, principalement), il fau-
drait avoir les réponses aux questions suivantes, et en tenir compte dans le lo-
giciel : de quel modèle de carte graphique dispose-t-on ? (et il faut donc adapter
le logiciel à chaque possibilité) ; l’affichage doit-il se faire dans un fenêtre de
terminal ? laquelle ? (ici, on se contente de dire « sortie standard » et l’OS gère
l’affichage) ; quelle police faut-il utiliser pour l’affichage ? etc. On voit donc bien
le rôle simplificateur de l’OS comme interface entre le matériel et le logiciel.
233
8. Leçon 15 – Le système d’exploitation
Le système d’exploitation agit comme une interface entre les programmes utili-
sateurs et le matériel. Il s’assure que les ressources matérielles sont reparties de
manière équitable et sûre entre les différents programmes et utilisateurs.
Pour ce faire, il se repose (en général) sur les mécanismes de protection offerts
par le CPU : le noyau de l’OS est le seul à s’exécuter en mode maître, alors que le
reste des outils de l’OS et les programmes utilisateurs s’exécutent en mode esclave.
Les routines de gestion d’interruption font aussi partie de l’OS, et c’est à l’aide
d’une interruption particulière appelée « appel système » que les programmes
utilisateurs peuvent faire appel à l’OS.
Remarque La question de savoir quelles fonctionnalités de l’OS doivent être placées dans
le noyau et donc s’exécuter en mode maître fait l’objet de débats et a donné lieu à plusieurs
propositions. On distingue ainsi les noyaux monolithiques et modulaires. Un noyau monoli-
thique est, comme son nom l’indique, fait d’un seul programme machine qui est chargé en
mémoire au démarrage. Au contraire, un noyau modulaire est constitué d’une partie qui réa-
lise les services essentiels de l’OS, et qui chargé au démarrage ; et de modules qui sont des
petits programmes machines qu’on peut charger ou retirer durant l’exécution, afin d’ajouter
des services à l’OS. Ces modules permettent en général d’activer le support de tel ou tel maté-
riel. Le système GNU/Linux par exemple possède un noyau modulaire, et des outils comme
lsmod ou insmod permettent de manipuler les modules.
L’idée de faire sortir du noyau tout ce qui n’y est pas strictement nécessaire, à l’aide de
modules, a donné lieu à l’idée de micro-noyau (microkernel en anglais) [10, 38]. Il s’agit d’un
noyau dans lequel on a fait l’exercice de ne garder que la partie qui doit absolument s’exécuter
en mode maître, tout le reste étant placé dans des modules qui s’exécutent en mode esclave.
Cela permet d’augmenter la sécurité du système. En effet, comme le noyau s’exécute en mode
maître et qu’il a donc « tous les droits » sur le système, il faut absolument s’assurer qu’il est
sans erreur ni susceptible d’être victime d’attaques (virus ou autres). En rejetant toute une
série de traitements hors du noyau, et donc hors du mode maître, on diminue le risque ces
parties du code détruisent ou compromettent des données sensibles, par exemple.
234
8.3. Tâches dévolues au système d’exploitation
Cas des systèmes d’exploitation temps-réel Dans certains applications, le délai de ré-
ponse des applications est important. Par exemple, si on souhaite réaliser un logiciel de contrôle
d’un ABS qui doit analyser des mesures provenant des capteurs sur les roues d’un véhicule
et contrôler les freins en fonction de ces mesures, il est impératif de fixer un délai maximum
entre le moment où la mesure est prise et le moment où la commande est envoyée aux freins.
Cela ne signifie pas pour autant que cette commande doit arriver le plus vite possible, mais
bien en respectant un délai fixé.
Certains OS sont capables d’offrir ce genre de garanties, à la condition que la charge du
système ne soit pas trop importante, c’est-à-dire qu’il n’y a pas trop de tâches à effectuer en
même temps. Ces OS sont appelés temps-réel, et sont obtenus en réalisant un ordonnanceur
qui est capable de tenir compte de ces délais et des urgences relatives des différentes tâches
à exécuter.
L’ordonnancement temps-réel est une discipline propre de l’informatique théorique [41]
qui étudie les conditions sous lesquelles les délais peuvent être tenus, et avec quelles straté-
gies d’ordonnancement, par exemple. Ce champ de recherche a de nombreuses applications
industrielles et il existe sur le marché plusieurs OS temps-réel qui implémentent ces idées.
On peut citer par exemple QNX 5 ou RTAI 6 .
235
8. Leçon 15 – Le système d’exploitation
1. on peut implémenter des limites sur le temps CPU total dont l’utilisateur dispose ;
2. on peut associer un système de permissions aux fichiers et répertoires afin que les don-
nées d’un utilisateur ne soit pas accessibles par d’autres ;
4. on peut mettre en place un système de facturation en fonction des ressources que l’uti-
lisateur utilise (par exemple facturer les impressions à la page) ; etc. . .
Ces droits, contraintes et permissions peuvent en général être regroupées à l’aide des outils
de l’OS, et il existe souvent un utilisateur en particulier, appelé super-utilisateur qui possède
le maximum de droits dans le système (en particulier celui de d’attribuer ou des révoquer les
permission des autres utilisateurs).
ß
Les principaux OS qu’on utilise aujourd’hui sur les ordinateurs person-
nels (Windows, GNU/Linux, MacOS,. . . ) sont multi-utilisateurs. En particulier,
GNU/Linux et MacOS descendent de l’OS Unix, qui possédait dès le début un
système d’identification pour les utilisateurs, ayant été développé pour des ser-
veurs de grande dimension. Sur les systèmes de type Unix, le super-utilisateur
est en général appelé root (« racine » en anglais), et les autres utilisateurs ont
un login et un mot de passe.
Sous MacOS, par exemple, l’outil « Moniteur d’activité » permet, entre autres
choses, de faire la liste des programmes en cours d’utilisation, et indique quel
est l’utilisateur qui l’a exécuté. On en voit un exemple sur la Figure 8.4. Par fa-
cilité, certains utilisateurs virtuels peuvent exister comme _windowserver sur
l’exemple.
../.
236
8.3. Tâches dévolues au système d’exploitation
237
8. Leçon 15 – Le système d’exploitation
F IGURE 8.4. – Le Moniteur d’Activités de macOS 11.5.2, affichant différents programmes avec
l’utilisateur associé (colonne de droite).
F IGURE 8.5. – Le contenu d’un répertoire sous MacOS avec les noms des utilisateurs possé-
dant les fichiers et les droits associés.
238
8.3. Tâches dévolues au système d’exploitation
Systèmes en traitement par lots Sur les premiers ordinateurs, l’OS était essentiellement
un système de gestion de file d’attente auquel les utilisateurs soumettaient les travaux à exé-
cuter. L’utilisateur fournissait une série de travaux sous forme de lots (ou batch en anglais),
avec éventuellement, des instructions supplémentaires pour récupérer les résultats d’un pro-
gramme et les transmettre comme entrée au programme suivant à exécuter dans le lot. On
peut ainsi imaginer un premier programme qui calcule les traitements des employés d’une
entreprise, et qui transmet ces données à un second programme qui réalise les fiches de paye.
Une des particularités du traitement pas lot est qu’il n’est pas interactif, c’est-à-dire que
l’utilisateur ne peut pas interagir avec le programme pendant son exécution. Il fournit les
données en entrée, une description du lot à exécuter et récupère les données en sortie. C’est
évidemment très contraignant pour l’utilisateur, mais cela représente une situation relati-
vement simple à gérer pour l’OS ; et ce type de fonctionnement est bien adapté à certains
domaines d’application où l’automatisation est forte, par exemple.
Afin de décrire l’ordre dans lequel les différents programmes du lot doivent s’exécuter,
et comment ils doivent communiquer, les OS en traitement par lot disposent d’un langage
propre. Un exemple (historique) est le Job Control Language d’IBM [14].
La plupart des OS modernes sont plutôt interactifs. Néanmoins, ils offrent en général la
possibilité de faire également du traitement par lots. Par exemple, les serveurs de calcul, où
l’OS reçoit de nombreuses requêtes qui demandent un temps de calcul très important (plu-
sieurs heures, plusieurs jours. . . ), et où il doit donc disposer d’un maximum de latitude pour
ordonnancer les travaux sans pénaliser aucun utilisateur. Sur ces serveurs, le temps CPU en
mode interactif est en général limité, ce qui signifie qu’un programme qui s’exécute plus de
cinq minutes en mode interactif (par exemple) est automatiquement arrêté.
7. Les premières versions étaient électromécaniques et ressemblaient à des sortes de machines à écrire :
l’écran était remplacé par une feuille de papier sur laquelle la machine imprimait !
239
8. Leçon 15 – Le système d’exploitation
F IGURE 8.6. – Un terminal DEC VT100. Cet appareil a eu tellement de succès qu’il a constitué
une norme, et les programmes d’émulation de terminal continuent à se com-
porter en mode « vt100 » encore aujourd’hui. . .
ß
Le premier système d’exploitation sur les ordinateurs IBM PC (voir Section 8.4)
s’appelait PC-DOS (pour Disk Operating System) et était un OS doté d’un CLI.
La Figure 8.7 montre l’exécution du logiciel DosBox-X 8 qui émule sur un OS
moderne le système DOS. Le prompt se présente sous la forme Z:\>, où Z:\
indique le répertoire courant sur la mémoire secondaire. On a tapé la com-
mande dir qui affiche le contenu (fichiers et répertoires) du répertoire cou-
rant. On voit la réponse affichée sous la commande et le prompt qui se répète
pour attendre une nouvelle commande. Sous DOS, l’interpréteur de commande
s’appelle COMMAND.COM, qui est le nom du fichier contenant son code machine
fourni avec l’OS.
240
8.3. Tâches dévolues au système d’exploitation
F IGURE 8.7. – Une illustration d’un OS avec un CLI mais pas de GUI : DOS exécuté dans
DosBox-X.
ß
Les OS modernes (qui disposent d’une interface graphique comme interface
principale, voir paragraphe suivant) ont en général la possibilité d’interagir via
une CLI malgré tout. Nous en avons déjà vu un exemple à la Figure 8.5. Elle
montre une fenêtre du programme « Terminal » sur macOS qui constitue une
CLI. Ici, le prompt est [ggeeraer@Haphaistos:chapters], et l’interpréteur de
commande est le programme Bash (voir https://www.gnu.org/software/b
ash/).
241
8. Leçon 15 – Le système d’exploitation
L’exemple suivant illustre brièvement comment un système d’interface graphique peut être
implémenté en pratique :
ß
Une implémentation typique sur l’OS Linux consiste à avoir :
1. Le noyau de l’OS qui communique avec le matériel et est capable d’affi-
cher des images bitmap
2. Un serveur de fenêtres (comme X.org par exemple, voir https://www.x.
org/) qui remplit notamment les fonctions suivantes :
a) Affichage de fenêtres à l’écran, via des appels systèmes ;
b) Gestion des interactions avec la souris et le clavier : les déplacements
de la souris, les clics et les touches pressées aux clavier sont trans-
mises au serveur de fenêtre ;
c) Gestion de la communication entre les différentes fenêtres
3. Une bibliothèque (comme GTK par exemple, voir https://www.gtk.or
g/) qui implémente des fonctions comme « créer une fenêtre à l’écran »,
« ajouter un bouton », etc, de manière à avoir une interface unifiée. C’est
à travers ces fonctions que le programme utilisateur interagit avec le ser-
veur de fenêtres.
La Figure 8.8 montre un exemple de programme Python qui utilise un GUI grâce
à la bibliothèque GTK déjà mentionnée. Il n’est pas nécessaire de comprendre
tous les détails de l’exemple pour bien voir ce que cette bibliothèque et ce que
l’OS apportent comme simplifications. Les lignes 2 à 4 servent à invoquer le bi-
bliothèque GTK. La fonction on_activate est une fonction qui sera automati-
quement appelée lorsque l’application est lancée. Elle consiste à créer la fenêtre
à l’aide d’un appel à Gtk.ApplicationWindow, à créer un bouton avec une éti-
quette « Hello World ! », à l’aide d’un appel à Gtk.Button, à associer une action
(en l’occurrence fermer la fenêtre) au click du bouton, et ajouter le bouton à la
fenêtre (ligne 15). Les dernières lignes permettent de démarrer l’application en
déclarant que c’est on_activate qui doit être appelée.
À aucun moment le programmeur n’a eu à se soucier de la façon dont le bouton
serait affiché, d’implémenter une boucle qui vérifie en permanence si la souris
est cliquée, et à quel endroit (sur le bouton ou non). . . Tous ces détails en pris
en charge par la bibliothèque, le serveur de fenêtre et l’OS.
Concrètement, quand l’application est lancée, les appels aux fonctions de GTK
communiquent avec le gestionnaire de fenêtre, qui, à son tour, fait les appels
systèmes nécessaires pour afficher la fenêtre et les bouton. Quand l’utilisateur
clique sur le bouton, une interruption est générée, dont le gestionnaire fait par-
tie de l’OS. L’OS fait alors appel au serveur de fenêtre en transmettant les coor-
données du clic. Le serveur de fenêtre (qui s’exécute en mode esclave), vérifie
sur quel objet le clic a eu lieu, et appelle le code qui a été déclaré par la biblio-
thèque GTK comme étant celui qu’il fallait exécuter en cas de clic sur le bouton.
242
8.4. Quelques repères historiques
F IGURE 8.8. – Un exemple de programme Python utilisant la bibliothèque GTK pour l’affi-
chage d’une fenêtre. Source : https://www.gtk.org/docs/language-bin
dings/python/, consultée le 20 août 2021.
Comme nous l’avons déjà expliqué, les tous premiers ordinateurs ne possédaient pas de
système d’exploitation. Sur les premiers ordinateurs commerciaux, les utilisateurs obtenaient,
pour un temps limité, un accès exclusif à l’ordinateur et à toutes ses ressources. Les utilisa-
teurs devaient alors entrer leur programme, en langage machine, et ce à l’aide de cartes ou
de rubans perforés. Après l’exécution du programme, les résultats étaient rendus disponibles
par les mêmes biais.
243
8. Leçon 15 – Le système d’exploitation
9. Proposé en 1954 par John B ACKUS et son groupe de recherche, le FORmula TRANslating System est un
langage de programmation destiné à réaliser du calcul numérique. John B ACKUS (3 décembre 1924–17 mars 2007)
recevra le prix Turing en 1977 pour ses travaux.
10. Le COmmon Business Oriented Language est un langage de programmation proposé en 1959 pour les ap-
plications de gestion, qui serait communs à plusieurs modèles d’ordinateurs différents, ce qui était neuf pour
l’époque. Il se base sur le langage FLOW-O-MATIC inventé par Grace Hopper.
11. Dans [31], l’auteur explique que, sur l’IBM 701 installée chez General Motors dans les années 1950, jusqu’à
10 minutes d’une session de 15 minutes pouvaient être consacrées à la mise en place du programme
244
8.4. Quelques repères historiques
L’histoire a retenu le système GM-NAA I/O (pour General Motors and North American Avia-
tion Input/Output) [26] comme le premier système d’exploitation. Il a été développé en 1954
par Robert L. PATRICK et Owen M OCK, travaillant respectivement chez General Motors 12 et
North American Aviation 13 pour l’IBM 704 [13] en 1956 ; voir Figure 8.9. Il s’agissait prin-
cipalement d’un OS batch : il permettait d’automatiser les différentes étapes successives et
nécessaires à l’exécution d’une série de programmes.
À partir de ce point, de nombreux utilisateurs vont commencer à développer des systèmes
d’exploitation pour la machine qu’ils utilisaient.
Compatible Time-Sharing System Le CTSS [47] est un OS développé au MIT pour les
machines IBM 709 (à l’origine) en 1961. C’est le premier OS à implémenter le principe du time
sharing, que nous étudierons dans le Chapitre 10. Ce principe permet d’exécuter plusieurs
programmes à la fois sur un même ordinateur et est encore largement utilisé dans les OS
modernes.
245
8. Leçon 15 – Le système d’exploitation
F IGURE 8.9. – Une IBM 704 au National Advisory Committee for Aeronautics (USA) en 1954.
Cette machine est un ordinateur utilisant des tubes à vide commercialisé par
IBM de 1954 à 1960. Il s’agit de la première machine capable de faire du calcul
en virgule flottante.
Le processeur est constitué des deux armoires à angle doit, montrant des
câbles, sur la gauche de la photo. Au milieu, figure le panneau de commande.
L’opératrice au premier plan manipule une machine pour lire les cartes perfo-
rées. Le premier système d’exploitation a été développé pour une machine de
ce type.
246
8.4. Quelques repères historiques
MULTICS MULTICS signifie Multiplexed Information and Computing Service. Il s’agit d’un
système d’exploitation au succès mitigé, mais qui a eu une influence profonde sur l’histoire
des OS. Il est développé au MIT, en collaboration avec General Electrics et Bell Labs 16 à partir
de 1964. La première version est disponible en 1967 pour des ordinateurs General Electrics,
mais cette société sera plus tard rachetée par Honeywell qui commercialise MULTICS jusque
dans les années 1980 (le dernier système connu ayant été arrêté définitivement en 2000).
MULTICS a souvent été jugé trop complexe et trop lourd à faire fonctionner, notamment par
Ken T HOMPSON et Denis R ITCHIE dont nous reparlerons quand nous aborderons Unix. Néan-
moins, MULTICS a introduit de nombreuses idées novatrices pour son temps :
1. MULTICS est le premier OS à utiliser un système de fichiers hiérarchique (voir Cha-
pitre 10), c’est-à-dire un système de fichiers dans lequel des dossiers peuvent contenir
d’autres dossiers et des fichiers, formant une hiérarchie. Ce principe est en usage dans
tous les OS modernes.
2. MULTICS est un des premiers systèmes d’exploitation véritablement sécurisé, grâce à
un système de niveaux de privilège qui raffine les modes maître et esclave.
3. L’interpréteur de commandes de MULTICS est un programme à part entière qu’on peut
donc remplacer par un autre si on le désire. De même, chaque commande correspond
à un programme qui est exécuté quand on fait appel à la commande.
En outre, MULTICS est un OS multi-tâche (avec du partage de temps) et multi-utilisateur.
Unix Unix est un système d’exploitation central dans l’histoire de l’informatique. Il a été dé-
veloppé à partir de 1969 par Ken T HOMPSON 17 et Denis R ITCHIE 18 , deux anciens membres
du projet MULTICS, et membre des Bell Labs. Déçus par les aléas de ce projet, ils décident de
réaliser leur propre système d’exploitation, qu’ils appellent UNICS (un jeu de mot sur MUL-
TICS). L’orthographe finalement retenue sera Unix. La première version d’Unix a été écrite
pour le DEC PDP-11 19 , la Figure 8.10 montre T HOMPSON et R ITCHIE à la console d’une telle
machine aux Bell Labs.
À partir de 1978, Unix va être adapté à d’autres machines. Sa popularité va croître très rapi-
dement dans les universités et centres de recherche. L’Université de Nouvelle-Galles du Sud
(Australie) est la première à installer une copie d’Unix en-dehors des États-Unis. En 1978,
l’Université de Berkeley en Californie commence à distribuer sa propre version d’Unix, appe-
lée Berkeley Software Distribution (BSD). Sur base de cette distribution, de nombreuses autres
versions d’Unix vont être développées. On trouve notamment Solaris (développé par Sun
Microsystems) ou Darwin (développé par Apple, voir le paragraphe sur MacOS ci-dessous) ;
247
8. Leçon 15 – Le système d’exploitation
F IGURE 8.10. – Une célèbre photo de Peter H AMER montrant Denis R ITCHIE (g.) et
Ken T HOMPSON (d.) à la console du PDP-11, vers 1972.
248
8.4. Quelques repères historiques
mais aussi des version gratuites et libres comme FreeBSD 20 et OpenBSD 21 qui fonctionnent
sur une grande gamme d’architectures. Parallèlement aux avatars de BSD, AT&T (qui possède
les Bell Labs) continue à développer sa propre version d’Unix. Elle sera rachetée notamment
par HP qui en dérive HP-UX ; par Microsoft, qui en dérive Xenix ; par IBM qui en dérive AIX ;
par Silicon Graphics qui en dérive Irix. . . Mais la version la plus célèbre d’Unix aujourd’hui est
sans doute GNU/Linux, qui est à la base d’Android et dont nous reparlerons ci-dessous. Par
ailleurs, le système d’exploitation mobile iOS d’Apple (cfr. infra) est lui aussi dérivé d’Unix.
On peut donc affirmer que la quasi-totalité des OS mobiles en usage aujourd’hui sont déri-
vés d’Unix. De même, on estime que 80% des serveurs Web utilise un système d’exploitation
dérivé d’Unix [46].
L’IBM PC et MS-DOS Quelques années plus tard, en 1981, IBM décide de se lancer dans
le marché des ordinateurs personnels avec son modèle 5150 (voir Figure 8.11), qui constitue
le premier « PC ». Cet ordinateur est basé sur un processeur Intel 8088 25 . Pour cet ordinateur,
IBM s’est chargé du développement du matériel, mais a décidé de faire appel à une entreprise
externe pour le développement de l’OS.
Initialement, IBM a pris contact avec Digital Research dans le but d’adapter leur OS CP/M 26
249
8. Leçon 15 – Le système d’exploitation
Source : Rama & Musée Bolo (https://commons.wikimedia.org/wiki/File:IBM_PC- IMG_7271_(transparent).png), ‘IBM PC-IMG 7271 (transparent)’,
https://creativecommons.org/licenses/by-sa/2.0/fr/deed.en.
au PC, mais les négociations n’ont pas abouti. IBM s’est ensuite tourné vers une entreprise
alors relativement jeune : Microsoft. Celle-ci engage Tim PATERSON 27 qui développe un Disk
Operating System (DOS) pour le PC, en s’inspirant de CP/M. Cet OS sera décliné en de nom-
breuses versions. En particulier, Microsoft commercialise MS-DOS entre 1981 (version 1.0)
et 1994 (version 6.22), et cet OS aura été le plus populaire au monde (en raison du succès de
l’IBM PC) durant la majeure partie de cette période. D’autres versions ont été développées
par IBM (PC-DOS) Digital Research (DR-DOS), et il en existe aujourd’hui une version libre :
FreeDos 28 .
Malgré son succès remarquable, DOS restait un OS très rudimentaire. Il offrait un inter-
préteur de commandes à travers une CLI (Figure 8.7), mais pas de GUI. Il ne permet pas le
multi-tâche, il est mono-utilisateur, et n’offre pour ainsi dire aucune sécurité étant donné
que le processeur fonctionne tout le temps en mode maître. Il offre un système de gestion
de fichiers appelé FAT (voir Chapitre 10), et une gestion de la console, à travers des appels
systèmes.
américain né le 19 mai 1942 et décédé le 11 juillet 1994) chez Digital Research. CP/M était très populaire sur une
série de systèmes 8 bits, notamment ceux construits autour du processeur Intel 8080. Il est d’ailleurs considéré
comme le premier système d’exploitation pour les systèmes à micro-processeur. Il offrait principalement un sys-
tème de gestion des fichiers et un interpréteur de commandes.
27. Informaticien américain, né le 1er juin 1956. Il est considéré comme le père de DOS.
28. Voir https://www.freedos.org/.
250
8.4. Quelques repères historiques
Microsoft Windows Conscient de l’importance d’une interface graphique pour les ordi-
nateurs grand public, et fort de son succès avec MS-DOS, Microsoft lance, en 1981, le déve-
loppement d’un GUI pour le PC. Il s’agira de Windows, dont la version 1.01 est disponible en
novembre 1985, un peu après le lancement du Macintosh d’Apple. Durant les premières ver-
sions, Windows est un ajout à MS-DOS : l’ordinateur démarre en utilisant MS-DOS comme
OS, puis l’utilisateur a la possibilité de lancer Windows, qui offre alors le GUI, mais également
du multi-tâche coopératif (voir Chapitre 10) permettant d’exécuter plusieurs programmes en
même temps dans des fenêtres différentes. Cette première partie de l’histoire de Windows
culmine en 1993 avec la version 3.11.
À partir de 1995, Windows est système d’exploitation à part entière : la machine démarre
sous Windows, directement dans le GUI, même si une bonne partie du code sous-jacent reste
une évolution de MS-DOS. Il s’agit des versions Windows 95, Windows 98 et Windows Me.
Parallèlement à ces versions grand public, Microsoft développe une version destinée aux ser-
veurs d’entreprise, Windows NT, qui n’est pas basée sur MS-DOS. Ces deux lignes de dévelop-
pement sont fusionnées en 2001 pour produire Windows XP (qui ne repose donc plus sur un
dérivé de MS-DOS, même s’il offre un interpréteur de commandes compatible). La version
actuelle de Windows est la version 11.
29. À noter que les concepts du GUI (fenêtres, icônes, utilisation de la souris,. . . ) ont été principalement inven-
tés et développés par la firme américaine Xerox, célèbre pour ses photocopieurs ; et ce, au début des années 1970,
sous la direction de Charles P. Thacker (né le 26 février 1943, décédé le 12 juin 2017, récipiendaire du prix Turing).
En 1973, le Xerox Alto est le premier ordinateur au monde a disposer d’un GUI commandé à la souris. Son succès
commercial sera néanmoins limité, et les concepts développés pour l’Alto seront repris par Apple et Microsoft
pour être adaptés aux ordinateurs personnels.
30. Né le 16 mars 1953, informaticien et activiste américain à l’origine du projet GNU, de la Free Software Foun-
dation et de l’éditeur Emacs, parmi d’autres projets.
251
8. Leçon 15 – Le système d’exploitation
F IGURE 8.12. – Le premier modèle d’Apple Macintosh, le Mac 128k. Il dispose d’un écran
monochrome de 23cm de diagonale, de 128ko de mémoire vive, et d’un pro-
cesseur Motorola 68000. Il s’agit d’un processeur utilisant des registres et des
adresses de 32 bits, mais avec un bus de 16 bits. Il est représenté ici exécutant
MacOS 4.1.
252
8.4. Quelques repères historiques
et fonde la Free Software Foundation 31 en 1985 pour promouvoir le logiciel libre. Le dévelop-
pement de l’OS GNU progresse rapidement : de nombreux outils comme les compilateurs ou
l’éditeur de texte Emacs sont développés par S TALLMAN et de nombreux bénévoles. Mais la
partie centrale de l’OS, le noyau (appelé Hurd) prend du retard.
Parallèlement à cela, en 1991, un jeune étudiant finlandais de l’université d’Helsinki, Linus
T ORVALDS 32 décide d’écrire sa propre version de Unix. En effet, à cette époque, le projet GNU
n’avait toujours pas de noyau fonctionnel, et le seul dérivé de Unix avec des sources ouvertes
était Minix 33 . Mais ce dernier était limité à des processeurs 16 bits, alors que les processeurs
sur les PC de l’époque étaient des processeurs 32 bits (Intel 386 et 486). T ORVALDS annonce
publiquement sa première version sur un forum Internet :
Dès le début du projet, T ORVALDS a utilisé la licence GPL de la Free Software Foundation
pour être certain que le code source de Linux serait disponible et librement modifiable. Ce
choix a aussi permis d’utiliser le noyau de Linux comme noyau pour le système GNU. C’est
ainsi qu’est né le système GNU/Linux qu’on peut donc résumer comme un noyau (Linux)
auquel on a ajouté les outils du projet GNU (Linux a d’ailleurs été développé en utilisant le
compilateur C de GNU, gcc).
Vers le milieu des années 1990, le système GNU/Linux a pris de plus en plus d’ampleur,
253
8. Leçon 15 – Le système d’exploitation
Android Android est le nom d’une société californienne fondée en 2003 par Andy RUBIN,
Rich M INER, Nick S EARS, et Chris W HITE, dans le but de développer un système d’exploitation
pour des appareils photo numériques. La société change rapidement de but et se concentre
sur les smartphones. La société est acquise par Google en 2005, et la première version du
système Android est disponible en 2008. Android est un système d’exploitation basé sur le
noyau de Linux, ce qui fait donc de lui un autre descendant d’Unix. Il en est actuellement à
sa version 12.
iOS Le principal concurrent d’Android est son équivalent développé par Apple pour ses
équipements mobiles (téléphones, tablettes et montres connectées). Il s’agit d’iOS dont la
première version a été présentée en 2007, pour équiper le premier iPhone. iOS est également
un dérivé d’Unix : son noyau est le noyau XNU de Darwin (comme macOS), qui est lui-même
un dérivé de BSD.
M8N
254
9. Leçon 16 et 17 – Gestion de la mémoire
primaire
Comme nous l’avons vu dans le chapitre précédent, la gestion de la mémoire primaire
est une des tâches confiées à l’OS. En effet, dans un environnement multitâche, il est né-
cessaire de stocker plusieurs programmes en cours d’exécution de manière simultanée en
mémoire primaire. Cela doit se faire de manière transparente pour les programmes, ce qui
signifie qu’un programme ne devrait pas être écrit en tenant compte explicitement de la pré-
sence en mémoire d’autres programmes en cours d’exécution. En d’autres termes, il faudrait
qu’on puisse continuer à concevoir et exécuter les programmes « comme si » ils étaient seuls
à s’exécuter. En outre, il est souhaitable de mettre en place des mécanismes de protection
des données : un programme ne doit pas être en mesure d’accéder aux données des autres
programmes.
La grande majorité des OS modernes implémentent une technique de gestion de la mé-
moire primaire appelée pagination à la demande, dont nous allons exposer les principes
dans ce chapitre. Pour bien fonctionner, cette technique a besoin d’un support matériel :
le CPU doit être doté d’un module appelé Memory Management Unit (MMU), comme nous
le verrons.
Mais nous commençons par regarder quelques exemples qui vous nous permettre de bien
saisir les difficultés liées à la gestion de la mémoire primaire.
255
9. Leçon 16 et 17 – Gestion de la mémoire primaire
a.v.
1 Variable d
3 AD
4 ??????
5 ??????
manière naturelle : le premier a le numéro 0 (il est sur le dessus la figure), le second a le nu-
méro 1, etc. Quand il s’agira de cases mémoires, ces numéros constitueront évidemment des
adresses.
256
9.1. Problèmes à surmonter
0 0
1 Variable d 1
2 2
3 AD 3 Variable d
4 00 4
5 01 5 AD
6 6 00
7 7 03
8 8
9 9
F IGURE 9.2. – Deux façons différentes de charger notre programme en mémoire. Observez
que l’adresse à laquelle l’instruction se réfère change dans les deux cas.
Une adresse virtuelle est une adresse relative au début du programme ; le pre-
mier octet du programme a donc l’adresse virtuelle 0.
Une adresse réelle est une adresse relative au début de la mémoire, c’est le nu-
méro de case mémoire correspondant.
ß
L’adresse virtuelle de la variable d , dans notre exemple est de 1. Sur la Figure 9.1,
il faudrait donc remplacer les points d’interrogation par cette adresse (en met-
tant l’octet de poids fort 00 dans la case 4 et l’octet de poids faible 01 dans la
case 5).
Si le programme est chargé comme à la gauche de la Figure 9.2, l’adresse réelle
de d est 1 également, car le programme est chargé à partir de l’adresse réelle 0.
Si le programme est chargé comme à la droite de la figure, l’adresse réelle de d
est 3.
Cet exemple montre clairement une des grosses difficultés qui est celle de faire corres-
pondre les adresses virtuelles (qui sont celles utilisées par le programme), et les adresses
réelles (qui sont celles dont la mémoire a besoin pour fonctionner).
Une solution simple est appelée relocation. Elle consiste, au moment du chargement du
programme, à ajouter à toutes les adresses (virtuelles) l’adresse réelle du début du programme,
qui constitue donc un décalage du programme en mémoire. Ainsi, si on a chargé le pro-
gramme à partir de la case d’adresse réelle 2 (comme sur la Figure 9.2, droite), on doit ajou-
257
9. Leçon 16 et 17 – Gestion de la mémoire primaire
ter 2 à toutes les adresse virtuelles, et on trouve bien 1 + 2 = 3 pour notre variable d . Il faut
néanmoins tempérer la simplicité apparente de la relocation. On peut parfaitement confier
cette tâche à l’OS qui lancera l’exécution du programme, mais celui-ci devra être en mesure
de retrouver, dans l’ensemble du contenu binaire du programme, quels sont les octets qui re-
présentent une adresse ou pas. Rappellons-nous la discussion de la fin du Chapitre 2 où nous
avions conclu que rien ne permet de savoir si une séquence binaire donnée représente une
valeur entière, ou un caractère, ou même une instruction machine. . . Le cas échéant, il fau-
dra donc ajouter au programme une table de relocation qui liste l’ensemble des positions du
programme où se trouve une adresse à modifier. Cette table peut être construite au moment
où le programme est traduit du langage de haut niveau vers le langage machine.
La relocation est l’opération qui consiste à ajuster toutes les adresses virtuelles
d’un programme en fonction de sa position en mémoire, pour transformer ces
adresses virtuelles en adresses réelles.
4. Observons que les valeurs que nous utilisons à titre d’exemple dans cette section sont tout à fait irréaliste.
Nous les choisissons car elles nous permettent de conserver nos exemples relativement petits.
258
9.2. Pagination
0 0
1 P 1 P
2 2
3 3
Q
4 Terminaison de Q
−−−−−−−−−−−−→ 4
5 5
6 6
R R
7 7
8 8
9 9
F IGURE 9.3. – Illustration de la fragmentation. L’espace libre est de 3 octets au total, mais on
ne peut pas y charger un programme de cette taille.
Nous allons maintenant introduire la technique de la pagination (que nous étendrons après
en pagination à la demande) pour résoudre ces problèmes.
9.2. Pagination
La technique de pagination (paging en anglais) repose sur plusieurs éléments, logiciels et
matériel :
1. les programmes à charger en mémoire sont découpés en portions qui ont toutes la
même taille ℓ, et qu’on appelle des pages ;
2. la mémoire primaire est elle-même découpée en portions qui sont aussi de taille ℓ, et
qu’on appelle des cadres de pages (page frames en anglais).
Puisque toutes les pages et tous les page frames ont la même taille on constate que n’importe
quelle page de n’importe quel programme peut être chargé dans n’importe quel page frame,
mais cela ne doit pas forcément être de façon contiguë, ni même dans l’ordre original du pro-
gramme. Puisque les pages des programmes peuvent maintenant se retrouver « mélangées »
dans la mémoire, il faut un mécanisme efficace pour faire correspondre adresses réelles et
adresses virtuelles, ce sera la tâche du troisième élément nécessaire à la pagination :
3. le processeur ne manipulera, dans ses registres que des adresses virtuelles. Un circuit
spécial, le memory management unit (MMU), se chargera de traduire les adresses vir-
tuelles en adresses réelles à destination de la mémoire primaire.
259
9. Leçon 16 et 17 – Gestion de la mémoire primaire
ß
Tout au long de cette section, nous allons considérer l’exemple de la Figure 9.4.
La mémoire primaire comporte 16 cases et nous souhaitons y charger (par for-
cément de manière simultanée) :
— un programme P de longueur 9 ;
— un programme Q de longueur 2 ;
— un programme R de longueur 5 ;
Enfin, nous avons fixé la longueur des pages et des page frames à ℓ = 3.
Commençons par ajouter à notre exemple l’identification des page frames. Rappelons que
la longueur des page frames est unique et fixée à ℓ. Ils sont construits comme suit.
260
9.2. Pagination
Les page frames sont numérotés à partir de 0. Le page frame i est constitué des
cases de la mémoire primaire dont les adresses (réelles) vont de i × ℓ à (i + 1) ×
ℓ − 1 (comprises).
Comme la mémoire n’a pas forcément un taille qui est un multiple de ℓ, il se peut que les
dernières cellules ne fassent partie d’aucun page frame et soient donc inutilisables. C’est le
cas sur notre exemple avec la cellule d’adresse 15.
261
9. Leçon 16 et 17 – Gestion de la mémoire primaire
contrairement au cas de la mémoire primaire, on ne peut pas se permettre d’ignorer les der-
nières cases du programme qui sont peut-être essentielles à son fonctionnement. On va donc
ajouter aux programmes des octets avec un contenu arbitraire (remplis de zéros par exemple)
pour obtenir des longueurs qui sont des multiples de ℓ. Sous cette hypothèse, nous avons une
définition des pages qui est très similaire à celle des page frames :
ß
Sur notre exemple, le programme P est constitué de trois pages que nous ap-
pelons P 0 , P 1 , P 2 , avec leurs numéros en indice. La page P 0 va bien de l’adresse
0 × ℓ = 0 × 3 = 0 à l’adresse 1 × ℓ − 1 = 3 − 1 = 2. La page P 1 va de l’adresse 3 à la 5,
et la page P 2 de l’adresse 6 à la 8.
Pour le programme Q, nous avons ajouté une case d’adresse 2 pour avoir un
multiple de 3, et ce programme ne comporte donc qu’une seule page Q 0 .
Enfin le programme R comporte, après ajout d’une case d’adresse 8, deux pages
R 0 et R 1 .
La Figure 9.5 illustre également cela.
Maintenant que la mémoire est découpée 5 en page frames et que les programmes sont dé-
coupés en pages, nous pouvons commencer à charger les pages en mémoire. Naturellement,
nous allons charger les pages dans les page frames, ce qui revient à faire coïncider le début
d’une page avec le début du page frame que l’on a choisi. La force du système de pagination
réside dans sa flexibilité : n’importe quelle page peut être chargée dans n’importe quel page
frame (libre), sans qu’on soit obligé de respecter l’ordre ou la continuité du programme.
Ainsi, chaque page frame libre peut être immédiatement exploité.
Plus tard, nous aurons besoin de retrouver la correspondance entre les pages et les page
frames. C’est pourquoi le système maintient, pour chaque programme chargé en mémoire,
une table des pages, qui comporte deux colonnes et autant de lignes qu’il y a de pages dans le
programme. Chaque ligne indique dans quel page frame est chargée la page correspondante,
est ces lignes sont appelées les descripteurs de pages. Le choix des page frames à assigner aux
programmes pour leurs pages, et la constitution de la table des pages sont des rôles de l’OS :
il fait ce choix et constitue cette table au moment de charger le programme en mémoire, en
fonction des page frames qui sont libres. L’OS doit donc également maintenir une liste des
page frames libres.
5. Il importe de remarquer à ce point que ces découpes sont virtuelles : il ne s’agit pas d’une découpe au niveau
matériel, seulement une vue de l’esprit qui permet d’expliquer comment fonctionne le système de pagination.
262
9.2. Pagination
Prog. P
Page P.F. Prog. Q
Page P.F.
0 2
1 0 0 1
2 3
F IGURE 9.6. – Les pages des programmes P et Q chargées en mémoire, avec leurs tables des
pages.
ß
La Figure 9.6 illustre le chargement des pages des programmes P et Q en mé-
moire. On voit que les pages du programme P sont chargées de façon disconti-
nue et dans un ordre différent de celui du programme. Les adresses virtuelles
et réelles ne coïncident donc pas. Par exemple, l’adresse virtuelle 4 de P qui
se trouve dans la page P 1 se retrouve à l’adresse réelle 1, puisque que c’est la
deuxième adresse de cette page. L’adresse virtuelle 0 de Q est à l’adresse réelle 3,
etc.
Les tables de pages de P et Q, qui retiennent où chaque page est chargée, sont
dans le bas de la Figure 9.6 : P 0 est chargée dans le page frame 2, P 1 dans le page
frame 0, etc.
263
9. Leçon 16 et 17 – Gestion de la mémoire primaire
Effet sur la fragmentation Notons que cette solution résout presque complètement le
problème de la fragmentation, puisque n’importe quelle page peut être insérée dans n’im-
porte quel page frame. La seule fragmentation qui reste est celle qui provient des cases sup-
plémentaires que nous avons dû insérer à la fin des programmes dont la taille n’est pas un
multiple de ℓ (on parle d’une fragmentation interne), mais elle est négligeable par rapport au
gain obtenu. L’exemple suivant montre que les page frames libres peuvent bien être utilisés
pour n’importe quel programme, contrairement aux fragments qui ne pouvaient pas toujours
accueillir un programme entier dans notre exemple du début du chapitre.
ß
La Figure 9.7 continue l’exemple précédent : le programme Q est maintenant
terminé et on a chargé le programme R en mémoire à sa place. Bien que l’espace
libre soit fragmenté, les fragments constituent des page frames et sont donc uti-
lisables pour les pages de R (et de n’importe quel autre programme d’ailleurs).
Effet sur le calcul des adresses Par contre, le problème du calcul des adresses est main-
tenant plus aigu que dans le cas de la relocation, car les programmes ne sont pas nécessai-
rement chargés d’un bloc, ni même dans l’ordre d’origine. Nous devons donc trouver une
solution pour faire cette traduction de manière efficace.
Traduction des adresses Voyons maintenant comment cette traduction peut se concevoir,
nous verrons plus tard comment l’implémenter de façon efficace. Supposons que nous avons
une adresse virtuelle α que nous voulons traduire en son adresse réelle correspondante.
264
9.2. Pagination
Prog. P
Prog. R
Page P.F.
Page P.F.
0 2
0 4
1 0
1 1
2 3
F IGURE 9.7. – Les pages des programmes P et R chargées en mémoire, avec leurs tables des
pages ; après que le programme Q a fini son exécution.
données
adresse adresse
virtuelle réelle Mémoire
CPU MMU
primaire
données
265
9. Leçon 16 et 17 – Gestion de la mémoire primaire
1. La première étape du calcul consiste à trouver la page qui contient cette adresse α.
Comme il y a, grosso modo, ℓ fois plus d’adresses que de pages (puisque chaque page
contient ℓ adresses), il faut diviser l’adresse virtuelle par ℓ pour trouver le numéro de
page. Il s’agit d’une division entière, que nous notons ÷. Le numéro de page correspon-
dant à α est donc :
p = α ÷ ℓ.
2. La seconde étape du calcul consiste à retrouver le page frame qui contient la page que
nous avons identifiée. Pour ce faire, nous allons utiliser la table des pages pour obtenir
le descripteur desc(p) de la page :
pf = desc(p)
= desc(α ÷ ℓ).
3. Une fois le page frame identifié, nous savons que la distance entre l’adresse virtuelle α
et le début de la page p est la même que la distance entre son adresse réelle et le début
du page frame pf . En effet, les informations d’une page sont chargées dans le même
ordre dans le page frame. Ainsi, le premier octet de la page est aussi le premier octet
du page frame, etc. Nous pouvons donc chercher à identifier cette distance. On peut
voir aisément qu’il s’agit du reste de la division entière que nous avons calculée au-
dessus. Nous appelons cette distance le décalage (entre l’adresse virtuelle et le début
de sa page) :
∆ = α mod ℓ.
4. Finalement, nous pouvons trouver l’adresse réelle en ajoutant (selon notre raisonne-
ment) ce décalage à l’adresse de début du page frame. Cette dernière est facile à obte-
nir, étant donnée la définition que nous avons donnée des page frames : le numéro i
commence à l’adresse i × ℓ :
Étant donné une adresse virtuelle α dans un système de pagination avec des
pages de longueur ℓ, l’adresse réelle correspondante est donnée par l’expres-
sion :
a.r. = desc(α ÷ ℓ) × ℓ + α mod ℓ
266
9.2. Pagination
Implémentation efficace de ce calcul dans le MMU Maintenant que nous avons établi
le principe de la traduction d’adresses virtuelles en adresses réelles, voyons comment nous
pouvons l’implémenter de façon efficace dans le MMU. Nous pouvons observer que les opé-
rations nécessaires sont la division, la multiplication et le modulo par ℓ. Or, toutes les valeurs
que le CPU, le MMU et la mémoire manipulent sont évidemment représentées en binaire,
et nous nous souvenons (voir Chapitre 2) que la division, la multiplication et le modulo par
une puissance de deux sont plus simples en calculer à binaire, puisqu’ils se réduisent à des
décalages et à un masque.
Nous allons donc supposer que ℓ = 2k pour une valeur de k entière, et voir quelle in-
fluence cela peut avoir sur notre calcul d’adresses. Pour fixer les idées, nous supposons que
les adresses sont représentées sur n bits. Notre adresse virtuelle α se présente donc comme
un tableau de n bits :
n −1 0
α=
adresse virtuelle
267
9. Leçon 16 et 17 – Gestion de la mémoire primaire
2. Équipés du numéro de page p, nous pouvons consulter la table des pages et obtenir
le numéro de page frame pf correspondant. Nous multiplions ensuite cette valeur par
ℓ = 2k ce qui revient à ajouter k zéros de poids faible :
n −1 k k −1 0
pf × ℓ = pf × 2k =
num. de page frame pf 0······0
3. Finalement, nous obtenons l’adresse réelle en ajoutant cette valeur pf × ℓ au déca-
lage ∆. Or, celui-ci tient sur k bits comme nous l’avons vu. Ces k bits de ∆ vont donc
venir remplacer les k bits de poids faible de pf ×ℓ (qui sont nuls), nous obtenons donc :
n −1 k k −1 0
adresse réelle = pf × ℓ + ∆ =
num. de page frame pf décalage ∆
Il est maintenant intéressant de comparer l’adresse réelle et l’adresse virtuelle. Le seul chan-
gement affecte les n − k bits de poids fort : ils contiennent le numéro de page dans l’adresse
virtuelle et le numéro de page frame dans l’adresse réelle. Le travail du MMU peut se résumer
à remplacer le numéro de page par le numéro de page frame, ce qui n’implique aucune opéra-
tion arithmétique : il suffit d’isoler les bits de poids fort pour obtenir le numéro de page et le
passer à la table des symboles.
ß
La Figure 9.9 montre un programme P de taille 16, divisé en quatre pages de
taille ℓ = 22 = 4 et chargé en mémoire. Si on considère l’adresse virtuelle 10012
(dans cet exemple, toutes les valeurs sont en binaire), on obtient le décalage ∆
en consultant les 2 bits de poids faible (puisque k = 2), soit 012 . Les bits de
poids fort restant sont donc le numéro de page, soit 102 . La table des pages nous
dit que cette page est dans le page frame 0112 . En multipliant cette valeur par
22 , on effectue en fait un décalage de deux positions vers la gauche, et on ob-
tient 011002 . Quand on ajoute cette valeur au décalage, on obtient bien l’adresse
réelle 011012 qui est constituée du décalage pour ses deux bits de poids faible ;
et du numéro de page frame pour ses trois bits de poids fort.
268
9.3. Pagination à la demande
a.v. : 10 01
Prog. P
Page P.F.
00 100 pf × 22 : 011 00
01 010 pf : 011
10 011 + ∆ : 01
11 000
a.r. : 011 01
269
9. Leçon 16 et 17 – Gestion de la mémoire primaire
réduit : nous avons besoin de la page qui contient l’instruction pour permettre le fetch, ainsi
qu’une éventuelle autre page qui contiendrait une donnée que l’instruction manipule.
On peut donc étendre le mécanisme de pagination en pagination à la demande (on-demand
paging en anglais), dans lequel le système chargera une page du programme en mémoire
primaire au moment où celle-ci est nécessaire (au lieu de charger l’ensemble des pages du
programme comme dans le mécanisme de pagination de base). Les pages qui ne seront pas
chargées en mémoire primaire seront conservées en mémoire secondaire, en attente d’être
chargées. C’est ce mécanisme qui est utilisé dans la plupart des systèmes d’exploitation mo-
dernes.
Cette façon de faire offre certains avantages :
1. tout d’abord, le temps de chargement des programmes est amélioré, puisqu’il suffit
de ne charger que la ou les quelques pages nécessaires pour démarrer l’exécution du
programme (typiquement celle qui contient la première instruction à exécuter dans le
programme, ce qu’on appelle le « point d’entrée ») ;
2. ensuite, et c’est l’avantage le plus important, on peut exécuter un ensemble de pro-
grammes qui sont, individuellement ou collectivement, plus grands que la mémoire
physique disponible. Par exemple, sur un processeur 32 bits (et dont le registre PC fait
aussi cette taille), chaque programme peut avoir une taille de 232 octets, soit 4 Gio,
et ce, même si la mémoire primaire ne fait que quelques Mio. C’est la raison pour la-
quelle cette technique est souvent appelée mémoire virtuelle, car il permet à chaque
programme de s’exécuter dans un espace mémoire virtuel aussi vaste que les registres
d’adressage le permettent.
Mais ce n’est pas une solution parfaite non plus, elle offre quelques inconvénients que nous
étudierons à la fin du chapitre. Voyons maintenant comment fonctionne ce mécanisme, de
manière concrète.
270
9.3. Pagination à la demande
Le chargement de la page manquante sera confié à l’OS. En effet, la page manquante est
présente sur la mémoire secondaire, et c’est l’OS qui gère la structure de cette mémoire. Le
MMU devra donc faire en sorte que le programme qui cherche à accéder à la page manquante
soit interrompu temporairement, pour permettre d’exécuter une routine de l’OS qui charge
la page en mémoire. Le MMU va donc déclencher une interruption, qui s’appelle default de
page (page fault). Comme avec toute interruption, cela va sauvegarder les registres et tout ce
qui est nécessaire à la bonne exécution du programme et faire pointer PC vers la première
instruction du gestionnaire d’interruption. Celui-ci sera en charge de s’assurer que la bonne
page est chargée en mémoire (cfr. infra).
Concrètement, la manière dont les pages sont stockées dans la mémoire secondaire dé-
pend fort de l’OS, et c’est bien pour pouvoir jouir de cette flexibilité que le chargement des
pages se fait via l’OS et non pas de façon matérielle. Par exemple, sous GNU/Linux, c’est en
général une partition de la mémoire secondaire qui sert à cela. Tandis que sous Windows,
c’est en général un fichier qui contient ces copies.
ß
La Figure 9.10 reprend notre exemple initial dans le contexte de la pagination à
la demande. Le programme R n’a pas été chargé en mémoire et ses pages sont
donc stockées dans la mémoire secondaire. Les tables des pages ont été adap-
tées pour montrer les bits de présence.
7. Le verbe swap en anglais signifie échanger. En effet, la zone de mémoire secondaire où sont gardées les
pages en attente s’appelle souvent le fichier ou la partition d’échange, ou swap file, swap partition voire simple-
ment swap en anglais
271
9. Leçon 16 et 17 – Gestion de la mémoire primaire
Prog. P
Prog. R
Page P.F. Prés. Prog. Q
Page P.F. Prés.
Page P.F. Prés.
0 2 1
0 ? 0
1 0 1 0 1 1
1 ? 0
2 3 1
F IGURE 9.10. – Les pages des programmes P et Q chargées en mémoire, avec leurs tables des
pages, dans le cadre de la pagination à la demande. Les pages du programme
R sont dans la mémoire secondaire.
272
9.4. Difficultés liées à la pagination
ß
Continuons notre exemple, et supposons qu’on accède à l’adresse virtuelle 4
de R, qui se trouve dans la page R 1 . Le MMU va commencer par calculer ce
numéro de page, et va constater que le bit de présence de R 1 est 0. Il va donc
déclencher un page fault. L’OS va se rendre compte que le page frame 4 est libre,
et va effectuer le swap in : il va charger R 1 dans le page frame 4, et va mettre la
table des pages de R à jour en associant ce page frame à la page 1 et en mettant
son bit de présence à 1. Le résultat est montré à la Figure 9.11.
Ensuite, supposons qu’on accède à l’adresse virtuelle 2 de R, qui est dans la
page R 0 . À nouveau, cette page n’est pas en mémoire et un page fault a lieu.
Cette fois-ci, il n’y a plus de page frame de libre, et l’OS choisit comme victime
P 0 . Celle-ci est swappée out, c’est-à-dire copiée dans la mémoire secondaire et
son bit de présence est mis à 0 dans la table des pages. Puis R 0 est swappée in :
elle est chargée dans le page frame 2 qu’occupait P 0 et la table des pages de R
est mise à jour. Le résultat est montré à la Figure 9.12.
Taille et stockage de la table des pages La table des pages peut rapidement devenir très
grande ! Plus les pages sont petites, et plus la table des pages est grande. . . Supposons qu’on
ait des pages de 210 octets soit 1 kio, et un programme de 230 octets, soit 1 Gio. Il a donc 220
entrées dans sa table des pages. En supposant qu’un descripteur tiennent sur 4 octets, on a
273
9. Leçon 16 et 17 – Gestion de la mémoire primaire
Prog. P
Prog. R
Page P.F. Prés. Prog. Q
Page P.F. Prés.
Page P.F. Prés.
0 2 1
0 ? 0
1 0 1 0 1 1
1 4 1
2 3 1
F IGURE 9.11. – Des pages des programmes P , Q et Rchargées en mémoire, avec leurs tables
des pages, dans le cadre de la pagination à la demande. R 1 vient d’être char-
gée.
274
9.4. Difficultés liées à la pagination
Prog. P
Prog. R
Page P.F. Prés. Prog. Q
Page P.F. Prés.
Page P.F. Prés.
0 2 0
0 2 1
1 0 1 0 1 1
1 4 1
2 3 1
F IGURE 9.12. – Des pages des programmes P , Q et R chargées en mémoire, avec leurs tables
des pages, dans le cadre de la pagination à la demande. R 0 vient d’être chargée
à la place de P 0 .
275
9. Leçon 16 et 17 – Gestion de la mémoire primaire
donc un taille de table des pages 222 octets, soit 4 Mio. On peut évidemment augmenter la
taille des pages, mais cela augmente la fragmentation interne, c’est-à-dire l’espace potentiel-
lement libre à l’intérieur de la dernière page du programme.
Comme la table des pages peut devenir très grande, on n’est pas forcément capable de la
stocker toute entière dans un registre du MMU. Il faut donc la placer en mémoire primaire,
ce qui pose de nouvelles difficultés. D’une part, elle occupe de la place qui pourrait être utili-
sée pour des programmes, d’autre part, il faut s’arranger pour que les zones de mémoire qui
la contienne ne soient jamais swappées out. La plupart des CPUs et des OS supportent un
mécanisme permettant de marquer certaines pages qui sont interdites de swap out (on parle
de pages « sticky », collantes). Ces pages peuvent également servir à contenir les routines de
gestion d’interruption du page fault qui doivent évidemment être en mémoire au moment
où cette interruption a lieu !
En pratique, il y a souvent plusieurs niveaux de tables des pages, comme nous le verrons
dans l’exemple qui suit.
Phénomène de thrashing Comme nous l’avons déjà évoqué, les swap in et swap out sont
des mécanismes très coûteux en temps, puisqu’il faut déclencher une interruption, exécuter
une routine de traitement, interroger la mémoire secondaire (qui est très lente par rapport au
CPU et à la mémoire primaire), etc. Parfois, tout cela est nécessaire pour exécuter une seule
instruction machine ! Si le système se retrouve trop chargé, avec trop de programmes à exécu-
ter pour peu de mémoire primaire libre, il risque de se retrouver à passer plus de temps dans
les swaps que dans l’exécution des programmes. Ce phénomène est appelé thrashing 8 . Il était
particulièrement marqué il y a quelques années quand la mémoire secondaire était consti-
tuée de disques durs avec des pièces mécaniques et bruyantes, qui se mettaient à émettre en
continue un bruit rappelant les moulins à café électriques. . .
Nécessité d’un MMU Naturellement, la pagination ne pourrait pas fonctionner sans MMU.
Tous les processeurs modernes qui sont utilisés sur les ordinateurs personnels en possèdent
un, mais il existe encore de nombreux processeurs, appelés micro-contrôleurs qui n’en ont
pas. Ceux-ci sont en général utilisés dans des application spécifiques pour lesquelles aucun
OS n’est nécessaire, typiquement dans les systèmes embarqués : électro-ménager, systèmes
de contrôles de véhicules, etc
276
9.5. Exemple : pagination sur l’Intel 486
Le système de pagination à la demande de l’i486 est plus complexe que ce que nous avons
décrit en théorie ci-dessus, et il illustre bien la réalité de la pagination sur un vrai processeur.
Néanmoins, il repose sur les même principes.
Les adresses virtuelles de l’i486 tiennent sur 32 bits, et les pages ont une taille fixée de ℓ =
212 octets, soit 4 kio. Un programme peut donc avoir jusqu’à 220 pages, soit à peu près un
million de pages.
Une adresse peut donc être décomposée en deux parties : le décalage qui est constitué des
12 bits de poids faible et le « numéro de page » dans les 20 bits de poids fort :
31 12 11 0
numéro de page décalage
31 22 21 12 11 0
a.v. :
directory d table t décalage ∆
277
9. Leçon 16 et 17 – Gestion de la mémoire primaire
ß
Supposons qu’on ait l’adresse virtuelle :
31 22 21 12 11 0
a.v. : directory d table t décalage ∆
00 0010 1110 00 0000 1010 1001 1100 0011
31 0
CR3 :
1010 1010 1010 1010 1010 0000 0000 0000
On commence par consulter l’entrée numéro 00 0010 1110 du directory, qui est
à l’adresse :
31 0
adresse de l’entrée du directory :
1010 1010 1010 1010 1010 0000 0010 1110
31 12 11 0
entrée du directory :
0101 0101 0101 0101 0101 ···
31 12 11 0
adresse du descripteur :
00 0101 0101 0101 0101 0101 00 0000 1010
31 12 11 0
descripteur :
1001 1001 1001 1001 1001 ···
31 12 11 0
adresse réelle :
1001 1001 1001 1001 1001 1001 1100 0011
278
9.5. Exemple : pagination sur l’Intel 486
F IGURE 9.13. – Le mécanisme de traduction d’adresse de l’i486, extrait de [12]. Notons que
dans la documentation technique d’Intel, les adresses virtuelles sont appelées
« adresses linéaires » (linear address sur la figure).
tion.
Enfin, notons qu’avec ce système de table des pages « à deux niveaux » la traduction d’une
adresse virtuelle en adresse réelle est un mécanisme coûteux qui demande plusieurs accès
mémoire, ce qui n’est pas réaliste. C’est pourquoi l’i486 implémente un mécanisme similaire
à un cache : il possède un Translation Lookaside Buffer (TLB) qui est une table à 32 entrées
dans lequel il stocke les 32 descripteurs les plus récents, et ce, directement dans le CPU (il
n’y a donc pas d’accès en mémoire nécessaire). Comme chaque page fait 4 kio, ces 32 entrées
permettent de couvrir 128 kio de mémoire, et Intel indique qu’une adresse réelle peut être
traduite en adresse virtuelle uniquement à l’aide du TLB (donc, sans accès mémoire) dans
98% des cas.
M8N
279
9. Leçon 16 et 17 – Gestion de la mémoire primaire
9.6. Exercices
Dans ces premiers exercices, nous considérons un système hypothétique dans lequel il y a
deux processus P et Q. P a une taille de 15 octets et Q, 6 octets. La mémoire de ce système
est gérée à l’aide du mécanisme de pagination à la demande, où les pages ont une taille de 4
octets. La mémoire disponible fait 17 octets, les registres sont des registres de 8 bits. Notons
que ces valeurs ne sont pas réalistes (elles sont beaucoup trop petites) mais elles sont plus
faciles à manipuler en pratique, et les raisonnements sont identiques pour des valeurs plus
grandes.
Supposons en outre que l’état de la mémoire est le suivant, où P i et Q i désignent respecti-
vement la i e page de P et Q (numérotées à partir de 0) :
Les pages qui ne sont pas en mémoire sont présentes en mémoire secondaire.
Ex. 48 De combien de bits a-t-on besoin pour représenter une adresse réelle dans ce système ?
Et pour une adresse virtuelle ?
Ex. 49
1. Combien de page frames y a-t-il en mémoire ?
2. De combien de pages sont constitués les processus P et Q ?
3. Quelles sont les adresses réelles contenues dans chacun des page frames ?
280
9.6. Exercices
4. Y a-t-il des adresses réelles qui ne sont dans aucun page frame ? Si oui, lesquelles ?
5. Quelles sont les adresse virtuelles qui sont contenues dans la page numéro 2 du proces-
sus P ?
Ex. 50 Sur les n bits qui composent une adresse virtuelle (cfr. exercice 48), quels sont ceux qui
codent le numéro de page ? le décalage dans la page ?
Ex. 52 Étant donné l’adresse virtuelle 9 dans P . À quelle page cette adresse appartient-elle ?
Quelle est son décalage dans la page ? Quelle est l’adresse réelle correspondante ? Faites d’abord
les calculs en décimal. Ensuite, refaites les en binaire, en utilisant le résultat de l’exercice 50
(remplacez les bits codant le numéro de page par les bits codant le numéro de page frame).
Ex. 53 On vous demande d’indiquer quel sera l’effet sur la gestion mémoire de ces actions
(pages accédées, page faults, etc) en supposant que l’algorithme de choix de victime consiste
à utiliser les page frames dans l’ordre dans lequel ils apparaissent en mémoire (d’abord le 0,
puis le 1, et ainsi de suite de manière cyclique). Donnez également l’état de la mémoire à la fin
de ces opérations.
9.6.1. Corrections
Correction de l’exercice 48 Comme on n’a que 17 octets de mémoire, toutes les adresse réelles tiendront sur 5
bits. Les adresse virtuelles sont limitées par la taille des registres, soit 8 bits.
Correction de l’exercice 49
1. Comme on a 17 octets de mémoire et que chaque page ou page frame fait 4 octets, on peut mettre 4 page
frames en mémoire.
2. Par le même raisonnement, P fait 4 pages et Q, 2 pages.
3. Le page frame numéro 0 commence à l’adresse réelle 0 et contient 4 adresses, donc les adresses réelles 0,
1, 2 et 3. En continuant ce raisonnement, on a :
P.F. Adresses
0 0, 1, 2, 3
1 4, 5, 6, 7
2 8, 9, 10, 11
3 12, 13, 14, 15
4. Oui, l’adresse réelle 16 (la dernière de la mémoire) n’est dans aucun page frame puisque 17 n’est pas un
multiple de 4.
281
9. Leçon 16 et 17 – Gestion de la mémoire primaire
Correction de l’exercice 50 Comme les pages sont de taille 4, il ne faut que 2 bits pour coder le décalage. Ce sont
les 2 bits de poids faible. Les 6 bits de poids fort donnent le numéro de page.
Correction de l’exercice 51
Page PF présence
0 ?? 0
Pour P : 1 ?? 0
2 1 1
3 ?? 0
Page PF présence
Pour Q : 0 2 1
1 0 1
Correction de l’exercice 52 Il s’agit de la page 2, le décalage est de 1. L’adresse réelle est égale à l’adresse de début
du page frame qui contient la page, plus le décalage, soit 4 + 1 = 5.
En binaire : 910 = 10012 . Sur 8 bits, l’adresse virtuelle est donc 00001001. Les 6 bits de poids fort donnent le
numéro de page, à savoir 000010 = 210 . Les 2 bits de poids faible donnent le décalage, à savoir 01 = 110 . Pour
trouver l’adresse réelle, on remplace les 6 bits codant le numéro de page par les 3 bits codant le numéro de page
frame. Le page frame correspondant est le 1, sur 3 bits : 001. L’adresse réelle est donc 001 01 = 510 .
Correction de l’exercice 53
— Le processus P devient le processus actif : on charge le descripteur de page de P dans le MMU.
— On accède à l’adresse virtuelle 9 dans le processus P : on accède à l’adresse réelle 5 dans le PF 1 (cfr supra).
— On accède à l’adresse virtuelle 1 dans le processus P : cette adresse est dans la page 0 (décalage 1), qui
n’est pas en mémoire. Il y a un page fault, et la page P 0 est chargée dans le PF libre, à savoir le PF 3. La
table des pages de P devient :
Page PF présence
0 3 1
1 ?? 0
2 1 1
3 ?? 0
Une fois cette page chargée, on accède à l’adresse réelle donnée par
(début PF 3) + décalage = 12 + 1 = 13
— On accède à l’adresse virtuelle 14 dans le processus P : cette adresse est dans la page 3 (décalage 2), qui
n’est pas en mémoire. Il y a un page fault, et on choisit de swapper out le contenu du PF 0. La table des
pages de Q devient donc :
Page PF présence
0 2 1
1 0 0
282
9.6. Exercices
Page PF présence
0 3 1
1 ?? 0
2 1 1
3 0 1
Une fois cette page chargée, on accède à l’adresse réelle donnée par
(début PF 0) + décalage = 0 + 2 = 2
1
P3 PF 0
2
5
P2 PF 1
6
9
Q0 PF 2
10
11
12
13
P0 PF 3
14
15
16
M8N
283
284
10. Leçon 18 – Gestion des processus et de
la mémoire secondaire
Dans ce dernier chapitre, nous allons nous intéresser à deux tâches importantes de l’OS, à
savoir la gestion des processus et la gestion de la mémoire secondaire. La gestion des proces-
sus nous montrera comment on s’y prend pour exécuter plusieurs programmes en parallèle
sur un ordinateur qui a moins d’unités d’exécutions que de programmes en cours d’exécu-
tion. Ensuite, nous étudierons la façon dont l’OS offre une vue structurée en fichiers, en ré-
pertoires, etc de la mémoire secondaire.
285
10. Leçon 18 – Gestion des processus et de la mémoire secondaire
Ordonnan- Préemption /
cement Rendre la main
Bloqué En attente
F IGURE 10.1. – Le cycle de vie des processus. Les changements de statut en trait discontinu
sont des décisions de l’ordonnanceur.
— les contenus des différents registres de travail, qui sont des données nécessaires à la
bonne exécution du programme ;
— le statut du processus (cfr. infra) ;
— ...
Comme le suggère cette définition, la gestion des processus est une tâche confiée à l’OS, ce
qui est bien compatible avec notre idée que l’OS a un rôle d’arbitre. L’OS veillera donc à ce que
les ressources matérielles de l’ordinateur soit réparties de manière équitable et juste entre les
différents processus. Naturellement, la plus importante de ces ressources matérielles sera
l’accès au(x) CPU(s).
Afin de bien comprendre quel rôle joue l’OS dans la gestion des processus, nous allons nous
intéresser aux deux questions suivantes :
1. Quel est le cycle de vie d’un processus ?
2. Comment peut-on exécuter plus de processus qu’il n’y a d’unités d’exécution dispo-
nibles ? c’est-à-dire, comment faire du multitâche ?
286
10.1. Gestion des processus
1. Création : en général, un processus ne peut être créé que par l’OS, et uniquement à
la demande d’un autre processus. Cette demande se fait à l’aide d’un appel système.
Après l’appel système, il y a un processus de plus sur le système. Le processus qui a fait
la demande de création est appelé processus père, et le nouveau processus est appelé
processus fils.
Lors de la création, l’OS doit essentiellement créer un nouveau contexte pour le nou-
veau processus, et retenir dans ses tables les caractéristiques de ce nouveau processus.
Comme un processus ne peut être créé que s’il en existe déjà un autre, il faut qu’un
premier processus sont créé au démarrage du système, de manière automatique.
2. Actif : Lorsque le processus s’exécute sur un des CPU, on dit qu’il est actif.
3. En attente : Lorsque le processus n’a pas fini de s’exécuter, qu’il désire disposer du
CPU, mais qu’il n’en dispose pas, il est en attente.
4. Bloqué : Lorsque le processus n’a pas fini de s’exécuter, mais qu’il n’est pas capable de
continuer à s’exécuter (par exemple parce qu’il attend un périphérique), le processus
est bloqué.
5. Terminé : Lorsque le processus a terminé son exécution, il le signale à l’OS à l’aide d’un
appel système. L’OS doit alors libérer les dernières ressources encore utilisées par le
processus (mémoire, fichiers encore ouverts,. . . ). Un processus peut aussi être tué par
l’OS : quand le processus commet une erreur (trap, accès à une zone mémoire interdite
détectée lors d’un page fault), l’OS peut décider de le faire passer à l’état terminé. On
risque alors de perdre des données.
Les différents changements de statut possibles sont illustrés par les flèches à la Figure 10.1.
Les transitions cruciales sont celles qui concernent les états actif, bloqué et en attente. C’est
parce qu’on va pouvoir faire en sorte qu’un processus est « en attente » qu’on va pouvoir
exécuter plusieurs processus en même temps sur un même processeur, c’est-à-dire faire du
multitâche (en partage de temps), comme nous allons le voir dans la section suivante.
ß
Sur les systèmes de type Unix, c’est le processus init qui est automatiquement
créé au lancement du système, et qui va avoir la charge de créer les autres pro-
cessus nécessaires au bon fonctionnement du système. Une fois créé, init lit
un fichier de configuration sur le disque, qui lui indique ce qu’il y a lieu de faire.
En général, il s’agit du fichier /etc/inittab. Ce fichier contient en général le
nom d’un script qui doit être exécuté au démarrage du système et qui va dé-
marrer tous les services nécessaires (initialisation du réseau, du service d’im-
pression, etc). Ensuite, init crée un processus dont le rôle est d’attendre les
commandes de l’utilisateur, de les interpréter (à l’aide de l’interpréteur de com-
mande si nécessaire) et de les exécuter. Cela peut être une invite de commande
où l’utilisateur entre des commandes au clavier de manière textuelle, ou bien
un panneau comprenant des boutons sur lesquels on clique pour lancer des ap-
plications (barre des tâches, dock,. . . ) ../.
287
10. Leçon 18 – Gestion des processus et de la mémoire secondaire
288
10.1. Gestion des processus
dans leur exécution très rapidement, et l’utilisateur aura l’illusion qu’ils s’exécutent tous en
parallèle.
Nous devons donc examiner deux choses : comment un processus passe-t-il de l’état d’at-
tente à l’état actif, et vice-versa ? Pour répondre à cette question, un élément important de
l’OS est l’ordonnanceur (scheduler en anglais). C’est ce module qui décidera quel est le pro-
chain processus qui aura accès au CPU, en fonction de différentes contraintes. En particulier,
dans le cas d’un OS temps-réel, le choix du processus qui doit s’exécuter sur le CPU doit ga-
rantir qu’aucun ne rate son échéance. Dans un OS « classique », l’ordonnanceur se contentera
en général de donner le CPU au processus l’un après l’autre de façon circulaire.
Pour passer d’un processus à l’autre, l’OS réalise un changement de contexte qui consiste à :
1. sauvegarder le contexte de l’ancien processus (par exemple, les valeurs de registres de
travail) afin de pouvoir le restaurer plus tard ; et
2. charger le contexte du nouveau processus afin qu’il puisse continuer à s’exécuter.
Cela peut naturellement prendre du temps, surtout si, par exemple, le nouveau processus a
ses pages présentes en mémoire secondaire et qu’il faut les recharger.
Nous avons donc maintenant bien compris que c’est l’ordonnanceur qui décidera quel est
le processus qui pourra passer de l’état « en attente » à l’état « actif » (voir Figure 10.1). Voyons
maintenant comment un processus peut passer de l’état « actif » à l’état « en attente » pour
permettre à l’ordonnanceur de prendre sa décision. Il existe deux techniques qui constituent
deux variations du time sharing.
Le time sharing préemptif Dans ce cas, l’horloge système est programmée pour déclen-
cher, à intervalle réguliers (séparés par le quantum de temps), une interruption. Le processus
actif sera donc interrompu volens nolens. À nouveau, le gestionnaire d’interruption consis-
tera à faire appel à l’ordonnanceur qui désignera le prochain processus à exécuter, sauvegar-
dera le contexte de l’ancien, et chargera le nouveau.
En cas de surcharge du système (trop de processus), les changements de contexte peuvent
devenir très fréquents. Si les processus présents consomment beaucoup de mémoire, il y a
un risque que chaque changement de contexte entraîne le chargement d’une page mémoire
depuis la mémoire secondaire. Les changements de contexte prennent alors énormément de
temps, au pire cas, plus de temps que l’exécution des processus eux-mêmes. On a alors un
phénomène de thrashing, comme dans le cas de la surcharge due à la pagination.
289
10. Leçon 18 – Gestion des processus et de la mémoire secondaire
Volumes et systèmes de fichiers Typiquement, la vue que l’OS nous offre de la mémoire
secondaire se décompose comme suit :
Volume Au plus haut niveau, l’ensemble de la mémoire secondaire est vue comme un en-
semble de volumes. Un même périphérique de mémoire secondaire peut contenir un
ou plusieurs volumes. Par exemple, les disques durs peuvent être découpés en parti-
tions, qui constituent chacune un volume. Concrètement, une partition est une plage
d’adresses physiques auxquelles on accède comme si elles formaient un disque à part.
Chaque volume possède en général un système de fichiers, qui peut être d’un type dif-
férent d’un volume à l’autre.
Répertoires ou dossiers À l’intérieur d’un système de fichiers (et donc d’un volume), on
peut créer un ou plusieurs répertoires ou dossiers (directory en anglais). Ceux-ci se com-
portent de manière analogue aux dossiers papiers : leur but est d’offrir une structure,
mais pas de fournir de l’information. Un répertoire peut donc contenir d’autres réper-
toires mais aussi des fichiers. Chaque répertoire est identifié par un nom.
Fichiers Finalement, l’unité élémentaire de données est celle du fichier. Par exemple, un do-
cument réalisé dans un traitement de texte sera stocké dans un seul fichier. Le code
290
10.2. Gestion de la mémoire secondaire
machine d’un programme sera lui aussi stocké dans un ou plusieurs fichiers. Un fichier
est en général identifié par un nom, qui peut comporter une extension : il s’agit d’une
série de caractères qui apparaissent après un point dans le nom du fichier, et qui per-
met d’obtenir de l’information sur le type du contenu du fichier.
Comme un fichier se trouve dans un répertoire, lequel se trouve dans un volume, il est
nécessaire de fournir le nom du volume et les noms des répertoires qui contiennent ce
fichier afin de l’identifier complètement. L’exemple suivant illustre tout cela.
ß
Sous Windows, chaque volume est identifié par une lettre. En règle général, la
partition principale du disque dur principal possède la lettre C 1 , et est donc
appelé « C : ». C’est ce volume qui contient le système d’exploitation.
Chaque répertoire et chaque fichier possède un nom. On utilise le caractère \
pour spécifier la séquence des répertoires qui contiennent un fichier donné,
dans un volume. Par exemple :
C:\Mes Documents\Alan Turing\preuveQuePégalNP.txt
est un « chemin » qui identifie un fichier appelé preuveQuePégalNP.txt. Celui-
ci se trouve dans le répertoire Alan Turing ; qui est dans le répertoire Mes
Documents ; ce dernier étant dans le volume C:. Dans le nom du fichier, l’ex-
tension .txt semble indiquer que le fichier contient du texte.
Dans un système de type Unix, comme GNU/Linux, les volumes n’ont pas de
nom propre, mais sont identifiés à des répertoires. On utilise le caractère / pour
spécifier un chemin, qui commence toujours par /. Par exemple :
/home/aturing/helloworld.cpp
identifie le fichier helloworld.cpp, dans le répertoire aturing, qui est lui-
même dans le répertoire home. Ce dernier n’est contenu dans aucun autre ré-
pertoire et est donc « à la racine ». Ce chemin ne nous permet pas de savoir quel
est le volume qui contient le fichier. Il se pourrait que l’ensemble du répertoire
/home/ soit stocké sur un volume indépendant du reste des fichiers.
Ces exemples montrent bien qu’un système de fichiers induit une structure arborescente :
chaque répertoire se divise en plusieurs répertoires et fichiers, qui forment autant de ramifi-
cations. Le répertoire au plus haut niveau est appelé la racine et le chemin qui permet d’ac-
céder à un fichier depuis la racine est une branche. La Figure 10.3 illustre cela.
Accès aux fichiers Maintenant que nous avons compris la structure de haut niveau qui
contient les fichiers, voyons comment on peut y accéder de façon typique dans un OS. Un
fichier sera vu comme un espace de stockage qui peut s’étendre en fonction des besoins (on
peut ajouter des l’information « à la fin du fichier » dans la limite de l’espace disponible sur le
périphérique), et contigu. On aura donc, tout naturellement, un système d’adresses virtuelles
à l’intérieur du fichier : on accédera au premier octet du fichier (adresse 0), au second, etc
pour y lire ou écrire. Ce sera la tâche de l’OS de traduire ces adresses virtuelles en adresses
réelles (numéro de bloc sur le périphérique) afin de réaliser l’opération demandée.
291
10. Leçon 18 – Gestion des processus et de la mémoire secondaire
ls helloworld.cpp coucoulemonde.py
F IGURE 10.3. – Un exemple de structure arborescente d’un système de fichiers Unix. Les
nœuds en forme de rectangle sont des fichiers, les autres sont des répertoires.
Le fichier /usr/bin/ls est un lien virtuel vers /bin/ls.
Fichiers virtuels Un des grands avantages d’avoir un accès à la mémoire secondaire à tra-
vers une structure de fichiers et que rien n’oblige à ce que chaque fichier ait une existence
physique sur un des périphériques. Puisqu’on accède au contenu des fichiers par des appels
systèmes à l’OS, ce dernier peut associer à certains noms de fichiers des sources d’informa-
tions qui ne sont pas sur les périphériques, et renvoyer ces informations en cas de lecture
comme s’il s’agissait d’un véritable fichier.
Un exemple typique est l’utilisation de liens symboliques (ou alias) qui consiste à faire en
sorte que certains noms de fichiers soient en fait des substituts pour d’autres fichiers, ce qui
évite la duplication d’information. Par exemple, sur la Figure 10.3, /usr/bin/ls est un lien
virtuel vers /bin/ls. Cela signifie qu’on peut accéder à /usr/bin/ls comme s’il s’agissait
d’un « vrai » fichier sur le disque, mais que l’OS traitera toutes ces requêtes en accédant à
/bin/ls (qui, lui, a une véritable existence sur le disque).
Un autre exemple est le contenu du répertoire /proc sous GNU/Linux (et d’autres OS de
type Unix). Les fichiers contenus dans ce répertoire ne sont pas des vrais fichiers, mais bien
des sortes de « points d’entrée » pour obtenir de l’information venant de l’OS. Par exemple,
si on consulte le contenu du « fichier » /proc/cpuinfo, on accède à des informations sur le
type de CPU installé. Néanmoins, ces informations ne sont pas stockées sur le disque : quand
on accède au fichier, l’OS exécute des routines pour obtenir cette information, qu’il formate
et renvoie comme si on lisait dans un fichier. On peut encore citer /dev/random qui renvoie
des nombres aléatoires, ou /dev/null qui absorbe toute l’information qu’on y écrit. . .
292
10.2. Gestion de la mémoire secondaire
Il nous reste à expliquer comment l’OS effectue la traduction des requêtes de l’utilisateur
(qui sont exprimées sur la structure logique de la mémoire secondaire) en requêtes pour les
périphériques. Par exemple, un programme utilisateur pourrait vouloir écrire le quinzième
octet du fichier /home/aturing/helloworld.cpp, et l’OS doit traduire cela en un accès à
un bloc particulier à modifier.
Pour ce faire, l’OS doit donc maintenir une structure qui fait le lien entre les fichiers et
les blocs qu’ils occupent. Comme les fichiers peuvent voir leur taille changer dans le temps,
les blocs occupés par un même fichier ne sont pas nécessairement contigus ni dans l’ordre du
fichier, exactement comme les page frames qu’un programme occupe. Typiquement, l’OS va
maintenir des tables, stockées dans un endroit réservée de la mémoire secondaire, et qui
contiennent les informations suivantes :
1. une table d’allocation des blocs permettra, pour chaque bloc, de savoir s’il est libre ou
s’il est occupé par un fichier ;
2. une table d’allocation des fichiers permettra d’obtenir toutes les informations néces-
saires sur un fichier donnée : son nom, ses permissions, sa date de création, la liste des
blocs qu’il occupe, etc.
À l’aide de ces deux tables, l’OS peut facilement effectuer les opérations de base nécessaires
sur les fichiers :
Lire / écrire à l’adresse a Pour lire ou écrire à l’adresse (virtuelle, relative) a dans le fi-
chier, l’OS doit d’abord déterminer le bloc qui contient cette adresse. Il divise donc a
par la taille du bloc, et obtient le numéro d’ordre du bloc à accéder (mettons que c’est
le i e bloc du fichier). Il consulte la table d’allocation des fichiers pour obtenir l’adresse
physique de ce bloc. Il calcule a mod b (où b est la taille d’un bloc) pour trouver la
position recherchée à l’intérieur de ce bloc.
Modifier la taille du fichier S’il est nécessaire d’étendre la taille du fichier ou de libérer de
la place, l’OS pourra utiliser la table d’allocation des blocs pour ajouter un nouveau
bloc au fichier, ou rendre libre un bloc qui n’est plus utilisé par le fichier, selon le cas.
Outre ces tables qui décrivent la structure des fichiers sur les périphériques de mémoire se-
condaire, l’OS maintient en général des tables, associées à chaque processus, qui retiennent
quels fichiers sont ouverts, et de quelle manière (par exemple, en cas de lecture dans un fi-
chier, où en est l’avance de la lecture, c’est-à-dire un pointeur dans le fichier). Ces tables font
naturellement partie du contexte du processus.
Voyons maintenant un exemple de système de fichiers, avec ses tables.
293
10. Leçon 18 – Gestion des processus et de la mémoire secondaire
ß
À titre d’exemple, nous décrivons brièvement le système FAT (File Allocation
Table, qui est un système de fichiers très simple mais largement utilisé depuis
qu’il a été adopté comme système de fichiers pour MS-DOS dans les années
1980. Plus précisément, nous discutons la version FAT16, qui est obsolète, mais
permet des exemples plus compacts. Ses évolutions, les versions FAT32 et exFAT
se basent sur le même principe [4].
Voici les hypothèses : on suppose que les blocs (appelés clusters dans FAT) font
4 kio. Dans cette norme, chaque cluster possède une adresse physique sur 16
bits, c’est-à-dire sur 4 chiffres en hexadécimal. Comme expliqué plus haut, la
structure en fichiers repose sur plusieurs tables : une table d’allocation des clus-
ters qui permet de savoir quels clusters sont libres et de reconstituer la liste de
clusters d’un fichier ; et une table de répertoire pour chaque répertoire du sys-
tème (il y en a au moins une pour la racine), qui contient la liste des fichiers et
répertoires que le répertoire en question contient.
La table des clusters C contient une entrée C [i ] pour chaque cluster i . Si le
contenu C [i ] est 016 , le cluster i est libre. Si C [i ] contient une valeur dans l’inter-
valle [216 , ffef16 ], alors cette valeur indique quel est le cluster qui suit le cluster
i dans le fichier. Si C [i ] = ffff16 , alors le cluster i est le dernier du fichier.
La Figure 10.4 illustre cela. Dans cet exemple, on a un fichier HELLO.CPP et
un répertoire DOS à la racine. Le répertoire DOS contient lui-même un fichier
EDIT.COM. La table d’allocation des clusters nous permet de repérer les clusters
libres : le 4, le 7, le 8, le b. . . Si on suppose que le premier cluster de HELLO.CPP
est le 3, on voit que ce cluster est bien occupé (le contenu de la table n’est pas
nul) ; que ce n’est pas le dernier du fichier (le contenu de la table n’est pas ffff),
et que le cluster suivant est le c. Après le cluster c vient le cluster a, qui est lui le
dernier (la table contient ffff à la ligne a). On voit bien que les clusters ne sont
pas contigus, ni dans le même ordre que dans le fichier.
Ensuite, chaque répertoire est stocké dans un cluster et celui-ci contient une
table d’allocation des fichiers. Cette table fait la liste de tous les fichiers et de
tous les répertoires contenus dans le répertoire qui correspond au cluster. Le
cluster du répertoire racine est à un endroit bien identifié : sur notre exemple,
c’est le cluster 2. Son contenu est montré à la Figure 10.4. Pour chaque fichier et
répertoire présent, la table renseigne (entre autres choses) : le nom, l’extension,
la nature de l’entrée (fichier ou répertoire), des informations comme la date de
création, et surtout le numéro du premier cluster du fichier ou du répertoire.
Par exemple, pour accéder à \DOS\EDIT.COM, on commence par consulter le
contenu du cluster 2 (racine), qui nous dit que la racine contient bien un ré-
pertoire DOS, stocké dans le cluster 5. On consulte alors le cluster 5, qui nous
indique que le fichier EDIT.COM commence au cluster 6. En consultant la table
d’allocation des clusters, on voit que ce fichier est constitué des clusters 6 et 9.
Cet exemple simple montre comment l’OS peut gérer un système de fichiers sur base d’une
simple structure en blocs de la mémoire secondaire. En réalité, la structure de la mémoire
294
10.2. Gestion de la mémoire secondaire
0 ···
\
1 ···
2 ffff cluster de \
3 c Premier cluster de HELLO.CPP
DOS HELLO.CPP
4 0
5 ffff cluster de DOS
6 9 Premier cluster de EDIT.COM
EDIT.COM
7 0
8 0
9 ffff
a ffff
b 0
c a
..
. ···
295
10. Leçon 18 – Gestion des processus et de la mémoire secondaire
secondaire peut être plus complexe. Par exemple, sur une disque dur « physique », composé
d’une pile de disques magnétisés que l’ont lit à l’aide de têtes qu’il faut déplacer, la véritable
adresse physique est en fait constituée du numéro du disque (appelé plateau) où se trouve
l’information, et de la position à lire sur ce disque (caractérisée par une piste et un secteurs
sur le plateau). Le contrôleur du disque est en général en charge de traduire le numéro de
bloc en la position adéquate de la tête de lecture/écriture sur les plateaux, et de commander
les moteurs pour déplacer cette tête.
Les systèmes de fichiers modernes qu’on trouve sur les systèmes d’exploitation pour or-
dinateurs personnels offrent de nombreuses autres fonctionnalité, comme une fonction de
journal, qui permet, le cas échéant, d’annuler les modifications les plus récentes apportées
aux fichiers. C’est le cas, par exemple, du système ext3 de GNU/Linux [9].
M8N
296
Bibliographie
[1] 8259A Programmable Interrupt Controller. Intel Corporation. 1988. URL : https : / /
pdos.csail.mit.edu/6.828/2010/readings/hardware/8259A.pdf.
[2] D. A NDREWS, D. N IEHAUS et P. A SHENDEN. “Programming models for hybrid CPU/FPGA
chips”. In : Computer 37 (fév. 2004), p. 118-120. DOI : 10.1109/MC.2004.1260732.
[3] Mike B ANAHAN, Declan B RADY et Mark D ORAN. The C Book. Addison Wesley, 1991. URL :
http://publications.gbdirect.co.uk/c_book/.
[4] Microsoft Knowledge B ASE. Spécification du système de fichiers exFAT. URL : https :
//docs.microsoft.com/fr-FR/windows/win32/fileio/exfat-specification
(visité le 19/09/2021).
[5] G. B OOLE. An Investigation of the Laws of Thought on Which are Founded the Mathe-
matical Theories of Logic and Probabilities. Une édition récente (1954) est disponible
en ligne à l’adresse http://www.gutenberg.org/etext/15114. Walton & Maberly,
1854.
[6] N. P. B ROUSENTSOV et al. Development of ternary computers at Moscow State Univer-
sity. URL : http : / / www . computer - museum . ru / english / setun . htm (visité le
08/08/2018).
[7] Code page 737 — Wikipedia, The Free Encyclopedia.
: https://en.wikipedia.
URL
org/w/index.php?title=Code_page_737&oldid=782268129 (visité le 01/09/2017).
[8] Edsger W. D IJKSTRA. “The Structure of the “THE”-Multiprogramming System”. In : Com-
mun. ACM 11.5 (mai 1968), p. 341-346. ISSN : 0001-0782. DOI : 10 . 1145 / 363095 .
363143. URL : https://www.cs.utexas.edu/users/EWD/ewd01xx/EWD196.PDF.
[9] Ext3 — Wikipédia, l’encyclopédie libre. URL : https://en.wikipedia.org/wiki/
Ext3 (visité le 19/09/2021).
[10] David B. G OLUB et al. “Microkernel operating system architecture and mach”. In : Pro-
ceedings of the USENIX Workshop on Micro-Kernels and Other Kernel Architectures.
1992, p. 11-30.
[11] Richard W. H AMMING. “Error detecting and error correcting codes”. In : Bell System
Technical Journal 29.2 (1950), p. 147-160.
[12] i486 Microprocessor Programmer’s Reference Manual. Intel Corporation. 1990. URL :
http : / / bitsavers . trailing - edge . com / components / intel / 80486 / i486 _
Processor_Programmers_Reference_Manual_1990.pdf.
[13] IBM. 704 Data Processing System. URL : https : / / www . ibm . com / ibm / history /
exhibits/mainframe/mainframe_PP704.html (visité le 05/04/2021).
297
Bibliographie
[14] IBM System/360 Operating System : Job Control Language Reference. IBM Sytems Refe-
rence Library. 1971. URL : http://www.bitsavers.org/pdf/ibm/360/os/R20.1_
Mar71/GC28-6704-1_OS_JCL_Reference_Rel_20.1_Jun71.pdf.
[15] IBM System/360 Reference data. IBM. URL : http://www.bitsavers.org/pdf/ibm/
360/referenceCard/GX20-1703-9_System360_Reference_Data_2up.pdf.
[16] IEEE Standard for Floating-Point Arithmetic. Rapp. tech. 754-2008. IEEE, août 2008,
p. 1-70. DOI : 10.1109/IEEESTD.2008.4610935. URL : https://ieeexplore.ieee.
org/servlet/opac?punumber=4610933.
[17] Information technology – Automatic identification and data capture techniques – QR
Code bar code symbology specification. Rapp. tech. ISO/IEC 18004 :2015. International
Organization for Standardization, 2015. URL : https://www.iso.org/standard/
62021.html.
[18] Information technology – Computer graphics and image processing – Portable Network
Graphics (PNG) : Functional specification. Rapp. tech. ISO/IEC 15948 :2004. Internatio-
nal Organization for Standardization, 2004. URL : https://www.iso.org/standard/
29581.html.
[19] Information technology – Digital compression and coding of continuous-tone still images :
Requirements and guidelines. Rapp. tech. ISO/IEC 10918-1 :1994. International Organi-
zation for Standardization, 1994. URL : https://www.iso.org/standard/18902.
html.
[20] ISA bus specification and application notes. Intel Corporation. 1989. URL : https://
archive.org/details/bitsavers_intelbusSpep89_3342148.
[21] B. K ERNIGHAN et D. R ITCHIE. The C programming language. Printice Hall, 1978. URL :
https://archive.org/details/TheCProgrammingLanguageFirstEdition.
[22] Tim L INDHOLM et al. The Java Virtual Machine Specification (Java SE 7 Edition). URL :
https://docs.oracle.com/javase/specs/jvms/se7/html/index.html (visité
le 28/02/2013).
[23] MCS6500 Micorcomputer Family Programming Manual. MOS Technology Inc. Jan. 1976.
URL : http : / / archive . 6502 . org / books / mcs6500 _ family _ programming _
manual.pdf.
[24] Operating system Family / Linux. URL : https : / / www . top500 . org / statistics /
details/osfam/1/.
[25] Ordinateur — Wikipédia, l’encyclopédie libre. URL : https://fr.wikipedia.org/w/
index.php?title=Ordinateur&oldid=150970460 (visité le 16/08/2018).
[26] R. PATRICK. General Motors/North American Monitor for the IBM 704 Computer. Rapp.
tech. RAND Corporation, 1987. URL : https://www.rand.org/content/dam/rand/
pubs/papers/2008/P7316.pdf.
298
Bibliographie
[27] David A. PATTERSON et David R. D ITZEL. “The Case for the Reduced Instruction Set
Computer”. In : SIGARCH Comput. Archit. News 8.6 (oct. 1980), p. 25-33. ISSN : 0163-
5964. DOI : 10 . 1145 / 641914 . 641917. URL : http : / / doi . acm . org / 10 . 1145 /
641914.641917.
[28] Physical aspects, operations of ENIAC are described. Press Release, War Department,
Bureau of Public Relations. URL : https://americanhistory.si.edu/comphist/
pr4.pdf.
[29] Quantities and units – Part 13 : Information science and technology. Rapp. tech. IEC
80000-13 :2008. International Organization for Standardization, 2008. URL : https://
www.iso.org/standard/31898.html.
[30] Irving S. R EED et Gustave S OLOMON. “Polynomial Codes Over Certain Finite Fields”.
In : Journal of the Society for Industrial and Applied Mathematics 8.2 (1960), p. 300-304.
[31] George F. RYCKMAN. “The IBM 701 Computer at the General Motors Research Labora-
tories”. In : Annals of the History of Computing 5.2 (1983), p. 210-212. DOI : 10.1109/
MAHC.1983.10026.
[32] SCC14.3 - U NIT S YMBOLS S UBCOMMITTEE. IEEE Standard Letter Symbols for Units of
Measurement (SI Customary Inch-Pound Units, and Certain Other Units). Rapp. tech.
260.1-2004. IEEE, 2004. URL : https://standards.ieee.org/standard/260_1-
2004.html.
[33] Claude E. S HANNON. “A symbolic analysis of relay and switching circuits”. Mém. de
mast. MIT, 1937. URL : https://dspace.mit.edu/handle/1721.1/11173.
[34] Bjarne S TROUSTRUP. A Tour of C++. Addison-Wesley, 2018.
[35] Jonhathan S WIFT. Gulliver’s Travels Into Several Remote Regions of the World. Version
en ligne du projet Gutenberg. Balliet, Thomas M. (Thomas Minard). URL : http : / /
www.gutenberg.org/ebooks/17157.
[36] Tagbanwa. Rapp. tech. The Unicode Consortium, 1991. URL : http://www.unicode.
org/charts/PDF/U1760.pdf.
[37] Tagbanwa (Unicode block) — Wikipedia, The Free Encyclopedia. URL : https://en.
wikipedia . org / w / index . php ? title = Tagbanwa _ (Unicode _ block ) &oldid =
775143371 (visité le 01/09/2017).
[38] A.S. TANENBAUM, J.N. H ERDER et H. B OS. “Can we make operating systems reliable and
secure ?” In : Computer 39.5 (2006), p. 44-51. DOI : 10.1109/MC.2006.156.
[39] Andrew TANENBAUM. Structured Computer Organisation, 5th edition. Prentice Hall.
[40] The Ferranti Mark 1. URL : http://curation.cs.manchester.ac.uk/computer50/
www.computer50.org/mark1/FM1.html (visité le 2003).
[41] Yuchu T IAN et David Charles L EVY, éd. Handbook of Real-Time Computing. Springer
Singapore, 2022.
[42] Alan M. T URING. “On Computable Numbers, with an Application to the Entscheidung-
sproblem”. In : Proceedings of the London Mathematical Society. T. 42. 1936, p. 230-265.
URL : https://doi.org/10.1112/plms/s2-42.1.230.
299
Bibliographie
[43] VAX-11 Architecture Reference Manual. Rapp. tech. revision 6.1. Digital Equipment Cor-
poration, 1982. URL : http://www.bitsavers.org/pdf/dec/vax/archSpec/EK-
VAXAR-RM-001_Arch_May82.pdf.
[44] J. VON N EUMANN. First Draft of a Report on the EDVAC. Rapp. tech. University of Penn-
sylvania, 1945. URL : https://archive.org/download/firstdraftofrepo00vonn/
firstdraftofrepo00vonn.pdf.
[45] W3T ECH. Usage statistics of Linux for websites. URL : https://w3techs.com/technologies/
details/os-linux.
[46] W3T ECH. Usage statistics of Unix for websites. URL : https://w3techs.com/technologies/
details/os-unix.
[47] D. WALDEN et T. VAN V LECK, éd. The Compatible Time Sharing System (1961–1973)
Fiftieth Anniversary Commemorative Overview. IEEE Computer Society, 2011. URL :
https://multicians.org/thvv/compatible-time-sharing-system.pdf.
[48] Maurice W ILKES. “The best way to design an automated calculating machine”. In :
Manchester University Computer Inaugural Conference. Ferranti Ltd., 1951. URL : https:
//www.cs.princeton.edu/courses/archive/fall10/cos375/BestWay.pdf.
[49] Maurice W ILKES. “The Genesis of Microprogramming”. In : Annals of the History of
Computing 8.2 (avr. 1986), p. 116-126. ISSN : 0164-1239. DOI : 10.1109/MAHC.1986.
10035.
[50] Pierre W OLPER. Introduction à la calculabilité : cours et exercices corrigés. Dunod, 2006.
300