Skip to content

Pour aller plus loin : sous le capot

Quand lire cette page, et pourquoi elle est à part

Le cours Architecture de von Neumann nomme les trois bus et les trois temps du cycle. Cette page les détaille, et ajoute les exercices qui vont avec.

Viens-y après les défis LMC, pas avant : le cycle se comprend beaucoup mieux quand on a déjà écrit et fait tourner ses propres programmes. Rien ici n'est nécessaire pour les défis.

Le programme de première borne le cours aux concepts généraux du modèle. Ce qui est exigible tient donc dans la page de cours ; ce qui suit est du détail utile, et les trois dernières sections sont franchement hors programme.

1. Les trois bus en détail

Un bus n'est pas un tuyau dans lequel les informations circulent les unes après les autres : c'est un groupe de fils parallèles qui portent chacun, en permanence, un signal électrique dans l'un de ses deux états possibles.

Chaque bus est un groupe de fils physiquement séparé des autres. À chaque instant, la mémoire "voit" l'état de tous les fils simultanément. Il n'y a pas de mélange possible : chaque information voyage sur son propre groupe de fils.

C'est le signal sur le bus de contrôle qui déclenche l'action. Sans lui, la mémoire ne fait rien, même si une adresse est présente sur le bus d'adresses.

Exemple : ajouter 2 à la valeur stockée à l'adresse 42

Supposons que la case mémoire 42 contient la valeur 10. Le CPU doit calculer 10 + 2 et réécrire le résultat en mémoire.

Les trois bus travaillent simultanément à chaque étape. C'est leur combinaison qui donne un sens à l'opération : le bus de contrôle dit quoi faire, le bus d'adresses dit où, et le bus de données transporte le contenu.

Étape 1 : Lire la valeur en mémoire

CPU  ══[ LIRE ]══►  Mémoire      (bus de contrôle : le CPU demande une lecture)
CPU  ══[  42  ]══►  Mémoire      (bus d'adresses  : le CPU envoie l'adresse)
CPU  ◄═[  10  ]═══  Mémoire      (bus de données  : la mémoire répond avec la valeur)

Le CPU envoie en même temps l'ordre LIRE et l'adresse 42. La mémoire répond en plaçant la valeur 10 sur le bus de données. Le CPU la récupère dans l'accumulateur.

Étape 2 : Le calcul

L'UAL effectue 10 + 2 = 12. Cette étape est interne au CPU, les bus ne sont pas sollicités.

Étape 3 : Écrire le résultat en mémoire

CPU  ══[ ÉCRIRE ]══►  Mémoire    (bus de contrôle : le CPU demande une écriture)
CPU  ══[   42   ]══►  Mémoire    (bus d'adresses  : le CPU envoie l'adresse)
CPU  ══[   12   ]══►  Mémoire    (bus de données  : le CPU envoie la valeur)

Le CPU envoie en même temps l'ordre ÉCRIRE, l'adresse 42 et la valeur 12. La mémoire reçoit les trois informations et écrit 12 à l'adresse 42.

À retenir : À l'étape 1, le bus de données va de la mémoire vers le CPU (la mémoire répond). À l'étape 3, il va du CPU vers la mémoire (le CPU envoie). Les bus d'adresses et de contrôle vont toujours du CPU vers la mémoire : c'est le CPU qui commande.

La case mémoire 42 contient maintenant 12.

Exercices sur les bus

Le CPU veut lire la valeur stockée à l'adresse 7

Complète le tableau :

Bus Valeur transportée Sens
Bus d'adresses ? ? → ?
Bus de contrôle ? ? → ?
Bus de données ? ? → ?
Réponse
Bus Valeur transportée Sens
Bus d'adresses 7 CPU → Mémoire
Bus de contrôle LIRE CPU → Mémoire
Bus de données la valeur à l'adresse 7 Mémoire → CPU

Le bus de données va de la mémoire vers le CPU car c'est une lecture : la mémoire répond.

Le CPU veut écrire la valeur 25 à l'adresse 3

Complète le tableau :

Bus Valeur transportée Sens
Bus d'adresses ? ? → ?
Bus de contrôle ? ? → ?
Bus de données ? ? → ?
Réponse
Bus Valeur transportée Sens
Bus d'adresses 3 CPU → Mémoire
Bus de contrôle ÉCRIRE CPU → Mémoire
Bus de données 25 CPU → Mémoire

Cette fois, les trois bus vont dans le même sens : le CPU envoie tout. C'est la différence avec la lecture.

Qu'est-ce qui changerait si on supprimait le bus de contrôle ?

On garde le bus d'adresses et le bus de données. Le CPU envoie l'adresse 3 et la valeur 25.

La mémoire doit-elle lire ou écrire ? Peut-elle le savoir ?

Réponse

Non, elle ne peut pas le savoir. Sans bus de contrôle, la mémoire voit une adresse (3) et une donnée (25) mais ne sait pas si elle doit :

  • lire la valeur à l'adresse 3 et la mettre sur le bus de données, ou
  • écrire 25 à l'adresse 3

Le bus de contrôle est indispensable pour lever cette ambiguïté.

2. Le cycle fetch-decode-execute en détail

Le cours dit que le processeur répète un cycle en trois temps. Voici ce que chaque temps recouvre exactement.

2.1 Fetch (recherche)

  1. L'UC lit l'adresse de la prochaine instruction dans le compteur ordinal (ou Program Counter)
  2. L'instruction est chargée depuis la mémoire vers le registre d'instruction
  3. Le compteur ordinal est incrémenté pour pointer vers l'instruction suivante

2.2 Decode (décodage)

L'UC analyse l'instruction pour déterminer :

  • Quelle opération effectuer
  • Quelles données utiliser (adresses, registres)

2.3 Execute (exécution)

L'UC commande l'UAL ou les autres composants pour réaliser l'opération.

Le cycle complet, sur une instruction LMC

Programme : additionner 3 + 5

00: LDA 10    // Charger la valeur à l'adresse 10
01: ADD 11    // Ajouter la valeur à l'adresse 11
02: STA 12    // Stocker le résultat à l'adresse 12
03: HLT       // Arrêter
...
10: DAT 3     // Première valeur
11: DAT 5     // Deuxième valeur
12: DAT 0     // Résultat (initialement 0)

Déroulement du cycle pour l'instruction ADD 11 (adresse 01) :

  • Fetch :

    • Le PC contient 01
    • L'UC lit l'instruction à l'adresse 01 : ADD 11 (code machine : 1011)
    • L'instruction est chargée dans le registre d'instruction
    • Le PC est incrémenté : PC = 02
  • Decode :

    • L'UC décode 1011 : le premier chiffre, 1, désigne l'instruction ADD
    • Opérande : adresse 11
    • Opération à effectuer : ajouter le contenu de l'adresse 11 à l'accumulateur
  • Execute :

    • L'UC lit la valeur à l'adresse 11 : 5
    • L'UAL additionne : accumulateur (3) + 5 = 8
    • Le résultat 8 est stocké dans l'accumulateur

Puis le cycle recommence avec l'instruction suivante à l'adresse 02 (STA 12).

Exercices sur le cycle

Dérouler un programme, phase par phase

Reprends le programme du cours :

Adresse | Instruction        Adresse | Valeur
--------|------------        --------|-------
0       | LDA 10             10      | 5
1       | ADD 11             11      | 3
2       | STA 12             12      | ?
3       | HLT

Recopie et remplis ce tableau. Cette fois, détaille les trois phases pour chaque instruction :

Instruction PC avant le fetch Fetch Decode Execute ACC après
LDA 10
ADD 11
STA 12
HLT

Une fois ton tableau rempli, saisis ce programme dans le simulateur, lance-le en mode pas à pas, et vérifie ta trace.

Avant d'ouvrir la correction

Explique en une phrase ce que tu as prédit pour l'accumulateur après ADD 11, et pourquoi. Puis compare.

Réponse

Instruction 0 : LDA 10

  • PC = 0 avant le fetch
  • Fetch : charge l'instruction à l'adresse 0 dans le registre d'instruction. PC passe à 1.
  • Decode : identifie LDA (code opération 5), adresse source = 10
  • Execute : lit la valeur à l'adresse 10 (= 5), la place dans l'accumulateur
  • ACC = 5

Instruction 1 : ADD 11

  • PC = 1 avant le fetch
  • Fetch : charge l'instruction à l'adresse 1. PC passe à 2.
  • Decode : identifie ADD (code opération 1), adresse source = 11
  • Execute : lit la valeur à l'adresse 11 (= 3), l'additionne à l'accumulateur (5 + 3)
  • ACC = 8

Instruction 2 : STA 12

  • PC = 2 avant le fetch
  • Fetch : charge l'instruction à l'adresse 2. PC passe à 3.
  • Decode : identifie STA (code opération 3), adresse destination = 12
  • Execute : écrit la valeur de l'accumulateur (8) à l'adresse 12
  • La case mémoire 12 contient maintenant 8

Instruction 3 : HLT. Le programme s'arrête.

Les bus pendant le fetch

Toujours sur le même programme. Lors de la phase fetch de l'instruction rangée en 1 (ADD 11) :

  1. Que transporte le bus d'adresses ? Dans quel sens ?
  2. Que transporte le bus de contrôle ?
  3. Que transporte le bus de données ? Dans quel sens ?
Réponse
  1. Le bus d'adresses transporte 1 (l'adresse de l'instruction à charger, celle contenue dans le PC). Sens : CPU → Mémoire.
  2. Le bus de contrôle transporte LIRE (le CPU veut récupérer l'instruction). Sens : CPU → Mémoire.
  3. Le bus de données transporte l'instruction ADD 11, c'est-à-dire le nombre 1011. Sens : Mémoire → CPU.

Le fetch est toujours une lecture en mémoire : le CPU va chercher l'instruction.

3. Les registres

Les registres sont de petites mémoires ultra-rapides intégrées au processeur. Le cours n'en nomme que deux, le compteur ordinal et l'accumulateur. Voici la liste complète de ceux qui interviennent dans le cycle :

  • Compteur ordinal (PC) : contient l'adresse de la prochaine instruction
  • Registre d'instruction (RI) : contient l'instruction en cours d'exécution
  • Accumulateur : contient la valeur sur laquelle l'UAL travaille, et le résultat de son calcul
  • Registres généraux : stockent temporairement des données pour les calculs

Le LMC n'a qu'un seul registre de travail, l'accumulateur : c'est ce qui rend ses programmes si contraignants à écrire, et c'est voulu. Un processeur réel en possède plusieurs dizaines.

4. Le goulot de von Neumann

Hors programme.

Les instructions et les données partagent la même mémoire et les mêmes bus. Le processeur passe donc une partie de son temps à attendre la mémoire, et cette attente devient le facteur limitant : c'est le goulot de von Neumann.

Trois réponses courantes, qui ne suppriment pas le problème mais le réduisent :

  • la mémoire cache, petite et très rapide, placée entre le processeur et la mémoire vive ;
  • l'architecture Harvard, qui sépare la mémoire des instructions de celle des données ;
  • le pipeline, qui commence le fetch de l'instruction suivante pendant qu'on exécute la courante.

5. De ton fichier à l'exécution, sur une vraie machine

Hors programme.

Sur le LMC, tu as écrit un fichier, cliqué sur « Assembler », puis sur « Charger ». Ces deux boutons ne sont pas une commodité du simulateur : ils sont la chaîne que suit tout programme, sur n'importe quelle machine.

Tu n'écris jamais des nombres. Tu écris du texte, dans un fichier, sur le disque. Ce texte n'est pas exécutable : le processeur ne lit pas des lettres.

Il se passe donc trois choses, dans cet ordre, et ce sont les mêmes pour Python, pour un jeu vidéo ou pour le système d'exploitation.

graph LR
    S["Fichier source<br/>du texte, sur le disque"] -->|assemblage| O["Fichier objet<br/>des nombres, sur le disque"]
    O -->|chargement| R["Mémoire vive<br/>les mêmes nombres, en RAM"]
    R -->|cycle fetch-decode-execute| P["Processeur"]
  1. Tu écris le source. Du texte lisible par toi : des mots, des noms que tu choisis, des commentaires. Il est rangé sur le disque, comme n'importe quel fichier.
  2. Un programme le traduit : l'assembleur. Il remplace chaque mot par le nombre que le jeu d'instructions de la machine lui associe, et il produit un second fichier, le fichier objet, qui ne contient plus que des nombres.
  3. Le fichier objet est chargé en mémoire vive. Le disque est un périphérique d'entrée-sortie : le processeur ne peut pas y exécuter quoi que ce soit. Il faut d'abord recopier les nombres en RAM. C'est seulement là que le compteur ordinal peut s'y promener.

Ce que l'assembleur enlève, et c'est le point important

Le fichier objet ne contient ni mnémonique, ni nom de variable, ni commentaire. Tout cela a disparu à la traduction : c'était pour toi, pas pour la machine.

Un assembleur n'est donc pas un programme intelligent. C'est un programme qui applique une table, exactement celle que ton groupe a fabriquée à la première séance. Un compilateur, que tu rencontreras plus tard, fait un travail beaucoup plus difficile : il traduit un langage où une seule ligne peut valoir des dizaines d'instructions machine.

Exemple : ce qu'un simple print demande à un vrai processeur

Voici ce qu'il faut écrire, pour un processeur de ton ordinateur, afin d'obtenir ce que Python écrit en une seule ligne, print("truc"). Rien ici n'est au programme, et tu n'as pas à le comprendre : regarde seulement la longueur.

; truc.asm
; Assemblage        : nasm -f elf64 truc.asm -o truc.o
; Édition de liens  : ld truc.o -o truc
; Exécution         : ./truc

section .data
    msg     db "truc", 10      ; le message, avec un saut de ligne (10 = '\n')
    msg_len equ $ - msg        ; longueur calculée automatiquement

section .text
    global _start

_start:
    ; appel système write(1, msg, msg_len)
    mov     rax, 1             ; numéro de syscall pour write
    mov     rdi, 1             ; descripteur de fichier 1 = stdout
    mov     rsi, msg           ; adresse du message
    mov     rdx, msg_len       ; nombre d'octets à écrire
    syscall

    ; appel système exit(0)
    mov     rax, 60            ; numéro de syscall pour exit
    xor     rdi, rdi           ; code de retour 0
    syscall

Une fois assemblé, cela fait huit instructions machine pour afficher quatre lettres. Et c'est le minimum : ce programme se contente de demander au système d'exploitation d'écrire cinq octets, là où le print de Python fait beaucoup plus de choses avant d'en arriver là.

Tu y reconnais des mots de ce cours : une adresse (msg), des registres (rax, rdi, rsi, rdx), un fichier source en texte, et un assembleur qui le traduit en nombres. Le reste, les numéros d'appel système et le rôle de chaque registre, s'apprend ailleurs.

Et ces huit instructions ne sont pas le bout du chemin. syscall ne fait qu'une chose : passer la main au système d'exploitation, qui ira écrire, lui, dans le terminal. Ce que la machine exécute ensuite est encore du code, écrit par d'autres, et il est de deux natures.

  • La porte d'entrée du système, elle, est bien écrite en assembleur, à la main (dans Linux, le fichier arch/x86/entry/entry_64.S). Elle doit sauvegarder les registres, changer de pile et basculer le processeur en mode noyau : des gestes qu'aucun langage de haut niveau ne sait exprimer.
  • Mais le write lui-même, celui qui écrit vraiment tes cinq octets, est écrit en C (fs/read_write.c), comme l'immense majorité du noyau. La porte est en assembleur, la pièce derrière ne l'est pas.

Ce qui reste vrai des deux côtés : assembleur ou C, tout finit en instructions machine, parce que le processeur ne sait rien lire d'autre. Et cela se mesure : sur une machine Linux, afficher ces quatre lettres demande environ dix mille instructions exécutées dans le noyau, contre les huit que tu as écrites. Tu n'as écrit ici que celles qui frappent à la porte.

Si tu veux l'essayer : nsi install nasm, puis les trois commandes écrites en tête du fichier.

6. Jeux d'instructions et architectures

Hors programme.

  • Jeu d'instructions : l'ensemble des opérations qu'un processeur sait exécuter. Les familles CISC (beaucoup d'instructions, complexes) et RISC (peu d'instructions, simples et rapides) reposent sur deux paris opposés.
  • Architectures alternatives : Harvard, et les variantes dites de von Neumann modifiée.

7. Ce que la machine voit vraiment : des nombres

À faire après les défis du langage machine.

7.1 Chaque instruction est un nombre

LDA, STA, les noms de cases : tout cela est pour toi. Dans la mémoire, il n'y a que des nombres de quatre chiffres. Les deux premiers disent quoi faire, les deux derniers sur quelle case. Ainsi 5042, c'est LDA 42.

Instruction Nombre
INP 9001
OUT 9002
HLT 0000
LDA 42 5042
STA 42 3042
ADD 42 1042
SUB 42 2042
BRA 42 6042
BRZ 42 7042
BRP 42 8042

Traduire à la main, une fois

Chaque ligne occupe une case, à partir de la case n°0.

Case n° Instruction
0 INP
1 STA 06
2 INP
3 ADD 06
4 OUT
5 HLT
6 DAT

Sur ton cahier : écris le nombre que contient chaque case.

Ensuite, dans le simulateur : assemble ce programme, ouvre le fichier .lmcobj qui apparaît et compare. Remplace 1006 par 2006, enregistre, clique Charger sans réassembler, puis Run : le programme fait maintenant une soustraction. C'est bien le fichier de nombres qui tourne.

Correction

9001 3006 9001 1006 9002 0000 0000. Il lit deux nombres et affiche leur somme.

7.2 Le saut peut tomber sur une donnée

Prédire l'impossible

Case n° Instruction
0 LDA 04
1 OUT
2 BRA 04
3 HLT
4 DAT 9002

Sur ton cahier : la liste des nombres affichés, et pourquoi le programme s'arrête alors qu'il ne passe jamais par la case n°3.

N'ouvre pas le simulateur tant que tu n'as pas écrit tes réponses.

Indice

Compare le nombre de la case n°1 et celui de la case n°4.

Correction

Il affiche −998, puis −998 encore.

  • Case n°0 : l'accumulateur reçoit le mot 9002. Case n°1 : premier affichage. La machine l'écrit −998, parce qu'elle lit tout mot à partir de 5000 comme un négatif ; le mot rangé, lui, vaut bien 9002.
  • Case n°2 : la machine va à la case n°4 et la lit comme une instruction. Or 9002, c'est OUT : second affichage.
  • Case n°5 : jamais écrite, elle vaut 0000, c'est-à-dire HLT.

Rien, dans la mémoire, ne distingue une instruction d'une donnée. C'est le compteur ordinal qui décide.

Le même mot a même été lu de trois façons ici : l'instruction OUT, le nombre −998, et les quatre chiffres 9002 que montre l'inspecteur mémoire. Une seule case, trois lectures.

7.3 Cybersécurité : quand une donnée devient du code

Un éditeur vend un logiciel qui demande une clé de licence au démarrage. Camille en a une copie, sans clé. Le programme a deux maladresses, comme en ont tous les logiciels, souvent nées d'un copier-coller.

Case n° Programme Ce que ça fait
00 INP lire la clé tapée
01 STA cle la ranger dans la case cle
02 SUB licence clé tapée moins clé enregistrée
03 BRZ demarre si ça vaut 0, la clé est bonne
04 LDA zero
05 OUT afficher 0 : refusé
06 cle: DAT 0 la case où atterrit la clé tapée
07 OUT afficher la clé tapée, pour la relire
08 HLT
09 demarre: LDA un
10 OUT afficher 1 : le logiciel démarre
11 HLT
12 licence: DAT 4242 la clé enregistrée
13 zero: DAT 0
14 un: DAT 1

Les deux maladresses

  • La case cle est au milieu du chemin : la machine va passer dessus et exécuter ce qu'elle contient.
  • La case n°07 ne fait pas ce que l'auteur croit : OUT affiche l'accumulateur, pas la case cle.

Attaque 1 : faire afficher la clé

Tu ne peux rien changer au programme, seulement le nombre que tu tapes. Quel nombre taper pour que le logiciel affiche 4242 ?

Indice

Ce que tu tapes atterrit en case n°06, et la machine va l'exécuter. Il faudrait que ce soit LDA licence, c'est-à-dire LDA 12.

Correction

5012, le nombre de LDA 12. Le refus s'affiche (0), puis la case n°06 met 4242 dans l'accumulateur, et la case n°07 l'affiche. Le logiciel a donné sa propre clé.

Attaque 2 : démarrer sans clé

Quel nombre taper pour que le logiciel démarre ?

Correction

6009, le nombre de BRA 09. Après le refus, la case n°06 fait sauter la machine en case n°09, derrière le contrôle. C'est un crack.

Ce qu'il faut en retenir

Une vérification faite sur la machine de celui qu'elle doit arrêter ne protège rien. D'où ce que font les éditeurs : vérifier la clé sur un serveur, ne jamais ranger un secret en clair dans un programme, marquer des zones de mémoire non exécutables, et ne jamais ranger une saisie sans la vérifier. Le même mécanisme porte des noms que tu entendras : injection de code, débordement de tampon.

Utiliser un logiciel payant sans licence est illégal. On étudie le mécanisme, pas la pratique.


Sources :

  • von Neumann, J. (1945). First Draft of a Report on the EDVAC. University of Pennsylvania.
  • Tanenbaum, A. S. (2013). Structured Computer Organization (6e éd.). Pearson.