|
|||||||||||||||
|
Pour bien comprendre l'assembleur, il faut déjà comprendre le fonctionnement interne d'un ordinateur. En effet, l'assembleur permet de toucher directement le système à tous les niveaux : processeur, mémoires, périphéries, etc. Donc, pour bien gérer tout ceci, nous allons donc rappeler les grandes lignes du fonctionnement d'un système informatique.
Un ordinateur se compose principalement
d'un processeur (CPU), de mémoire (RAM et ROM), et de composants périphériques
(clavier, port série, contrôleur en tout genre, carte son, etc.). Le CPU est
bien sur le maître à bord dans un ordinateur (comme dans une console de jeux
d'ailleurs), la mémoire et les périphériques étant à son service. Le CPU est
connecté aux périphériques et à la mémoire à l'aide d'un bus. Le bus est une
'chaîne' ou tous les périphériques sont connectés en parallèle. Un bus système
se divise généralement en trois 'sous bus' :
- Le bus de données (c'est la partie qui transporte les données brutes). Notons que plus ce bus est large plus le système sera rapide. En effet un système 16 Bits est plus rapide qu'un système 8 bits car il est capable de 'transporter' 2 octets à la fois alors que le système 8 bits n'en transporte qu'un à la fois. (1 octet=8bits)
- Le bus d'adressage (c'est celui qui désigne un endroit dans la mémoire ou un périphérique). Plus il est large plus on peut adresser de la mémoire. Exemple : bus d'adressage 16bits =64Ko addressable ,bus 32bits=4Go!)
- Le bus de contrôle (Il désigne le mode
de l'opération à effectuer : Lecture ou écriture, périphérique ou Mémoire, Interruption, etc.)
Voici un exemple de diagramme d'un système :
Pour exécuter un programme, le CPU va chercher
les instructions dans la mémoire et les exécuter de façon séquentielle. Le CPU
génère pour cela les signaux nécessaires sur le bus pour lire dans la mémoire.
Chaque périphérique répond à une adresse précise (par exemple sur PC, le clavier
se situe a l'adresse n°96), et il ne doit pas y avoir 2 périphériques partageants
la même adresse sinon il y a conflit... (courant sur PC).
Notons qu'il y a des composants permettant d'accélérer le système : par exemple,
le DMA (Direct Memory Access) permet de faire des transferts entre un périphérique
et la mémoire sans passer par le CPU. Les transferts sont alors très rapides
et cela libère le CPU pour une autre tache.
Pour programmer sur un système, il faut alors programmer le composant principal
en premier lieu : le CPU. Pour cela, on fait appel à l'Assembleur.
L'Assembleur permet de contrôler directement le CPU : Ceci permet d'avoir une totale maîtrise du système et surtout permet de faire des programmes très très rapide par rapport aux langages de haut niveau (C++, Basic, ...). En effet, bien que ces langages permettent de faire des programmes facilement et rapidement, ils n'optimisent pas le code d'exécution et donc font des programmes (beaucoup) plus lent et beaucoup plus volumineux... Notons que l'on peut insérer de l'assembleur dans certains langages (Pascal et C par exemple) pour accélérer l'exécution du programme. L'assembleur permet donc de programmer le CPU, mais avant d'entrer dans le vif du sujet présentons le CPU : Le CPU (Central Processing Unit), charge, analyse et exécute les instructions présentes en mémoire de façon séquentielle, c'est-à-dire une instruction à la suite de l'autre. Le CPU contient une unité de calcul en nombre entier (ALU), d'un bus interne, d'un bus externe se connectant au système, d'un décodeur d'instructions qui comme son nom l'indique décode l'instruction compilée pour pouvoir l'exécuter, et des cases mémoires que l'on appelle les Registres.
Voici par exemple le diagramme interne du Z80 :
Ce sont les registres qui permettent
la 'communication' entre le programme et le CPU. Ils sont 'l'interface' du
CPU. Pratiquement toutes les données qui passent par le CPU pour être traité
par celui-ci, devront se trouver dans les registres du CPU. Ces cases mémoires
sont les plus rapides de tout le système... Il y a différents types de registres
dans un CPU : Les registres de traitements et les registres d'adressages, et
les registres d'état.
Les Registres de traitement sont les registres destinés au traitement des
valeurs contenues dans celle-ci. Par exemple, on peut effectuer une addition
d'un registre de traitement avec un autre registre, ou effectuer des multiplications,
ou des traitements logiques...
Les Registres d'adressage permettent de pointer un endroit de la mémoire.
Ils sont utilisés pour lire ou écrire dans la mémoire...
Les registres d'état (Ou volet : FLAG en anglais), sont de petits registres
(de 1 Bit) indiquant l'état du processeur et 'le résultat' de la dernière
instruction exécutée. Les plus courants sont le Zéro Flag (ZF) qui indique
que le résultat de la dernière opération est égale a zéro (après une soustraction
par exemple), le Carry Flag (CF) qui indique qu'il y a une retenue sur la
dernière opération effectuée, Overflow Flag qui indique un dépassement de capacité
de registre...
Pour gérer ces registres, on fait appel
aux instructions du processeur : Ces instructions permettent d'effectuer des
tâches très simples sur les registres. Elles permettent de mettre des valeurs
dans les registres, d'effectuer des traitements logiques (Fonctions OU, ET,
...), des traitements arithmétiques, des traitements de chaîne de caractères,
etc.
Ces instructions sont formées à l'aide de code binaire. Pour nous, il est
beaucoup plus facile d'utiliser des mots-clés à la place de ces codes binaires
obscurs... C'est pour cela que l'on fait appel aux compilateurs qui permettent
de transformer un programme assembleur fait avec des mots-clés compréhensibles
pour nous (mais incompréhensibles pour la machine), en un programme exécutable,
compréhensible par le processeur.
Ces mots-clés ou mnémoniques, sont souvent la compression d'un mot ou d'une
expression en anglais présentant l'action de l'instruction. Par exemple sur
les processeurs 8086, l'instruction MUL permet la multiplication (MULtiply),
sur Z80 l'instruction LD permet de charger une valeur dans un registre ou
dans la mémoire (LoaD). Les instructions sont souvent suivies d'opérandes permettant
d'indiquer sur quel(s) registre(s) on veut effectuer le traitement, quelle
valeur on veut utiliser, etc.
Exemple : pour additionner 2 registres (2 registres 16 bits AX et BX)
sur 8086 :
ADD AX, BX
Cette instruction correspond en gros à AX=AX+BX
La même chose sur 68000 :
ADD.W D1, D0
Cette instruction correspond en gros à D0=D0+D1
Sur cet exemple, nous pouvons remarquer
la différence de syntaxe entre 2 processeurs différents : Lorsqu'un constructeur
conçoit un processeur, il détermine aussi son assembleur. C'est pour cela que
l'assembleur n'est pas universel, car il est différent sur chaque processeur
(ou plutôt concepteur). Et même si par hasard une instruction est identique
entre 2 CPU différents, elle ne sera pas compatible sur chaque processeur,
car le code binaire correspondant sera différent. Ceci est l'un des plus gros
inconvénients de la programmation en assembleur, car il faut alors reprogrammer
de A à Z le logiciel si on veut le porter sur un autre processeur.
Chaque processeur comporte un nombre plus ou moins important d'instructions.
Ce nombre permet de séparer 2 grandes familles :
- Les CPU RISC comportant peu d'instructions, mais étant plus rapides (instructions
de même taille).
- Les CPU CISC comportant plus d'instructions, mais généralement moins performant.
Connaissant les registres et les instructions
d'un processeur, on peut alors commencer à le programmer.
Pour les détails (registres, instructions, ...) sur un processeur, cliquez sur
un des liens suivants :
Famille X86 (essentiellement PC)
Processeur 68000 (Atari ST, Amiga, TI89, etc.)
Processeur Z80 (ZX81, TI82, etc.)
Nous n'allons pas détailler ici chaque processeur,
car il faudrait beaucoup plus qu'un site Web...
Allez voir les liens de ce site, il existe beaucoup de site parlant de la
programmation en assembleur sur différents processeurs allant du Z80 au Pentium
III en passant par le 68000 et autres...
(C) HxC2001 / Jean-François DEL NERO