Le langage machine

(actualisé le ) par administrateur


1. Le transistor, composant électronique de base d ’un processeur


Le processeur d’un ordinateur est composés de composants électroniques appelés transistors. Un processeur actuel comporte plusieurs milliards de transistors.

Le nombre de transistors sur un processeur suit la loi de Moore, du nom d’un des fondateurs d’Intel. Celle loi prédit que le nombre de transistors sur une puce de processeur double tous les 18 mois.

Depuis le tout premier circuit intégré (le 4004) jusqu’à nos jours, cette loi s’est toujours vérifiée, même si on s’en éloigne actuellement. Voici un exemple avec quelques processeurs d’Intel :

  • 1971 : Intel 4004 : 2 300 transistors
  • 1978 : Intel 8086 : 29 000 transistors
  • 1982 : Intel 80286 275 000 transistors
  • 1989 : Intel 80486 : 1 160 000 transistors
  • 1993 : Pentium : 3 100 000 transistors
  • 1995 : Pentium Pro : 5 500 000 transistors
  • 1997 : Pentium II : 27 000 000 transistors
  • 2001 : Pentium 4 : 42 000 000 transistors
  • 2004 : Pentium Extreme Edition : 169 000 000 transistors
  • 2006 : Core 2 Quad : 582 000 000 transistors
  • 2010 : Core i7 : 1 170 000 000 transistors

Remarques :

  • L’augmentation du nombre de transistors sur une puce est possible par la miniaturisation des transistors. Néanmoins, cela ne ne sera pas possible indéfiniment. En 2010, les transistors sont gravés en 32 nanomètres, soit 32 milliardièmes des mètres : c’est la taille de quelques dizaines d’atomes.
  • En réduisant trop la taille de la structure en maillage de transistors, il risque de se créer des interactions électroniques parasites entre les transistors eux même (l’effet tunnel par exemple, un effet de la mécanique quantique) et cela peut créer des problèmes...
  • Un jour, il faudra donc utiliser une solution autre que celle des puces en silicium, si l’on veut continuer à augmenter la puissance des ordinateurs...

2. L’architecture de Von Neuman


Pour traiter une information, un microprocesseur seul ne suffit pas, il faut l’insérer au sein d’un système minimum de traitement programmé de l’information. John Von Neumann est à l’origine d’un modèle de machine universelle de traitement programmé de l’information (1946). Cette architecture sert de base à la plupart des systèmes à microprocesseur actuel. Elle est composé des éléments suivants :

  • une unité centrale
  • une mémoire principale
  • des interfaces d’entrées/sorties

Les différents organes du système sont reliés par des voies de communication appelées bus.

Architecture de Von Neumann

Remarques :

  • La présence d’un "décodeur d’adresses" est nécessaire du fait de la multiplication des périphériques autour du processeur.
  • Le processeur peut communiquer sur le bus de données avec les différentes mémoires et les différents périphériques. Pour éviter les conflits on attribue à chaque périphérique un zone d’adresse et une fonction "décodage d’adresse" chargée de "mettre" le processeur en communication avec le bon périphérique

Le processeur travaille par cycles. Ces cycles sont effectués sur l’ensemble du système de transistors et chaque cycle permet d’avancer dans les calculs. Pour donner un ordre de grandeur, un processeur dont la fréquence est de 3,4 GHz effectue 3,4 milliards de cycles par seconde. Ce cadencement du processeur est assuré par une horloge (un quartz qui vibre sous l’effet piézoélectrique). Concrètement, le signal d’horloge est un signal électrique alternatif qui déclenche un nouveau cycle de calcul à chaque période du signal. Le processeur suit le signal d’horloge et effectue un nouveau calcul à chaque cycle. Les calculs sont donc effectués les uns à la suite des autres.


3. Le langage assembleur


Les transistors d’un microprocesseur fonctionnent en mode saturé comme des interrupteurs : soit ils laissent passer le courant (état noté 1), soit ils ne le laissent pas passer (état noté 0).

Le langage machine est le seul langage qu’un processeur puisse exécuter : il est composé d’une suite de 0 et de 1 représentant les deux états électriques possibles d’un transistor fonctionnant en mode saturé. Chaque famille de processeurs (x86, amd64, arm...) utilise un jeu d’instructions différent.

Remarque : contrairement aux langages évolués tels que PYTHON, C++..., l’assembleur, ou « langage d’assemblage » est constitué d’instructions directement compréhensibles par le microprocesseur ; c’est ce qu’on appelle un langage de bas niveau.

Si on veut que le processeur effectue un calcul, il faudrait lui communiquer quelque chose de ce genre là :

« 01010100 10110101 01101101 110110101 10101010 11011110 10101011 10111100 1100011 0101101 01111110 »

Ceci est totalement impossible pour un humain. Afin d’alléger l’écriture, on peut utiliser à la place du binaire, l’hexadécimal : « 54 B5 6D DA D5 6F 55 DE 63 5A FC ». Cela reste illisible pour un humain.

Les fabricants de processeurs ont donc donné des noms « facile à retenir » pour chaque instruction en binaire. Par exemple, le nom mov correspond à l’instruction 10101101 01111001. L’ensemble des noms faciles à retenir constitue le langage assembleur.

Voici, par exemple, quelques-unes des quelques dizaines d’instructions utilisées pour programmer un processeur Intel x86 :

  • mov : (move) déplace le contenu d’une case mémoire dans une autre case ;
  • inc : (increment) incrémente la valeur contenue dans une case mémoire ;
  • neg : (negative) prend un nombre et donne son opposé ;
  • cmp : (compare) compare deux nombres ;
  • jmp : (jump) « saute » à un autre endroit du programme.

Le langage assembleur n’est constitué que des instructions basiques comme celles-ci.

Exemple : un programme en assembleur...

loadn 14 R0 # place le nombre 14 dans le registre R0 (processeur)
loadn 2 R1 # place le nombre 2 dans le registre R1 (processeur)
storer R1 R2 # copie le contenue de R1 dans R2 (processeur)
storer R1 R3 # copie le contenue de R1 dans R3 (processeur)
add R0 R2 # addition R0+R2 et place le résultat dans R2 (processeur)
mul R0 R3 # multiplication R0×R3 et place le résultat dans R3 (processeur)
store R0 0 # place le contenu de R0 à l’adresse mémoire @0 (RAM)
store R1 1 # place le contenu de R1 à l’adresse mémoire @1 (RAM)
store R2 2 # place R2 dans @2 (RAM)
store R3 6 # place R3 dans @6 (RAM)

Stockage dans la RAM

Remarque : Un registre est un emplacement de mémoire interne à un processeur. Les registres se situent au sommet de la hiérarchie mémoire : il s’agit de la mémoire la plus rapide d’un ordinateur, mais dont le coût de fabrication est le plus élevé car la place dans un microprocesseur est limitée. Leur capacité dépasse donc rarement quelques dizaines d’octets.

Même si coder en assembleur est plus rapide qu’en binaire, cela reste une étape fastidieuse : manipuler les données et les placer octet par octet dans la mémoire ou dans les registres, c’est long.

De plus que chaque processeur possède des noms et des numéros d’instructions différents ! Ainsi, un code en assembleur qui fonctionne sur un processeur Pentium ne marchera pas forcément sur un AMD 64 ou sur un processeur ARM. Si le programme ne marche que sur un processeur et non sur un autre, on dit que le programme n’est pas portable.

Dès les débuts de l’informatique moderne on a donc inventé un moyen pour palier à ces deux inconvénients que sont la lenteur à coder et la non portabilité : les langages de haut niveau tels que JAVA, PYTHON, PERL, PHP, C++... Ceux-ci permettent une programmation rapide et plus simple que l’assembleur. Le code source est humainement compréhensible. Chaque langage possède sa syntaxe et un champ d’application particulier.

Le problème des langages de haut niveau c’est qu’ils ne sont absolument pas compréhensibles par le processeur ! Avant de pouvoir être utilisé, le code doit être traduit en langage assembleur. Cette traduction, c’est ce qu’on appelle la compilation. Une fois en langage assembleur, il faut ensuite l’assembler en langage binaire :

Compilation et assemblage

Les langages de haut niveaux permettent donc d’accélérer la création d’un programme, et de le rendre portable !


4. Binaire (base 2)


Dans la vie de tous les jours, on compte en base 10 (la base décimale), c’est-à-dire que nos nombres comportent 10 chiffres allant de 0 à 9. Pourquoi ? Parce que nous avons 10 doigts et que nos nombres héritent de la méthode ancestrale consistant à compter avec nos doigts. Rappelons que, l’écriture n = 2013 (base 10) signifie que :

Les ordinateurs quant à eux, utilisent la base 2 qui comporte 2 chiffres : 0 et 1.

Pour obtenir la représentation en base 2 d’un nombre donné en base 10, on effectue une succession de divisions euclidiennes par 2, jusqu’à obtenir un quotient égal à 0.

Exemple : 11 (base 10) = 1011 (base 2)

Réciproquement, pour convertir un nombre binaire en nombre décimal, on le décompose en la somme des puissances de 2 successives :


5. L’octet, unité de stockage de l’information


En informatique, un bit (binary digit) peut prendre la valeur 0 ou 1. Un octet est un multiplet de 8 bits codant une information.

Exemple : 1010 1010

Dans ce système de codage, s’appuyant sur le système binaire, un octet permet de représenter 2 à la puissance 8 nombres, soit 256 valeurs différentes. Un octet permet de coder des valeurs numériques ou jusqu’à 256 caractères différents.

Exemples :

  • 0000 0000 (code 0 en décimal)
  • 1111 1111 (code 255 en décimal)

Le terme octet est couramment utilisé comme unité de mesure en informatique (symbole : o) pour indiquer la capacité de stockage des mémoires (mémoire vive ou morte, capacité des clés USB ou des disques, etc.). À cette fin, on utilise couramment des multiples de l’octet, comme le kilooctet (ko) ou le mégaoctet (Mo).

Cette unité permet aussi de quantifier la rapidité de transfert d’informations en octets par seconde.

Traditionnellement, les préfixes « kilo », « méga », « giga », etc. dans le monde informatique, ne représentaient pas une puissance d’un nombre en base 10, mais une puissance d’un nombre en base 2. Cependant cette tradition viole les normes en vigueur pour les autres unités, et n’est même pas appliquée uniformément aux tailles exprimées en octets, notamment dans la mesure de la capacité des disques durs. Une nouvelle norme a donc été créée pour noter les multiples :« kibi », « mébi », « gibi », etc.

Mutliples et sous multiples de l’octet (source Wikipédia)

6. Hexadécimal (base 16)


Le langage assembleur utilise préférentiellement la base 16. Les 16 symboles utilisés en hexadécimal sont :

  • 10 chiffres : de 0 à 9
  • 6 lettres : de A à F

Le chiffre A équivaut à 10 en décimal, B à 11, etc.

DEC 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
HEX 0 1 2 3 4 5 6 7 8 9 A B C D E F

Chaque chiffre d’un nombre exprimé en hexadécimal est associé à une puissance de 16.

Par exemple :

Réciproquement :

On peut également directement convertir le binaire en hexadécimal en raisonnant sur des demi-octets :


7. Codage des caractères


Le code ASCII (American Standard Code for Information Interchange) définit un jeu de 128 caractères numérotés de 0 à 127. Sept bits suffisent donc à les coder. Cependant, comme la plupart des ordinateurs travaillent sur des données représentées sur un nombre de bits multiple de huit, les caractères ASCII sont codés sur un octet, le bit de poids fort étant fixé à 0.

Le jeu de caractères ASCII comprend :

  • les 26 lettres de l’alphabet latin en versions majuscules et minuscules,
  • les 10 chiffres,
  • les symboles de ponctuation y compris l’espace,
  • les couples de parenthèses, crochets et accolades

et quelques autres symboles divers. Le codage ASCII, utilisé pour coder les texte en anglais ne comprend pas les lettres accentuées.

Tous ces caractères, parfois désignés comme caractères imprimables ont un code compris entre 32 pour l’espace et 126 pour le tilde ( ).

Les autres caractères de code compris entre 0 et 31, plus le caractère de code 127, sont appelés caractères de contrôle. On y trouve entre autre la tabulation (notée \t dans les chaînes de caractères) de code 9 et le passage à la ligne (noté \n dans les chaînes) de code 10.

Pour coder des caractères ou des symboles de toutes les langues en tenant compte y compris des acce,tatuations, il a été nécessaire d’utiliser une nouvelle norme complémentaire à l’ASCII : l’UTF8.


8. Codage des images


Il existe deux grandes catégories d’images :

  • les images bitmap ou pixelisées. L’image est décrite point par point. Chaque point correspond à un pixel et chaque pixel est décrit par un nombre indiquant sa couleur. L’image est donc représentée par une série de nombres stockée dans un tableau. Le codage est simple mais l’image bitmap occupe beaucoup de place mémoire : les pixels sont petits, donc nombreux ! Ce qui explique la nécessité de compression.
  • les images vectorielles. Elles sont des représentations d’entités géométriques telles qu’un cercle, un rectangle ou un segment. Ceux-ci sont représentés par des formules mathématiques (un rectangle est défini par deux points, un cercle par un centre et un rayon, une courbe par plusieurs points et une équation). C’est le processeur qui sera chargé de "traduire" ces formes en informations interprétables par la carte graphique.

Au niveau du périphérique (affichage ou impression), l’image est toujours matricielle, elle se présente donc sous la forme d’un tableau, chaque case du tableau contient une valeur qui représente la couleur du pixel associé. Il existe différents codages suivant le nombre de bits des cases du tableau dans lesquelles sont stockés les pixels :

  • Bimap noir et blanc : Chaque case contient 1 bit (0 ou 1) permettant de définir 2 couleurs : noir ou blanc.
  • Bitmap 16 couleurs : chaque case contient 4 bits permettant de définir 16 couleurs (24 possibilités).
  • Bitmap 256 couleurs : chaque case contient 8 bits (1 octet) permettant de définir 256 couleurs (28 possibilités).
  • Codage RVB (Rouge, Vert, Bleu) on peut attribuer 3 valeurs à chaque pixel : Rouge (de 0 à 255), Vert (de 0 à 255) et Bleu (de 0 à 255) ce qui correspond à 256 x 256 x 256 = 16 777 216 de couleurs différentes.

On utilise aujourd’hui des écrans possédant une définition spatiale WSXGA : composés de 1 764 000 pixels (1 050 lignes et 1680 colonnes). Chaque pixel contenant une couleur codée sur 3 octets, il faut donc disposer d’une mémoire d’environ 5 mégaoctets. Aujourd’hui la difficulté n’est plus de stocker les images de 5Mo mais de les transférer rapidement et en grand nombre dans le réseau. La nécessité de compression reste donc toujours d’actualité.
On distingue deux grandes familles de compression d’images : avec ou sans perte.

  • Compression avec perte : dans ce cas, on accepte de dégrader légèrement la qualité de l’image reconstruite (après avoir été compressée) pour diminuer la taille de la mémoire nécessaire à son stockage. Un œil humain ne peut distinguer qu’un nombre limité de nuances de couleurs, il ne fera pas la différences entre une image originale et la même image dont les couleurs sont codées sur 2 octets au lieu de 3. On obtient ainsi très facilement un gain de stockage de l’ordre de 30 %.
  • Compression sans perte : dans ce cas, on se contente de diminuer cette taille tout en préservant l’intégralité des informations contenues dans l’image initiale. Par exemple, en repérant dans l’image des suites de n pixels voisins possédant la même couleur C ; on remplace alors chaque suite de n fois la valeur associée à C, donc stockée sur n x 3 octets, par un seul couple (n, C) qui sera stocké sur 1 + 3 oct.


9. Conclusion


Il faut retenir que ....

  • Les informations (caractères, images...) utilisées par un ordinateur sont stockées sous la forme de nombres binaires.
  • Un bit à la valeur 0 ou 1.
  • Un octet est composé de 8 bits : c’est l’unité d’information utilisée en informatique.
  • Le processeur effectue des calculs sur des nombres.
  • Ces nombres sont stockés dans la mémoire .
  • Les nombres transitent entre le processeur et la mémoire en empruntant un chemin appelé bus.
    Le processeur et la mémoire sont des assemblages de transistors fonctionnant en régime saturé.
  • Le nombre de transistors dans un processeur est de l’ordre du milliard, et ils sont gravés sur une plaque de silicium, à même la matière .
  • Les processeurs permettent de faire un très grand nombre de types de calculs différents.
  • Le processeur effectue un calcul élémentaire par cycle ; plusieurs cycles à la suite permettent à l’ordinateur de faire des calculs complexes .
  • La fréquence du processeur est égale au nombre de cycles par seconde : elle s’exprime en Hz. Plus elle est haute, plus le processeur calcule vite, et donc plus il est puissant.
  • La fréquence d’un processeur est de l’ordre du GigaHertz.
  • Les programmes sont des suites d’instructions en binaire que le processeur exécute.
  • Le langage assembleur est une transcription humainement compréhensible des instructions en binaire. C’est un langage de bas niveau.
  • L’assembleur devient très vite long et lent à coder. On a donc inventé les langages de haut niveau.
  • Les langages de haut niveau sont faciles et rapides à coder mais il faut passer par une étape supplémentaire appelée compilation pour pouvoir utiliser finalement le programme.
  • Enfin, il y a plein de langages de haut niveau ayant chacun sa propre syntaxe d’écriture (C++, Java, Python...).