Vous êtes sur la page 1sur 300

INFO-F-102 – Fonctionnement des

ordinateurs
Cours et exercices corrigés

Gilles G EERAERTS

Année académique 2022–2023

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

2. Leçons 2 à 4 – Représentation de l’information 31


2.1. Unités de quantité d’information . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.2. Écriture des nombres dans différentes bases . . . . . . . . . . . . . . . . . . . . . 33
2.2.1. Changements de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.2.2. Opérations en base 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.2.3. Représentation des entiers non-signés . . . . . . . . . . . . . . . . . . . . 43
2.2.4. Représentation des nombres entiers signés . . . . . . . . . . . . . . . . . . 43
2.2.5. Représentation des nombres « réels » . . . . . . . . . . . . . . . . . . . . . 47
2.3. Représentation des caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.4. Représentation d’images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
2.5. Représentation des instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
2.6. Détection et correction d’erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
2.6.1. Bit de parité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.6.2. Code de H AMMING . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.6.3. Applications des codes correcteurs d’erreur . . . . . . . . . . . . . . . . . 61
2.7. Conclusion : sémantique d’une représentation binaire . . . . . . . . . . . . . . . 62
2.8. Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.8.1. Changements de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.8.2. Représentation des nombres négatifs . . . . . . . . . . . . . . . . . . . . . 65
2.8.3. Représentation des nombres en virgule flottante . . . . . . . . . . . . . . 66
2.8.4. Corrections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

3. Leçon 5 & 6 – Organisation de l’ordinateur 71


3.1. Mémoire primaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
3.1.1. Structure et adresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.1.2. Mémoire cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.1.3. Ordre des octets dans les mots . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.1.4. Réalisation de la mémoire primaire . . . . . . . . . . . . . . . . . . . . . . 76
3.2. Le processeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.2.1. Composants du processeur . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.2.2. Le chemin des données – datapath . . . . . . . . . . . . . . . . . . . . . . 82

5
Table des matières

3.2.3. Exécution des instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . 82


3.2.4. Machine à pile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
3.2.5. Choix du jeu d’instructions machine . . . . . . . . . . . . . . . . . . . . . 86
3.2.6. Techniques pour améliorer l’efficacité des processeurs . . . . . . . . . . . 88
3.3. Les périphériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3.3.1. Mémoire secondaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
3.3.2. Les périphériques d’entrée/sortie . . . . . . . . . . . . . . . . . . . . . . . 98

II. Les portes logiques 101


4. Leçons 7 à 9 – Niveau 0 : portes logiques 103
4.1. L’algèbre Booléenne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.2. Les circuits logiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.2.1. Les portes logiques en pratique . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.2.2. De la formule Booléenne au circuit logique . . . . . . . . . . . . . . . . . . 113
4.3. Circuits pour réaliser l’arithmétique binaire . . . . . . . . . . . . . . . . . . . . . 114
4.3.1. Circuit additionneur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
4.3.2. Demi-additionneur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
4.3.3. Décalage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
4.3.4. Décodeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
4.3.5. Le « sélecteur » . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
4.3.6. ALU simplifiée (1 bit) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
4.3.7. ALU n bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
4.4. Circuits pour réaliser des mémoires . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4.4.1. Mémoire élémentaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4.4.2. Bascules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.4.3. Flip-flops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
4.4.4. Un circuit de mémoire complet . . . . . . . . . . . . . . . . . . . . . . . . 135
4.5. Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
4.5.1. Tables de vérité et formules logiques . . . . . . . . . . . . . . . . . . . . . 140
4.5.2. Exercices à faire sous Logisim . . . . . . . . . . . . . . . . . . . . . . . . . . 141
4.5.3. Exercices supplémentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
4.5.4. Corrections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

III. Le micro-langage 145


5. Leçons 10 à 12 – La microarchitecture 147
5.1. Le datapath du MIC-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
5.1.1. Composants du Mic-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
5.1.2. Cycle d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
5.1.3. Micro-instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
5.2. Le langage machine IJVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
5.2.1. Instructions du langage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

6
Table des matières

5.2.2. Représentation d’un programme IJVM en mémoire . . . . . . . . . . . . . 158


5.3. Implémentation de l’IJVM sur le MIC1 . . . . . . . . . . . . . . . . . . . . . . . . . 160
5.3.1. Structure de l’interpréteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
5.3.2. Lecture et décodage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
5.3.3. Représentation de la pile en mémoire . . . . . . . . . . . . . . . . . . . . . 162
5.3.4. Microcode de l’interpréteur . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
5.3.5. L’unité de contrôle du Mic-1 . . . . . . . . . . . . . . . . . . . . . . . . . . 166
5.3.6. Le Control Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
5.3.7. Le séquenceur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
5.4. Pour aller plus loin. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
5.5. Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
5.5.1. Comprendre l’effet des micro-instructions . . . . . . . . . . . . . . . . . . 171
5.5.2. Comprendre l’interpréteur . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
5.5.3. Corrections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

IV. Le langage Machine 177

6. Leçon 13 – Langage machine 179


6.1. Notion d’architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
6.2. Instructions machine typiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
6.3. Mécanismes de protection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
6.4. Représentation des instructions et des opérandes . . . . . . . . . . . . . . . . . . 193
6.4.1. Opcodes à taille variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
6.4.2. Adressage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
6.5. Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
6.5.1. Langage x86 32 bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
6.5.2. Exemple : Branchement conditionnel en assembleur x86 . . . . . . . . . 198
6.5.3. Exemple : Boucles en assembleur x86 . . . . . . . . . . . . . . . . . . . . . 200
6.5.4. Autres exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
6.5.5. Corrections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201

7. Leçon 14 – Le mécanisme d’interruption 205


7.1. Exemple introductif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
7.2. Déroulement d’une interruption . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
7.2.1. Interruption interruptibles ? . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
7.2.2. Boucle d’interprétation modifiée . . . . . . . . . . . . . . . . . . . . . . . . 208
7.2.3. Retour d’une interruption . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
7.2.4. Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
7.3. Applications du mécanisme d’interruption . . . . . . . . . . . . . . . . . . . . . . 219
7.4. Extensions possibles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
7.5. Cas de l’Intel 486DX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
7.6. Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
7.6.1. Corrections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223

7
Table des matières

V. Le système d’exploitation 225


8. Leçon 15 – Le système d’exploitation 227
8.1. Nécessité d’un système d’exploitation . . . . . . . . . . . . . . . . . . . . . . . . . 227
8.2. Principe général de fonctionnement . . . . . . . . . . . . . . . . . . . . . . . . . . 229
8.2.1. Mécanismes de protection et de l’appel système . . . . . . . . . . . . . . 231
8.3. Tâches dévolues au système d’exploitation . . . . . . . . . . . . . . . . . . . . . . 234
8.3.1. La gestion de la mémoire primaire . . . . . . . . . . . . . . . . . . . . . . . 234
8.3.2. La gestion des processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
8.3.3. La gestion des périphériques . . . . . . . . . . . . . . . . . . . . . . . . . . 235
8.3.4. La gestion des différents utilisateurs . . . . . . . . . . . . . . . . . . . . . . 235
8.3.5. La gestion de la mémoire secondaire . . . . . . . . . . . . . . . . . . . . . 237
8.3.6. La gestion d’une interface utilisateur . . . . . . . . . . . . . . . . . . . . . 237
8.4. Quelques repères historiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
8.4.1. Les premiers langages de programmation et les OS batch . . . . . . . . . 244
8.4.2. Les années 1960, 1970 et l’ère des mainframes . . . . . . . . . . . . . . . . 245
8.4.3. Les années 1980 et 1990 : l’informatique personnelle . . . . . . . . . . . . 249
8.4.4. L’informatique portable : les années 2000 et après . . . . . . . . . . . . . . 254

9. Leçon 16 et 17 – Gestion de la mémoire primaire 255


9.1. Problèmes à surmonter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
9.1.1. Problème d’adressage et relocation . . . . . . . . . . . . . . . . . . . . . . 256
9.1.2. Problème de fragmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
9.2. Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
9.2.1. Les cadres de page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
9.2.2. Les pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
9.2.3. Le MMU et la traduction des adresses . . . . . . . . . . . . . . . . . . . . . 264
9.3. Pagination à la demande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
9.3.1. Le défaut de page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
9.3.2. L’échange et le choix de victime . . . . . . . . . . . . . . . . . . . . . . . . 271
9.4. Difficultés liées à la pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
9.5. Exemple : pagination sur l’Intel 486 . . . . . . . . . . . . . . . . . . . . . . . . . . 276
9.6. Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
9.6.1. Corrections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281

10.Leçon 18 – Gestion des processus et de la mémoire secondaire 285


10.1.Gestion des processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
10.1.1. Cycle de vie d’un processus . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
10.1.2. Systèmes en Time sharing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
10.2.Gestion de la mémoire secondaire . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
10.2.1. Structure physique de la mémoire secondaire . . . . . . . . . . . . . . . . 290
10.2.2. Structure logique de la mémoire secondaire . . . . . . . . . . . . . . . . . 290
10.2.3. De la structure logique à la structure physique . . . . . . . . . . . . . . . . 293

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

2.1. Le code Baudot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52


2.2. Le code ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
2.3. Le Code Page 737 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
2.4. Extrait du standard Unicode, alphabet Tagbanwa [37]. . . . . . . . . . . . . . . . 55
2.5. Un exemple de contenu de fichier PGM avec l’image qu’il représente. . . . . . . . 56
2.6. Un code QR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.7. Une représentation, différentes interprétations . . . . . . . . . . . . . . . . . . . 64

3.1. Un modèle simple de l’organisation d’un ordinateur. . . . . . . . . . . . . . . . . 71


3.2. Petit et gros boutistes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.3. Un tube Williams. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.4. Deux mémoires à lignes de délai . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.5. Détail d’une mémoire à tores de ferrite. . . . . . . . . . . . . . . . . . . . . . . . . 79
3.6. Un exemple de programme en langage machine i486. . . . . . . . . . . . . . . . 82
3.7. Le chemin des données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
3.8. La boucle d’interprétation du CPU. . . . . . . . . . . . . . . . . . . . . . . . . . . 84
3.9. Un exemple de pile. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.10.Un exemple de langage machine i486 utilisant le FPU. . . . . . . . . . . . . . . . 86
3.11.Sir Maurice W ILKES en 1980. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.12.Illustration d’un pipeline à cinq étages. . . . . . . . . . . . . . . . . . . . . . . . . 89
3.13.Un pipelie superscalaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
3.14.Un ordinateur CDC 6600. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
3.15.L’ILLIAC IV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
3.16.Une Cray 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
3.17.L’organisation d’un ordinateur avec 2 CPUs . . . . . . . . . . . . . . . . . . . . . 94
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écessaire sur les produits. . . 96
3.19.Du ruban perforé. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

9
Table des figures

3.20.Trois types de disquettes : de 8, 5, 25, et 3, 5 pouces. . . . . . . . . . . . . . . . . . 97


3.21.Illustration d’un disque dur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98

4.1. Quelques portes logiques de base. . . . . . . . . . . . . . . . . . . . . . . . . . . . 110


4.2. Exemples de transistors. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.3. Exemple de tubes IBM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.4. Le demi-additionneur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
4.5. Un additionneur complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
4.6. Additionneur n bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
4.7. Le circuit de décalage 3 bits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
4.8. Un décodeur 4 bits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
4.9. Des afficheurs 7 segments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
4.10.Le principe d’un sélecteur n bits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
4.11.Une ALU 1 bit avec 4 opérations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
4.12.La représentation d’une ALU. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
4.13.Une ALU 4 bits réalisée sur base de 4 ALUs 1 bit . . . . . . . . . . . . . . . . . . . 128
4.14.Une boucle de portes non qui permet de stocker une valeur binaire. . . . . . . 130
4.15.Une bascule. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
4.16.Une bascule avec horloge. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
4.17.Une bascule D. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
4.18.Un générateur de pulsation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
4.19.Le circuit logique d’une flip-flop. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
4.20.Une flip-flop, avec ses deux entrées D et horloge, et ses deux sorties Q et Q. . . 135
4.21.Mémoire : lecture du bit i parmi 4 adresses. . . . . . . . . . . . . . . . . . . . . . 137
4.22.Mémoire : écriture d’un bit i parmi 4 adresses. . . . . . . . . . . . . . . . . . . . . 138
4.23.Une mémoire de 4 mots de 3 bits. . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

5.1. Ferranti Mark 1 et IBM System/360 . . . . . . . . . . . . . . . . . . . . . . . . . . 149


5.2. Le chemin des données du Mic1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
5.3. Un exemple de programme en IJVM, qui calcule la somme de trois nombres, et
son effet sur la pile. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
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. . . . 158
5.5. Structure de l’interpréteur IJVM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
5.6. Une illustration de la pile en mémoire, et des registres correspondants. . . . . . 162
5.7. L’interpréteur IJVM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
5.8. Le Mic1 complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
5.9. Le format binaire des microinstructions. . . . . . . . . . . . . . . . . . . . . . . . 169
5.10.Tableau à compléter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

6.1. Instructions du 6502 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186


6.2. Calcul de b 2 − 4ac en langage machine i486. . . . . . . . . . . . . . . . . . . . . . 187
6.3. Un « if » P YTHON en langage machine . . . . . . . . . . . . . . . . . . . . . . . . . 188
6.4. Appels de procédures en langage machine. . . . . . . . . . . . . . . . . . . . . . . 189

10
Table des figures

6.5. Boucle en langage machine. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190


6.6. Exemples d’adressage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196

7.1. Lecture sur périphérique : sans DMA et sans interruption . . . . . . . . . . . . . 206


7.2. Lecture sur périphérique, avec le mécanisme d’interruption. . . . . . . . . . . . 207
7.3. La boucle d’interprétation du CPU avec le mécanisme d’interruption. . . . . . . 210
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.5. Exemple d’interruption (2). Situation juste avant d’exécuter le gestionnaire d’in-
terruption. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
7.6. Exemple d’interruption (3). Situation après l’exécution de l’instruction à l’adresse
2048. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
7.7. Exemple d’interruption (4). Situation juste avant d’exécuter l’instruction à l’adresse
2051. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
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.9. Exemple d’interruption (6). Situation après l’exécution de l’IRET : le programme
interrompu reprend son exécution normalement. . . . . . . . . . . . . . . . . . . 218

8.1. Exemple de programme Python manipulant in fichier . . . . . . . . . . . . . . . 229


8.2. Le système d’exploitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
8.3. Un appel système sous GNU/Linux . . . . . . . . . . . . . . . . . . . . . . . . . . 232
8.4. Des processus et l’utilisateur associé . . . . . . . . . . . . . . . . . . . . . . . . . . 238
8.5. Les droits associés à des fichiers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
8.6. Un DEC VT100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
8.7. Le CLI de DOS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
8.8. Exemple de GTK sous Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
8.9. Une IBM 704 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
8.10. T HOMPSON et R ITCHIE au PDP-11 . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
8.11.Un IBM modèle 5150. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
8.12.Un Macintosh 128k. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252

9.1. Notre programme d’exemple. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256


9.2. Deux positions différentes pour le programme . . . . . . . . . . . . . . . . . . . . 257
9.3. La fragmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
9.4. Exemple de pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
9.5. Page frames et pages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
9.6. Les pages de P et Q chargées. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
9.7. Les pages de P et R chargées. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
9.8. Le MMU. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
9.9. Pagination avec ℓ = 2k . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
9.10.Pagination à la demande (1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
9.11.Pagination à la demande (2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274

11
Table des figures

9.12.Pagination à la demande (3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275


9.13.Pagination sur l’i486 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279

10.1.Le cycle de vie des processus. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286


10.2.La commande ps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
10.3.Système de fichiers Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
10.4.FAT16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295

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

6.1. Quelques instructions du langage d’assemblage x86 32 bits. . . . . . . . . . . . . 199

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.

Ceci est une définition ou un concept particulièrement important. Gardez les


yeux grand ouverts, et soyez attentif !

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 ?

1.1. Définitions et étymologie


L’objet de ce cours est, comme son titre l’indique, d’expliquer comment fonctionne un or-
dinateur. Le but ici est de dégager une série de principes de base qui régissent et permettent
d’expliquer le fonctionnement des ordinateurs tels que nous les connaissons aujourd’hui,
mais aussi tels qu’ils ont toujours existé (et, espérons le, tels qu’ils existeront dans le futur).
Nous allons donc commencer par nous demander ce qu’est un ordinateur.
Commençons par observer que le mot « ordinateur » est un mot relativement neuf dans
l’acception qui nous intéresse ici. Il a été proposé par Jacques P ERRET 1 , qui avait été consulté
par le directeur d’IBM France en 1955. IBM cherchait à l’époque un nom français pour ces
nouvelles machines que l’on nommait computer en anglais. Jacques P ERRET répond par une
lettre du 16 avril 1955 restée célèbre (voir Figure 1.1).

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

1.2. Un premier modèle : l’architecture de von Neumann


Maintenant que nous savons ce que fait un ordinateur, nous devons répondre à la question
du comment ? Dans une première approche, nous allons partir d’un exemple de programme,
emprunté au cours de Programmation (INFO-F-101). Il s’agit d’un exemple écrit en Python,

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

F IGURE 1.2. – Un exemple de programme écrit en langage Python.

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

Unité arithmético-logique (ALU) Entrées


/
Sorties
Registres

Mémoire

F IGURE 1.3. – L’architecture VON N EUMANN, un premier modèle d’ordinateur.

Dans le Chapitre 3, nous expliquerons plus en détail le fonctionnement de cette boucle.


Dans le Chapitre 7, nous raffinerons cette boucle pour introduire un mécanisme d’interruption,
qui est essentiel au bon fonctionnement des ordinateurs modernes.

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

1.3. Une vision différente : structure en niveaux et traductions


Maintenant que nous avons identifié les différents composants d’un ordinateur et leurs
fonctions, nous pouvons approfondir la question du comment ? posée au début de la section
précédente. Nous consacrerons évidemment tout le cours à expliquer comment les différents
composants que nous avons identifiés remplissent leur fonction, mais nous pouvons déjà
donner une vue globale qui correspondra au plan de l’exposé.

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

5. À l’aide d’une boucle similaire à la boucle fetch–decode–execute.

26
Intel 80486DX2 Architecture 64 Bit Interunit Transfer Bus
Core
32 Bit Data Bus Clock
Clock Clock
32 Bit Data Bus Multiplier

Linear Address 32 Bit


PCD
PWT 2
A31 - A2,
Barrel Segmentation Cache Address BE3# - BE0#
32 Bit
Shifter Unit 20 Bit Unit Drivers
Paging
Descriptor Unit Physical
Register Write Buffers
32 Bit Address 32 Bit
File Registers 4 x 32
8 KiB
Limit and Translation Cache
Data Bus D31 - D0
ALU Attribute Lookaside 32 Bit
Tranceivers
PLA Buffer
Bus Control ADS#, W/R#, D/C#, M/IO#,
PCD, PWT, RDY#, LOCK#,

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

32 Byte Code Burst Bus BRDY#, BLAST#


24 Bit Queue Control
Control &
Instruction
FPU Protection
Decode Code Stream (2 x 16 Byte)
Test Unit Bus Size BS16#, BS8#
Decoded Control
Floating Instruction Path
KEN#, FLUSH#, AHOLD,
Point Control Cache EADS#
Register ROM Control
File
Parity Generation DP3 - DP0, PCHK#
and Control

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

Source : Appaloosa (https://commons.wikimedia.org/wiki/File:80486DX2_arch.svg), « 80486DX2 arch », https://creativecommons.org/licenses/by-sa/3.0/legalcode

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

Interprétation et Compilation On voit donc qu’on a affaire à une chaîne de traductions


successives. Celles-ci peuvent être effectuées de deux façon différentes :

— soit un programme d’un certain niveau est intégralement traduit en un programme


du niveau inférieur, avant le début de l’exécution. On parle alors de compilation. Le
résultat de la traduction peut être stocké et réutilisé, mais ne sera plus modifié durant
l’exécution. La compilation est réalisée à l’aide d’un programme appelé compilateur.
C’est le cas des applications que nous téléchargeons sur Internet (que ce soit pour un
ordinateur de bureau ou pour un smartphone) : elles ont été écrites dans un langage de
haut niveau par leurs concepteurs, puis compilées en langage machine. C’est le résultat
de cette compilation qui est distribué et téléchargé par les utilisateurs finaux ;

— 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

— un logiciel (software en anglais) est une représentation informatique des informations


nécessaires à un traitement réalisé par l’ordinateur, à savoir, la séquence d’instructions
à exécuter, et les données.

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++,. . . )

Compilation (appels systèmes)

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

F IGURE 1.5. – Les 6 couches décrivant le fonctionnement d’un ordinateur.

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.

La représentation binaire signifie que toute l’information sera représentée à


l’aide de deux symboles uniquement. Ces deux symboles sont en général les
chiffres 0 et 1, mais on peut également considérer que ce sont les valeurs lo-
giques faux et vrai.

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

comme nous le verrons dans le Chapitre 3.

2.1. Unités de quantité d’information


Commençons par fixer des unités permettant de mesurer la quantité d’information. Nous
utiliserons la taille de la représentation binaire de l’information comme mesure :

Chaque chiffre (0 ou 1) d’une représentation binaire est appelé bit (contraction


de l’anglais binary digit, ou chiffre binaire). Une séquence de 8 bits est appelée
un octet (ou byte en anglais).

Les symboles associés à ces unités sont les suivants [32] :

Unité Symbole
bit b
octet o
byte B

Nous mesurerons donc l’information en terme de bits ou d’octets (bytes). Naturellement,


les quantités d’information manipulées usuellement s’expriment en milliers, millions,. . . d’oc-
tets, il faut donc fixer des noms pour ces unités. Les préfixes officiellement reconnus par l’in-
dustrie [29] sont similaires aux kilo-, méga-,. . . du système SI. Ces préfixes ne se réfèrent pas
à des puissances de 10 comme dans le système SI mais à des puissances de 2 :

Nom Abréviation Quantité


kibioctet kio 210 = 1024 ≈ 103
mébioctet Mio 2 = 1 048 576 ≈ 106
20

gibioctet Gio 230 = 1 073 741 824 ≈ 109


tebioctet Tio 2 = 1 099 511 627 776 ≈ 1012
40

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 :

Nom Abréviation Quantité


kilooctet ko 103 octets
mégaoctet Mo 106 octets
gigaoctet Go 109 octets
teraoctet To 1012 octets

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 .

2.2. Écriture des nombres dans différentes bases


Nous allons maintenant rappeler les techniques permettant de représenter des nombres
dans différentes bases, car ces techniques serviront à expliquer les différents encodages bi-
naires des nombres entiers et rationnels.
La représentation usuelle des nombres est une représentation décimale et positionnelle.
Cela signifie :
1. que les nombres sont représentés par une séquence de chiffres de 0 à 9 inclus. Il y a
bien dix chiffres différents utilisés, d’où le terme décimal ; et
2. que la position de chaque chiffre dans la séquence permet d’associer ce chiffre à une
puissance de 10.
Dans cette représentation usuelle, 10 est appelé la base. On parle aussi de représentation en
base 10.

ß
Par exemple, la représentation :

1234, 56

signifie que le nombre est composé de :


— 4 unités, ou 4 × 100 ;
— 3 dizaines, ou 3 × 101 ;
— 2 centaines, ou 2 × 102 ;
— 1 millier, ou 1 × 103 ;
— 5 dixièmes, ou 5 × 10−1 ; et
— 6 centièmes, ou 6 × 10−2 .
On voit donc que les puissances de 10 associées aux différentes positions (aux
différents chiffres) vont décroissantes quand on lit le nombre de gauche à droite,
et que la puissance 100 correspond au chiffre qui se trouve juste à gauche de la
virgule.

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 :

N = d n × b n + d n−1 × b n−1 + · · · + d 0 × b 0 + d −1 × b −1 + · · · d −k × b −k (2.1)


Xn
= di × b i , (2.2)
i =−k

où tous les d i sont des chiffres dans {0, . . . b − 1}.

Si on fixe b = 2, le nombre 1001, 101 représente :


ß 1 × 23 + 0 × 22 + 0 × 21 + 1 × 20 + 1 × 2−1 + 0 × 2−2 + 1 × 2−3
1 1
= 8+0+0+1+ +0+
2 8
= 9, 625.

La dernière valeur est bien entendu donnée en base 10.


Symétriquement, la valeur −4, 5 (en base 10) est représentée par :

−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 :

a5f, b = 10 × 162 + 5 × 161 + 15 × 160 + 11 × 16−1


= 2655, 687510 .

Sur base de ces définitions, nous pouvons déjà faire quelques remarques utiles.

Chiffres de poids fort, de poids faible Dans la représentation positionnelle usuelle, le


chiffre le plus à gauche est associé à une puissance plus élevée de la base. On parle donc de
chiffre de « poids fort » ; le chiffre étant associé à la puissance la plus faible est appel chiffre
de « poids faible ». En particulier, en binaire, on parle de bit de poids fort et bit de poids faible.
Comme nous l’avons dit, le bit de poids fort est, par convention à gauche dans la repré-
sentation (positionnelle) usuelle. Mais quand on s’intéresse à des valeurs qui ne sont plus
écrites sur papier, mais bien stockées ou manipulées par des circuits électroniques (où les
notions de « gauche » et « droite » n’ont pas forcément de sens), il est parfois utile de préciser
explicitement quel est le bit de poids fort ou le bit de poids faible.

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 :

Dans toute base b, déplacer la virgule de k position vers la droite ( ou ajouter


k zéros à droite dans le cas d’un nombre entier) revient à multiplier par b k .
Déplacer la virgule de k positions vers la gauche revient à diviser par b k . Si on
souhaite faire une division entière par b k , le reste est constitué des k chiffres
de poids faible, et on obtient le quotient en retranchant ces k chiffres de poids
faible.

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

ß 101, 1112 × 410 = 101, 1112 × 22


= 10111, 12
101, 1112 /210 = 10, 11112
1011 10112 ÷ 1002 = 1011 10112 ÷ 22
= 10 1110
1011 10112 mod 1002 = 112 .

Combien de nombres représentables sur n chiffres ? Enfin, l’observation suivante aura


toute son importance dans la suite. Fixons une base b et considérons des nombres entiers
positifs sur n chiffres. Nous avons b choix pour chacun de ces n chiffres, soit un total de
b n nombres différents que l’on peut représenter sur n chiffres. Ces nombres sont tous les
nombres de :
| ·{z
0 · · 0}
n chiffres
à:
(b − 1) · · · (b − 1) = b n − 1.
| {z }
n chiffres

En base b, il y a b n nombres (entiers positifs) représentables sur n chiffres : les


nombres de 0 à b n − 1.

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.

2.2.1. Changements de base


Comment peut-on, étant donné un nombre N donné dans une certaine base, trouver sa
représentation dans une autre base b ? Répondre à cette question revient à calculer les valeurs
d i de l’équation (2.2) dans la base b.

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

ce qui nous donne un quotient q 0 = N ÷b et un reste r 0 = N mod b. La relation existant entre


ces valeurs est :

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 ,

En combinant cette équation avec (2.3), nous obtenons :

N = (q 1 × b + r 1 ) × b + r 0
= q1 × b 2 + r 1 × b + r 0 .

Si q 1 > 0, on recommence en divisant q 1 par b, pour obtenir un nouveau quotient q 2 et un


nouveau reste r 2 tels que :

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 .

Si nous continuons ce développement en réalisant k + 1 divisions, nous obtenons une suite


de restes r 0 , r 1 ,. . . r k tels que :

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

Pour exprimer un nombre N en base b, on divise N par b de manière répétée. La


séquence des restes calculés donne la représentation de N en base b du chiffre
de poids faible au chiffre de poids fort (autrement dit : « à l’envers »).

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

où α0 représente la séquence de chiffres de la partie fractionnaire du nombre. En multipliant


N par b on obtient un nouveau nombre de la forme :

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

Multiplions maintenant N1 par b, nous obtenons un nombre de la forme :

N1 × b = β2 , α2

avec 0 ≤ β2 ≤ b − 1. Nous pouvons à nouveau poser N2 et obtenir :

N2 = 0, α2
β2 N 2
N1 = + . (2.6)
b b

En combinant (2.6) et (2.5), nous obtenons :

β
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

Nous pouvons continuer ce développement, en multipliant successivement tous les Ni =


0, αi par b, pour obtenir un suite aussi longue que souhaitée de βi tels que :

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.

Considérons N = −24, 4210 à exprimer en base 2. Nous allons commencer par


ß exprimer 24 en base 2, en divisant successivement 24 par 2. On commence par
24/2 = 12 avec un reste de 0. Autrement dit q 0 = 12 et r 0 = 0. On poursuit en
divisant q 0 par 2, etc :

i qi ri
0 12 0
1 6 0
2 3 0
3 1 1
4 0 1

On lit maintenant la séquence des restes de haut en bas, en on obtient : 2410 =


110002 . ../.

39
2. Leçons 2 à 4 – Représentation de l’information

Pour la partie fractionnaire, on procède par multiplication successive par 2. On


... commence par 0, 42 × 2 = 0, 84. Autrement dit, β1 = 0 et N1 = 0, 84. On multiplie
à nouveau N1 par 2, soit 2 × 0, 84 = 1, 68. On obtient donc :

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

On voit donc que N21 = N1 , la suite du développement se poursuivra donc ad


infinitum de manière cyclique. Donc :

0, 4210 = 0, 0110 1011 1000 0101 00112

et finalement :

−24, 4210 = −1 1000, 0110 1011 1000 0101 00112 .

2.2.2. Opérations en base 2


Comme la base 2 est celle qui nous sera la plus utile dans la suite, nous allons maintenant
nous intéresser aux différentes opérations que nous pouvons appliquer aux nombres dans
cette base.

Opérations arithmétiques On peut utiliser l’addition, la soustraction, la multiplication et


la division euclidienne sur les nombres dans n’importe quelle base, en particulier la base 2.
Pour l’addition, on procède en base 2 comme en base 10, en faisant d’abord la somme des
unités, puis des chiffres correspondant au poids 21 (au lieu de 101 en base 10), 22 , etc. . . Il faut
simplement être attentif au fait qu’un report a lieu dès que la somme dépasse 210 = 102 . Par
exemple : 1 + 0 donne une somme de 1, et un report de 0. Par contre, 1 + 1 = 102 donne une
somme de 0 et un report de 1. De même, 1 + 1 + 1 = 112 donne une somme de 1 et un report
de 1. Par exemple :

ß 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 :

x y x∧y x y x∨y x y x XOR y


x ¬x
0 0 0 0 0 0 0 0 0
0 1 0 0 1 1 0 1 1 0 1
1 0 0 1 0 1 1 0 1 1 0
1 1 1 1 1 1 1 1 0

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 :

1010 01012 mod 810 = 1010 01012 mod 23


= 1010 01012 ∧ 0000 0 |{z}
111
3
= 0000 01012
= 1012 .

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

2.2.3. Représentation des entiers non-signés


Si nous devons manipuler des nombres entiers non-négatifs 6 uniquement, on peut se
contenter d’exprimer ce nombre en base 2. Cette représentation n’utilisera que les symboles
0 et 1 et constitue donc bien une représentation binaire. Ces nombres sont souvent appelés
les nombres « non signés » en informatique, car il n’est pas nécessaire d’utiliser de symbole
pour représenter leur signe. Dans les langages comme C ou C++, par exemple, on trouve le
type unsigned int [3, 34].
Si on considère une représentation sur un nombre n fixé de bits, cette technique permet
de représenter tous les nombres de 0 (représenté par 0 · · 0}) à 2n − 1 (représenté par 1
| ·{z | ·{z
· · 1}).
n n

2.2.4. Représentation des nombres entiers signés


Voyons maintenant comment nous pouvons incorporer les nombres négatifs dans notre re-
présentation. On ne peut pas se contenter de convertir les nombres vers la base 2, car le signe
− qui est utilisé pour signaler qu’un nombre est négatif n’est pas directement manipulable
par un ordinateur qui manipule des valeurs en binaire. Il faut donc trouver une technique
pour représenter ce signe à l’aide de 0 et de 1 uniquement. Nous allons étudier quatre tech-
niques différentes. Nous présenterons ces techniques en supposant que l’on considère une
représentation sur n bits (par exemple, n = 8).

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.

Cette technique présente plusieurs inconvénients :

— la valeur 0 possède deux représentations : 0 · · · 0 et 10 · · · 0 (qui représente −0) ;

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.

Complément à 1 Le complément à 1 d’un nombre binaire N est le nombre N qu’on ob-


tient en inversant tous les bits de N . On peut utiliser le complément à 1 pour signaler qu’un
nombre est négatif. Plus précisément, si on a une représentation sur n bits, on représente
tous les nombres entiers non-négatifs par leur représentation binaire habituelle, et tous les
nombres non-positifs par le complément à 1 de leur valeur absolue. Afin de pouvoir distin-
guer les nombres positifs des nombres négatifs, on limite les valeurs qu’on peut représen-
ter : si on a une représentation sur n bits, on se limite aux nombres qui, en valeur absolue,
tiennent sur n − 1 bits. Ainsi, le bit de poids fort sera toujours égal à 0 pour les nombres posi-
tifs, et à 1 pour les nombres négatifs.

Le nombre 5 est représenté par 0000 0101. La représentation de −5 est obtenue


ß en inversant tous les bits de la représentation de 5, soit 1111 1010. Sur 8 bits, la
valeur 128, par exemple, n’est pas représentable, elle s’exprime en binaire par
un nombre de 8 bits au moins : 1000 0000, et cette représentation s’interprète,
en complément à 1, comme −0111 11112 = −127.

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.

Malheureusement, comme avec le bit de signe, 0 possède toujours deux représentations en


complément à 1 : 0 · · · 0 et 1 · · · 1. De ce fait, on ne peut, sur n bits, représenter que les nombres
de −2n−1 + 1 à 2n−1 − 1, soit à nouveau 2n − 1 valeurs différentes.

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.

Voici 3 exemples sur n = 8 bits :


ß — 1 est représenté par 00000001 ;
— −1 est représenté par la représentation de 28 − 1, soit 11111111 ;
— −5 est représenté par la représentation de 28 − 5, soit 11111011.

La technique du complément à 2 possède de nombreux avantages. Tout d’abord, 0 ne pos-


sède plus qu’une seule représentation, à savoir 0 · · · 0. Ensuite, on peut faire usage de l’ad-
dition usuelle sur tous les nombres positifs ou négatifs en complément à deux (il n’est pas
nécessaire d’utiliser le report circulaire comme dans le cas du complément à 1, celui-ci peut
être oublié).

Voici un exemple d’addition sur 4 bits. Considérons la somme 7−5 = 7+(−5). En


ß binaire et avec le complément à 2, −5 est représenté par la représentation « clas-
sique » de 24 − 5 = 16 − 5 = 1110 , soit 10112 . On a donc : 01112 + 10112 = 1 00102 ,
que l’on tronque sur 4 bits (on oublie systématiquement le dernier report) pour
obtenir 00102 , soit 210 .

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.

Donc, en ajoutant 1 de part et d’autre de cette équation, nous avons :

N + N + 1 = 2n − 1 + 1
= 2n .

45
2. Leçons 2 à 4 – Représentation de l’information

TABLE 2.1. – Comparaison des différentes représentations (sur n bits).


Valeur représentée
Représentation Non signé Bit Signe Cpl. 1 Cpl. 2 Excès K
00 · · · 00 0 0 0 0 −K
00 · · · 01 1 1 1 1 −K + 1
.. .. .. .. .. ..
. . . . . .
01 · · · 11 2n−1 − 1 2n−1 − 1 2n−1 − 1 2n−1 − 1 2 n−1
−1−K
10 · · · 00 2n−1 0 −2n−1 + 1 −2n−1 2n−1 − K
10 · · · 01 2n−1 + 1 −1 −2n−1 + 2 −2n−1 + 1 2n−1 + 1 − K
.. .. .. .. .. ..
. . . . . .
n n−1 n
11 · · · 11 2 −1 −2 +1 0 −1 2 −1−K

En retranchant N de deux côtés, nous avons :

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

Voici trois exemples sur 8 bits avec K = 2n−1 = 27 = 12810 :


ß — 1 est représenté par la représentation binaire de 1 + 27 = 129, soit
1000 0001 ;
— −1 est représenté par la représentation de 27 − 1, soit 0111 1111 ;
— −5 est représenté par la représentation de 27 − 5, soit 0111 1011.

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

2.2.5. Représentation des nombres « réels »

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 :

0000 0000 0010 1010 1010 0000 0000 0000

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 :

7. Il s’agit des nombres réels qui ne sont pas rationnels.


8. C’est-à-dire qu’elle ne peut pas être exprimée sous la forme d’un préfixe fini suivi d’une répétition infinie
d’une séquence finie, car il est connu que tout nombre qui peut être exprimé de cette manière est rationnel.

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 :

216 = 1 0000 0000 0000 00002 .

On a ici affaire à un overflow.

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 :

42, 625 = 0, 42625 × 102


0, 00034 = 0, 34 × 10−3
25 = 0, 25 × 102 .

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 :

— un bit de signe s, le bit de poids fort ;

— suivi de 8 bits représentant un exposant e, exprimé en excès à 127 ;

— suivis de 23 bits de signifiant f .

Une telle représentation est l’encodage du nombre suivant :

(−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 :

42, 62510 = 10 1010, 1012 .

Nous normalisons ensuite cette représentation en déplaçant la virgule de 5 po-


sitions vers la gauche pour obtenir un nombre de la forme 1, f × 2e :

42, 62510 = 1, 0101 0101 × 25 .

À 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 :

0 1000 0100 0101 0101 0 · · · 0

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

10. Le site web http://babbage.cs.qc.cuny.edu/IEEE-754/ implémente un convertisseur automatique


qu’on peut utiliser pour se familiariser avec cette norme.

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.

Un problème que nous devons encore résoudre est la représentation de 0. En effet, 0 ne


peut pas s’exprimer sous la forme 1, f , il faut donc fixer une représentation spéciale. La norme
IEEE754 en retient deux : 10 · · · 0, soit 00 · · · 0.
Notons enfin que la norme admet également des valeurs spéciales, comme ∞ et Not a
Number (ou NaN). Ces valeurs sont utilisées pour certains résultats des opérations arithmé-
tiques. Par exemple pour une division par 0, comme on peut le voir dans le tableau suivant :

x/y y 6= 0, ∞, NaN y =∞ y =0 y = NaN


x 6= 0, ∞, NaN valeur la plus proche de x/y 0 ∞ NaN
x =∞ ∞ NaN ∞ NaN
x =0 0 0 NaN NaN
x = NaN NaN NaN NaN NaN

En pratique, la manipulation de cette représentation demande des circuits spéciaux, que


l’on trouve sur la plupart des processeurs modernes.

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

2.3. Représentation des caractères


Maintenant que nous avons étudié des techniques pour représenter et manipuler des nombres
en binaire, intéressons-nous à d’autres types d’informations. Nous commencerons par le
texte, c’est-à-dire des séquences de caractères. L’idée générale de la représentation (en bi-
naire) des caractères consiste à faire correspondre à chaque caractère un et un seul nombre
naturel, et à représenter le caractère par la représentation binaire de ce naturel. Comme il
n’existe pas de manière universelle et naturelle d’associer un nombre à chaque caractère, il
faut fixer une table, qui fait correspondre chaque caractère à un nombre.

50
2.3. Représentation des caractères

Codes historiques : Émile Baudot et les téléscripteurs Le


besoin de représenter et transmettre de l’information de ma-
nière mécanique est une préoccupation qui a reçu une pre-
mière réponse lors de l’introduction du télégraphe, où le fa-
meux code Morse était utilisé pour représenter les lettres par
une série de traits et de points. Au dix-neuvième siècle, un fran-
çais, Jean-Maurice Émile B AUDOT 11 invente le premier code
pratique permettant de représenter tout l’alphabet latin (ainsi
que des symboles de ponctuation usuels) sur 5 bits. Ce code est
présenté à la Figure 2.1.
La machine de B AUDOT comprenait un clavier de 5 touches,
avec lequel on entrait les caractères à transmettre, selon le code Émile B AUDOT
que nous avons présenté. Le récepteur disposait d’une ma-
chine imprimant automatiquement les caractères transmis sur un ruban de papier. La ca-
dence à laquelle les caractères pouvaient être transmis (par exemple, 10 caractères par mi-
nute) était mesurée en bauds, une unité que l’on connait encore aujourd’hui, bien qu’elle
soit tombée en désuétude. Le système de B AUDOT sera plus tard amélioré pour utiliser du ru-
ban perforé pour stocker les informations à transmettre. Cela donnera lieu aux téléscripteurs
(ou teletypes en anglais, abbrévié TTY), sortes de machines à écrire qui ont la capacité d’en-
voyer et de recevoir du texte en utilisant le code de B AUDOT, sur des lignes téléphoniques.
Ces systèmes ont été largement utilisés pour la transmission d’information, notamment par
la presse, jusque dans les années 1980.

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,

ou, en binaire, par :

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 :

11. Ingénieur français, né le 11 septembre 1845, mort le 28 mars 1903.

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.

2.4. Représentation d’images


Il existe de nombreux formats permettant de stocker et manipuler des images, les plus uti-
lisés étant sans doute le JPEG 13 [19], le PNG 14 [18] ou le TIFF 15 (on pourrait encore citer
12. Le consortium qui conçoit et publie Unicode possède un site web https://unicode.org/.
13. Voir https://jpeg.org/
14. Voir : http://www.libpng.org/pub/png/
15. Voir : https://www.loc.gov/preservation/digital/formats/fdd/fdd000022.shtml

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

2.5. Représentation des instructions


Il y a encore un type d’informations que l’ordinateur doit pouvoir manipuler de manière
cruciale : ce sont les instructions (machine) des programmes à exécuter. En effet, notre défi-
nition de la notion d’ordinateur insiste sur le fait que le programme doit pouvoir être modifié
arbitrairement par l’utilisateur. Le programme doit donc être fourni à l’ordinateur dans un
format bien spécifié, afin qu’il puisse être analysé et exécuté (nous pensons ici à la boucle
fetch–decode–execute du CPU dont nous avons déjà parlé brièvement dans le Chapitre 1).
La manière dont les instructions du langage machine sont représentées dépend du type de
processeur qui va les exécuter, il est donc assez difficile de présenter des généralités. Nous
pouvons déjà dire que toute représentation binaire d’une instruction sera composée de deux
parties :
1. l’opcode (abréviation anglaise d’operation code), qui représente l’instruction à propre-
ment parler, c’est-à-dire le traitement à effectuer (par exemple : faire une addition ou
faire une soustraction) ; et
2. éventuellement un ou plusieurs opérandes, qui donnent accès aux données à traiter 17 .
Nous discuterons plus en détail du langage machine et de sa représentation dans le Cha-
pitre 6.

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

2.6. Détection et correction d’erreurs


Lorsque les informations sont stockées ou transmises, il arrive parfois que des erreurs soient
introduites (par exemple, sur un réseau non-fiable). Il est alors utile de développer des tech-

17. Notez que les mots opcode et opérande sont tous deux masculins.

57
2. Leçons 2 à 4 – Représentation de l’information

niques de représentation de l’information qui permettent au récepteur de détecter la pré-


sence d’erreurs éventuelles (auquel cas, il peut demander une re-transmission), voire de les
corriger par lui-même. Nous allons maintenant étudier deux de ces techniques. Elles sont
toutes les deux basées sur le même principe : introduire de la redondance dans l’information
transmise, de manière à pouvoir détecter un nombre restreint d’erreurs, voire reconstituer
l’information d’origine.

2.6.1. Bit de parité


Cette technique est sans doute la première à avoir été mise en œuvre. Elle consiste à ajouter,
à toute information transmise, un bit (appelé bit de parité) qui indique si le nombre de bits à 1
dans l’information à représenter est impair (bit de parité à 1) ou pair (bit de parité à 0). Ainsi,
si un seul bit est modifié, la parité change nécessairement, et cette erreur peut être détectée
(mais pas corrigée car on n’a pas de moyen d’identifier le bit qui a été modifié).
Plus précisément, étant donnée une information binaire

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 :

b c = b n−1 XOR b n−2 · · · XOR b 0 .

On peut vérifier que b c = 1 si et seulement si le nombre de bits à 1 parmi b n−1 ,. . . , b 0 est


impair.

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.

On peut facilement se convaincre que la technique du bit de parité permet de détecter un


nombre impair d’erreurs, mais ne permet d’en corriger aucune car rien ne permet d’identifier
le bit qui a été altéré.

58
2.6. Détection et correction d’erreurs

2.6.2. Code de Hamming


Le code de H AMMING est en fait une famille de codes dont la version de base (appelée
Hamming(7, 4) et que nous allons étudier ici) a été introduite dans les années 1950 [11] par
Richard H AMMING 18 , pour détecter et corriger des erreurs qui avaient lieu lors de la lecture
de cartes et de rubans perforés (voir Chapitre 3).
Ce système est plus puissant que le bit de parité : il permet de corriger les erreurs de trans-
mission, à condition que celles-ci affectent au maximum 1 bit. Naturellement, pour arriver à
ce résultat, il faudra utiliser plus d’un bit de contrôle.
Pour expliquer le code de H AMMING, nous allons changer quelque peu nos conventions et
numéroter les bits à partir de 1. Nous allons considérer, pour l’exemple, des représentations
(information et bits de contrôle) sur 7 bits, mais ces principes se généralisent aisément. Sur
ces 7 bits, tous les bits dont le numéro est une puissance de 2 (donc, les bits 1, 2 et 4) sont des
bits de contrôle (C i ), les 4 bits restants sont des bits de données (D i ) :
1 2 3 4 5 6 7
C1 C2 D3 C4 D5 D6 D7
Chacun des bits de contrôle sera en fait un bit de parité, mais ne portera pas sur tous les
bits de données (sans quoi il serait inutile d’avoir plusieurs bits de contrôle qui auraient tous
la même valeur). Au contraire, nous allons associer les bits de contrôle à des bits de données
bien choisis, de manière à pouvoir reconstituer le numéro du bit erroné en cas d’erreur. Nous
effectuons cette association de la manière suivante

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 .

Mettons ces idées en pratique sur un exemple :

ß
Supposons que nous ayons la donnée 1011 :

C1 C2 D3 C4 D5 D6 D7
1 0 1 1

En appliquant le développement ci-dessus, nous obtenons :

C1 = D 3 XOR D 5 XOR D 7 = 1 XOR 0 XOR 1 = 0;


C2 = D 3 XOR D 6 XOR D 7 = 1 XOR 1 XOR 1 = 1;
C4 = D 5 XOR D 6 XOR D 7 = 0 XOR 1 XOR 1 = 0.

La représentation de la donnée accompagnée de son code de H AMMING est


donc :
C1 C2 D3 C4 D5 D6 D7
0 1 1 0 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

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 0, ce qui est bien la bonne va-
leur ;
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 un problème sur le bit C 2 uniquement, c’est donc lui qui est erroné
et la donnée est donc transmise correctement.

2.6.3. Applications des codes correcteurs d’erreur


Comme nous l’avons déjà évoqué, le bit de parité est une technique qui a largement été
utilisée pour détecter des erreurs lors de la transmission de données en ASCII sur des lignes
téléphonique ou des réseaux informatiques. Le code de H AMMING a initialement été déve-
loppé pour détecter et corriger des erreurs lors de la lecture de cartes perforées.
On pourrait croire que les progrès technologiques ont diminué voire supprimé la nécessité
des codes correcteurs d’erreur. Il n’en est rien : nous sommes encore aujourd’hui confrontés à
une série d’applications où la transmission d’information fait l’objet d’erreurs. Prenons deux
exemples :
1. La lecture d’un CD ou d’un DVD est un processus délicat, car le rayon LASER qui doit
lire la surface du disque est très fin, et que le disque lui-même est souvent couvert de
poussières, de griffes, voire de traces de doigts. . . De nombreuses erreurs de lecture ont
donc lieu en permanence. . .
2. Avec l’âge des smartphones, une nouvelle famille de code barres, appelés codes matri-
ciels, ont fait leur apparition. Les plus célèbres sont les codes QR (la Figure 2.6 donne

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.

2.7. Conclusion : sémantique d’une représentation binaire


Cette section nous a convaincu que tous les types d’information manipulés par l’ordinateur
peuvent être représentés uniquement à l’aide de 0 et de 1. Il est maintenant naturel de se
poser la question inverse, à savoir : « quel est le sens à associer à une représentation binaire
donnée ? »

ß
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

1 # include < iostream >


2
3 int main () {
4 int x ; // x est un entier signé ( complément à deux )
5 unsigned int y ; // y est un entier non négatif
6 char z ; // z est un caractère
7
8 // Toutes ces variables tiennent sur 32 bits
9
10 // On place la même valeur de 32 bits dans les 3 variables
11 // avec le bit de poids fort valant 1
12 // 1000 0000 0000 0000 0000 0000 0010 1111
13
14 x = 0 b10000000000000000000000000101111 ;
15 y = 0 b10000000000000000000000000101111 ;
16 z = 0 b10000000000000000000000000101111 ;
17
18 // L ’ affichage des trois valeurs est différent
19 // L ’ affichage tient compte du type : ce sont les
20 // instructions qui donnent leur sémantique aux
21 // valeurs représentées en binaire .
22
23 std :: cout << x << std :: endl ;
24
25 std :: cout << y << std :: endl ;
26
27 std :: cout << z << std :: endl ;
28
29 }

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. 3 Effectuez les opérations suivantes en utilisant les opérations euclidiennes.


— 10112 + 11102 ,
— 1A716 + 90916 ,
— 4338 − 1368 .

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 ,

2.8.2. Représentation des nombres négatifs


Ex. 5 Pour cet exercice, on supposera qu’on manipule des représentations binaires sur 8 bits.
Donnez la représentation en base 10 de :
— 0000 11012 , en supposant qu’on a utilisé le bit de signe ;
— 1100 00002 , en supposant qu’on a utilisé le bit de signe ;

65
2. Leçons 2 à 4 – Représentation de l’information

— 1011 11012 , en supposant qu’on a utilisé le complément à 1 ;


— 0111 10012 , en supposant qu’on a utilisé le complément à 1 ;
— 0001 10012 , en supposant qu’on a utilisé le complément à 2 ;
— 1100 11012 , en supposant qu’on a utilisé le complément à 2 ;
— 0011 00112 , en supposant qu’on a utilisé l’excès à 128 ; et de
— 1000 10102 , en supposant qu’on a utilisé l’excès à 128.

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.

2.8.3. Représentation des nombres en virgule flottante


Ex. 9 Exprimez les valeurs suivantes en base 2 :
— 12,510
— 12,12510
— 12,62510
— 1432,4510

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 :

x/y y 6= 0, ∞, NaN y =∞ y =0 y = NaN


x 6= 0, ∞, NaN valeur la plus proche de x/y 0 ∞ NaN
x =∞ ∞ NaN ∞ NaN
x =0 0 0 NaN NaN
x = NaN NaN NaN NaN NaN

On vous demande d’établir un tableau similaire pour la multiplication.

⋆ 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

représentation min max


bit de signe −127 +127
complément à 1 −127 +127
complément à 2 −128 +127
excès à 128 −128 +127

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

x×y y 6= 0, ∞, NaN y =∞ y =0 y = NaN


x 6= 0, ∞, NaN valeur la plus proche de x × y ∞ 0 NaN
x =∞ ∞ ∞ NaN NaN
x =0 0 NaN 0 NaN
x = NaN NaN NaN NaN NaN

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 ;

2. la mémoire (primaire, principale) ;

3. les périphériques, notamment les périphériques d’entrée/sortie, mais également des


périphériques de stockage à long terme des données.

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.

3.1. Mémoire primaire


Nous allons commencer par discuter de la mémoire primaire (ou mémoire principale, ou
mémoire RAM). Il s’agit du composant de l’ordinateur où sont stockés les programmes en
cours d’exécution et les données de ces programmes. Le CPU lit les instructions à exécu-
ter depuis la mémoire primaire, et alimente les registres en données depuis la mémoire. Le
contenu de la mémoire peut provenir de différentes sources, notamment des périphériques
d’entrée, ou de la mémoire secondaire.

Mémoire
CPU Périph. 1 Périph. 2
primaire

Bus

F IGURE 3.1. – Un modèle simple de l’organisation d’un ordinateur.

71
3. Leçon 5 & 6 – Organisation de l’ordinateur

3.1.1. Structure et adresses

Indépendemment de la manière dont la mémoire est réalisée physiquement, elle a toujours


la même structure : il s’agit d’une succession de bits, qui sont groupés par séquences de taille
fixée, lesquelles sont stockées dans des cellules. Au cours de l’histoire, la taille des cellules a
varié d’un ordinateur à l’autre, mais on a aujourd’hui adopté des cellules de 8 bits, ou 1 octet
(byte).
Cette structure de mémoire peut être lue ou écrite : chaque cellule possède un identifiant
propre qu’on appelle adresse et qui est un nombre naturel non-négatif. La première cellule a
donc l’adresse 0, la seconde l’adresse 1, etc. La mémoire permet, soit de consulter le contenu
d’une cellule à une adresse donnée, soit de stocker une information dans une cellule à une
adresse donnée. On n’accède donc en général pas aux bits individuels.
Il arrive souvent que plusieurs cellules mémoires puissent être manipulées en même temps.
En effet, chaque processeur admet une taille de données fixée qui est l’unité fondamentale
d’information qu’il manipule. On appelle cela un mot, et tous les composants de l’ordinateur
sont conçus en fonction de la taille d’un mot : les registres ont une capacité d’un mot, l’ALU
peut traiter des données d’un mot, les bus peuvent transmettre des données d’un mot, et
enfin, la mémoire peut lire et écrire des données d’un mot.
Dans le cas de la mémoire, cela signifie que l’adresse donnée lors d’une opération est celle
de la première cellule où effectuer les opérations. Par exemple, avec des mots de 4 octets,
écrire à l’adresse 20 signifie que les octets du mot seront écrits dans les cellules 1 20, 21, 22 et
23.

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

3.1.2. Mémoire cache


Le principe de la mémoire cache est une famille de mécanismes permettant d’optimiser
fortement la vitesse d’accès (par le CPU) aux données présentes en mémoire primaire. Il
consiste à insérer, en petite quantité, des mémoires très rapides entre le CPU et la mémoire
primaire qui est à la source des données 2 . Quand on a besoin de consulter une donnée à la
source, on vérifie d’abord si celle-ci n’est pas présente dans la cache. Si c’est le cas (on parle
de cache hit), on utilise la copie en cache, ce qui est plus rapide que d’accéder à la source. Si-
non (cache miss), on doit consulter la source, et on en profite pour stocker la donnée lue dans
la mémoire cache, afin d’espérer profiter d’un cache hit si cette même donnée est consultée
dans un futur proche. Pour assurer la rapidité de la mémoire cache, celle-ci est souvent (en
tous cas, en partie) présente physiquement dans le CPU, et son accès ne requiert donc pas
l’utilisation des bus externes.
Concrètement la mémoire cache peut être vue comme un tableau à deux colonnes : la pre-
mière indique l’adresse de la case mémoire dont le contenu est copié dans la cache, et la
seconde indique ce contenu. Par exemple :

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

— un programme manipulant un tableau accédera souvent de manière séquentielle aux


éléments de ce tableau. Comme dans le cas de la boucle, la cache peut donc se révéler
efficace si le programme accède plusieurs fois de suite au même tableau, qui a pu être
maintenu en cache.
L’efficacité du cache est encore accrue par des algorithmes complexes de prédiction qui tentent
de prédire quelle sera la prochaine instruction ou la prochaine donnée à laquelle on accè-
dera, de manière à pouvoir la charger dans la mémoire cache avant même que le CPU ne la
demande.
Les processeurs modernes comprennent souvent 3 niveaux de caches, appelés L1, L2, L3 :
1. la cache L1 est le plus proche du CPU. Elle est la plus rapide et de très petite taille
(quelques dizaines de KO) ;
2. en cas de cache miss dans L1, on consulte la cache L2 qui est plus lente mais plus grande
(quelques centaines de KO), ce qui augmente donc la probabilité d’un cache hit ; enfin
3. en cas de cache miss dans L2, on consulte la cache L3 qui est encore plus lente mais
encore plus grande (quelques MO).

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

3.1.3. Ordre des octets dans les mots


Quand on stocke en mémoire une information (un mot) qui occupe un espace supérieur à
la taille d’une case mémoire, il faut décider dans quel ordre les octets individuels de cette in-
formation seront stockés. Par exemple, supposons une machine ayant des cellules mémoire
d’un octet, et supposons qu’on manipule des mots sur 16 bits (dans le cas d’un CPU 16 bits,
par exemple). Le nombre 51710 est représenté en binaire (sur 2 octets) par 00000001 00000101.
Si on veut stocker ce mot à partir de l’adresse a, on peut donc décider :
— soit de stocker les octets constitutifs en partant de l’octet qui contient le bit de poids
fort. On stocke donc 00000001 à l’adresse a et 00000101 à l’adresse a + 1. Les machines
qui procèdent en stocker le byte contenant le bit de poids fort d’abord sont appelées
« gros boutistes » ou big endian.
— soit de faire l’inverse, et stocker 00000001 à l’adresse a + 1 et 00000101 à l’adresse a. On
parle alors machine « petit boutiste » ou little endian.
Il n’y a a priori pas de meilleur choix, le choix d’une politique gros ou petit boutiste est un
choix arbitraire dépendant du processeur utilisé. Cela peut naturellement poser des pro-
blèmes lors de transmissions de données d’une machine à l’autre. On peut imaginer des pro-
cédures pour remettre les octets dans l’ordre, mais celles-ci doivent tenir compte du type
de données représentée. Par exemple, considérons une machine 32 bits. Elle peut stocker
des entiers sur 32 bits (4 octets) ou bien des caractères, chacun sur un octet (code ASCII par
exemple), ce qui implique qu’un mot de 32 bits contiendra 4 caractères. Dans ce cas, il n’est

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

F IGURE 3.2. – Petit et gros boutistes.

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

F IGURE 3.3. – Un tube Williams.

Source : Sk2k52 (https://commons.wikimedia.org/wiki/File:Williams-tube.jpg, https://creativecommons.org/licenses/by-sa/4.0/legalcode.

3.1.4. Réalisation de la mémoire primaire

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.

Les mémoires à tores magnétiques consistent en un treillis de fils électriques passant


à travers des petits anneaux (tores) de ferrite. Chaque tore peut mémoriser (pour plusieurs
années) un bit sous forme d’un champ magnétique. La direction du champ magnétique dans
le tore indique l’information : 0 dans un sens, 1 dans l’autre. Chaque tore est traversé par 3
fils : les fils X et Y correspondent aux coordonnées du tore (une paire de fils X et Y corres-
pond à un et un seul tore), et le fil sense qui est plié de manière à traverser tous les tores. Pour
pouvoir changer le sens du champ magnétique dans un tore, il faut faire circuler un courant
électrique suffisamment fort dans les fils X et Y correspondant. La direction des courants
dans X et Y donne la direction du champ magnétique et donc l’information stockée. C’est
aussi sur ce principe qu’on effectue la lecture : on force le tore qui doit être lu à passer à 0, en
appliquant le courant nécessaire sur ses lignes X et Y . Si ce tore contenait 1, une pulsation
électrique apparaît sur la ligne sense. Autrement, le tore stockait 0. La lecture est destructive,
et il faut donc re-stocker le 1 si nécessaire.
Les tores de ferrite ont, à leur époque, révolutionné l’informatique car ils étaient beaucoup
plus compacts et pratiques que les autres solutions basées sur des lignes à délai. Par exemple,
IBM introduit en 1954 la 737, unité de stockage à base de tores de ferrite pour les machines
de la série 700. Ces armoires de plusieurs mètres de large peuvent stocker 4096 mots de 36
bits 3 .

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.

Le processeur est le composant principal de l’ordinateur. Il exécute (interprète)


les programmes en langage machine, qui sont stockés dans la mémoire pri-
maire. Il se compose principalement de l’unité de contrôle, de l’ALU et des re-
gistres.

3.2.1. Composants du processeur


Rappelons rapidement ce que sont les différents composants du processeur :
3. Voir : http://www-03.ibm.com/ibm/history/exhibits/701/701_1415bx37.html.

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.

Source : Dessus : Ed Thelen (https://commons.wikimedia.org/wiki/File:Mercury_memory.jpg), « Mercury memory », https://creativecommons.org/lice


nses/by-sa/3.0/legalcode. Dessous : Photo de l’auteur.

78
3.2. Le processeur

F IGURE 3.5. – Détail d’une mémoire à tores de ferrite.

Source : Konstantin Lanzet (https://commons.wikimedia.org/wiki/File:KL_Kernspeicher_Makro_1.jpg), « KL Kernspeicher Makro 1 », https://creativeco


mmons.org/licenses/by-sa/3.0/legalcode.

79
3. Leçon 5 & 6 – Organisation de l’ordinateur

— L’unité de contrôle se charge d’obtenir les instructions dans la mémoire principale,


et commande l’ALU et les registres pour s’assurer que l’instruction est correctement
exécutée.
— L’unité arithmético-logique (ALU) se charge d’effectuer les opérations arithmétiques
et logiques nécessaires à la bonne exécution de l’instruction. Elle obtient les données
depuis les registres, et y place également les résultats de ses calculs.
— Les registres sont des petites mémoires de travail locales au CPU (c’est-à-dire qu’elles
se trouvent physiquement sur le même circuit que le CPU). Le CPU y accède donc plus
rapidement qu’à la mémoire primaire. Les instructions machines sont en général exé-
cutées sur les données qui sont dans les registres.
On distingue deux types de registres : les registres de contrôle, qui servent à assurer le
bon fonctionnement du CPU, et les registres de travail, qui contiennent des données.
Parmi les registres de contrôle, on distingue deux registres importants :
— le pointeur d’instruction, que nous appellerons ici PC (pour program counter), et
qui servira à retrouver en mémoire la prochaine instruction à exécuter en donnant
son adresse ; et
— le registre d’instruction, que nous appellerons ici IR (pour instruction register), et
dans lequel nous stockerons la prochaine instruction à exécuter, en vue de son
décodage.
Les autres registres de contrôle sont généralement des registres d’état (qui donnent des
informations sur l’état de fonctionnement de la machine).
Généralement, les registres ont tous la même taille, qui est celle d’un mot. On parlera
par exemple de « machine 32 bits » pour désigner un ordinateur dont les registres (de
travail) ont 32 bits. Cette taille constitue une caractéristique fondamentale du CPU car :
1. elle permet de savoir la quantité d’information qui peut être traitée en une ins-
truction ;
2. le registre d’instruction étant lui aussi de taille fixée, le format des instructions est
limité ;
3. cette taille s’applique également au registre PC 4 . S’il a une capacité de n bits, on
ne peut différencier que 2n adresses différentes. La quantité d’information à la-
quelle on peut accéder via le registre PC est donc de 2n × c, où c est le nombre de
bits d’une cellule (on parle de la quantité de mémoire adressable car c’est la por-
tion de la mémoire à laquelle on peut assigner une adresse qu’on pourra mani-
puler dans les registres du processeur). Cela limite donc la taille des programmes
que le processeur peut exécuter. En général, cela limite également la taille utile
de la mémoire car tous les composants qui communiquent avec la mémoire (re-
gistres contenant des adresses et bus) sont limités à cette même taille.
Par exemple, sur les machines 32 bits avec des cellules d’un octet, PC fait 32 bits,
et on est donc limité à 232 octets, soit 4 Go. Toute mémoire additionnelle ajoutée à
l’ordinateur serait parfaitement inutile, étant donné que le processeur ne pourrait
manipuler les adresses de ces cases mémoires supplémentaires.
4. Et aux éventuels autres registres contenant des adresses pour la lecture ou l’écriture en mémoire.

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

Regardons maintenant un exemple de petit programme en langage machine, et voyons


comment il utilise les registres de travail.

ß
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

1 mov eax , 42 ; place 42 dans eax


2 mov ecx , 7 ; place 7 dans ecx
3 add eax , ecx ; eax reçoit eax + ecx

F IGURE 3.6. – Un exemple de programme en langage machine i486.

Nous allons maintenant nous concentrer sur l’ALU et les registres qui permettent l’exécu-
tion des instructions machine.

3.2.2. Le chemin des données – datapath

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

L’ensemble constitué de l’ALU, des registres et des bus d’interconnection est


appelé le chemin des données (ou datapath en anglais).

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.

3.2.3. Exécution des instructions

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 boucle d’interprétation du processeur (ou boucle fetch–decode–execute) est la


boucle exécutée en permanence par le processeur pour interpréter les instruc-
tions machine :
1. Charger, dans le registre IR, l’instruction située en mémoire (M) à
l’adresse donnée par PC : IR ← M[PC ]
2. Incrémenter PC : PC ← PC +1
3. Analyser l’instruction dans IR
4. Exécuter l’instruction (ceci peut modifier PC)
5. Aller en 2.

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.

3.2.4. Machine à pile


Jusqu’à présent, nous avons toujours décrit le fonctionnement du processeur comme une
machine à registres : comme nous l’avons vu dans le cadre du datapath, les données à traiter
sont lues dans les registres, et les résultats sont inscrits également dans les registres. Il existe

83
3. Leçon 5 & 6 – Organisation de l’ordinateur

IR ← M[PC ]

PC ← PC +1

Analyser l’instruction dans IR

Exécuter l’instruction dans IR

F IGURE 3.8. – La boucle d’interprétation du CPU.

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.

La pile comme structure de données Commençons par décrire, de manière abstraite, le


fonctionnement d’une pile (ou stack en anglais), en supposant que nous souhaitons stocker
des valeurs entières uniquement sur notre pile. Intuitivement, une pile ressemble à une pile
d’assiettes dans la vie réelle : on y empile les données, il y a donc une donnée au sommet,
une en bas, et toutes les autres suivent un ordre donné par la pile.
Comme avec une véritable pile d’assiettes, on ne peut accéder qu’à son sommet 8 . Pour
notre structure de données, cela signifie que seule la donnée au sommet peut être lue, à l’aide
d’une opération appelée Top. Ensuite, la pile peut être modifiée, mais toujours via son som-
met : un ajout de données, se fait nécessairement au somme de la pile (pas d’insertion « au

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

F IGURE 3.9. – Un exemple de pile.

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.

Implémentation d’une pile Concrètement, comment réalise-t-on cette structure abstraite ?


Une possibilité consiste à stocker la pile en mémoire, en plaçant l’élément en bas de la pile à
une certaine adresse a, puis celui juste au-dessus à l’adresse a + 1, etc. Pour accéder à la pile,
il suffit alors de retenir l’adresse s du sommet de la pile. On peut alors : (1) faire une lecture
en mémoire à l’adresse s pour réaliser un Top ; (2) écrire à l’adresse s +1 pour faire un Push, et
incrémenter s ; (3) décrémenter s pour réaliser un Pop. Il est naturellement possible de faire
l’inverse : de stocker le sommet de la pile à une adresse a, l’élément en-dessous à l’adresse
a + 1, etc.
Une autre possibilité est d’utiliser un ensemble de registres, numérotés pour stocker les
différentes cases de la pile (par exemple le registre numéro 0 contiendra le bas de la pile,
le registre 1 la donnée au-dessus, etc). Comme dans le cas d’un stockage en mémoire, on
retiendra le numéro du registre qui contient le sommet de la pile.

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

1 fldpi ; Charge pi au sommet de la pile


2 fldl2t ; Charge log2 (10) au sommet de la pile
3 fadd ; Réalise la somme et place le résultat au sommet
4 ; on aurait aussi pu écrire fadd st0 , st1 :
5 ; réaliser la somme de st0 ( le sommet ) et
6 ; st1 et placer le résultat dans st0.

F IGURE 3.10. – Un exemple de langage machine i486 utilisant le FPU.

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

3.2.5. Choix du jeu d’instructions machine


La boucle fetch-decode-execute permet une grande flexibilité, et autorise les concepteurs de
microprocesseurs à choisir librement les instructions qu’ils souhaitent voir apparaître dans
le langage machine de leur processeur. Naturellement, plus le langage machine est riche,
plus confortable sera la tâche des programmeurs (qui pourront utiliser directement une ins-
truction machine pour réaliser telle ou telle tâche au lieu de devoir écrire un morceau de
programme qui réalise la même chose), mais plus ardue sera la tâche des concepteurs du
processeur.
Par exemple, le processeur i486 possède des instructions comme fadd ou fmul pour effec-
tuer des opérations sur des valeurs en virgule flottante (à l’aide du FPU, ce qui est très effi-
cace). Mais rien n’empêche de réaliser le même traitement en écrivant un petit programme
n’utilisant que les instructions du processeur manipulant des entiers (add, mul, etc).
La question de savoir que mettre dans un langage machine est donc un vieux débat. De
manière générale, les fabricants de processeurs essaient de faire en sorte que des familles de
processeurs soient compatibles entre eux, de manière à ce que les programmes écrits pour
les anciens processeurs soient toujours exécutables sur les nouveaux. Par exemple, l’i486 est
capable (tout comme ses descendants actuels) d’exécuter tous les programmes écrits pour

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 processeurs SPARC, utilisés dans


les stations de travail Sun.
— Les processeurs MIPS, utilisés notam-
ment dans les stations de travail Sili-
con Graphics, dans certains modèles
de la PlayStation. . . Sur ces proces-
seurs, par exemple, il n’y a que trois
types d’instructions :

— Les instructions Load/Store qui


copient des valeurs entre les re-
gistres et la mémoire. F IGURE 3.11. – Sir Maurice W ILKES en 1980.

— 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

— Les instructions de saut/bran-


chement.
Les processeurs Intel de la famille x86, dont l’i486, par contre sont des processeurs CISC.

3.2.6. Techniques pour améliorer l’efficacité des processeurs


L’efficacité des processeurs est évidemment un des facteurs clef pour assurer les perfor-
mances des ordinateurs modernes, qui doivent traiter de grandes quantités d’information en
peu de temps. Une des caractéristiques essentielles d’un processeur est sa fréquence, c’est-à-
dire le nombre de cycles de traitements qu’il peut effectuer par seconde 15 . Si les premiers mi-
croprocesseurs fonctionnaient à environ 1 Mhz, les processeurs modernes atteignent quelques
GHz, ce qui les rend, sur ce plan-là, environ 1000 fois plus rapides. Mais l’augmentation de la
fréquence entraine également une augmentation de la chaleur dissipée par les processeurs,
ce qui entraine de gros problèmes d’ingénierie. D’autres techniques ont donc été dévelop-
pées pour augmenter l’efficacité des processeurs.

Exécution en parallèle des instructions Il y a différentes façon de découper l’exécu-


tion d’une instruction en étapes successives (nous en avons vue une lors de la boucle fetch–
decode–execute). Une façon de faire est la suivante :
1. lecture en mémoire ;
2. décodage ;
3. lecture des opérandes ;
4. exécution ;
5. écriture du résultat.
On peut concevoir un processeur dans lequel chacune de ces étapes est réalisée par un mo-
dule indépendant, chaque module étant connecté au suivant. Cette architecture, illustrée à
la Figure 3.12 est appelé un pipeline (dans ce cas, à 5 étages), et l’exécution d’une instruction
consiste donc à la faire « rentrer » à gauche du pipeline et à la faire transiter jusqu’à la sortie à
sa droite.
Comme le pipeline est purement séquentiel, il n’y a, pour une instruction donnée, qu’un
seul de ses éléments qui est actif à chaque instant. On peut donc rendre le processeur plus
efficace, en faisant fonctionner les différents étages du pipeline en parallèle, sur des instruc-
tions différentes : une fois qu’une première instruction a été lue en mémoire, on peut lancer
la lecture de l’instruction suivante durant le décodage de la première, et ainsi de suite.
Évidemment, il existe parfois des dépendances entre les instructions, ce qui peut ralentir
le pipeline en créant des « bulles ». Par exemple, dans le code suivant :
1 add eax , ebx ; place dans eax la somme eax + ebx
2 add ecx , eax ; place dans ecx la somme ecx + eax
15. Attention que cette fréquence ne correspond en général pas au nombre de cycles de la boucle fetch–decode–
execute. Ainsi, un processeur fonctionnant à 1 GHz n’exécute pas forcément 1 milliard d’instructions machine par
seconde. Il s’agit en général du nombre de cycles du chemin de données (cfr. Chapitre 5), et chaque instruction
machine peut demander un nombre de cycles variable pour s’exécuter.

88
3.2. Le processeur

Lire l’instruction Décoder Lire l’opérande Exécuter Écrire le résultat


en mémoire l’instruction en mémoire l’instruction en mémoire
(instruction fetch) (instruction decode) (operand fetch) (execute) (write back)

F IGURE 3.12. – Illustration d’un pipeline à cinq étages.

Décoder Lire l’opérande Exécuter Écrire le résultat


l’instruction en mémoire l’instruction en mémoire
(instruction decode) (operand fetch) (execute) (write back)

Lire l’instruction
en mémoire
(instruction fetch)

Décoder Lire l’opérande Exécuter Écrire le résultat


l’instruction en mémoire l’instruction en mémoire
(instruction decode) (operand fetch) (execute) (write back)

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.

Architectures superscalaires Une évolution intéressante du pipeline consiste à dédoubler


certaines des cinq unités pour traiter en parallèle les étapes correspondantes, comme sur
la Figure 3.13. C’est particulièrement intéressant si certaines unités sont plus rapides que
d’autres (typiquement, le décodage sera plus rapide que l’ALU). On parle alors d’un pipeline
superscalaire.
Une architecture superscalaire consiste donc à mettre en parallèle plusieurs (morceaux de)
pipelines. Ces idées étaient déjà présentes dans l’architecture du CDC 6600, un ordinateur
conçu par Seymour C RAY 16 , et commercialisé par la Control Data Corporation à partir de
1964. Il était, à l’époque, considéré comme l’ordinateur le plus rapide du monde. Son ar-
chitecture superscalaire était tout à fait révolutionnaire pour l’époque. Ce principe n’a com-
mencé à être utilisé de manière régulière, dans les microprocesseurs, qu’à la fin des an-
nées 1980.

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

F IGURE 3.14. – Un ordinateur CDC 6600.

Source : Jitze Couperus (https://commons.wikimedia.org/wiki/File:CDC_6600.jc.jpg), « CDC 6600.jc », https://creativecommons.org/licenses/by/2.


0/legalcode.

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 .

Multiprocesseurs Une idée très naturelle consiste simplement à multiplier le nombre de


processeurs (totalement indépendants) dans l’ordinateur. On parle alors de système multi-
processeur, on obtient alors une architecture comme sur la Figure 3.17 (avec deux proces-
seurs, mais on peut en avoir plus). Comme on le voit, la mémoire est partagée, ce qui pose
certaines difficultés : il ne faut pas que les deux processeurs tentent d’accéder à une même
case mémoire au même moment (par exemple, en écriture), sans quoi les données pourraient
être corrompues. Le bus est partagé également, ce qui peut ralentir globalement le système
en cas de congestion.
Ces architectures sont particulièrement utiles quand on veut exécuter plusieurs tâches in-
dépendantes en même temps, comme avec un système d’exploitation multi-tâche (voir Cha-
pitre 8).

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.

Grappes et fermes de calcul Les notions de grappes et de fermes de calcul dépassent


la notion de processeur en tant que composant d’un ordinateur. Mais ces techniques s’ins-

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.

Source : Clemens PFEIFFER (https://commons.wikimedia.org/wiki/File:Cray-1-deutsches-museum.jpg), « Cray-1-deutsches-museum », https://creati


vecommons.org/licenses/by/2.5/legalcode.

93
3. Leçon 5 & 6 – Organisation de l’ordinateur

Mémoire
CPU 1 CPU 2 Périph. 1 Périph. 2
principale

Bus

F IGURE 3.17. – L’organisation d’un ordinateur avec 2 CPUs

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.

3.3. Les périphériques


Terminons notre discussion de la Figure 3.1 en parlant des périphériques. Comme leur nom
l’indique, ils ne sont pas, à proprement parler, essentiels à l’exécution des programmes, mais
ils permettent d’utiliser les ordinateurs de manière pratique, notamment en l’autorisant à

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.

3.3.1. Mémoire secondaire


La mémoire (primaire) que nous avons décrite jusqu’à présent est la mémoire de travail de
l’ordinateur. Elle est donc intrinsèquement temporaire. Pour des stockages à plus long terme,
on fait appel à la mémoire secondaire, qui peut être vue comme un cas particulier de périphé-
rique. Le mémoire secondaire est typiquement de plus grande capacité, mais aussi plus lente
que la mémoire primaire. Par exemple, on trouve aujourd’hui des ordinateurs personnels
avec une mémoire primaire de quelques GO (2 ou 4, voire un peu plus), mais une mémoire
secondaire (disque dur ou SSD) de plusieurs centaines de GO (voire même un téraoctet). Le
temps d’accès des disques durs est typiquement de l’ordre de la micro-seconde, alors que les
temps d’accès des mémoires sont de l’ordre de la dizaine de nano-seconde.
À part ces différences de capacité et de vitesse, la mémoire secondaire se comporte essen-
tiellement comme la mémoire primaire : elle stocke l’information sous forme binaire, dans
des cases qui possèdent chacune une adresse. Naturellement, les codes détecteurs et correc-
teurs d’erreurs peuvent être utilisés.
Au long de l’histoire de l’informatique, la mémoire secondaire a pris plusieurs formes, en
fonction de l’évolution de la technologie. En voici quelques-unes.

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.

Source : Cliché de l’auteur.

F IGURE 3.19. – Du ruban perforé.

96
3.3. Les périphériques

F IGURE 3.20. – Trois types de disquettes : de 8, 5, 25, et 3, 5 pouces.

usage aujourd’hui, car elles permettent de stocker de grandes quantités d’information à un


coût très bas. Les temps d’accès sont par contre assez mauvais, car il faut faire défiler la bande
pour atteindre la zone à lire.

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

F IGURE 3.21. – Illustration d’un disque dur.

Source : I, Surachit (https://commons.wikimedia.org/wiki/File:Hard_drive-fr.svg), « Hard drive-fr », https://creativecommons.org/licenses/by-sa/


3.0/legalcode

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

3.3.2. Les périphériques d’entrée/sortie


Pour pouvoir communiquer avec le monde extérieur, l’ordinateur utilise des périphériques,
qui permettent d’acquérir de l’information ou de la restituer. Les périphériques sont généra-
lement composés de deux parties :

98
3.3. Les périphériques

1. le périphérique physique, c’est-à-dire le dispositif qui va réaliser l’acquisition ou la res-


titution de l’information (par exemple : un écran, un clavier) ; et
2. un contrôleur : un circuit électronique qui réalise la connexion du périphérique phy-
sique au bus de l’ordinateur. Il a pour mission de récupérer les informations qui cir-
culent sur le bus et qui lui sont adressées, et de piloter le périphérique en conséquence
(par exemple : la carte graphique, qui va recevoir des instructions du processeur pour
afficher des images à l’écran), ou bien de convertir l’information qui vient du périphé-
rique pour l’émettre sur le bus (par exemple : le contrôleur du clavier).
Parmi les différents types de périphériques, on trouve, parmi d’autres exemples :
— les écrans et périphériques d’affichage ;
— les claviers ;
— les souris ou trackpads ;
— les modems, qui permettent de transmettre des informations sur des lignes télépho-
niques, par exemple ;
— les « cartes son » accompagnées des haut-parleurs, micros, etc. ;
— les appareils et caméras numé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

Les portes logiques

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.

4.1. L’algèbre Booléenne


On doit le développement de l’algèbre
Booléenne [5] au mathématicien anglais
George B OOLE 1 . L’algèbre Booléenne ser-
vira de base, en 1937 au mémoire de mas-
ter de Claude E. S HANNON 2 [33] dans lequel
il jette les bases des circuits logiques. Ces
travaux sont considérés comme fondateurs
pour l’informatique moderne.
Dans l’algèbre Booléenne, on ne peut ma-
nipuler que deux valeurs : le 0 et le 1 (parfois George B OOLE (g.) et Claude E. S HANNON (d.)
remplacées par les valeurs logiques « faux »
et « vrai », ce qui fait qu’on parle souvent Source : Jacobs, Konrad (https://commons.wikimedia.org/wiki/File:Claude
Shannon_MFO3807.jpg), « ClaudeShannon MFO3807 », https://creativecomm
de logique Booléenne au lieu d’algèbre Boo- ons.org/licenses/by-sa/2.0/de/legalcode.

léenne). Ainsi, toutes les variables dans les


1. Né le 2 novembre 1815, mort le 8 décembre 1864.
2. Né le 30 avril 1916, mort le 4 février 2001. Mathématicien et informaticien américain, considéré comme le
fondateur de la théorie de l’information.

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.

Passons maintenant en revue les opérateurs Booléens de base :

L’opérateur et L’opérateur et est un opérateur binaire 3 . La notation habituelle de l’opé-


rateur et est : ∧. Par définition, la valeur de l’opérateur est 1 si et seulement si les deux opé-
randes valent 1 (la valeur de l’opérateur est donc 0 dans les autres cas). On représente cela
par la table de vérité :

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

L’opérateur ou L’opérateur ou est également un opérateur binaire, qui vaut 1 si et seule-


ment si un des deux opérandes vaut 1. On le représente par ∨ et sa table de vérité est :

a b a ∨b
0 0 0
0 1 1
1 0 1
1 1 1

L’opérateur ou exclusif Cet opérateur est proche du ou : il vaut 1 si et seulement si exac-


tement un des opérandes vaut 1 (intuitivement, avoir un des opérandes à 1 exclut que l’autre
le soit également). On le représente XOR et sa table de vérité est :

a b a XOR b
0 0 0
0 1 1
1 0 1
1 1 0

Observez la différence avec la table de vérité du ou (dans la dernière ligne).

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 :

a NOR b 6= (¬a) ∨ (¬b).

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

On peut s’en convaincre en considérant par exemple le cas où a = 1 et b = 0. Dans ce cas :

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

Le ∨ et le XOR ont la même priorité.

ß
Par exemple, l’expression :
x ∨ ¬y ∧ z
se comprend et s’évalue comme :
¡ ¢
x ∨ (¬y) ∧ z

Identités remarquables On peut identifier certaines propriétés des opérateurs ∧ et ∨ qui


seront utiles par la suite. En se référant aux tables de vérité données ci-dessus, on peut se
convaincre que les équations suivantes sont vraies pour toute valeur (Booléenne) de x :

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

On peut également noter que, pour tout x :

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 :

(φ1 ∧ φ2 ) ∨ (φ1 ∧ φ3 ) = φ1 ∧ (φ2 ∨ φ3 ) (4.14)

On peut s’en convaincre en comparant les tables de vérité.


Finalement, on n’oubliera pas les célèbres lois de D E M ORGAN 5 , qui permettent de ré-
écrire la négation d’un et ou d’un ou : pour toute paire d’expressions φ et ψ :

¬(φ ∨ ψ) = ¬φ ∧ ¬ψ, (4.15)


¬(φ ∧ ψ) = ¬φ ∨ ¬ψ. (4.16)

On remarquera que quand la négation « rentre » dans la parenthèse, le et se transforme en


ou et vice-versa.

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.

On construit un table de vérité à 8 = 23 lignes. On commence par remplir une


colonne qui correspond à ¬y ; puis une colonne qui correspond à (¬y) ∧ z, en
utilisant la colonne construite pour ¬y ; puis enfin une colonne pour toute l’ex-
pression :
¡ ¢
x y z ¬y (¬y) ∧ z x ∨ (¬y) ∧ z
0 0 0 1 0 0
0 0 1 1 1 1
0 1 0 0 0 0
0 1 1 0 0 0
1 0 0 1 0 1
1 0 1 1 1 1
1 1 0 0 0 1
1 1 1 0 0 1

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.

Cet exemple suggère la méthode systématique suivante :

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

Porte ET : Porte OU EXCLUSIF :

Porte OU : Porte NON :

F IGURE 4.1. – Quelques portes logiques de base.

La formule finale est donc bien : φ2 ∨ φ6 ∨ φ8 = (¬x ∧ ¬y ∧ z) ∨ (x ∧ ¬y ∧ z) ∨ (x ∧


... y ∧ z), comme annoncé.
Remarquons que cette formule peut être simplifiée. En appliquant (4.14) sur les
deux premières parenthèse, on peut mettre (¬y ∧ z) en évidence et on a :

(¬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).

4.2. Les circuits logiques


Une porte logique est un dispositif qui calcule un des opérateurs de la logique Booléenne.
Une porte logique est représentée graphiquement à l’aide d’un symbole distinctif, qui pos-
sède des entrées et une sortie, représentées sous forme de traits. On combine les portes lo-
giques entre elles en connectant la sortie d’une porte aux entrées d’autres portes, ce qui
forme un circuit, capable de calculer une fonction Booléenne. La Figure 4.1 illustre ces portes.

4.2.1. Les portes logiques en pratique


Concrètement, nous voulons réaliser ces
portes logiques à l’aide de matériel élec-
tronique qui possèderont des connecteurs
(pattes métalliques ou câbles) d’entrée et de
sortie pour obtenir les valeurs d’entrée et
produire les valeurs de sortie. Des voltages
différents sont utilisés pour représenter les
valeurs Booléennes. Par exemple, on utili-
sera 0 volt pour faux (valeur 0) et 5 volts
pour vrai (valeur 1). Une fois cette conven-
tion fixée, on peut envisager différentes ma- F IGURE 4.2. – Exemples de transistors.
nières de réaliser ces portes logiques.
Dans les ordinateurs modernes, on utilise des transistors, qui sont des composant électro-
niques à 3 connexions appelés respectivement base, émetteur et collecteur. On en trouve sous

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

F IGURE 4.3. – Exemple de tubes IBM.

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 .

Les circuits logiques aujourd’hui, et demain. . . La technologie actuelle permet de réa-


15
liser des transistors ayant une taille d’environ 10nm . Sans entrer dans les détails (qui mé-

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.

4.2.2. De la formule Booléenne au circuit logique


Une fois qu’on possède une formule Booléenne (qu’on a par exemple extraite d’une table
de vérité selon la méthode décrite plus haut), il est relativement aisé de construire le circuit
16. Pour l’instant, les applications commerciales de ces idées se cantonnent aux systèmes embarqués (voir les
produits commercialisés par Xilinx ou Altera), mais Intel a annoncé en mai 2018 préparer un processeur hybride
avec FPGA intégré.
17. Discipline étudiant l’ensemble des techniques permettant de transmettre des messages de manière secrète,
comme c’est nécessaire, par exemple, dans les applications bancaires en ligne.

113
4. Leçons 7 à 9 – Niveau 0 : portes logiques

logique correspondant. On se contente de suivre l’ordre de calcul des différents opérateurs


logiques, selon la priorité ou les parenthèses.
¡ ¢
Par exemple, pour l’expression x ∨ ¬y ∧ z = x ∨ (¬y) ∧ z déjà considérée ci-
ß dessus, on commence par calculer ¬y :

On calcule ensuite (¬y) ∧ z, en connectant la sortie de la porte non (qui calcule


donc ¬y) à une des deux entrées d’une porte et, l’autre entrée étant connectée
à l’entrée z du circuit :

On termine en calculant un ou entre la sortie du et et l’entrée x :

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.

4.3. Circuits pour réaliser l’arithmétique binaire


Dans cette section, nous allons étudier des circuits logiques qui permettent de réaliser des
opérations arithmétiques sur des nombres représentés en binaire. Nous allons donc fixer un

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)

4.3.1. Circuit additionneur


On se souviendra qu’on calcule la somme de deux nombres binaires 18 a = a n−1 · · · a 1 a 0 et
b = b n−1 · · · b 1 b 0 comme on le ferait en base 10 : en sommant d’abord les bits de poids faibles
a 0 et b 0 , puis les bits a 1 et b 1 avec un éventuel report venant de la somme de a 0 et b 0 , etc. Par
exemple, sur n = 4 bits :
1 1 1 1
1 0 1 1
+ 0 1 1 1
1 0 0 1 0
Cette technique nous permet de décomposer la réalisation de notre circuit additionneur en
plusieurs sous-problèmes :
— tout d’abord, il nous faut un circuit capable de réaliser la somme binaire de a 0 et b 0
(les bits de poids faible). Cette opération produit une valeur binaire qui tient sur deux
bits 19 . Le bit de poids faible, s 0 constitue le bit de poids faible du résultat ; le bit de
poids fort, r 0 , constitue le report de la colonne suivante :
r0
· · · a1 a0
.
+ · · · b1 b0
··· s0
Un tel circuit a donc deux entrées et deux sorties.
— Ensuite, nous avons besoin d’un circuit qui fait la somme de trois bits, pour réaliser le
calcul qui se présente à nous dans toutes les autres colonnes. Dans la colonne i (i >
1), ces trois bits seront : les deux bits a i et b i des deux nombres a et b à additionner
et le report r i −1 provenant de la colonne précédente. Le résultat de l’addition tient à
nouveau sur 2 bits : le bit de poids faible, s i , apparaît dans le résultat, et le bit de poids
fort, r i , est le report pour la colonne suivante (si cette colonne existe, autrement, i = n
et r i est le bit de poids fort du résultat) :
ri r i −1
··· a i +1 ai a i −1 · · ·
.
+ ··· b i +1 bi b i −1 · · ·
··· si ···
Le premier de ces deux circuits est appelé demi-additionneur ; le second est appelé addition-
neur complet (ou simplement, additionneur). Nous allons maintenant étudier ces deux cir-
cuits, l’additionneur complet pouvant être compris comme une combinaison de deux demi-
additionneurs.
18. où les a i et les b i sont les bits individuels des deux nombres, les bits a 0 et b 0 étant les bits de poids faible
19. Le plus grand nombre qu’on peut obtenir est en effet 210 = 102

115
4. Leçons 7 à 9 – Niveau 0 : portes logiques

a
s

r
b

F IGURE 4.4. – Le demi-additionneur.

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

F IGURE 4.5. – Un additionneur complet, sur base de deux demi-additionneurs (rectangles


pointillés).

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

F IGURE 4.6. – Un additionneur 4 bits sur base de 4 additionneurs.

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)

Additionneur n bits Finalement, on obtient un additionneur sur n bits (c’est-à-dire un


circuit additionnant deux nombres sur n bits) en combinant plusieurs additionneurs sur un
bit, et en connectant correctement les reports. Un exemple sur 4 bits est donné à la Figure 4.6.
Chacun des carrés en gras représente un additionneur complet avec ses deux entrées a et b
au-dessus, son entrée r pr ec à droite, sa sortie r à gauche et sa sortie s en-dessous. L’entrée
r pr ec du premier additionneur (c’est-à-dire celui qui fait la somme des bits de poids faibles
a 0 et b 0 ) est connecté à la constante 0. On aurait donc pu utiliser un demi-additionneur en
lieu et place de cet additionneur complet. La sortie r de l’additionneur correspondant aux
bits de poids forts constitue le bit de poids fort de la somme.

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 :

— si C = 0 (décalage vers la gauche), on veut S 2 = D 1 , S 1 = D 0 et S 0 = 0 ; par contre

— si C = 1 (décalage vers la droite), on veut S 2 = 0, S 1 = D 2 et S 0 = D 1 .

On voit donc que les sorties ne dépendent pas toutes des 4 entrées. Plus précisément :

— S 0 ne peut prendre que la valeur constante 0 ou la valeur D 1 (en fonction de C ), et ne


dépend donc pas ni de D 0 ni de D 2 . La table de vérité pour S 0 est donc :

C D1 S0
0 0 0
0 1 0
1 0 0
1 1 1

De cette table, on déduit que : S 0 = C ∧ D 1 .

— Par un raisonnement similaire, on voit que S 2 ne dépend que de C et de D 1 également :

C D1 S2
0 0 0
0 1 1
1 0 0
1 1 0

On voit donc que S 2 = ¬C ∧ D 1 .

— 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

F IGURE 4.7. – Le circuit de décalage 3 bits.

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

En appliquant la méthodologie de la Section 4.1, on trouve une expression simplifiée


pour S 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

les équations deviennent :

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

Et si on ajoute un décalage de K bits (au lieu de 1), on obtient :

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

On voit donc que :

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.8. – Un décodeur 4 bits.

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.

On obtient dès lors le circuits de la Figure 4.8.


De manière générale, pour un décodeur n bits, la formule correspondant à la sortie S i est
de la forme :
^
n
Si = αj
j =1

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

2. 2n entrées appelées E i (pour 0 ≤ i ≤ 2n − 1) ; ainsi que


3. 1 seule sortie S.
Ce circuit doit recopier sur sa sortie S l’entrée E i dont le numéro i est donné en binaire sur
I n−1 · · · I 0 (d’où le nom de « sélecteur » : les entrées I i servant à sélectionner une entrée E j ).

Par exemple, si n = 3, et que I 2 = 0, I 1 = 1 et I 0 = 1, le numéro de l’entrée sélec-


ß tionnée est 0112 = 310 , et le circuit doit donc faire en sorte que S = E 3 (sans que
les autres entrées n’aient d’influence sur la sortie).

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) ;

— pour tout j 6= k, S j = 0, et la sortie de la porte et qui combine S j et E j vaut donc :

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.

4.3.6. ALU simplifiée (1 bit)


Sur base des circuits que nous avons vus précédemment, nous pouvons maintenant conce-
voir une unité arithmético-logique ou ALU. Ce composant du processeur a été présenté à la
section 3.2.
Nous commencerons par considérer une ALU simplifiée qui effectue un opération parmi
quatre ; et qui effectue l’opération choisie sur deux valeurs d’1 bit. Le principe présenté ici
pourra facilement être étendu à plus de 4 opérations, et nous expliquerons plus tard com-
ment combiner plusieurs de ces ALU 1 bit pour obtenir une ALU ayant en entrée des valeurs
de taille arbitrairement grande. Les quatre opérations sont données à la Table 4.1. Notons

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

F IGURE 4.10. – Le principe d’un sélecteur n bits.

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.

4.3.7. ALU n bits


Expliquons maintenant comment nous pouvons réaliser une ALU n bits, pour un n arbi-
traire (mais fixé). Par exemple, dans un processeur moderne, nous aurons n = 32 ou n = 64
bits. Précisons d’abord ce que nous attendons. Nous souhaitons un circuit avec 2n +3 entrées
et n + 1 sorties. Les 2n + 3 entrées sont :
— les n bits A n−1 , . . . , A 0 constitutifs de l’entrée A ;
— les n bits B n−1 , . . . , B 0 constitutifs de l’entrée B ;
— un bit r pr ec constituant le « report précédant » pour l’opération d’addition ; et

125
4. Leçons 7 à 9 – Niveau 0 : portes logiques

r pr ec

S
B

r sui v

F0

F1

F IGURE 4.11. – Une ALU 1 bit avec 4 opérations.

126
4.3. Circuits pour réaliser l’arithmétique binaire

A B
F0
F1

r pr ec ALU r sui v

F IGURE 4.12. – La représentation d’une ALU.

— les deux bits F 0 et F 1 permettant de sélectionner l’opération à effecteur 21 , selon la


Table 4.1.
Les n + 1 sorties sont :
— les n bits S n−1 , . . . S 0 représentant la valeur de sortie calculée par l’ALU ; et
— le bit r sui v constituant le dernier report dans le cas de l’addition.
La sémantique des opérations est la suivante :
1. Le opérations Booléennes et, ou et non s’appliquent bit à bit, c’est à dire que, pour
tout 0 ≤ i ≤ n − 1 : S i = A i ∧ B i , ou S i = A i ∨ B i ou S i = ¬B i en fonction de l’opération
choisie.
2. L’addition doit réaliser la somme des nombres A n−1 A n−2 · · · A 0 , B n−1 B n−2 · · · B 0 et r pr ec
donnés en binaire (complément à deux). Autrement dit, A 0 et B 0 sont les bits de poids
faibles des entrées A et B . Le résultat sera représenté par r sui v S n−1 S n−2 · · · S 0 . Autre-
ment dit, S 0 sera le bit de poids faible de la sortie, et r sui v le bit de poids fort.

Par exemple, pour une ALU 4 bits, si A 3 = 1, A 2 = 0, A 1 = 0, A 0 = 0, B 3 = 1, B 2 = 0,


ß B 1 = 1, B 0 = 0 et r pr ec = 1, on réalise la somme de A = 10002 = 810 , B = 10102 =
1210 et r pr ec = 1. Le résultat 2110 = 101012 tient bien sur 5 bits, on veut donc
avoir r sui v = 1, et S = 0101, soit S 3 = 0, S 2 = 1, S 1 = 0 et S 0 = 1.

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

r pr ec ALU0 ALU1 ALU2 ALU3

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 :

1 add ebx , edx


Les 32 bits de poids faible du résultat sont donc placés dans ebx, le 33e bit dans le carry flag.
On peut ensuite faire la somme des bits de poids forts et du carry flag :

128
4.4. Circuits pour réaliser des mémoires

1 adc eax , ecx

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

4.4. Circuits pour réaliser des mémoires


Maintenant que nous avons à notre disposition des circuits pour traiter l’information, exa-
minons les circuits permettant de stocker l’information, et donc de réaliser des mémoires.

4.4.1. Mémoire élémentaire


Le plus simple circuit permettant de stocker une valeur binaire qu’on puisse imaginer est
donné à la Figure 4.14. Ce circuit présente plusieurs caractéristiques intéressantes :
1. Tout d’abord, nous constatons que le circuit n’a pas d’entrée. De ce fait, il n’est pas
possible de modifier la valeur stockée. Ce circuit n’a donc aucune application pratique,
mais permet d’illustrer certaines notions intéressantes pour la suite.
2. Ensuite, nous constatons que le circuit est essentiellement une boucle : la sortie de cha-
cune des portes est connectée à l’entrée de l’autre. C’est une nouveauté, car les circuits
que nous avons considérés jusqu’à présent ne comportaient aucune boucle.
Dans tous les circuits que nous avons vus précédemment, la ou les sorties dépendaient de
manière unique des entrées. Par exemple, pour le demi-additionneur, la sortie s vaut 0 quand
les deux entrées valent 1, et cette sortie ne peut pas prendre d’autre valeur pour ces en-
trées. Dans les circuits de mémoire, ce phénomène n’est plus vrai (justement en raison des
boucles) : pour une même entrée, les sorties peuvent être différentes. Cela peut sembler sur-
prenant, mais c’est en fait une chose heureuse. En effet, une mémoire retient l’information
qui y a été stockée par le passé. Donc sa sortie (c’est-à-dire la valeur qu’elle contient) ne dé-
pend pas, en général que des entrées à un moment donné, mais doit également tenir compte
de la valeur qui a été écrite précédemment dans la mémoire. Dans le cas des circuits de mé-
moire, on s’intéressera donc à la notion d’état du circuit, ce que nous allons illuster à l’aide
de l’exemple de la Figure 4.14.
Un état du circuit est une fonction qui assigne une valeur à chaque entrée et à chaque
sortie. Évidemment, tous les états ne seront pas possibles pour un circuit donné. Par exemple,
dans le cas du demi-additionneur, l’état a = 1, b = 1, s = 1 et r = 1 n’est pas possible 22 , car si
a = b = 1, alors s = 0 et r = 1, puisque s = a XOR b et r = a ∧ b (cfr. Figure 4.4).
Le circuit de la Figure 4.14 n’admet que deux états, à savoir :

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.

En effet, les équations qui correspondent à ce circuit sont :

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

F IGURE 4.15. – Une bascule.

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

Par un raisonnement similaire, on déduit que Q vaut 1 si et seulement si Q et S sont faux,


autrement dit : Q = ¬Q ∧ ¬S = Q NOR S. Les deux équations du circuit sont donc :

Q = Q NOR R, (4.17)
Q = Q NOR S. (4.18)

Ce circuit est représenté à la Figure 4.15. On l’appelle bascule ou bistable.


Comme dans le cas du circuit de la Figure 4.14, regardons maintenant quels sont les états
possibles de ce circuit :
— Si S = 1 et R = 0, les équations (4.17) et (4.18) donnent respectivement :

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

On en déduit donc que Q = 0 et que Q = ¬Q = ¬0 = 1. L’entrée S a donc bien l’effet


attendu 23 , car le seul état possible quand S = 1 et R = 0 est celui où la mémoire stocke
la valeur 1 (Q = 1).
— Si S = 0 et R = 1, on déduit, par un raisonnement similaire que Q = 1 et Q = 0. L’entrée
« reset » a donc également l’effet attendu.
— Finalement, si S = 0 et R = 0, la bascule est censée conserver la dernière valeur écrite.
En remplaçant R par 0 dans l’équation (4.17), on obtient :

Q = ¬Q (4.19)

comme ci-dessus. En remplaçant S par 0 dans l’équation (4.18), on a :

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

F IGURE 4.16. – Une bascule avec horloge.

— 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

F IGURE 4.17. – Une bascule D.

S
E
I
I
E
S
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

F IGURE 4.18. – Un générateur de pulsation : le délai de propagation dans la porte « et » va


générer une sortie à vrai pendant un court instant (on suppose que le délai
des portes est d’une unité de temps).

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

F IGURE 4.19. – Le circuit logique d’une flip-flop.

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.

4.4.4. Un circuit de mémoire complet


Sur base des flip-flops, nous pouvons maintenant présenter un circuit qui réalise toutes
les fonctions d’une mémoire classique, à savoir : la lecture et l’écriture à une adresse don-
née. Nous nous concentrerons sur un exemple de petite dimension qui peut facilement être
étendu à des dimensions plus réalistes : une mémoire capable de stocker quatre valeurs (ou
quatre mots, ou quatre cases) de trois bits chacun. Cette mémoire admettra donc quatre
adresses différentes qui seront représentées sur deux bits. Plus précisément on souhaitera
les entrées suivantes :
— 3 entrées I 0 , I 1 et I 2 pour spécifier la valeur (sur 3 bits) à écrire ;
— 2 entrées A 0 et A 1 pour spécifier l’adresse (pour lire ou écrire). A 1 est le bit de poids fort
de l’adresse, et A 0 le bit de poids faible ;

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

F IGURE 4.21. – Mémoire : lecture du bit i parmi 4 adresses.

137
4. Leçons 7 à 9 – Niveau 0 : portes logiques

Ii

RD
D Q
(i , 0)
CS
Q

Ligne d’écriture à l’adresse 0

D Q
(i , 1)
Q

A0 Ligne d’écriture à l’adresse 1

D Q
(i , 2)
Q

Ligne d’écriture à l’adresse 2


A1

D Q
(i , 3)
Q

Ligne d’écriture à l’adresse 3

F IGURE 4.22. – Mémoire : écriture d’un bit i parmi 4 adresses.

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

F IGURE 4.23. – Une mémoire de 4 mots de 3 bits.


4. Leçons 7 à 9 – Niveau 0 : portes logiques

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

4.5.2. Exercices à faire sous Logisim


Pour tous ces exercices, des corrections sont disponibles sous forme de fichiers LogiSim
sur l’UV.

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.

4.5.3. Exercices supplémentaires


Ces exercices supplémentaires suggèrent de refaire, sous LogiSim, les circuits logiques étu-
diés dans ce chapitre.

Ex. 20 Réalisez les quatre circuits suivants :


1. Une mémoire de 4 mots d’un bit à l’aide de flip-flops dont les adresses sont codées cha-
cune par une ligne d’entrée et dans laquelle on peut lire uniquement. Cette mémoire a
donc 4 entrées (une pour chaque mot), qui servent à sélectionner le mot qu’on désire lire
(une entrée par mot) et une sortie qui permet d’obtenir la valeur lue dans le mot corres-
pondant (un seul bit). On ne peut activer qu’un seul mot à la fois : si la ligne i vaut 1, on
lit le mot i , pour i ∈ {0, 1, 2, 3}.
2. 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 écrire uniquement. Cette mé-
moire a donc 6 entrées : 4 pour les adresses (elles servent à sélectionner dans quel mot

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. 23 Réalisez le même exercice pour l’additionneur.

Ex. 24 Construisez, à l’aide de demi-additionneurs et additionneurs, un additionneur permet-


tant de sommer deux nombres quelconques de 3 bits. Votre circuit aura donc six bits d’entrée
(trois bits par nombre) et quatre bits de sortie (un bit par somme intermédiaire ainsi qu’un bit
supplémentaire pour l’éventuel dernier report).

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

On obtient ensuite la formule en prenant la disjonction de ces quatre formules :


¡ ¢ ¡ ¢ ¡ ¢ ¡ ¢
¬x 1 ∧ x 2 ∧ ¬x 3 ∨ x 1 ∧ ¬x 2 ∧ ¬x 3 ∨ x 1 ∧ ¬x 2 ∧ x 3 ∨ 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 :

x NOR x = ¬(x ∨ x) Définition du NOR


= ¬(x)

À 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 :

a ∧ b = ¬((¬a) ∨ (¬b)) (4.15)


= (¬a) NOR(¬b) Définition du NOR
= (a NOR a) NOR(b NOR b) x NOR x = ¬x

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.

a XOR b = (a ∧ ¬b) ∨ (¬a ∧ b) Définition du XOR


= ¬(¬a ∨ b) ∨ ¬(a ∨ ¬b) (4.15)
= (¬a NOR b) ∨ (a NOR ¬b) Définition du NOR
¡ ¢ ¡ ¢
= (a NOR a) NOR b ∨ a NOR(b NOR b) x NOR x = ¬x

Posons maintenant :

A = (a NOR a) NOR b
B = a NOR(b NOR b)

La dernière expression devient donc :


£ ¤ £ ¤
A ∨ B = A NOR B NOR A 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.

5.1. Le datapath du MIC-1


Le chemin des données de notre processeur hypothétique, le Mic-1, est représenté à la
Figure 5.2. Détaillons les éléments qui le composent.

5.1.1. Composants du Mic-1


Registres Le chemin des données du Mic-1 contient 8 registres 4 , nommés MAR, MDR, PC, IR,
SP, TOS, OPC et H. Tous ces registres font 32 bits, sauf IR qui fait 8 bits. Ces noms ont tous une
2. Ces détails sont d’ailleurs souvent gardés secrets par les fabricants. . .
3. Pour rappel, le chemin des données, ou datapath est l’ensemble constitué des registres et de l’ALU dans
un microprocesseur. Ces composants sont ceux qui manipulent les données et réalisent donc la sémantique des
instructions machine. Voir Section 3.2.2.
4. Notez qu’il y en a d’autres dans l’exemple original de TANENBAUM, que nous avons supprimé de l’exemple
par souci de simplification.

148
5.1. Le datapath du MIC-1

Source : Université de Manchester http://curation.cs.manchester.ac.uk/digital60/www.digital60.org/

Source : Bundesarchiv, B 145 Bild-F038812-0014 / Schaack, Lothar / CC-BY-SA 3.0 (https://commons.wikimedia.org/wiki/File:Bundesarchiv_B_145_Bild-F


038812-0014,_Wolfsburg,_VW_Autowerk.jpg), « Bundesarchiv B 145 Bild-F038812-0014, Wolfsburg, VW Autowerk », https://creativecommons.org/licenses
/by-sa/3.0/de/legalcode

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.

5.1.2. Cycle d’exécution


Maintenant que nous avons décrit les composants du Mic-1, intéressons nous à son fonc-
tionnement. Comme nous l’avons déjà expliqué, le comportement d’un chemin des données
est cyclique, et chaque cycle du Mic-1 peut être décomposé en quatre étapes successives :
(1) d’abord le contenu d’un registre est recopié sur le bus B pour alimenter l’entrée B de

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

Une microinstruction est la description du comportement du chemin des don-


nées durant un cycle. Plus précisément, une microinstruction contient cinq in-
formations :
1. l’identifiant du registre qui alimente le bus B ;
2. les opérations que doivent effectuer l’ALU et le Shifter ;
3. les registres qui reçoivent le résultat du bus C ;
4. l’éventuelle opération de lecture/écriture à initier avec la mémoire (Read,
Write, Fetch, ou rien) ;
5. l’identifiant de la prochaine microinstruction à exécuter.

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 :

opération registres ; mémoire ; saut

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 :

MDR = TOS = MDR+H ; wr ;

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.

5.2. Le langage machine IJVM


Comme expliqué dans l’introduction, ce langage est un langage fictif 8 , qui se base sur le
principe de la machine à pile (voir Section 3.2.4).

5.2.1. Instructions du langage


L’ouvrage d’A. TANENBAUM propose une présentation d’une large série d’instructions ma-
chine supportées par l’IJVM. Nous nous contenterons d’en regarder un ensemble restreint,
bien suffisant pour notre propos :
— NOP. Cette instruction ne fait rien. Il est parfois utile de disposer d’une telle instruction,
comme nous le verrons par la suite.
— BIPUSH v, où v est une valeur sur un octet. Push cette valeur au sommet de la pile. À
noter que le préfixe « BI » dans BIPUSH signifie Boolean or Integer : on peut en effet faire
un push des valeurs interprétées comme des booléens ou comme un entier (32 bits).
— POP. Cette instruction sans paramètre pop le sommet de la pile.
— IADD, ISUB. Ces instructions sans paramètre réalisent respectivement la somme et la
différence des deux valeurs au sommet de la pile, puis pop ces deux valeurs, et push le
résultat à leur place. Ici aussi le « I » signifie Integer.
— GOTO o. Cette instruction réalise un saut de o positions 9 dans le code. Le décalage o est
une valeur sur 2 octets. Remarquons que ce décalage peut être négatif 10 , afin de sauter

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.

Comme dans le reste de ce document, on n’écrira pas explicitement le décalage à effectuer


lors d’un GOTO ou d’un IFEQ, mais on utilisera des étiquettes (voir les exemples qui suivent).

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

Une exécution où le saut vers equ: n’est pas exécuté :


43
ISUB IFEQ equ POP BIPUSH 0 GOTO fin NOP
42 −−−→ 1 −−−−−−−→ 1 −−→ −−−−−−−→ 0 −−−−−−−→ 0 −−→ 0
... ... ... ... ... ... ...

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.

5.2.2. Représentation d’un programme IJVM en mémoire


Maintenant que nous avons fixé les instructions de l’IJVM, nous devons préciser comment
ces instructions sont représentées et stockées en mémoire. En effet, notre prochaine tâche
sera d’utiliser le microcode pour analyser et exécuter ces programmes, il faut donc fixer une
représentation, qui sera, naturellement, binaire.
Concrètement, toutes les instructions IJVM sont stockées sous la forme de :

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

11. Notez que le registre IR fait exactement un octet.

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

5.3. Implémentation de l’ijvm sur le MIC1


Voyons maintenant comment nous pouvons, à l’aide du microcode, exécuter l’IJVM à l’aide
du Mic-1. Notre tâche sera de produire, à l’aide d’un microprogramme, l’ensemble des fonc-
tionnalités du microprocesseur, à savoir, toute la boucle fetch–decode–execute (Section 3.2.3).
Concrètement, cela signifie que nous allons implémenter, en microcode, un interpréteur
pour l’IJVM. Nous avons déjà expliqué la notion d’interpréteur à la Section 1.3 : celui-ci lit, en
mémoire, les instructions (IJVM) une à une, les analyse et les exécute. Comme nous l’avons
déjà expliqué également, les registres PC et IR seront essentiels : PC donne l’adresse du début
de la prochaine instruction IJVM à exécuter, et IR sert à stocker une copie de cette instruction
(ou d’une partie de l’instruction, pour les instructions qui tiennent sur plusieurs octets).

5.3.1. Structure de l’interpréteur


Sur base de ces idées, nous pouvons déjà dégager la structure globale de l’interpréteur,
que nous présentons à la Figure 5.5 (notons que nous avons utilisé le caractère # pour dé-
limiter des commentaires). Cette première ébauche appelle déjà à plusieurs commentaires.
Tout d’abord, nous pouvons observer la structure générale : les premières lignes servent à lire
l’instruction en mémoire à l’adresse PC, à la charger, à l’aide d’un fetch dans IR, et à la « dé-
coder », ce qui se réduit, dans notre cas, à faire un goto vers la bonne partie du microcode.
Remarquons que nous incrémentons PC de 1, mais cela ne signifie pas pour autant qu’à la
fin de l’exécution de l’instruction machine, PC sera incrémenté de 1. En effet, une instruction
IJVM peut tenir sur plusieurs octets : si l’instruction à exécuter est, par exemple, BIPUSH v,
incrémenter PC de 1 dans les première lignes de l’interpréteur reviendra à faire pointer PC
vers le paramètre v. Le microcode du BIPUSH devra alors faire un nouveau fetch pour lire
cette valeur v, et incrémenter PC à nouveau.
Nous allons maintenant passer en revue les différentes parties de cet interpréteur et les
compléter.

160
5.3. Implémentation de l’ IJVM sur le MIC1

Main1 : ... ; fetch # lecture de l ’ opcode


PC = PC + 1; ...; goto (...) # saut en fonction
# de l ’ opcode
nop1 : ... # microcode du NOP
...; ...; goto ( Main1 )
iadd1 : ... # microcode du IADD
...
...; ...; goto ( Main1 )
isub1 : ... # microcode du ISUB
...
...; ...; goto ( Main1 )

# et ainsi de suite pour toutes les instructions IJVM

F IGURE 5.5. – Structure de l’interpréteur IJVM.

5.3.2. Lecture et décodage


Nous commençons par compléter les premières lignes de l’interpréteur. Une première ap-
proche consister à réaliser la lecture et le décodage de l’instruction en trois microinstruc-
tions :

0 fetch # Lecture à l ’ adresse PC


1 PC = PC + 1 # Incrément de PC
2 goto ( IR ) # Le résultat du fetch est dans IR
Nous insistons bien sur le fait qu’il s’agit là de trois microinstructions, qui demandent donc
trois cycles du chemin des données pour s’exécuter. Ceci est rendu nécessaire par le délai
de lecture en mémoire : la première microinstruction effectue le fetch, mais l’opcode poin-
tée par PC ne sera disponible dans IR que deux cycles plus tard. Le second cycle est utilisé
pour incrémenter PC, et le troisième effectue le décodage à proprement parler. Ce décodage
consiste à sauter (dans le microcode) à l’adresse qui est donnée par l’opcode. Cela explique
comment les opcodes ont été choisis : il s’agit simplement de l’adresse, dans le microcode,
de la première microinstruction réalisant l’instruction IJVM en question.
Malheureusement, la solution proposée est peu efficace. Comme nous le verrons, la plu-
part des instructions IJVM s’exécutent en trois cycles, et il n’est donc pas raisonnable de
consacrer trois cycles supplémentaires à la lecture et au décodage. Une solution simple consiste
à faire en sorte que l’instruction à exécuter soit déjà dans IR au moment où le microcode
revient à la ligne Main1:. Ainsi, il n’est pas nécessaire d’attendre deux cycles au début de
l’exécution d’une instruction IJVM, on peut directement sauter dans la bonne partie du mi-
crocode. On effectuera malgré tout un fetch au début du microcode, afin de lire, de manière
anticipée, ce qui suit l’opcode en mémoire : soit l’opcode de l’instruction suivante (ainsi, elle
sera bien chargée dans IR, comme prévu), soit le paramètre de l’instruction (et on gagnera
aussi du temps dans le microcode qui exécute l’instruction).

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

F IGURE 5.6. – Une illustration de la pile en mémoire, et des registres correspondants.

Le début de notre interpréteur peut maintenant tenir en une ligne :

0 Main1 : PC = PC + 1 ; fetch ; goto ( IR )

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.

5.3.3. Représentation de la pile en mémoire

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

— pour effectuer un Push, il faudra incrémenter SP ;

— 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

5.3.4. Microcode de l’interpréteur


Nous pouvons maintenant passer en revue l’implémentation sous forme de microcode des
différentes instructions IJVM. L’ensemble du microcode de l’interpréteur est donné à la Fi-
gure 5.7.

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 :

— MAR = SP = SP - 1; rd. Cette microinstruction décrémente le pointeur de stack SP,


place l’adresse du nouveau sommet dans MAR et lance une lecture (de la nouvelle valeur
au sommet).
— Un cycle vide est nécessaire, pour laisser le temps à la mémoire de répondre la valeur à
placer dans TOS.
— TOS = MDR; goto(Main1). Cette microinstruction écrit dans TOS la valeur répondue
par la mémoire (et donc placée dans MDR), puis revient au début de l’interpréteur.

163
5. Leçons 10 à 12 – La microarchitecture

0 Main1 : PC = PC + 1; fetch ; goto ( IR )


1 nop1 : goto ( Main1 )
2 iadd1 : MAR = SP = SP - 1; rd # lecture dans la pile
3 H = TOS
4 MDR = TOS = MDR + H ; wr ; goto ( Main1 )
5 isub1 : MAR = SP = SP - 1; rd # lecture dans la pile
6 H = TOS
7 MDR = TOS = MDR - H ; wr ; goto ( Main1 )
8 pop1 : MAR = SP = SP - 1; rd
9 # Cycle vide
10 TOS = MDR ; goto ( Main1 )
11 bipush1 : SP = MAR = SP + 1
12 PC = PC + 1; fetch # lecture du prochain opcode
13 MDR = TOS = IR ; wr ; goto ( Main1 )
14 goto1 : OPC = PC - 1
15 goto2 : PC = PC + 1; fetch # lecture du 2 eme octet de l ’ offset
16 H = IR << 8
17 H = IRU or H # reconstruction de l ’ offset
18 PC = OPC + H ; fetch
19 goto ( Main1 ) # permet le fetch
20 ifeq1 : MAR = SP = SP -1; rd # pour mettre à jour TOS
21 OPC = TOS # sauvegarde temporaire de TOS
22 TOS = MDR
23 Z = OPC ; if ( Z ) goto ( T ) else goto ( F )
24 T: OPC = PC - 1; fetch ; goto ( goto2 )
25 F: PC = PC + 1 # on saute l ’ opérande
26 PC = PC + 1; fetch
27 goto ( Main1 ) # permet le fetch

F IGURE 5.7. – L’interpréteur IJVM.

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.

Instruction GOTO Contrairement aux opérations précédentes, cette opération ne modifie et


ne consulte pas la pile. Pour rappel, la sémantique précise de GOTO o est de placer dans PC la
valeur a + o où a est l’adresse mémoire de l’opcode de GOTO o. Remarquez qu’au moment où
on atteint la ligne goto1:, PC contient la valeur a + 1, étant donné que PC est systématique-
ment incrémenté à la ligne Main1. Il faudra donc décrémenter PC pour retrouver l’adresse de
l’opcode.
Par ailleurs, l’instruction GOTO o tient en mémoire sur 3 octets (donc aux adresses a, a + 1
et a + 2), car le décalage o tient sur 2 octets. Or, la lecture du code IJVM se fait au travers du
registre IR qui ne peut lire qu’un octet à la fois. Il font donc lire successivement les deux
octets o 1 et o 2 composant la valeur o, et recomposer la valeur o, en faisant un décalage vers
la gauche de 8 bits de o 1 et en faisant un OR bit à bit entre cette valeur et o 2 :

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

— PC = PC + 1; fetch. Au moment d’exécuter cette microinstruction, la valeur o 1 est


dans IR (en raison du fetch de la ligne Main1:). On incrémente PC pour qu’il pointe
vers la valeur o 2 et on lance une lecture (fetch).
— H = IR << 8. On décale (à l’aide du circuit de décalage) la valeur o 1 (qui est encore
dans IR) de 8 bits vers la gauche et on place le résultat dans H.
— H = IRU OR H. On combine la valeur o 1 décalée de 8 bits (qui est dans H) avec la valeur
o 2 (qui est maintenant dans IR) à l’aide d’un OR. On utilise IRU eu lieu de IR pour
compléter correctement la valeur de IR sur 32 bits (avec des 0). Le registre H contient
maintenant l’opérande o reconstituée.
— PC = OPC + H; fetch. On calcule la nouvelle valeur de PC à l’aide de l’adresse du
GOTO (stockée dans OPC) et du décalage (calculé dans H). On lance une nouvelle lec-
ture, pour lire l’opcode de l’instruction machine suivante. Cela prend un cycle, ce qui
explique pourquoi nous effectuons le goto Main1 au cycle suivant
— goto Main1. Un cycle s’est écoulé, l’opcode de l’instruction suivante sera dans IR à la
fin de ce cycle. On revient au début de l’interpréteur.

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.

5.3.5. L’unité de contrôle du Mic-1


Afin de clôturer ce chapitre, nous allons décrire brièvement la partie manquante du Mic-
1, à savoir l’unité de contrôle. Dans le cas d’un processeur basé sur le microcode, l’unité de
contrôle doit faire en sorte que le chemin des données exécute le microprogramme. Pour ce
faire, l’unité de contrôle est composée de deux parties : le Control Store, qui est une mémoire
(morte) contenant le microprogramme, et le séquenceur, qui se charge de connecter les mi-
croinstructions successives au chemin des données. Nous en donnons maintenant un des-
cription brève, basée sur la Figure 5.8, qui représente le Mic-1 complet (chemin des données
et unité de contrôle).

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

5.3.6. Le Control Store


Le control store est une mémoire non-modifiable (morte), à l’intérieur du processeur, qui
contient l’ensemble du microprogramme. Expliquons maintenant comment les microins-
tructions sont représentées, en binaire 13 . Chaque microinstruction contient toute une série
d’informations commandant le comportement du chemin des données, à savoir :
— Quel registre est sélectionné pour écrire sur le bus B ? Comme il ne peut y avoir qu’un
registre à la fois qui écrit sur le bus B, et qu’il y a 7 possibilités (5 registres peuvent écrire
sur le bus B, dont IR qui peut émettre sur B de deux manière différentes), on n’a besoin
de que de 3 bits pour coder cette information.
— Quels registres reçoivent la valeur émise sur le bus C ? Comme il y a 7 candidats poten-
tiels, et que toute combinaison de ces 7 candidats est possible (puisqu’on peut écrire
dans plusieurs registres), on a besoin de 7 bits pour spécifier cela.
— Quelle(s) opération(s) l’ALU et le circuit de décalage effectuent-ils ? Il faut 6 bits pour
couvrir les instructions de l’ALU et 2 bits pour les instructions du shifter. Au total, cela
fait donc 8 bits.
— Va-t-il y avoir une communication avec la mémoire via les registres MAR et MDR ? Comme
il y a trois réponses possibles (pas de communication, lecture rd ou écriture wr), il faut
2 bits pour spécifier cette information.
— Va-t-il y avoir une communication avec la mémoire (un fetch) via les registres PC et
IR ? La réponse à cette question est binaire, il faut donc un seul bit pour cette informa-
tion.
D’après ce bilan, on a donc besoin de 21 bits pour encoder le comportement d’un seul cycle.
Mais ce n’est pas suffisant pour encoder une microinstruction ! En effet, une microinstruction
doit également préciser comment passer à la microinstruction suivante. Pour ce faire, nous
allons utiliser des bits supplémentaires :
— 5 bits pour indiquer l’adresse de la microinstruction suivante à exécuter, en cas de saut.
Notre microcode contient 29 microinstructions, il faut donc au moins 5 bits. Si on veut
avoir un microcode plus long, il faut évidemment étendre ces adresses.
— 2 bits pour indiquer comment la microinstruction instruction suivante est choisie. Dans
notre cas 14 , il n’y a que quatre possibilités : (1) passer à l’instruction physiquement sui-
vante ; (2) faire un saut inconditionnel (goto dans le microcode), à une adresse fixée,
donnée dans le champ précédent. C’est ce qui arrive quand nous faisons goto(Main1)
par exemple ; ou bien (3) faire un saut inconditionnel (goto) à une adresse donnée
par le registre IR. C’est ce que nous faisons dans la première ligne du microcode, par
exemple ; ou encore (4) faire un saut conditionnel, dépendant de la valeur de la sor-
tie Z de l’ALU (comme à la ligne 23). Deux bits sont donc suffisants pour coder ces 4
possibilités

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

F IGURE 5.9. – Le format binaire des microinstructions.

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

5.4. Pour aller plus loin. . .

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.

5.5.1. Comprendre l’effet des micro-instructions


Commençons par une série d’exercices pour comprendre le fonctionnement des micro-
instructions.
Pour rappel, une micro-instruction correspond à un cycle du data path. Les différentes
étapes du cycle sont expliqués à la Section 5.1.2 :
1. Sélectionner un registre pour alimenter le bus B.
2. Choisir l’opération que l’ALU va exécuter sur la valeur présente sur B et A (alimenté
par le registre H).
3. Écrire la valeur calculée par l’ALU dans un ou plusieurs registres de sortie.
4. Initier une lecture ou une écriture en mémoire (la mémoire est adressée par mot d’un
octet), en utilisant un des trois mécanismes suivants :

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) :

TOS SP MAR MDR H stack

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

Ex. 30 Quel va être l’effet de l’exécution de la séquence de micro-instruction suivantes ? Répon-


dez à la question en complétant le tableau de la Figure 5.10.

1 MAR = SP = SP - 1; rd
2 H = TOS
3 MDR = TOS = MDR + H ; wr

Ex. 31 À quelle instruction IJVM correspond la séquence de micro-instructions donnée ci-dessus ?

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

Étape TOS SP MAR MDR H stack

2004c : 5
1 5 8013 ?? ?? 0 20048 : 5
···

:
2 :
···

:
3 :
···

:
4 :
···

F IGURE 5.10. – Tableau à compléter.

173
5. Leçons 10 à 12 – La microarchitecture

5.5.2. Comprendre l’interpréteur


Pour rappel, le schéma de fonctionnement d’un interpréteur est le suivant :
1. Charger la première instruction machine dans un registre (ici IR) et mettre PC à 0.
2. Incrémenter PC, lancer la lecture en mémoire l’instruction machine pointée par PC.
Celle-ci n’apparaîtra dans IR qu’au point 4, en raison du temps nécessaire à la mémoire
pour faire la lecture et répondre
3. Déterminer la séquence de micro-instructions correspondant à l’instruction dans IR (il
ne s’agit donc pas encore de l’instruction dont on a lancé la lecture au point précédent)
4. Exécuter cette séquence
5. Aller en 2
Il s’agit donc d’une grande boucle (écrite en micro-code) qui analyse chaque instruction
(IJVM) l’une après l’autre et l’exécute (voir Figure 5.7). Notez bien cependant que l’interpré-
teur implémenté dans le simulateur est plus complet, car il admet d’autres instructions ma-
chine.

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

Ex. 34 Chargez ce programme dans le simulateur et observez le fonctionnement, micro-instruction


par micro-instruction. Essayez de prévoir l’effet de la micro-instruction avant d’avancer.

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 27 En hexadécimal : 0B 07 0B 05 1C 02 1C 02 02

Correction de l’exercice 30

Étape TOS SP MAR MDR H stack

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 31 Il s’agit d’un IADD.

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.

Remarque importante Il nous semble important de rappeler, en ce début de chapitre, la


convention prise à la Section 3, et discutée dans l’exemple de la page 81. Les instructions
machine sont représentées en mémoire, et manipulées par le processeur en binaire. Afin
d’améliorer la lisibilité du texte, nous ne représenterons évidemment pas ces instructions
en binaire, mais bien en utilisant la syntaxe de l’assembleur (qui est en fait au niveau 3, voir
Figure 1.5). Cette notation a, dans le cadre de ce chapitre, deux avantages non-négligeables :
1. les opérandes et les noms de registres sont notés avec une syntaxe textuelle. On note
ainsi mov pour l’instruction de copie, et on dénote les registres par leur noms eax, ebx,
etc. Par exemple : mov eax, ebx ;
2. on peut utiliser des étiquettes pour les sauts, comme nous l’avons fait dans les chapitres
précédents.

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.

6.1. Notion d’architecture


Quand on parle de processeurs, on entend souvent parler de la notion d’architecture : on
dira que tel ou tel processeur relève de l’architecture Intel x86, ou Sparc, ou ARM. Les com-
pilateurs gcc 3 ou clang 4 , qui sont les compilateurs standards pour le langage C sur Linux
ou MacOS, possèdent d’ailleurs un option ARCH, qui permet de préciser à quelle architecture
appartient le processeur sur lequel le programme compilé devra être exécuté. On ne spécifie
donc pas le modèle précis de microprocesseur visé, et on comprend donc qu’une architec-
ture est un ensemble de caractéristiques communes à une famille de microprocesseurs qui
garantissent la compatibilité entre eux.

Une architecture est un ensemble de caractéristique communes à une famille


de microprocesseurs, qui garantissent que les programmes sont exécutables sur
tous les microprocesseurs appartenant à cette architecture. De ce fait, une ar-
chitecture définit un modèle abstrait de l’ordinateur. Outre la syntaxe et la sé-
mantique du langage machine, ces caractéristiques recouvrent en général :
— le modèle mémoire : comment la mémoire est-elle organisée ;
— les registres du processeurs : combien y en a-t-il, de quelle capacité et pour
quel usage ;
— les types de données que le processeur peut manipuler.

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 :

— Il possède 5 registres de 8 bits : A, X, Y, SP et P ; et un registre de 16 bits : PC (il s’agit ici


des registres accessibles par le langage machine). PC sert de pointeur de programme,
A est l’accumulateur, et sert aux opérations arithmétiques et logiques, X et Y servent à
calculer des adresses, SP est le pointeur de stack et P est un registre de flags.

— 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 :

5. Firme américaine (Pennsylvanie, États-Unis d’Amérique), fondée en 1969 et dissoute en 2001.

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 :

1111 0000 1010 1010


+ 0000 0001 1111 0000
0000 0000 0000 0001 0000 1111 1010 1010

À noter qu’une même adresse physique peut correspondre à plusieurs


adresses dans des segments différents. Par exemple, les adresses 0000 : 00A0 et
000A : 0000 correspondent aux mêmes adresses physiques.
Le mode protégé fonctionne de manière similaire, sauf que les décalages dans
les segments tiennent maintenant sur 32 bits. Un segment peut donc faire jus-
qu’à 4Go, ce qui correspond à la taille de la mémoire physique (puisque l’i486 a
un bus d’adresse de 32 bits, il ne peut donc adresser que 232 octets). On pour-
rait donc se contenter de travailler toujours dans le segment 0, et les décalages
correspondent alors aux adresses physiques. L’avantage des segments est qu’ils
sont assortis d’un mécanisme de protection, qui permet, par exemple, de dé-
finir si un segment contient du code ou des données (le processeur interdira
alors d’exécuter du code depuis un segment de données), ou d’interdire à du
code contenu dans certains segments d’accéder à des données contenues dans
un autre. En plus de la segmentation, l’i486 propose également un mécanisme
de pagination. Il s’agit d’un autre mécanisme de gestion mémoire que nous étu-
dierons en détail au Chapitre 9, et qui est tout à fait transparent pour le langage
machine.

182
6.1. Notion d’architecture

Registres Comme nous l’avons vu dans le Chapitre 5, un microprocesseur peut utiliser de


nombreux registres, mais tous ne sont pas accessibles et manipulables par les instructions
machines. Dans l’exemple du chapitre précédent, le registres TOS est utilisé pour contrôler la
pile et ainsi exécuter les opérations arithmétiques, mais ne peut pas être manipulé directe-
ment par une instruction machine (cela n’aurait aucun sens).
Les processeurs possèdent typiquement deux types de registres :
— Les registres de travail qui servent à contenir des données pour effectuer des opéra-
tions. Ils sont parfois complètement interchangeables, et parfois ont des fonctions dé-
diées.
— Les registres de contrôle, qui contiennent des informations nécessaires au bon dérou-
lement du programme, qui ne sont pas des données.

ß
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 :

1 s = " hello world ! "


2 t = s . capitalize ()
3 print ( t )

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.

— des données numériques. On trouvera en général des entiers, signés en complément à


deux ; ou non-signés, mais toujours limités à la taille des registres : si on souhaite mani-
puler des données qui requièrent plus que la taille d’un registre, il faudra implémenter
l’opération à l’aide de plusieurs instructions machine. La plupart des processeurs mo-
dernes permettent également des calculs sur des nombres en virgule flottante selon le
standard IEE754 (Section 2.2.5). Enfin, certains processeurs supportent encore l’ancien
format BCD (pour Binary Coded Decimal), qui consiste à représenter, en binaire et sur
4 bits, chaque chiffre de la représentation en base 10 d’un nombre. Par exemple, 12310
sera représenté par 0001 0010 0011.

— 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

6.2. Instructions machine typiques


Le cœur du langage machine, c’est évidemment son jeu d’instructions. Comme nous l’avons
déjà dit, une instruction est en général caractérisée par un opcode, qui identifie de manière
unique l’instruction, et par une série d’opérandes (entre 0 et 3, en règle générale) qui décrit les
paramètres de l’opération. En fonction de la complexité du processeur, ce jeu d’instruction
peut être plus ou mains étendu

ß
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 :

Mouvements de données Ces instructions permettent de déplacer/copier des données


entre les registres et la mémoire. Sur de nombreuses architectures, seuls les mouvement de
registre à registre, ou de registre à mémoire sont permis : les mouvements d’un emplacement
mémoire vers un autre emplacement mémoire ne sont pas permis en une seule instruction
(il faut faire transiter les instructions par les registres), et ce, afin d’éviter des instructions trop
longues à exécuter.

ß
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

1 ; On suppose que eax , ebx et ecx contiennent a , b et c.


2 imul ebx , ebx ; élève ebx au carré
3 imul eax , ecx ; eax reçoit eax * ecx ( ac )
4 imul eax , 4 ; eax contient maintenant 4 ac
5 sub ebx , eax ; ebx reçoit ebx - eax

F IGURE 6.2. – Calcul de b 2 − 4ac en langage machine i486.

Instructions de saut, comparaison et branchement Dans certains cas, on ne souhaite


pas exécuter, comme instruction suivante, celle qui suit physiquement en mémoire. Les pro-
cesseurs possèdent des instructions de saut qui permettent de donner l’adresse de l’instruc-
tion suivante à exécuter. Ces instructions sont donc des modifications du pointeur d’instruc-
tion PC. Selon les cas, on peut modifier PC : (1) soit en lui assignant une adresse absolue, la
prochaine adresse exécutée sera donc celle dont l’adresse est donnée ; (2) soit en lui ajoutant
une valeur (potentiellement négative), comme nous avons fait dans l’IJVM. L’opérande donne
alors l’amplitude du saut (l’offset), qui est relatif à l’adresse actuelle de PC.
Ce type de saut est souvent décrit comme un saut inconditionnel. On dispose également de
sauts conditionnels, qui n’ont lieu que si une certaine condition est remplie. En règle général,
ce type de branchement s’effectue à l’aide de deux instructions successives :
1. une instruction de comparaison, qui va comparer deux valeurs et mettre à jour les flags
du processeur en fonction du résultat de la comparaison ;
2. une instruction de saut conditionnel à proprement parler, qui n’effectue le saut qu’à
certaines conditions

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

Appels de procédures Les processeurs possèdent également des d’instructions spéciales


pour gérer les appels de fonctions. En effet, les appels de fonction ne peuvent pas se résumer

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 :

F IGURE 6.3. – Un if en P YTHON et son équivalent en langage machine i486.

à 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

F IGURE 6.4. – Appels de procédure en P YTHON et un équivalent en langage machine i486.


On suppose que le paramètre de la fonction est placé dans le registre eax pour
simplifier l’exemple (en réalité, les paramètres sont, en général placés sur la
pile, eux aussi).

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.

récent au sommet de la pile ;


— à chaque retour, on saute vers la dernière adresse présent au sommet de la pile, que
l’on Pop.

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

Entrées/sorties Les processeurs doivent également disposer d’instructions spéciales pour


communiquer avec les périphériques. Nous n’entrons pas dans les détails.

190
6.3. Mécanismes de protection

6.3. Mécanismes de protection

Les processeurs modernes disposent de modes d’exécution, qui permettent d’empêcher


d’exécuter certaines instructions considérées comme privilégiées. Ces instructions sont celles
qui permettent d’accéder à des parties sensibles du système, comme les périphériques. Nous
verrons dans les sections suivantes (notamment dans la Partie V), que ces mécanismes sont
essentiels au bon fonctionnement des ordinateurs tels que nous les connaissons aujourd’hui,
avec les systèmes d’exploitation (Linux, MacOS, Windows) permettant d’effectuer de mul-
tiples tâches en même temps. En effet, quand l’ordinateur exécute plusieurs programmes
en même temps, on ne peut plus confier aux programmes la responsabilité d’écrire, par
exemple, directement sur le disque, car un programme pourrait ainsi détruire (de manière
volontaire ou non) les données d’un autre programme. Dans ces systèmes, il faut un arbitre :
ce sera le système d’exploitation. Il gérera le disque et les autres ressources de manière équi-
table entre les programmes en cours d’exécution, et le mécanisme de protection introduit
par le mode d’exécution (au niveau matériel, dans le CPU) empêchera les programmes de
contourner cet arbitre.

On peut résumer les idées de ce système de protection en considérant un modèle simple :

Les instructions machine sont réparties en deux classes :


1. les instructions privilégiées, qui sont considérées comme « sensibles ». Ce
sont typiquement les instructions qui modifient le statut du processeur
ou accèdent aux périphériques ;
2. les instructions non-privilégiées, comme les instructions arithmétiques,
ou les instructions d’écriture en mémoire primaire.
Le processeur, quant à lui, peut fonctionner sous deux modes (stocké en général
dans un registre d’état) :
1. le mode maître ou superviseur, dans lequel il peut exécuter toutes les ins-
tructions (y compris les instructions privilégiées) ; et
2. le mode esclave ou utilisateur, dans lequel il n’exécute que les instructions
non-privilégiées, et déclenche une erreur si on cherche à exécuter une ins-
truction privilégiée (cela sera typiquement détecté lors du décodage et de
l’analyse de l’instruction machine).

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

6.4. Représentation des instructions et des opérandes


Maintenant que nous avons une bonne idée du type d’instructions que nous nous atten-
dons à trouver dans un langage machine, nous allons voir comment réaliser cela en pratique.
Les principes généraux exposés dans les sections précédentes appellent en effet plusieurs
commentaires qui montrent que leur mise en pratique peut poser certaines difficultés.
Pour ce faire, considérons l’exemple d’un hypothétique processeur 32 bits, et supposons
que nous souhaitions doter son langage machine d’une instruction de saut vers une adresse
fixée, cette adresse étant elle-même sur 32 bits afin de pouvoir accéder à n’importe quelle
case mémoire. Dans son entièreté, cette instruction devra donc tenir sur plus de 32 bits,
étant donné qu’il faut au moins un bit pour l’opcode. Nous sommes donc immédiatement
confronté à un choix :
— soit nous insistons sur le fait que l’adresse du saut doit tenir sur 32 bits. Dans ce cas, il
faudra faire au moins deux lectures successives en mémoire pour lire toute l’instruction
(une pour l’adresse et une pour l’opcode) ;
— soit nous souhaitons que toutes les instructions tiennent sur 32 bits, afin de ne pas
devoir faire plusieurs lectures en mémoire. Dans ce cas, nous devons abandonner l’idée
que notre adresse de saut tienne sur 32 bits. Se pose alors la question de savoir combien
de bits (sur les 32) consacrer à l’opcode, afin d’avoir un bon compromis entre le nombre
d’opcodes et la taille des opérandes.
Pour répondre à ces problèmes, nous allons étudier deux (familles de) techniques : les op-
codes à taille variable, et les techniques d’adressage.

6.4.1. Opcodes à taille variable


La technique des opcodes à taille variable permet de répondre à la question de savoir com-
bien de bits consacrer à l’opcode dans une représentation des instructions à taille fixée. Re-
prenons notre exemple d’un processeur 32 bits. Si nous décidons de consacrer très peu de bit
(par exemple, 2) à l’opcode, nous aurons beaucoup de place restant (30 bits) pour les opé-
randes, mais cela ne nous permet d’avoir que 22 = 4 opcodes. On peut évidemment choisir
d’augmenter la taille de l’opcode, mais on perd alors de la place pour les opérandes :

Taille de l’opcode (bits) Nombre d’opcodes Taille des opérandes (bits)


2 4 30
8 256 24
16 65536 16
... ... ...

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

Supposons qu’on ait un opcode o 1 = 1010 1010 et un autre opcode o 2 =


ß 1010 1010 1111 1111. Cela risque de poser un problème de décodage au pro-
cesseur. En effet, les huit bits de poids fort des opérandes de l’instruction o 1
pourraient bien être les huit bits de poids faible d’o 2 , soit : 1111 1111. Si mainte-
nant le processeur lit en mémoire un mot de la forme :

31 24 23 16 15 8 7 0
1010 1010 1111 1111 ... ...

comment peut-il faire la différence entre l’opcode o 1 suivie d’un paramètre


commençant par 1111 1111 et l’opcode o 2 ?

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.

Regardons maintenant comment ces idées sont appliquées en pratique.

ß
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

1 mov eax , 42 ; adressage immédiat


2 ; eax contient 42
3 mov eax , [42] ; adressage direct
4 ; eax contient la valeur à l ’ adresse 42
5 mov eax , ebx ; adressage par registre
6 ; la valeur d ’ ebx est copiée dans eax
7 mov eax , [ ebx ] ; adresse par registre avec indirection
8 ; eax contient la valeur à
9 ; l ’ adresse donnée par ebx

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

où R 1 et R 2 sont des registres au choix, f est un facteur multiplicatif qui peut


être 1, 2, 4 ou 8, et C est une adresse de base constante.
Si on souhaite parcourir un tableau dont les éléments font chacun 4 octets, par
exemple, on peut utiliser R 1 +C pour fixer l’adresse de base (éventuellement en
mettant R 1 ou C à 0), f à 4, et compter le nombre d’éléments dans R 2 .

M8N

8. Pensez aux listes de P YTHON. . .

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.

6.5.1. Langage x86 32 bits


Nous commençons par expliquer plus en détail l’architecture x86 32 bits et le langage ma-
chine associé.

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.

6.5.2. Exemple : Branchement conditionnel en assembleur x86


Nous commençons par étudier un exemple de structure if . . . else . . . en langage d’assem-
blage (voir aussi l’exemple de la Figure 6.3). Supposons qu’on veuille réaliser le code Python
suivant (où on a remplacé les variables par des registres) :

1 if ebx == ecx :
2 edx = ecx
3 else :
4 ecx = edx
Voici son équivalent en assembleur x86 :

1 if : cmp ecx , ebx ; compare ebx avec ecx , modifie flags


2 ; ( Z =1 si égaux )
3 jnz else ; saut à l ’ étiquette " else " si Z !=1
4 mov edx , ecx ; branche " if " : copie ecx dans edx
5 jmp end ; fin de la branche " if " , on saute à la fin
6 else : mov ecx , edx ; branche " else " : copie edx dans ecx
7 end : hlt ; fin du programme
Notons l’utilisation d’étiquettes (dans l’exemple, if:, else: et end:) pour préciser où doivent
être faits les sauts 9

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

TABLE 6.1. – Quelques instructions du langage d’assemblage x86 32 bits.

199
6. Leçon 13 – Langage machine

6.5.3. Exemple : Boucles en assembleur x86


Nous étudions maintenant un second exemple qui montre comment réaliser une boucle
en assembleur x86
1 for ebx in range (6):
2 ecx = ecx + 1
Voici l’équivalent en assembleur x86 :
1 mov ebx ,0 ; ebx va servir d ’ indice de boucle
2 loop : cmp ebx ,6 ; compare eax avec 6 ( Z =1 si égaux )
3 jnz end ; si ebx est égal à 6 , on a fini
4 ; et on sort de la boucle
5 inc ecx ; incrémente ecx
6 inc ebx ; incrémente ebx ( l ’ indice de la boucle )
7 jmp loop ; saut à l ’ étiquette loop , on boucle
8 end : hlt ; fin du programme
Notons (1) que nous avons utilisé un saut inconditionnel (jmp) à la fin du corps de la boucle
pour forcer le programme à remonter au code qui compare le registre ebx à 6 ; et (2) que
nous aurions également pu utiliser le registre ecx comme compteur de boucle et l’instruction
spécifique JecxZ qui permet d’effectuer un saut lorsque le registre ecx est à zéro.

Ex. 36 Comme pour l’exemple précédent, recopiez le code ci-dessous dans SASM et observez-en
l’effet.

6.5.4. Autres exercices


Ex. 37 Écrivez un programme qui place une valeur (au choix) dans le registre eax, une autre
(au choix) dans le registre ebx et finalement leur somme dans le registre eax.

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.

Ex. 44 On considère le programme suivant :


1 mov eax , 20 ; Initialisation
2 mov ebx , 2
3 mov ecx , 3
4 ; Boucle
5 label1 : cmp ecx , 00000000 h
6 jz label2
7 sub eax , ebx
8 dec ecx
9 jmp label1
10 label2 : hlt
Combien de fois ce code visitera-t-il la ligne label1: ? Quelles sont les valeurs dans les registres
eax, ebx et ecx à chaque passage par la ligne label1: ? Vérifiez vos réponses dans SASM.

⋆ Ex. 45 Supposons qu’on a un vecteur de 8 valeurs 32 bits stockées de manière consécutive en


mémoire à partir de l’adresse V. Écrivez un programme qui parcourt ce vecteur et place dans
eax la valeur 1 si tous les éléments du vecteur sont pairs, et 0 sinon.

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

1 mov eax , 0 x732 ; valeur arbitraire pour tester


2 ; le programme
3 mov ebx , 0 x1
4 xor eax , ebx
5 and eax , ebx
6 ret

Correction de l’exercice 41

1 mov eax , 733 ; Valeur arbitraire


2 ; pour tester le programme
3 mov ebx , 0 x7
4 and eax , ebx
5 mov ebx , eax
6 ret

Correction de l’exercice 42

1 mov eax , 3 ; valeurs arbitraires


2 mov ebx , 1 ; pour tester
3 mov ecx , 2 ; le programme
4
5 cmp eax , ebx
6 jbe saut1
7 xchg eax , ebx
8 saut1 :
9 cmp eax , ecx
10 jbe saut2
11 xchg eax , ecx
12 saut2 :
13 cmp ebx , ecx
14 jbe saut3
15 xchg ebx , ecx
16 saut3 :
17 ret

Correction de l’exercice 43

1 ; On commence par placer des valeurs


2 ; arbitraires dans les 4 registres pour tester
3 ; le programme.
4 mov edx , 0 x10
5 mov eax , 0 x0
6 mov ebx , 0 x0
7 mov ecx , 0 x1
8
9 ; La soustraction se fait à l ’ aide de sub et sbb
10 sub eax , ecx ; bits de poids faible
11 sbb edx , ebx ; bits de poids fort en utilisant
12 ; l ’ emprunt du sub précédant.

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

eax ebx ecx


1 20 2 3
2 18 2 2
3 16 2 1
4 14 2 0

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.

7.1. Exemple introductif


Pour illustrer le mécanisme et son intérêt, nous considérons le cas où le processeur doit in-
terroger un périphérique pour en obtenir des données. Par exemple, le cas où le processeur
demande à la mémoire secondaire de lire un ensemble important de données (le contenu
d’un fichier), à stocker en mémoire pour traitement ultérieur. On peut alors envisager plu-
sieurs scénarios, illustrés à la Figure 7.1.
Un premier scénario correspond au cas où le processeur :
1. envoie l’ordre de lecture au périphérique ;
2. attend que le périphérique ait fini sa lecture. Le périphérique envoie alors un bloc de
données au processeur à travers le bus ;
3. place le bloc de données reçu sur le bus en mémoire, puis recommence au point 1 pour
obtenir le bloc de données suivant.

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’

F IGURE 7.1. – Lecture sur périphérique : sans DMA et sans interruption

À 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

F IGURE 7.2. – Lecture sur périphérique, avec le mécanisme d’interruption.

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.

7.2. Déroulement d’une interruption


La déroulement d’une interruption peut être schématisé comme suit :
1. Le périphérique (ou son contrôleur) qui désire déclencher une interruption le signale
au processeur à travers le bus. On parle alors de demande d’interruption ou IRQ (pour
interrupt request).
2. Le CPU détecte ce signal. Cela doit se faire le plus tôt possible après l’IRQ. Le CPU va
donc tester la présence d’une demande d’interruption à chaque tour de la boucle d’in-
terprétation. Si une telle demande existe, Le CPU détermine la source de l’interruption
car il peut y avoir plusieurs périphériques qui sont capables de déclencher une IRQ, et
le traitement n’est pas forcément le même. Comme nous le verrons plus loin, le CPU
lui-même peut avoir déclenché une demande d’interruption.
3. Le CPU sauvegarde PC et certains registres (les registres de contrôle essentiellement)
sur le stack, car il va arrêter d’exécuter le programme en cours et commencer à exécu-
ter le gestionnaire d’interruption. En général, on choisira également de passer la ma-
chine en mode maître à ce moment-là, puisque les interruptions servent en général à
communiquer avec les périphériques, ce qui requiert le mode maître.
4. Le CPU modifie le registre PC de manière à ce qu’il pointe vers la première instruction
du gestionnaire d’interruption. Cette adresse de début du gestionnaire est en géné-
ral appelée un vecteur d’interruption. Les gestionnaires d’interruption sont des petits
programmes machine qui font partie du système d’exploitation, et qui sont en général
chargés en mémoire au moment du démarrage. Le vecteur d’interruption est obtenu à
l’aide d’une table que le CPU connaît et qui doit être, elle aussi, chargée en mémoire
(son adresse de début est en général stockée dans un registre dédié). Chaque case de la
table donne le vecteur d’interruption correspondant à un type d’interruption donné.

207
7. Leçon 14 – Le mécanisme d’interruption

Ensuite, le gestionnaire d’interruption pourra être exécuté. Il devra :


1. sauvegarder les autres registres (de travail) si nécessaire ;
2. traiter l’interruption ;
3. restaurer les registres de travail sauvés (une fois le traitement terminé) ;
4. restaurer le registre PC, les registres de contrôle et remettre la machine en mode es-
clave.
Les deux derniers points, qui constituent le retour de l’interruption, sont en général réalisés
par une instruction machine dédiée, pour des raisons qui deviendront claires par la suite.

En résumé, le mécanisme d’interruption permet à un périphérique de deman-


der au processeur de s’interrompre temporairement dans son traitement en
cours, pour exécuter un gestionnaire d’interruption, qui est un programme ma-
chine faisant partie du système d’exploitation. L’adresse de début du gestion-
naire est appelé le vecteur d’interruption. Une fois le gestionnaire terminé (et
donc, la demande traitée), le processeur reprend sont exécution de manière
transparente pour le programme interrompu.

7.2.1. Interruption interruptibles ?


Comme les gestionnaires d’interruption sont des programmes en langage machine, ils sont
également susceptibles d’être interrompus par une nouvelle demande d’interruption. . . C’est
une situation que nous souhaitons éviter (afin de ne pas avoir à stocker une pile d’adresses
de retour). Pour ce faire, nous allons supposer, dans notre modèle, que la machine n’est pas
interruptible tant qu’elle exécute un gestionnaire d’interruption. Concrètement, nous allons
supposer que le mode maître est un mode dans lequel le processeur ne peut pas être inter-
rompu ; cela ne peut arriver qu’en mode esclave. Comme nous le verrons vers à la fin du
chapitre (Section 7.4), il s’agit d’une simplification, mais un tel modèle est déjà suffisant pour
bien comprendre 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.

7.2.2. Boucle d’interprétation modifiée


Voyons maintenant comment réaliser ce mécanisme d’interruption. Nous allons modi-
fier la boucle d’interprétation du processeur (que nous avions présentée à la Figure 3.8, Sec-
tion 3.2), afin que le processeur puisse être interrompu entre deux instructions machine. La
boucle d’interprétation modifiée, qui suit la description donnée plus haut est présentée à la
Figure 7.3. Dans ce modèle, nous avons supposé que le processeur dispose d’un flag IRQ, qui

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.

7.2.3. Retour d’une interruption


Que se passe-t-il une fois que le gestionnaire d’interruption est terminé ? Il faut avoir la
possibilité de revenir au programme qui a été interrompu, de manière transparente, c’est-à-
dire sans que l’exécution future de ce dernier ne soit affecté par l’exécution du gestionnaire 2 .
Le retour au programme interrompu se fera de manière logicielle, c’est-à-dire qu’il y aura
typiquement une instruction machine qui aura pour effet :

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

Sauver PC et les registres d’état


NON

Passer en mode
maître / non-interruptible

IR ← M[PC] PC ← vecteur d’interruption i

PC ← PC+1

Analyser l’instruction dans IR

Exécuter l’instruction dans IR

F IGURE 7.3. – La boucle d’interprétation du CPU avec le mécanisme d’interruption.

210
7.2. Déroulement d’une interruption

1. de restaurer les registres de contrôle, en particulier PC ; et


2. de remettre la machine en mode esclave et interruptible, sans quoi la protection offerte
par les modes serait compromise.
Observons que, dans notre modèle, une seule instruction machine doit effectuer ces deux
actions 3 . Cela peut sembler contre-intuitif, car on essaye en général de garder le langage
machine aussi simple que possible. Mais c’est une nécessité pour le bon fonctionnement
du mécanisme ! En effet, supposons que le gestionnaire d’interruption se termine par deux
instructions machine successives :
1. restaurer PC (et les registres de contrôle)
2. remettre la machine en mode esclave
Dans ce cas, la restauration de PC aura pour effet que la prochaine instruction machine exé-
cutée sera cella pointée par PC (comme toujours dans la boucle d’interprétation), soit l’ins-
truction du programme interrompu. . . La seconde instruction machine n’est donc pas exé-
cutée. À vrai dire, la première de ces deux instructions est en fait un saut dans le programme
interrompu ; qui se retrouve promu en mode maître !
Une autre possibilité consiste à exécuter ces mêmes deux instructions machine dans l’ordre
inverse :
1. remettre la machine en mode esclave
2. restaurer PC (et les registres de contrôle)
Malheureusement, dans ce scénario, nous avons un autre problème qui surgit : une fois la
première instruction machine exécutée (remise en mode esclave et, donc, interruptible), la
boucle d’interprétation du processeur repasse par le test. Si par malheur une demande d’in-
terruption a eu lieu durant l’exécution du gestionnaire, le processeur sera à nouveau inter-
rompu sans qu’on soit repassé par le programme qui a été interrompu. Le phénomène peut
évidemment se répéter à la fin du nouveau gestionnaire d’interruption, et on peut ainsi se re-
trouver avec un programme interrompu par un gestionnaire, lui-même interrompu par une
autre gestionnaire, etc ad libitum. . . Tout cela complique sérieusement la gestion des sauve-
gardes des valeurs des registres (dont PC), puisqu’il faut maintenant retenir la séquence des
valeurs à restaurer pour espérer un jour revenir au programme interrompu.
Puisqu’aucune des deux solutions n’est praticable, on conclut qu’il est nécessaire que les
deux opérations (remettre la machine en mode esclave/interruptible et restaurer PC) se fassent
de manière atomique.

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.

Finalement, l’exécution de l’IRET à l’adresse 2052 restaure PC et remet la ma-


... chine en mode esclave. Nous nous retrouvons a lors dans la situation de la Fi-
gure 7.9. Notons bien qu’à part le contenu d’IR (qui sera de toute façon écrasée
par le fetch de l’instruction à l’adresse 257), la situation est identique à celle de
la Figure 7.4. Le programme utilisateur peut donc continuer son exécution de
manière transparente (à condition évidemment qu’aucune autre demande d’in-
terruption ne soit apparue durant l’exécution du gestionnaire d’interruption).

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

F IGURE 7.6. – Exemple d’interruption (3). Situation après l’exécution de l’instruction à


l’adresse 2048.

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

7.3. Applications du mécanisme d’interruption


Il existe de nombreux cas où le mécanisme d’interruption est utile. Voici une liste des prin-
cipales applications :
Périphériques Comme nous l’avons déjà expliqué dans l’exemple introductif, les interrup-
tions peuvent être utilisées pour permettre aux périphériques de signaler au processeur
qu’une information doit être traitée. C’est ce qui arrive par exemple quand un disque
a fini une lecture, ou quand on presse une touche du clavier (l’identifiant des touches
pressées sont placées dans une mémoire tampon), ou encore quand on « clique » un
bouton de la souris. . . Cette famille d’interruptions provient donc d’une demande qui
est extérieure au processeur.
Exceptions Une exception est une interruption qui est déclenchée par le processeur lui-
même, lors de l’exécution d’une instruction. Les exceptions peuvent avoir lieu pour
plusieurs raisons : soit parce que l’instruction a causé une erreur, comme, par exemple,
une division par zéro, un dépassement de capacité ou un opcode invalide.
Défaut de page Nous verrons plus tard (Chapitre 9) que le défaut de page (page fault en
anglais) est une exception qui est essentielle pour le mécanisme de pagination, qui est
le système standard de gestion de la mémoire sur les systèmes d’exploitation actuels.
Appels superviseurs Ce mécanisme permet à des programmes s’exécutant en mode esclave
de demander à faire exécuter des instructions privilégiées (par exemple, la lecture sur
un périphérique) par le système d’exploitation. Nous en reparlerons dans le Chapitre 8.

7.4. Extensions possibles


Les principes que nous avons présenté ici permettent de réaliser un mécanisme d’inter-
ruption fonctionnel et simple. Mais nous pouvons en envisager certaines extensions.

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.

7.5. Cas de l’Intel 486DX


Nous terminons ce chapitre en expliquant brièvement comment ce mécanisme est réalisé
sur notre processeur d’exemple, l’Intel 486DX. Les détails peuvent être trouvés dans le guide
du programmeur [12, Chapitre 9].
Les interruptions sur l’i486DX sont classifiées en deux catégories : les interruptions à pro-
prement parler, et les exceptions.
Interruptions Sur l’i486DX, les interruptions proviennent d’événements qui sont extérieurs
au processeur. Elles sont de deux natures : les interruptions masquables (que l’on peut
donc choisir d’ignorer) et les interruptions non-masquables.
Exceptions Les exceptions sont des conditions qui sont détectées par le processeur (elles ne
proviennent pas d’une demande extérieure). L’i486DX reconnaît trois types d’excep-
tions : les faults, les traps et les aborts.
Chaque interruption et chaque exception possède un numéro allant de 0 à 255, inclus.
Étudions maintenant ces deux cas de figure plus détail.

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) :

Programme principal Gest. Interruption 4 Gest. Interruption 2


74 add R1 , R2 136 sub R1 , 6 270 mov R1 , 42
75 add R1 , R2 137 mov R2 , R1 271 sub R1 , R2
76 jmp 88 138 iret 272 iret

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

136 30 20 PC = 75 R1= 30 R2= 20 Maître / Non-interruptible


137 24 20 idem Maître / Non-interruptible
138 24 24 idem Maître / Non-interruptible

270 30 20 idem Maître / Non-interruptible


271 42 20 idem Maître / Non-interruptible
272 22 20 idem Maître / Non-interruptible

75 30 20 Vide Esclave / Interruptible


76 50 20 Vide Esclave / Interruptible
88 50 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

136 30 20 PC = 75 R1= 30 R2= 20 Maître / Interruptible

PC = 137 R1= 24 R2= 20


270 24 20 Maître / Non-interruptible
PC = 75 R1= 30 R2= 20
271 42 20 idem Maître / Non-interruptible
272 22 20 idem Maître / Non-interruptible

137 24 20 PC = 75 R1= 30 R2= 20 Maître / Interruptible


138 24 24 idem Maître / Interruptible

75 30 20 Vide Esclave / Interruptible


76 50 20 Vide Esclave / Interruptible
88 50 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

8.1. Nécessité d’un système d’exploitation


Dans toute la discussion que nous avons eue jusqu’à présent, nous avons supposé que
l’ordinateur exécute un et un seul programme provenant d’un et un seul utilisateur, et nous
avons volontairement éludé les problèmes qui pourraient survenir si on décidait d’exécuter
plusieurs programmes appartenant à plusieurs utilisateurs (qui ont chacun leurs données et
leurs caractéristiques propres) sur la même machine.
Historiquement, c’est ainsi qu’on se servait des premiers ordinateurs : l’utilisateur réservait
une plage horaire (mettons entre 10h et 11h), et se présentait au « centre de calcul » avec
un paquet de cartes perforées, ou un ruban perforé, pour faire exécuter son programme sur
l’ordinateur. Après avoir chargé son programme dans la mémoire de l’ordinateur, l’utilisateur
avait accès à toutes les ressources de l’ordinateur (toute la mémoire primaire, par exemple)
pour la période convenue. Une fois le programme terminé, les résultats étaient typiquement
imprimés ou sauvegardés sur des nouvelles cartes perforées, pour permettre un usage futur.
Une fois la session de l’utilisateur terminée, celui-ci récupérait tout son matériel, et toute
trace de son passage était effacé des mémoires de l’ordinateur, pour permettre à l’utilisateur
suivant d’utiliser une machine « comme neuve ».
Gardons cette hypothèse qu’il n’y a qu’un seul programme, provenant d’un seul utilisateur
qui s’exécute à la fois, et tentons d’en bien comprendre les implications :
— Le processeur n’exécute qu’un seul programme, du début jusqu’à la fin. Il n’est pas
possible de faire exécuter plusieurs programmes en même temps, comme on en a l’ha-
bitude sur les ordinateurs modernes (ce qu’on appelle du multitâche, nous y revien-
drons) ;
— Aussi bien la mémoire primaire que la mémoire secondaire n’ont d’autre structure que
leur découpe en cases de taille fixée, ayant chacune une adresse. Ainsi, pour la mémoire
secondaire, le programme ne peut rien faire d’autre que d’exécuter des instructions in-
diquant « écrire à telle adresse » ou « lire à telle adresse ». Il n’y a pas de notion de fichier,
ou de répertoire : si l’utilisateur souhaite en utiliser, il doit programmer lui-même une
traduction d’instructions comme « écrire dans le fichier à l’adresse quarante-deux » en
« écrire telles données à telles adresses de la mémoire secondaire ».
— Le programme peut exécuter toutes les instructions, privilégiées ou non (Section 6.3),
et a ainsi un accès total à tous les périphériques. En particulier, si on souhaite un pro-
gramme interactif (qui accepte des entrées au clavier ou à la souris durant son exécu-
tion, par exemple), il faudra ajouter au programme des routines qui vont, de manière
régulière, interroger ces périphériques pour vérifier si une interaction a eu lieu avec
l’utilisateur (touche du clavier pressée, souris déplacée, etc).

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) ?

Typiquement, nous souhaiterions que nos programmes accèdent à la mémoire secon-


daire à travers une structure organisée en répertoires et en fichiers, et que ces réper-
toires et fichiers soient dotés d’un système de permissions afin d’éviter qu’un programme
ou un utilisateur tierce ne vienne corrompre certaines données.

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

1 # Création d ’ un fichier et écriture d ’ une ligne de texte


2 f = open ( " / home / alanturing / src / hello . txt " , " a " )
3 f . write ( " Hello , World ! " )
4 f . close ()
5
6 # Lecture de ce texte et affichage
7 f = open ( " / home / alanturing / src / hello . txt " , " r " )
8 print ( f . read ())

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.

8.2. Principe général de fonctionnement

Le système d’exploitation (Operating System ou OS en anglais) est un composant logiciel


d’un système informatique qui a pour but de gérer, de manière équitable et sûre, les res-
sources matérielles du système. Le système d’exploitation a donc un rôle d’arbitre entre les
différents programmes des différents utilisateurs qui sont en concurrence pour l’utilisation
des ressources (mémoire, temps processeur, etc) de la machine. Pour ce faire, il est le premier
logiciel qui se lance au démarrage d’un système informatique.
Le système d’exploitation constitue en réalité une interface entre le matériel et les applica-
tions qui s’exécutent sur l’ordinateur. Cela signifie que tout accès aux ressources matérielles
nécessaires au bon fonctionnement d’une application devra se faire par le biais du système
d’exploitation, comme illustré à la Figure 8.2.
Sur cette figure, nous voyons que les logiciels qui souhaitent faire appel au matériel doivent
le faire via le système d’exploitation : aucune requête ne peut être envoyée directement au
matériel. Au contraire, le logiciel envoie sa demande à l’OS en utilisant le mécanisme de
l’appel système (que nous détaillons ci-dessous). Cette demande est analysée par l’OS pour
vérifier qu’elle est légitime, et traduite de manière à pouvoir être effectuée par le matériel.
L’OS peut alors exécuter les instructions privilégiées nécessaires pour faire exécuter la requête
par le matériel. Ensuite, les éventuelles réponses du matériel seront, elles aussi, analysées et
traduites par l’OS avant d’être renvoyées au logiciel. Rendons ce principe plus concret à l’aide
d’un exemple, avant d’analyser ces éléments en détail :

229
8. Leçon 15 – Le système d’exploitation

Logiciel (traitement de texte, navigateur Web. . . )

Mode esclave
Appel Système Réponse Mode maître

Requête Analyse, Système d’exploitation Analyse,


traduction (noyau de l’OS) traduction

Requête Réponse

Matériel (mémoire secondaire, écran, clavier,. . . )

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

... 3. Une fois l’écriture effectuée, la mémoire secondaire va renvoyer un mes-


sage au processeur (typiquement, à l’aide d’une interruption). Ce mes-
sage peut être une confirmation que l’écriture s’est bien passée, ou bien
un message d’erreur. La forme de ces messages peut différer en fonc-
tion du périphérique de mémoire secondaire (par exemple, s’il s’agit d’un
disque dur, ou d’un lecteur optique. . . ) ; et l’OS va donc traduire ce mes-
sage d’erreur dans une forme appropriée et standardisée pour le logiciel.
Par exemple, avec l’OS GNU/Linux, une écriture dans un fichier renvoie
la valeur -1 s’il y a eu une erreur, et le nombre d’octets effectivement écrits
dans le cas contraire.
4. La réponse, traduite par l’OS, est envoyée au logiciel qui a déclenché l’ap-
pel système.

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

8.2.1. Mécanismes de protection et de l’appel système


Pour que le mécanisme présenté à la Figure 8.2 fonctionne entièrement, il est important
que les programmes utilisateurs accèdent au matériel à travers l’OS uniquement, et donc
qu’ils ne puissent pas le contourner 3 . Pour ce faire, on se base sur les modes offerts par le
CPU, que nous avons étudiés à la section 6.3. Concrètement, et en supposant que le CPU
possède un mode maître (permettant d’exécuter toutes les instructions, y compris les ins-
tructions privilégiées qui donnent accès au matériel), et un mode esclave (permettant de
n’exécuter que les instructions non-privilégiées), le mécanisme de protection de l’OS est im-
plémenté comme suit :
1. L’OS est en général divisé en deux parties : le noyau (ou kernel en anglais) qui regroupe
les fonctionnalités les plus critiques de l’OS, et en particulier tout le code machine qui
interagit directement avec les périphériques ; et les outils, qui font appel au noyau. Par
exemple, tous les outils en ligne de commande qui permettent de manipuler des fichier
dans GNU/Linux, comme ls (lister les fichiers dans le répertoire courant) ou pwd (af-
ficher le chemin du répertoire courant) sont des programmes indépendants qui sont
fournis avec l’OS (et peuvent donc être considérés comme faisant partie intégrante de
l’OS), mais qui interagissent avec le matériel à travers le noyau (comme le reste des
programmes).
3. Notons que certains OS élémentaires permettaient malgré tout ce contournement. C’est le cas par exemple
avec les différentes versions du Disk Operating System (DOS) qui équipait les IBM PC et compatibles jusqu’au mi-
lieu des année nonante. Cela s’explique par le fait que le premier IBM PC était équipé d’un CPU qui ne permettait
pas l’utilisation des mécanismes de protection nécessaires.

231
8. Leçon 15 – Le système d’exploitation

1 mov eax , 4 ; 4 est l ’ appel système sys_write


2
3 mov edx , 10 ; longueur du message
4 mov ecx , msg ; msg est un pointeur vers la chaîne
5 ; de caractères en ASCII
6 mov ebx , 1 ; 1 désigne la sortie standard
7
8 int 0 x80 ; déclenchement de l ’ interruption

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)

2. Lorsque le CPU exécute des instructions du noyau il est (principalement) en mode


maître. Ceci permet donc à l’OS d’interagir avec les périphériques.
3. Les programmes utilisateurs et les outils de l’OS, quant à eux, s’exécutent en mode
esclave. Ainsi, toute tentative d’exécuter une instruction privilégiée (y compris les ins-
tructions communicant directement avec le matériel comme « écrire telle donnée à
telle adresse de la mémoire secondaire ») par un programme utilisateur résultera en
une erreur (exception) déclenchée par le CPU.
4. Afin de faire appel à l’OS, le programme utilisateur doit pouvoir faire passer le CPU en
mode maître, et lui faire exécuter une partie de l’OS qui traitera sa requête. Cela se fera
par le mécanisme d’interruption (voir Chapitre 7). Concrètement, le logiciel utilisateur
qui veut faire appel à l’OS va placer des valeurs convenues dans certains registres pour
caractériser sa requête, et va utiliser une instruction machine (non-privilégiée, donc),
pour déclencher une interruption. Toutes les routines de gestion d’interruption feront
partie du noyau (elles auront été installées en mémoire lors du démarrage du système),
et l’exécution de la routine de gestion d’interruption permettra donc à l’OS de traiter
la demande (en mode maître), puis de faire en sorte que l’exécution du programme
utilisateur reprenne après la demande.
L’ensemble des spécifications des fonctions de l’OS qui sont disponibles pour les pro-
gramme utilisateurs via le mécanisme de l’appel système est en général appelé Appli-
cation Programming Interface ou API.

ß
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

On peut donc résumer notre discussion de la manière suivante :

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.

8.3. Tâches dévolues au système d’exploitation


Voyons maintenant comment on peut exploiter ces principes et mécanismes en étudiant
une série de tâches typiquement confiées à l’OS.

8.3.1. La gestion de la mémoire primaire


Si nous souhaitons exécuter plusieurs programmes en même temps, nous devons pouvoir
stocker ces programmes (et leurs données) en mémoire primaire. L’OS sera donc utilisé pour
répartir la mémoire primaire entre les différents programmes qui s’exécutent. Dans ce cours,
nous étudierons la notion de pagination à la demande (voir Chapitre 9).

234
8.3. Tâches dévolues au système d’exploitation

8.3.2. La gestion des processus


Comme nous le verrons dans la suite, on appelle processus un programme en cours d’exé-
cution. Certains systèmes d’exploitation permettent l’exécution de plusieurs processus de fa-
çon simultanée. Ces OS sont appelés multitâche (par opposition à monotâche). Néanmoins,
il y a en général moins d’unités d’exécution 4 qu’il n’y a de programmes à exécuter en même
temps. Cela signifie qu’il faut doter l’OS d’un module qui puisse allouer le temps CPU à tel
ou tel programme en fonction des besoins. Ce module est appelé ordonnanceur, et la façon
dont il prend ses décisions dépend de toute une série de paramètres. Nous étudierons plus en
détail la gestion des processus (c’est-à-dire les programmes en cours d’exécution) et le rôle
de l’ordonnanceur dans le Chapitre 10.

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 .

8.3.3. La gestion des périphériques


L’accès à tous les périphériques (la souris, le clavier, l’écran) se fera toujours à travers des
appels à l’OS. Cela permet notamment d’éviter de devoir écrire des programmes différents
pour chaque matériel disponible. Par exemple, si un programme souhaite afficher des infor-
mations à l’écran, il utilisera des appels de l’OS, qui traduira ces demandes dans le format
adéquat pour la carte graphique spécifique qui est installée sur le système.

8.3.4. La gestion des différents utilisateurs


Certains OS sont appelés orientés utilisateurs. Cette famille de systèmes d’exploitation re-
groupe tous ceux qui sont capables d’identifier explicitement le ou les utilisateurs qui se
4. C’est-à-dire moins de processeurs ou moins de cœurs, dans un système multicœurs.
5. Voir https://blackberry.qnx.com/en.
6. Voir https://www.rtai.org/.

235
8. Leçon 15 – Le système d’exploitation

servent de l’ordinateur, et de traiter leurs requêtes de façon différenciée.


Concrètement, cela signifie que l’utilisateur qui veut interagir avec l’ordinateur devra d’abord
s’identifier ou se connecter, en général à l’aide d’un nom d’utilisateur (login en anglais), et
d’un mot de passe. L’OS peut alors vérifier que l’utilisateur est bien connu du système et lui
donner ou non accès à l’ordinateur. Les OS qui permettent à plusieurs utilisateurs d’inter-
agir avec un même ordinateur (simultanément ou pas) sont appelés multi-utilisateurs (et,
par opposition, ceux qui ne le permettent pas, sont des OS mono-utilisateur). Les OS multi-
utilisateurs sont en général équipés d’outils permettant d’ajouter ou d’enlever des utilisa-
teurs autorisés à utiliser le système.
La procédure d’identification d’un utilisateurs permet à l’OS de lui associer tous les pro-
grammes qu’il lancera, ainsi que les fichiers qu’il créera ou modifiera. Cela permet alors de
mettre en place des limites à ce que l’utilisateur est autorisé de faire :

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 ;

3. on peut mettre en place un système de quota concernant la quantité de données que


l’utilisateur peut stocker sur la mémoire secondaire ;

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

Toujours sous MacOS, la Figure 8.5 montre un exemple de l’exécution


... de la commande ls -lah, qui affiche la liste des fichiers dans le ré-
pertoire courant, avec des informations complémentaires sur les proprié-
taires de ces fichiers, ainsi que les permissions associées. Nous parlerons
plus en détail du système de fichiers dans le Chapitre 10, mais nous
pouvons déjà illustrer la notion d’utilisateur ici. Par exemple, la ligne :
-rw-r--r-- 1 ggeeraer staff 2,4K 18 mai 08:56 00-prelim.tex
indique l’existence d’un fichier appelé 00-prelim.tex (ce sont en fait les
sources des préliminaires de ce syllabus) qui appartient à l’auteur ggeeraer
(l’auteur de ces lignes), lequel appartient au groupe appelé staff. En effet, sur
les systèmes de type Unix, les utilisateurs peuvent être regroupés en groupes
pour leur attribuer des droits particuliers. La série de caractères au début de la
ligne indique les permissions concernant ce fichier. Si on ignore le premier tiret
(qui indique qu’on a affaire à un fichier et pas à un répertoire) :
— La chaîne rw- indique que le propriétaire (ggeeraer) possède les per-
missions de lecture (r), d’écriture (w) mais pas d’exécution (sans quoi il y
aurait eu un x comme troisième caractère) ;
— Les trois caractères suivants indiquent les permissions des autres
membres du groupe staff : ils ont le droit de lecture, mais pas d’écriture
ni d’exécution ;
— Les trois caractères suivants indiquent les permissions des autres utilisa-
teurs du système : eux aussi ont le droit de lecture, mais ni d’écriture ni
d’exécution.

8.3.5. La gestion de la mémoire secondaire


Comme nous l’avons déjà expliqué dans notre exemple plus haut, l’OS permet un accès
plus aisé à la mémoire secondaire pour les programmes : ceux-ci vont demander à lire et
écrire à des positions particulières dans des fichiers, et non pas dans des cases précises de
la mémoire secondaire. L’OS se charge alors de traduire les demandes des programmes en
adresses sur la mémoire secondaire. Cela permet également d’implémenter le mécanisme de
contrôle d’accès que nous avons présenté en parlant des utilisateurs. Nous discuterons de
tout cela dans le chapitre 10.

8.3.6. La gestion d’une interface utilisateur


Si le but premier de l’OS est d’être une interface entre les logiciels et le matériel, cela s’ex-
prime également par l’interface que les OS offrent aux utilisateurs. En effet, c’est à travers l’OS
et ses services que l’utilisateur contrôle l’ordinateur, et cela peut se faire de plusieurs façons
différentes.

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

Interface en ligne de commande Les premiers systèmes interactifs permettaient à l’utili-


sateur d’interagir avec l’ordinateur via un système de commandes textuelles, que l’utilisateur
tape au clavier. Une telle interface s’appelle une Interface en ligne de commande (Command
Line Interface ou CLI en anglais).
Dans un OS avec CLI, les deux éléments hardware d’interaction sont le clavier et l’écran.
Ce dernier invite l’utilisateur à entrer des commandes à l’aide d’une invite de commande
(ou prompt en anglais) qui est en général un curseur clignotant, accompagné de quelques
informations. Les données du programme sont en général affichées sous forme de texte éga-
lement. Ces éléments matériels sont complétés par un logiciel qui lit les commandes de l’uti-
lisateur et les exécute. Cet outil de l’OS s’appelle l’interpréteur de command (Command Line
Interpreter en anglais).
Cette méthode d’interaction est évidemment plus simple que les interfaces graphiques que
nous connaissons aujourd’hui (voir paragraphe suivant). Elle était bien adaptée aux systèmes
de grandes dimensions qu’on trouvait dans les années 60, 70 et 80, où un ordinateur central
unique, appelé serveur ou mainframe exécutait tous les traitements, tout en étant contrôlé
par plusieurs utilisateurs en même temps à travers des terminaux, connectés via le réseau.
Un terminal était un dispositif constitué d’un écran et d’un clavier 7 , mais incapables d’exé-

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

Source : Gorthmog (https://commons.wikimedia.org/wiki/File:DEC_VT100_terminal_transparent.png), https://creativecommons.org/licenses/b


y-sa/4.0/legalcode.

cuter un programme (ce qui leur valait le surnom de dumb terminal. . . )

ß
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/).

Interface Graphique Utilisateur La plupart des systèmes d’exploitation modernes et des-


tinés aux ordinateurs personnels se distinguent en général par la présence d’une Interface
Graphique Utilisateur (en anglais Graphical User Interface ou GUI). Il s’agit de la possibilité
de structurer l’affichage à l’écran sous la forme de fenêtres dans lesquelles les programmes
affichent leurs résultats et acquièrent des données, et qu’on peut manipuler à l’aide d’une
souris ou d’un dispositif similaire.
Pour avoir une interface unifiée (c’est-à-dire qui soit visuellement et fonctionnellement si-
milaire pour tous les programmes), le système d’exploitation est à nouveau essentiel. Ce sont
en général des bibliothèques de fonctions auxquelles les programmes utilisateurs peuvent
faire appel afin de demander de « créer une fenêtre à l’écran » ou « afficher un menu dérou-
lant », sans que ces programmes aient à se préoccuper de la manière dont ces éléments seront
affichés à l’écran.

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

1 # Import de la bibliothèque GTK


2 import gi
3 gi . require_version ( " Gtk " , " 4.0 " )
4 from gi . repository import Gtk
5
6 # Fonction qui sera appelée lorsque l ’ application est lancée
7 def on_activate ( app ):
8 # création de la fenêtre
9 win = Gtk . Applicati onWind ow ( application = app )
10 # création d ’ un bouton
11 btn = Gtk . Button ( label = " Hello , World ! " )
12 # Action liée au clic sur le bouton : fermer la fenêtre
13 btn . connect ( ’ clicked ’ , lambda x : win . close ())
14 # Ajout du bouton à la fenêtre
15 win . set_child ( btn )
16 win . present ()
17
18 app = Gtk . Application ( application_id = ’ org . gtk . Example ’)
19 app . connect ( ’ activate ’ , on_activate )
20 app . run ( None )

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.

8.4. Quelques repères historiques

Nous terminons ce chapitre en évoquant brièvement l’histoire des systèmes d’exploitation.


Cette perspective est en effet utile pour bien comprendre les besoins auxquels répond un OS,
besoin que nous avons expliqués tout au long de ce chapitre.

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

8.4.1. Les premiers langages de programmation et les OS batch


Un des premiers développements pratiques et importants de l’informatique a été l’intro-
duction de langages de haut niveau, notamment FORTRAN 9 et COBOL 10 . Ces langages per-
mettaient à l’utilisateur d’écrire son programme dans un dialecte plus proche du domaine
d’application que le langage machine, mais nécessitaient une étape supplémentaire de com-
pilation pour être traduits en langage machine. Typiquement, une session d’utilisation de
l’ordinateur pouvaient alors se dérouler comme suit :
1. L’utilisateur charge le compilateur dans la mémoire primaire, à l’aide de cartes per-
forées, et place son programme en langage de haut niveau dans le lecteur de cartes
perforées. Ce programme pouvait éventuellement être accompagné de bibliothèques
de code déjà écrits (afin de ne pas devoir ré-écrire tout le programme à chaque fois).
2. L’utilisateur exécute le compilateur pour produire l’équivalent en langage machine du
code de haut niveau, sur des cartes perforées.
3. L’utilisateur recharge ce code en langage machine dans la mémoire primaire à la place
du compilateur.
4. Finalement, l’utilisateur peut exécuter son programme.
Comme on le voit, les étapes nécessaires pour arriver à l’exécution du programme sont nom-
breuses, et peuvent demander plusieurs interventions de l’utilisateur. Par exemple, les com-
pilateurs demandaient souvent plusieurs passes sur le code de haut niveau, ce qui forçait
l’utilisateur à recharger ses cartes perforées. En outre, une bonne partie du temps durant le-
quel l’utilisateur profitait de la machine était consacré à des lectures et écritures de cartes
(ou d’autres supports de mémoire secondaire), durant lesquelles le processeur était large-
ment inactif. Or, le temps de calcul était alors très précieux, et cette situation était donc jugée
problématique 11 .
Ces constatations appelaient naturellement à la création d’un système permettant :
1. d’automatiser les tâches séquentielles (compiler une première fois, compiler une se-
conde fois, exécuter le programme, passer au programme suivant. . . ). C’est le principe
des OS batch que nous avons décrit ci-dessus ; et
2. d’exécuter plusieurs programmes sur un même système, en ayant la capacité de bas-
culer d’un programme à un autre. Ceci, afin de permettre d’exécuter un programme
pendant que le CPU est en attente d’une entrée/sortie dans un autre programme et
ainsi maximiser le temps pendant lequel le CPU est utilisé. Ceci implique de pouvoir
partager le processeur et la mémoire entre différents programme qui s’exécutent. Nous
verrons de telles techniques dans les chapitres 9 et 10.

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.

8.4.2. Les années 1960, 1970 et l’ère des mainframes


Dans les années 1960 et au début des années 1970, l’informatique personnelle n’existe
pas encore. Les ordinateurs sont des machines coûteuses à produire et à maintenir. Une en-
treprise typique possède quelques ordinateurs centraux, appelés mainframes, auxquels plu-
sieurs utilisateurs peuvent se connecter via des terminaux, comme le VT100 de la Figure 8.6.
Les systèmes d’exploitation de l’époque introduisent donc petit à petit les concepts qui per-
mettent à plusieurs utilisateurs d’exécuter plusieurs programmes « en même temps ». C’est
donc principalement durant les années soixante que se développent les concepts de multi-
programmation 14 , de multi-tâche (voir Chapitre 10), de mémoire paginée (voir Chapitre 9) et
de multi-utilisateur. Voici quelques-uns des OS remarquables qui sont apparus durant cette
période :

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.

THE OS Le nom de ce système d’exploitation est l’acronyme de Technische Hogeschool


Eindhoven, université où il a été développé de 1965 à 1968 [8], en particulier par le célèbre
informaticien Edsger D IJKSTRA 15 . Il est le premier système d’exploitation à implémenter un
système de pagination à la demande (que nous étudierons au Chapitre 9), principe qui est en
usage dans la majorité des OS modernes.

12. Société multinationale de construction automobile, originaire des États-Unis d’Amérique.


13. Ancienne société de construction aéronautique, originaire des États-Unis d’Amérique, et faisant mainte-
nant partie de Boeing.
14. Technique primitive permettant d’exécuter un programme pendant qu’un autre est en attente d’une en-
trée/sortie.
15. Né le 11 mai 1930 à Rotterdam et décédé le 6 août 2002 à Nuenen, D IJKSTRA est un célèbre, prolifique et
excentrique chercheur en informatique théorique. Il fut Professeur à l’Université d’Eindhoven, chercheur à la
Burroughs Corporation, et enfin Professeur à l’Université d’Austin au Texas. Il est récipiendaire du prix Turing en
1972, et est considéré comme un des pionniers de l’informatique. Ses contributions touchent en particulier au
calcul distribué, à l’algorithmique, aux langages de programmation. . .

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.

Source : Domaine public

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) ;

16. Voir note 10, page 111.


17. Né le 4 février 1943, informaticien américain, récipiendaire du prix Turing en 1983. Il passe la majorité de
sa carrière aux Bell Labs. Outre Unix, il a également collaboré à de nombreux projets, dont l’encodage UTF-8.
18. Né le 9 septembre 1941 et décédé le 12 octobre 2011, informaticien américain, récipiendaire du prix Turing
en 1983. Il est également l’inventeur, du langage de programmation C, et le co-auteur, avec Brian K ERNIGHAN, du
célèbre livre portant le même nom [21].
19. Le Programmable Data Processor (modèle 11) était un ordinateur commercialisé entre 1970 et 1993 par
Digital Equipment Corporation, une firme américaine. Il était très populaire dans les universités et les centres de
recherche.

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.

Source : Peter Hamer (https://commons.wikimedia.org/wiki/File:Ken_Thompson_(sitting)_and_Dennis_Ritchie_at_PDP-11_(2876612463).jpg), Ken


Thompson (sitting) and Dennis Ritchie at PDP-11 (2876612463)’, https://creativecommons.org/licenses/by-sa/2.0/legalcode

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

8.4.3. Les années 1980 et 1990 : l’informatique personnelle


L’année 1977 est considérée comme importante dans l’histoire de l’informatique car elle
voit la commercialisation des trois premiers micro-ordinateurs personnels qui ont eu un vé-
ritable impact auprès du grand public. Il s’agit du Commodore PET, de l’Apple II et du Tandy
TRS-80. Nous avons déjà évoqué les deux premiers qui sont basés sur le processeur 6502 (voir
Section 6.1). Le TRS-80 est lui basé sur un autre processeur 8 bits, le Z80 22 . Comme ces ordi-
nateurs étaient destinés à un public de non-spécialistes qui désiraient apprendre à program-
mer, il était important de permettre un interaction simple avec ces machines. Elles étaient
donc pourvues d’un interpréteur BASIC 23 , qui possédaient des commandes permettant de
gérer des fichiers sur bandes magnétiques 24 ou sur disquettes, formant ainsi un embryon
d’OS.

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

20. Voir https://www.freebsd.org/fr/.


21. Voir https://www.openbsd.org/.
22. Conçu et commercialisé par la firme américaine Zilog, il possède des registres de travail de 8 bits et ma-
nipule des adresses sur 16 bits. Il est présent dans une quantité très importante d’ordinateurs personnels des
années 1980.
23. Le Beginner’s All-purpose Symbolic Instruction Code est un langage de programmation simple inventé par
John G. K EMENY et Thomas E. K URTZ en 1964 pour permettre aux débutants d’apprendre à programmer facile-
ment.
24. Les premiers ordinateurs personnels utilisaient des cassettes audio comme mémoire secondaire. Cela per-
mettait aux utilisateurs de réutiliser leur lecteur de cassettes sans devoir investir dans un lecteur de disquettes,
très coûteux.
25. Processeur introduit en 1979. Il manipule des adresses sur 20 bits. Il possède des registres de travail sur 16
bits, mais son bus de communication avec la mémoire est de 8 bits pour en réduire les coûts (il faut donc deux
cycles pour lire ou écrire le contenu d’un registre en mémoire). Il s’agit d’une version simplifiée du 8086, lequel a
un bus de 16 bits.
26. Control Program/Monitor ou CP/M est un OS développé à partir de 1974 par Gary K ILDALL (informaticien

249
8. Leçon 15 – Le système d’exploitation

F IGURE 8.11. – Un IBM modèle 5150.

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

MacOS et OS X Disponible sur le marché à partir de 1984, le Macintosh d’Apple (Fi-


gure 8.12) est le premier ordinateur commercialisé en masse à disposer d’un GUI 29 . Les par-
ticularités du Macintosh reposent donc essentiellement sur son OS. Celui-ci offre une gestion
de base du matériel, un GUI, et un système de fichiers, mais pas de multi-tâche ni de support
multi-utilisateur. Le multi-tâche coopératif (voir Chapitre 10) sera introduit dans la version
5.0 en 1987. La gestion de la mémoire virtuelle (voir Chapitre 9) apparaît dans la version 7.0,
en 1991.
Après la version 9.2.2 (2001), Apple introduit un tout nouvel OS appelé OS X (« OS dix »), qui
est basé sur Darwin, un dérivé de l’Unix BSD. Il en a toutes les caractéristiques : multi-tâche
préemptif (voir Chapitre 10), multi-utilisateur, mémoire paginée (voir Chapitre 9), sécurité
élevée, etc. OS X connaîtra une série de nouvelles versions, jusqu’à la version 10.15 en 2019.
Depuis, Apple est revenu au nom MacOS pour les versions 11 et 12.

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.

GNU/Linux Le système d’exploitation que nous connaissons aujourd’hui sous le nom de


« Linux » est en fait le résultat de deux projets démarrés de façon assez indépendante. En
1983, Richard S TALLMAN 30 , alors chercheur au MIT, décide de réaliser un système Unix entiè-
rement libre : c’est-à-dire un système dont les utilisateurs pourraient accéder au code source,
et le modifier librement. Il nomme ce projet GNU (acronyme récursif de « GNU is Not Unix »)

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.

Source : http://www.allaboutapple.com/ (https://commons.wikimedia.org/wiki/File:Macintosh_128k_transparency.png), « Macintosh 128k


transparency », https://creativecommons.org/licenses/by-sa/2.5/it/deed.en

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 :

Hello everybody out there using minix -


I’m doing a (free) operating system (just a hobby, won’t be big and
professional like gnu) for 386(486) AT clones. This has been brewing
since april, and is starting to get ready. I’d like any feedback
on things people like/dislike in minix, as my OS resembles it somewhat
(same physical layout of the file-system (due to practical reasons)
among other things).
I’ve currently ported bash(1.08) and gcc(1.40), and things seem to
work. This implies that I’ll get something practical within a few
months, and I’d like to know what features most people would want.
Any suggestions are welcome, but I won’t promise I’ll implement them
:-)
Linus (torvalds@kruuna.helsinki.fi)
PS. Yes - it’s free of any minix code, and it has a multi-threaded
fs. It is NOT portable (uses 386 task switching etc), and it probably
never will support anything other than AT-harddisks, as that’s all
I have :-(.
– Linus Torvalds

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,

31. Association américaine a but non lucratif. Voir https://www.fsf.org/.


32. Né le 28 décembre 1969, ingénieur logiciel finlandais. Il développe la première version du noyau de Linux,
et en fait actuellement partie des mainteneurs.
33. Écrit par Andrew TANENBAUM, Minix est une version 16 bits, minimaliste mais fonctionnelle d’Unix, déve-
loppé dans un but d’enseignement. Les sources en étaient disponibles, mais les modifications étaient restreintes
en raison de la licence logicielle.

253
8. Leçon 15 – Le système d’exploitation

avec l’apparition de distributions 34 , qui permettent d’installer facilement le système et ses


outils. Certaines de ces distributions s’adressent au grand public, d’autres sont conçues pour
répondre aux besoins des entreprises en matière de serveur. Malgré ses débuts modestes,
GNU/Linux est aujourd’hui devenu un système d’exploitation incontournable : fin 2021, il
équipe les 500 super-ordinateurs le plus puissants dans le monde [24], et plus d’un tiers 35
des serveurs web dans le monde [45]. Enfin, le noyau de Linux est à la base du système d’ex-
ploitation mobile Android dont nous parlons dans la prochaine section.

8.4.4. L’informatique portable : les années 2000 et après


Au début des années 2000, l’informatique se rend encore plus présente dans notre vie quo-
tidienne avec l’apparition d’équipements dits « intelligents » : principalement des téléphones
(smartphones) et des tablettes tactiles. Ces nouvelles machines requièrent des systèmes d’ex-
ploitation adaptés. Plusieurs d’entre eux ont été proposés, et Windows est d’ailleurs l’OS
fourni avec les tablettes produites par Microsoft. Nous détaillons ici les deux OS mobiles prin-
cipaux, à savoir Android et iOS.

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

34. Comme Debian (http://www.debian.org), Ubuntu (http://www.ubuntu.com) ou Slackware (http:


//www.slackware.com).
35. Pour 37% des sites Web dans le monde, l’usage de Linux est confirmé, mais il est inconnu pour de nombreux
sites Web. Les estimations montent jusqu’à 77%.

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.

9.1. Problèmes à surmonter


Supposons que nous devions placer, en mémoire primaire, un petit programme de 10 oc-
tets (il occupera donc 10 cases mémoire), avec les caractéristiques suivantes :
1. le deuxième octet du programme est une zone pour stocker une donnée entière, qu’on
appellera d dans cet exemple ;
2. les neuf octets suivants sont du code machine. Parmi ces instructions, on en trouve
une qui accède à d . Pour fixer les idées, on peut supposer qu’on utilise l’instruction
LDA du processeur 6502 1 qui tient sur trois octets : l’opcode AD suivie des deux octets
de l’adresse mémoire.
Ce programme est illustré à la Figure 9.1 selon la convention que nous allons continuer à
utiliser à travers tout ce chapitre : les différents octets qui composent un programme, ou
les différents octets qui constituent les cases de la mémoire primaire sont représentés in-
dividuellement les uns au-dessus des autres. On observera qu’on a numéroté ces octets de

1. Voir Figure 6.1

255
9. Leçon 16 et 17 – Gestion de la mémoire primaire

a.v.

1 Variable d

3 AD
4 ??????

5 ??????

F IGURE 9.1. – Notre programme d’exemple.

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.

9.1.1. Problème d’adressage et relocation


La première question qui se pose à nous sur cet exemple est celui de savoir quelle adresse
placer dans les octets 4 et 5 de telle manière que cette adresse soit bien celle de la variable d
en début de programme ? Il est crucial d’observer ici qu’il est impossible de répondre à cette
question au moment où le programme est écrit. En effet, il faut mettre comme adresse celle
que la deuxième case du programme (qui contient la variable d ) occupera en mémoire, or rien
ne nous permet de nous assurer que le programme sera bien chargé à partir de l’adresse 0 !
La Figure 9.2 illustre cela avec deux manières correctes de charger le programme en mé-
moire : dans le premier cas, le programme est chargé à partir de l’adresse 0. Donc la variable d
se trouve à l’adresse 1 et on a bien indiqué 000116 dans les octets 4 et 5. Dans le deuxième cas,
le programme a été chargé à partir de l’adresse 2, et on doit donc mettre 000316 dans les octets
4 et 5.
Cette discussion nous amène à introduire plusieurs concepts. Les deux premiers sont ceux
des adresses virtuelles et des adresses réelles. Puisqu’on ne sait pas, a priori, où le programme
sera chargé en mémoire lors de l’exécution, il faut bien fixer une convention pour les adresses
au moment où l’on écrit le programme. On choisira tout naturellement d’utiliser des adresses
qui sont relatives au début du programme, c’est-à-dire les adresse qu’on aurait eu en faisant
« comme si » le programme était chargé en mémoire à partir de l’adresse 0. On appelle ces
adresses des adresses virtuelles 2 .
D’un autre côté, une fois que le programme est chargé en mémoire, chacun de ses octets
se voit attribuer une case mémoire et donc une adresse réelle 3 ou adresse physique.

2. D’où l’étiquette « a.v. » sur nos illustrations.


3. Abrégée cette fois-ci en « a.r. »

256
9.1. Problèmes à surmonter

a.r. Mémoire primaire a.r. Mémoire primaire

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.

9.1.2. Problème de fragmentation


Admettons que nous ayons à notre disposition un mécanisme de relocation efficace qui
nous permette de charger n’importe quel programme à partir de n’importe quelle adresse, et
voyons les conséquences que cela entraîne.
Supposons que nous ayons une mémoire de 10 octets 4 , dans laquelle nous avons chargé
successivement trois programmes, comme sur la Figure 9.3 : un programme P de taille 3, un
programme Q de taille 2 et un programme R de taille 4. Le dernier octet est libre.
Supposons ensuite que le programme Q se termine, les cases 3 et 4 se libèrent donc. Enfin,
supposons qu’on veuille charger un nouveau programme S qui fait 3 octets. On voit bien le
problème auquel on est confronté : bien qu’il y ait au total trois octets de mémoire libre, ils ne
forment pas un bloc continu, et on ne peut donc pas y charger le programme S. Une solution
consisterait à déplacer le programme R pour qu’il commence à l’adresse 3, ce qui implique
de faire une nouvelle relocation, tout cela risque de prendre du temps CPU qui ne peut plus
être consacré à l’exécution des programmes. . .
Le phénomène que nous avons illustré est appelé fragmentation, car l’espace mémoire
libre est fragmenté et, de ce fait, difficile à exploiter. Observons que ce phénomène provient
du fait qu’en général les programmes ont des tailles différentes (sans quoi, tout espace libéré
par un programme pourrait servir pour n’importe quel autre).

Résumons donc les deux problèmes que nous avons constaté :


1. Les adresses virtuelles, qui sont les seules connues au moment d’écrire le programme,
ne correspondent pas, en général, aux adresses réelles ;
2. les programmes étant de tailles différentes, les charger d’un seul bloc risque d’entraîner
de la fragmentation qui rend la mémoire libre inutilisable.

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

a.r. Mémoire primaire a.r. Mémoire primaire

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

a.r. Mémoire primaire


0
1
2 a.v. Q
a.v. P
3 0
0
4 1
1
5
2
6 a.v. R
3
7 0
4
8 1
5
9 2
6
10 3
7
11 4
8
12
13
14
15

F IGURE 9.4. – L’exemple que nous utiliserons dans cette section.

Détaillons maintenant ces éléments à l’aide de l’exemple suivant :

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

9.2.1. Les cadres de page

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

a.r. Mémoire primaire


0
1 PF 0 a.v. Q
2 0
a.v. P
3 1 Q0
0
4 PF 1 2
1 P0
5
2
6 a.v. R
3
7 PF 2 0
4 P1
8 1 R0
5
9 2
6
10 PF 3 3
7 P2
11 4 R1
8
12 5
13 PF 4
14
15

F IGURE 9.5. – La découpe en page frames et en pages de notre exemple.

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

Ainsi, sur notre exemple, le page frame 0 commence en 0 × ℓ = 0, et se termine


ß en 1 × ℓ − 1 = 3 − 1 = 2. Le page frame 0 contient donc les cellules de mémoire 0,
1 et 2. Le page frame 1 va de l’adresse 3 à l’adresse 5, etc. La Figure 9.5 montre la
découpe en page frames de la mémoire.

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.

9.2.2. Les pages


Nous découpons tous nos programmes à exécuter en pages de manière similaire. Malheu-
reusement, les programmes n’ont pas toujours une longueur qui est un multiple de ℓ, mais,

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 :

Les pages sont numérotés à partir de 0. La page i est constituée de la plage


d’adresses virtuelles allant de i × ℓ à (i + 1) × ℓ − 1 (comprises).

ß
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

a.r. Mémoire primaire


0
1 P1 PF 0 a.v. Q
2 0
a.v. P
3 1 Q0
0
4 Q0 PF 1 2
1 P0
5
2
6 a.v. R
3
7 P0 PF 2 0
4 P1
8 1 R0
5
9 2
6
10 P2 PF 3 3
7 P2
11 4 R1
8
12 5
13 PF 4
14
15

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.

9.2.3. Le MMU et la traduction des adresses


L’exemple précédent a bien montré que les adresses virtuelles et réelles ne coïncident plus
avec la pagination. Le CPU ne manipulera que des adresses virtuelles. En particulier, le re-
gistre PC qui contient l’adresse de la prochaine instruction à exécuter, contiendra toujours
une adresse virtuelle, donc, relative au début du programme en cours d’exécution. Cela sim-
plifie le chargement des programmes, puisqu’on peut les copier en mémoire sans besoin de
relocation ni de modification des adresses de quelque nature que ce soit.
Par contre, la mémoire a besoin d’adresses réelles pour fonctionner et il faut donc intro-
duire un mécanisme de traduction des adresses virtuelles en adresses réelles. Comme cette
traduction aura lieu au moins une fois par instruction machine (pour charger l’instruction
à l’adresse PC), cette traduction doit être très efficace, et elle est donc réalisée de façon ma-
térielle. Les processeurs modernes possèdent ainsi un circuit dédié, le Memory Management
Unit ou MMU, dont la position dans l’architecture de l’ordinateur 6 est illustré à la Figure 9.8 :
il reçoit les adresses virtuelles que le CPU émet sur le bus, les traduit en adresses réelles et les
envoie à la mémoire primaire. Les données qui transitent entre le CPU et la mémoire, par
contre, ne sont évidemment pas affectées.

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.

6. En pratique, c’est en général un circuit qui fait partie du CPU.

264
9.2. Pagination

a.r. Mémoire primaire


0
1 P1 PF 0 a.v. Q
2 0
a.v. P
3 1 Q0
0
4 R1 PF 1 2
1 P0
5
2
6 a.v. R
3
7 P0 PF 2 0
4 P1
8 1 R0
5
9 2
6
10 P2 PF 3 3
7 P2
11 4 R1
8
12 5
13 R0 PF 4
14
15

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

F IGURE 9.8. – Le MMU entre le CPU et la mémoire primaire.

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 × ℓ :

début du page frame = pf × ℓ.

En assemblant tous ces éléments, nous trouvons l’adresse réelle recherchée :

adresse réelle correspondant à α = début du page frame + ∆


= pf × ℓ + ∆
= desc(α ÷ ℓ) × ℓ + α mod ℓ.

É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

Revenons à notre exemple :

Considérons l’adresse virtuelle α = 7 du programme P . Nous pouvons voir sur la


ß Figure 9.6 qu’elle correspond à l’adresse réelle 10. Voyons comment nous pou-
vons calculer cela avec la formule ci-dessus (le lecteur est encouragé à comparer
les résultats avec la figure pour s’assurer qu’ils ont bien du sens) :
1. nous commençons par calculer la page qui contient α. Nous obtenons la
page α ÷ ℓ = 7 ÷ 3 = 2. Notre adresse est donc dans la page P 2 ;
2. ensuite, nous consultons la table des pages du programme P , qui nous
apprend que P 2 est chargée dans le page frame 3 : desc(2) = 3 ;
3. nous calculons ensuite la distance entre l’adresse virtuelle α = 7 et le dé-
but de sa page. Observons que le début de cette page est à l’adresse vir-
tuelle 6, la distance que nous voulons obtenir est donc de 1. En effet ∆ = α
mod ℓ = 7 mod 3 = 1.
4. nous calculons l’adresse de début du page frame 3 qui contient notre page,
c’est l’adresse réelle 3 × ℓ = 9 ;
5. finalement, nous ajoutons à cette adresse de début le décalage ∆, et nous
obtenons notre adresse réelle : ∆ + 9 = 1 + 9 = 10.

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

1. Commençons par calculer la numéro de page et le décalage dans la page de l’adresse


virtuelle. Le décalage s’obtient par α mod ℓ = α mod 2k , et correspond donc au k bits
de poids faible d’α. Le numéro de page vaut α ÷ ℓ = α ÷ 2k , et correspond donc au n − k
bits de fort de α. On voit donc qu’on obtient ces informations cruciales sans aucun
calcul : il suffit juste d’isoler les bonnes parties de la représentation binaire d’α :
n −1 k k −1 0
α=
num. de page p décalage ∆

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.

Quand les longueurs des pages sont des puissances de 2 (ℓ = 2k ), le décalage


dans la page s’obtient par les k bits de poids faible de l’adresse virtuelle, et le
numéro de page par les n − k bits de poids fort de l’adresse virtuelle (où n est
la taille de la représentation binaire de l’adresse). L’adresse réelle s’obtient alors
en remplaçant le numéro de page par le numéro de page frame.

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

9.3. Pagination à la demande


La flexibilité offerte par le mécanisme de pagination peut être exploitée d’avantage grâce
à l’observation suivante : à tout moment, il n’est pas nécessaire d’avoir en mémoire toutes
les pages d’un programme. À vrai dire, le CPU n’exécute à chaque moment qu’une seule ins-
truction machine, et le nombre de pages nécessaires pour exécuter une instruction est très

268
9.3. Pagination à la demande

a.r. Mémoire primaire


0
1 a.v. P
P 11 PF 0
0
10
11 1
P 00
100 10
101 11
PF 1
110 100
111 101
P 01
1000 110
1001 111
P 01 PF 10
1000
1010
1011 1001
P 10
1100 1010
1101 1011
P 10 PF 11
1100
1110
1111 1101
P 11
10000 1110
10001 1111
P 00 PF 100
10010
10011

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

F IGURE 9.9. – Un exemple de pagination du programme P avec des longueurs de page ℓ =


22 (donc k = 2). Les adresses, numéros de pages et numéros de page frames
sont donnés en binaire. Le calcul de l’adresse réelle correspondant à l’adresse
virtuelle 1001 est donné dans le bas de la figure.

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.

9.3.1. Le défaut de page


La première question que nous devons nous poser est de celle de savoir comment se rendre
compte qu’une page est manquante et doit être chargée en mémoire. Pour ce faire, nous al-
lons d’abord étendre la table des pages avec un bit de présence associé à chaque page :
— quand le bit de présence est à 1, la page est présente en mémoire primaire, et la colonne
page frame de la table nous dit dans quel page frame ;
— quand le bit de présence est à 0, la page n’est pas en mémoire primaire, et l’information
dans la colonne page frame peut être ignorée.
Ensuite, nous devons identifier le composant qui sera en mesure de détecter l’absence de
page, et le composant qui devra s’occuper du chargement de la page.
La détection de l’absence d’une page nécessaire à la bonne exécution du programme sera
faite par le MMU, au moment où celui-ci accède à la table des pages. Après avoir calculé le
numéro de page, celui-ci va consulter le bit de présence de la page en question. Si le bit est
à 1, il traduira l’adresse en adresse réelle comme expliqué précédemment. Si le bit est à 0, il
aura détecté un default de page (ou page fault en anglais).

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.

9.3.2. L’échange et le choix de victime


Quel est le traitement que l’OS va effectuer dans la routine de gestion d’interruption du
page fault ? Il y aura plusieurs étapes :
1. l’OS doit d’abord déterminer quelle est la page qui est manquante. Cela peut se faire à
l’aide de la sauvegarde de PC réalisée au moment où l’interruption a été déclenchée ;
2. ensuite, l’OS doit déterminer si cette page existe bien dans le programme et où elle se
trouve en mémoire secondaire. Il se pourrait que le programme tente d’accéder à une
adresse virtuelle qui n’existe pas dans le programme, et donc à une page qui n’existe
pas. Dans ce cas, l’OS doit mettre fin au programme et afficher une erreur (segmenta-
tion fault sous GNU/Linux, par exemple). L’OS doit également maintenir des tables qui
permettent de retrouver les pages sur la mémoire secondaire ;
3. finalement, l’OS doit charger la page en mémoire primaire et mettre à jour les tables
des pages là où c’est nécessaire.
La dernière étape est moins simple qu’elle n’y parait : s’il existe un page frame de libre,
on peut y charger la page demandée, et mettre son bit de présence à 1. Cette opération de
chargement s’appelle un swap in 7 .
Par contre, s’il n’existe pas de page frame de libre, il faut choisir une victime, c’est-à-dire
une page qu’on va enlever de la mémoire primaire pour faire de la place pour la nouvelle

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

a.r. Mémoire primaire


Mémoire secondaire
0
1 P1 PF 0
2
3
4 Q0 PF 1
R1
5
6
7 P0 PF 2
8
9
R0
10 P2 PF 3
11
12
13 PF 4
14
15

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

page. Avant d’enlever la victime de la mémoire primaire, il est nécessaire de la sauvegarder


entièrement sur la mémoire secondaire, car elle peut contenir des données qui seront peut-
être nécessaires plus tard (un nouveau page fault peut entraîner le chargement de cette page
plus tard). Cette opération de sauvegarde sur la mémoire secondaire s’appelle le swap out en
anglais. Une fois la sauvegarde effectuée, il faut mettre le bit de présence de la victime à 0,
puis la nouvelle page peut être chargée.
Le choix de victime dépend à nouveau de l’OS. Il existe plusieurs stratégies possibles, en
voici quelques exemples :
— l’OS peut maintenir un compteur du nombre d’accès à chaque page, et choisir comme
victime celle qui a le moins d’accès ;
— l’OS peut choisir comme victime la page à laquelle on n’a plus accédé depuis le plus
longtemps ;
— ...
De manière générale, le but du choix de victime est de conserver en mémoire les pages dont
l’usage dans un futur proche est le plus probable.

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

9.4. Difficultés liées à la pagination


Même si la pagination (et la pagination à la demande) offrent des avantages indéniables en
terme de flexibilité et de facilité d’écriture des programmes, ces techniques présentent tout
de même des difficultés propres.

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

a.r. Mémoire primaire


Mémoire secondaire
0
1 P1 PF 0
2
3
4 Q0 PF 1
5
6
7 P0 PF 2
8
9
R0
10 P2 PF 3
11
12
13 R1 PF 4
14
15

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

a.r. Mémoire primaire


Mémoire secondaire
0
1 P1 PF 0
2
3
4 Q0 PF 1
P0
5
6
7 R0 PF 2
8
9
10 P2 PF 3
11
12
13 R1 PF 4
14
15

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

9.5. Exemple : pagination sur l’Intel 486


Revenons sur notre processeur d’exemple, l’i486 et voyons comment il implémente la pa-
gination à la demande. Comme nous l’avons déjà évoqué au paragraphe Modèle Mémoire
du Chapitre 6, la gestion de la mémoire par l’i486 est complexe, en raison du mécanisme
de segmentation. L’i486 implémente la pagination à l’intérieur des segments, et donc, pour
simplifier l’exemple, nous ignorerons les segments (ce qui revient à considérer qu’il n’y en a
qu’un seul qui occupe toute la mémoire).
8. On notera qu’il s’agit de thrashing (qui signifie « emballement ») comme dans le genre musical (!) thrash
metal ; et non pas de trashing, qui signifie « saccage ».

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

En réalité, le « numéro de page » se décompose lui-même en deux paquets de 10 bits : l’index


dans le directory et l’index dans la table des pages. En effet, la table des pages n’est pas d’une
seule pièce contiguë de 220 entrées (ce qui serait trop long, en tous cas plus long qu’une page).
Elle est constituée :
1. d’un répertoire (page directory en anglais) qui est une table contenant 210 = 1024 en-
trées. Chacune de ces entrées contient notamment un pointeur vers une table des
pages. Le registre spécial CR3 de l’i486 contient l’adresse réelle à laquelle ce directory
se trouve.
2. de tables de pages individuelles (jusqu’à 1024 tables différentes) qui contiennent cha-
cune le numéro du page frame correspondant à la page.

31 22 21 12 11 0
a.v. :
directory d table t décalage ∆

Le mécanisme de traduction d’adresse est illustré à la Figure 9.13 et fonctionne comme


suit :
1. dans le directory qui commence à l’adresse CR3, on recherche l’entrée numéro d , où d
constitue les 10 bits de poids fort de l’adresse virtuelle. Concrètement, on accède donc
à l’adresse CR3+d ;
2. cette entrée du directory est sur 32 bits et nous permet de retrouver la bonne table des
pages. Concrètement, les 20 bits de poids fort de l’entrée du directory constituent la
valeur v, et la table des pages qu’on cherche se trouve à l’adresse v × 210 . Pour obtenir
l’adresse du descripteur de la page correspondant à notre adresse virtuelle, on regarde
l’entrée numéro t dans cette table des pages, où t est constitué des bits numéros 21 à
12 de l’adresse virtuelle. En pratique, cela se fait en concaténant l’adresse du début de
la table trouvée dans le directory avec t (ce qui revient à calculer t + v × 210 ) ;
3. dans le descripteur (sur 32 bits), les 20 bits de poids fort constituent le numéro de page
frame qu’on concatène avec le décalage ∆ pour obtenir l’adresse réelle (ici aussi, on se
permet de concaténer puisque les tailles de pages sont des puissances de 2).
Voyons cela en pratique sur un exemple.

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

et l’adresse du directory donnée par CR3 :

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

Supposons que cette entrée soit de la forme :

31 12 11 0
entrée du directory :
0101 0101 0101 0101 0101 ···

On obtient alors l’adresse du descripteur de page :

31 12 11 0
adresse du descripteur :
00 0101 0101 0101 0101 0101 00 0000 1010

Supposons ensuite que ce descripteur soit de la forme :

31 12 11 0
descripteur :
1001 1001 1001 1001 1001 ···

On obtient finalement l’adresse réelle en concaténant le numéro de page frame


avec le décalage :

31 12 11 0
adresse réelle :
1001 1001 1001 1001 1001 1001 1100 0011

Pour l’implémentation de la pagination à la demande, le bit de poids faible des entrées


du directory et des descripteurs indiquent si ces entrées son valides : dans le cas de l’entrée
de directory, un bit à 1 signale que les 20 bits de poids fort pointent bien vers une table des
pages. Dans le cas du descripteur, le bit à 1 indique que la page est présente en mémoire.
Si la page n’est pas présente en mémoire, les 31 autres bits du descripteur peuvent être uti-
lisés librement par l’OS ; par exemple pour indiquer où cette page se trouve sur la mémoire
secondaire.
Les autres bits de ces entrées permettent notamment d’implémenter des mécanismes de
protection de l’accès à la mémoire au niveau des pages. On peut ainsi spécifier s’il est autorisé
de lire ou d’écrire dans chacune des pages. En cas d’erreur, le CPU déclenche une interrup-

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) :

a.r. Mémoire primaire


0
1
Q1 PF 0
2
3
4
5
P2 PF 1
6
7
8
9
Q0 PF 2
10
11
12
13
PF 3
14
15
16

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. 51 Donnez les tables des pages de P et Q.

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

Ensuite, on considère la séquence d’actions suivantes :


— Le processus P devient le processus actif.
— On accède à l’adresse virtuelle 9 dans le processus P .
— On accède à l’adresse virtuelle 1 dans le processus P .
— On accède à l’adresse virtuelle 14 dans le processus P .

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

5. La page numéro 2 du processus P contient les adresses virtuelles de 8 à 11.

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

La page P 3 est alors chargée dans le PF 0, et la table des pages de P devient :

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

L’état de la mémoire après ces opérations est :

a.r. Mémoire primaire

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.

10.1. Gestion des processus


Comme nous l’avons dit dans l’introduction, un programme est l’ensemble des instruc-
tions et des données qui permettent de réaliser un traitement automatisé. Une fois le pro-
gramme écrit, il faut l’exécuter, c’est-à-dire, faire en sorte que le processeur exécute les ins-
tructions de ce programme. À partir du moment où un programme est en cours d’exécution
sur un système avec un OS multitâche, ce programme devient un processus.
Il est important de faire la différence entre programme et processus. En effet, on peut très
bien imaginer, sur un système multitâche, exécuter plusieurs copies du même programme.
Par exemple, on peut avoir le même traitement de texte qui s’exécute plusieurs fois pour per-
mettre l’édition de plusieurs documents en même temps. Dans ce cas, il faut pouvoir distin-
guer ces différentes versions, car même si le code (machine) du programme est le même, les
données que les différentes copies manipulent sont différentes et ne doivent pas être « mé-
langées » : pour l’OS, ces différentes copies seront des processus différents.
On voit donc que ce qui fait la différence entre un programme et un processus, ce sont
essentiellement l’ensemble des données que le programme manipule à un instant donné de
son exécution, ainsi que toutes les informations périphériques dont l’OS a besoin pour carac-
tériser l’avancement dans l’exécution du processus. Toutes ces informations sont appelées le
contexte.
La nature exacte du contexte dépend d’un OS à l’autre, mais il doit contenir au moins des
informations comme :
— une spécification de la zone mémoire qui appartient au processus, c’est-à-dire celle où
sont stockés le code du programme, les données, etc Si le système utilise de la pagina-
tion ou de la pagination à la demande, ceci inclut la table des pages du processus. . .
— la valeur du compteur de programme, qui indique la prochaine instruction à exécu-
ter. Quand le processus est occupé à s’exécuter, c’est la valeur qui se trouve dans le
registre PC ;

285
10. Leçon 18 – Gestion des processus et de la mémoire secondaire

Création Actif Terminaison

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) ;
— ...

Un processus est un programme en cours d’exécution, identifié et géré par l’OS.


Il est constitué du code machine du programme (chargé en mémoire primaire)
à proprement parler et d’un contexte qui contient toutes les informations néces-
saires à la bonne exécution du processus.

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 ?

10.1.1. Cycle de vie d’un processus


Comme un processus est un programme en cours d’exécution, il commence son existence
au moment où le programme est « lancé » et disparaît lorsque l’exécution du programme se
termine. Entre ces deux événements, le processus peut passer par plusieurs états différents
qu’on appelle « statuts ». L’ensemble du cycle de vie est résumé à la Figure 10.1. Passons
maintenant en revue ces différents statuts et transitions.

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

F IGURE 10.2. – La commande ps.

Les systèmes Unix disposent de plusieurs commandes qui permettent de gérer


... les processus « à la main ». Par exemple, la commande ps permet d’afficher la
liste des processus en cours d’exécution. La commande kill permet de termi-
ner un processus, en spécifiant son identifiant unique qu’on appelle un PID,
c’est-à-dire un process identifier, etc.
La Figure 10.2 montre la sortie de la commande ps sur macOS. On y voit trois
processus. La première colonne donne l’identifiant de l’utilisateur qui a lancé le
processus. La deuxième donne le PID du processus et la troisième donne le PID
de son parent (PPID), c’est-à-dire le processus qui l’a créé. La dernière colonne
donne la commande qui a servi à créer le processus. Ce même type d’informa-
tions est aussi montré à la Figure 8.4, avec un GUI.
Enfin, notons que sur les systèmes Unix, un processus peut également devenir
un zombie après être terminé. Cela arrive quand il doit encore renvoyer une va-
leur à son parent, et que celui-ci ne l’a pas encore lue. Le processus fils est donc
bien mort, mais il occupe encore de la place dans les tables de l’OS, comme
un processus vivant, pour stocker la valeur de retour. C’est donc bien un mort-
vivant. . .

10.1.2. Systèmes en Time sharing


Nous allons maintenant expliquer comment réaliser des OS multitâche, et en particulier,
des OS utilisant la technique du partage de temps (time sharing en anglais). Cette technique
permet à l’OS de faire exécuter plus de processus qu’il n’y a d’unités d’exécution, tout en
donnant l’illusion pour l’utilisateur que ces processus s’exécutent véritablement en paral-
lèle, c’est-à-dire, comme s’ils s’exécutaient chacun sur un CPU dédié. Le principe général
est simple : l’OS va attribuer, à chaque processus qui n’est pas bloqué, le droit de s’exécuter
sur le CPU pour un très court instant (qu’on appelle un quantum de temps). Cela revient à
choisir un processus en attente et à le faire passer par l’état actif pour la durée du quantum de
temps, puis à le faire revenir en attente (à moins qu’il ne se soit retrouvé bloqué entre-temps).
Comme ce quantum sera choisi très court, tous les processus auront l’occasion de progresser

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 coopératif Dans ce cas, le processus consulte régulièrement l’horloge


système et rend la main dès qu’il a épuisé son quantum de temps. Pour rendre la main, le
processus déclenche un appel système, dont le gestionnaire consistera à faire appel à l’or-
donnanceur.
Le bon fonctionnement du système repose donc sur la bonne volonté des programmeurs
qui ont écrit les programmes actifs, et sur le fait que ceux-ci ne se retrouvent pas bloqués
dans leur exécution (dans une boucle infinie, par exemple). Cette solution a été utilisée dans
les anciennes versions de MacOS et de Windows, par exemple. Ces techniques ont l’avan-
tage d’être hautement prévisibles, si on connaît bien les programmes exécutés, et sont donc
encore souvent utilisés dans le cadre des systèmes embarqués.

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

10.2. Gestion de la mémoire secondaire


Terminons ce chapitre en étudiant brièvement la façon dont le système d’exploitation peut
simplifier l’accès à la mémoire secondaire, tout en remplissant son rôle d’arbitre. Pour ce
faire, nous allons commencer par regarder comment la mémoire secondaire est organisée
physiquement ; puis quelle est la structure logique à travers laquelle nous accédons à la mé-
moire secondaire grâce à l’OS ; et enfin comment l’OS fait le lien entre la structure physique
et la structure logique.

10.2.1. Structure physique de la mémoire secondaire


Rappelons que la mémoire secondaire peut prendre plusieurs formes : disque dur, disque
optique (DVD-ROM, CD-ROM), clef USB, etc. Chaque périphérique est divisé en cases qu’on
appelle des blocs (ou clusters) dans le cas de la mémoire secondaire. Ces blocs sont similaires
aux cases de la mémoire primaire, mais de plus grande dimension. Typiquement, les blocs
font quelques kilooctets. Chaque bloc possède une adresse unique, qui est donc une adresse
réelle ou physique. Le CPU peut commander le contrôleur approprié à la mémoire secon-
daire à travers des instructions envoyées sur le bus, en demandant de lire ou d’écrire tel bloc
identifié par son adresse.

10.2.2. Structure logique de la mémoire secondaire


Quiconque a déjà utilisé un ordinateur moderne sait bien qu’on n’accède pas à la mémoire
secondaire à travers des adresses de blocs, mais bien à travers une structure en répertoires,
fichiers, etc. La structure particulière dépend du choix de l’OS, mais nous allons tenter d’ex-
traire certains principes généraux.

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

usr bin home

bin lib rm cp ls aturing

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.

L’OS permet d’accéder au contenu d’un fichier à travers un système d’adresses


virtuelles, similaire à la pagination dans la mémoire primaire. Une adresse est
cette fois-ci constituée de l’identifiant du fichier et d’une position dans ce fi-
chier, qui est vu comme un seul espace contigu. L’OS traduit ces adresses en
numéro de blocs pour communiquer avec le périphérique.

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

10.2.3. De la structure logique à la structure physique

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

Structure Table d’allocation des clusters

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

Table de la racine (contenu du cluster 2)

Nom Ext. Infos Premier cluster


HELLO CPP Fichier, créé en. . . 3
DOS Répertoire,. . . 5

Table du répertoire DOS (contenu du cluster 5)

Nom Ext. Infos Premier cluster


EDIT COM Fichier, créé en. . . 6

F IGURE 10.4. – Un exemple de système de fichiers FAT16.

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

Vous aimerez peut-être aussi