Fonctionnement d'un ordinateur/Les registres du processeur

Un livre de Wikilivres.
Sauter à la navigation Sauter à la recherche

Un programmeur (ou un compilateur) qui souhaite programmer en langage machine peut manipuler ces registres intégrés dans le processeur. A ce stade, il faut faire une petite remarque : tous les registres d'un processeur ne sont pas forcément manipulables par le programmeur. Il existe ainsi deux types de registres : les registres architecturaux, manipulables par des instructions, et les registres internes aux processeurs. Ces registres peuvent servir à simplifier la conception du processeur ou à permettre l'implémentation d'optimisations permettant de rendre notre ordinateur plus rapide. Le nombre de registres architecturaux varie suivant le processeur. Généralement, les processeurs RISC et les DSP possèdent un grand nombre de registres. Sur les processeurs CISC, c'est l'inverse : il est rare d'avoir un grand nombre de registres architecturaux manipulables par un programme. Quoi qu'il en soit, tous les registres cités plus haut sont des registres architecturaux.

Registres architecturaux[modifier | modifier le wikicode]

On peut se demander à quoi servent ces registres. Tout cela dépend du processeur, et tous nos processeurs ne gèrent pas ces registres de la même façon.

Taille des registres[modifier | modifier le wikicode]

Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Derrière cette appellation qu'on retrouve souvent dans la presse ou comme argument commercial se cache un concept simple. Il s'agit de la quantité de bits qui peuvent être stockés dans chaque registre du processeur. Attention : on parle bien des registres généraux, et pas forcément des autres registres. Notre processeur contient pas mal de registres et certains peuvent contenir plus de bits que d'autres. Par exemple, dans certains processeurs, les registres généraux sont séparés des registres stockant des flottants et ces deux types de registres peuvent avoir une taille différente. Pour les processeurs x86 de nos PC, il existe des registres spécialement dédiés aux nombres flottants et d'autres spécialement dédiés aux nombres entiers (ce sont les registres généraux qui servent pour les entiers). Les registres pour nombres entiers n'ont pas la même taille que les registres dédiés aux nombres flottants. Un registre pour les nombres entiers contient environ 32 bits tandis qu'un registre pour nombres flottants contient 80 bits.

Ce nombre de bits que peut contenir un registre est parfois différent du nombre de bits qui peuvent transiter en même temps sur le bus de donnée de votre ordinateur. Cette quantité peut varier suivant l'ordinateur. On l'appelle la largeur du bus de données. Exemple : sur les processeurs x 86 - 32 bits, un registre stockant un entier fait 32bits. Un registre pour les flottants en fait généralement 64. Le bus de donnée de ce genre d'ordinateur peut contenir 64 bits en même temps. Cela a une petite incidence sur la façon dont une donnée est transférée entre la mémoire et un registre. On peut donc se retrouver dans deux situations différentes : soit le bus de données a une largeur égale à la taille d'un registre, soit la largeur du bus de donnée est plus petite que la taille d'un registre. Dans le premier cas, le bus de donnée peut charger en une seule fois le nombre de bits que peut contenir un registre. Dans le second cas, on ne peut pas charger le contenu d'un registre en une fois, et on doit charger ce contenu morceau par morceau.

Adressage des registres architecturaux[modifier | modifier le wikicode]

Outre leur taille, les registres du processeur se distinguent aussi par la manière dont on peut les adresser, les sélectionner. Les registres du processeur peuvent être adressés par trois méthodes différentes. A chaque méthode correspond un mode d'adressage différent. Les modes d'adressage des registres sont les modes d'adressages absolu (par adresse), inhérent (à nom de registre) et/ou implicite.

Noms de registres[modifier | modifier le wikicode]

Dans le premier cas, chaque registre se voit attribuer une référence, une sorte d'identifiant qui permettra de le sélectionner parmi tous les autres. C'est un peu la même chose que pour la mémoire RAM : chaque byte de la mémoire RAM se voit attribuer une adresse bien précise. Et bien pour les registres, c'est un peu la même chose : ils se voient attribuer quelque chose d'équivalent à une adresse, une sorte d'identifiant qui permettra de sélectionner un registre pour y accéder. Cet identifiant est ce qu'on appelle un nom de registre. Ce nom n'est rien d'autre qu'une suite de bits attribuée à chaque registre, chaque registre se voyant attribuer une suite de bits différente. Celle-ci sera intégrée à toutes les instructions devant manipuler ce registre, afin de sélectionner celui-ci. Ce numéro, ou nom de registre, permet d'identifier le registre que l'on veut, mais ne sort jamais du processeur : ce nom de registre, ce numéro, ne se retrouve jamais sur le bus d'adresse. Les registres ne sont donc pas identifiés par une adresse mémoire.

Adressage des registres via des noms de registre.

Adresses de registres[modifier | modifier le wikicode]

Mais il existe une autre solution, assez peu utilisée. Sur certains processeurs assez rares, on peut adresser les registres via une adresse mémoire. Il est vrai que c'est assez rare, et qu'à part quelques vielles architectures ou quelques micro-contrôleurs, je n'ai pas d'exemples à donner. Mais c'est tout à fait possible ! C'est le cas du PDP-10.

Adressage des registres via des adresses mémoires.

Registres adressés implicitement[modifier | modifier le wikicode]

Toutefois, tous les registres n'ont pas forcément besoin d'avoir un nom. Par exemple, les registres chargés de gérer la pile n'ont pas forcément besoin d'un nom : la gestion de la pile se fait alors via des instructions Push et Pop qui sont les seules à pouvoir manipuler ces registres. Toute manipulation du Frame Pointer et du Stack Pointer se faisant grâce à ces instructions, on n'a pas besoin de leur fournir un identifiant pour pouvoir les sélectionner. C'est aussi le cas du registre d'adresse d'instruction : sur certains processeurs, il est manipulé automatiquement par le processeur et par les instructions de branchement. Dans ces cas bien précis, on n'a pas besoin de préciser le ou les registres à manipuler : le processeur sait déjà quels registres manipuler et comment, de façon implicite. Quand on effectue un branchement, le processeur sait qu'il doit modifier le Program Counter : pas besoin de lui dire. Pareil pour les instructions de gestion de la pile. On voit donc que certains registres n'ont pas besoin d'être sélectionnées. On les manipule implicitement avec certaines instructions. Le seul moyen de manipuler ces registres est de passer par une instruction appropriée, qui fera ce qu'il faut. C'est le cas pour le Program Counter : à part sur certains processeurs vraiment très rares, on ne peut modifier son contenu qu'en utilisant des instructions de branchements. Idem pour le registre d'état, manipulé implicitement par les instructions de comparaisons et de test, et certaines opérations arithmétiques.

Registres généraux contre registres spécialisés[modifier | modifier le wikicode]

Outre leur méthode d'adressage, les processeurs se distinguent aussi par le type de données que peuvent contenir leurs registres. Certains ont des registres banalisés qui peuvent contenir tout type de données, tandis que d'autres ont des registres spécialisés pour chaque type de données. Les deux solutions ont des avantages et inconvénients différents. Elles sont toutefois complémentaires et non exclusives.

Registres spécialisés[modifier | modifier le wikicode]

Certains processeurs disposent de registres spécialisés, qui ont une utilité bien précise. Leur fonction est ainsi prédéterminée une bonne fois pour toute. Le contenu de nos registres est aussi fixé une bonne fois pour toute : un registre est conçu pour stocker soit des nombres entiers, des flottants, des adresses, etc; mais pas autre chose. Pour donner quelques exemples, voici quelques registres spécialisés qu'on peut trouver sur pas mal de processeurs.

Registre Utilité
Le registre d'adresse d'instruction (Program Counter) Pour rappel, un processeur doit effectuer une suite d'instructions dans un ordre bien précis. Dans ces conditions, il est évident que notre processeur doit se souvenir où il est dans le programme, quelle est la prochaine instruction à exécuter : notre processeur doit donc contenir une mémoire qui stocke cette information. C'est le rôle du registre d'adresse d'instruction. Ce registre stocke l'adresse de la prochaine instruction à exécuter. Cette adresse permet de localiser l'instruction suivante en mémoire. Cette adresse ne sort pas de nulle part : on peut la déduire de l'adresse de l'instruction en cours d’exécution par divers moyens plus ou moins simples qu'on verra dans la suite de ce tutoriel. Cela peut aller d'une simple addition à quelque chose d'un tout petit peu plus complexe. Quoiqu'il en soit, elle est calculée par un petit circuit combinatoire couplé à notre registre d'adresse d'instruction, qu'on appelle le compteur ordinal. Ce registre d'adresse d'instruction est souvent appelé le Program Counter. Retenez bien ce terme, et ne l'oubliez pas si vous voulez lire des documentations en anglais.
Le registre d'état Le registre d'état contient plusieurs bits qui ont chacun une utilité particulière. Ce registre est très différent suivant les processeurs, mais certains bits reviennent souvent :
  • divers bits utilisés lors d'opérations de comparaisons ou de tests qui servent à donner le résultat de celles-ci ;
  • le bit d'overflow, qui prévient quand le résultat d'une instruction est trop grand pour tenir dans un registre ;
  • le bit null : précise que le résultat d'une instruction est nul (vaut zéro) ;
  • le bit de retenue, utile pour les additions ;
  • le bit de signe, qui permet de dire si le résultat d'une instruction est un nombres négatif ou positif.

Le registre d'état est mis à jour par les instructions de test/comparaison, mais peut aussi l'être par certaines instructions arithmétiques. Certains bits qu'il contient peuvent servir à autre chose : on peut utiliser un bit pour indiquer si le résultat d'une opération arithmétique est égal ou non à zéro, un autre pour indiquer si ce résultat est négatif ou non, encore un autre pour savoir si le résultat est pair ou impair, etc. Cela permet à des instructions arithmétiques de remplacer des instructions de test. Par exemple, on peut tester si deux nombres sont égaux en soustrayant leur contenu : le bit NULL indiquera le résultat de la comparaison. Ce bit sera à 1 si les deux nombres contiennent la même valeur et à 0 sinon. D'ailleurs, sur certains processeurs, l'instruction de comparaison cmp n'est qu'une soustraction déguisée dans un opcode différent (il faut aussi préciser que le résultat de la soustraction n'est pas sauvegardé dans un registre ou en mémoire et est simplement perdu). C'est le cas sur certains processeurs ARM ou sur les processeurs x86.

Un autre bit du registre d'état sert à détecter les débordements d'entiers. Le débordement est géré par le logiciel, qui peut décider de passer outre ou de le corriger. Encore faut-il qu'il sache qu'il y a eu débordement. Dans la plupart des cas, un bit du registre d'état est mit automatiquement à 1 en cas de débordement, ce qui permet de détecter le débordement. Un programme qui veut gérer le débordement a juste à utiliser un branchement conditionnel qui renverra le processeur vers un sous-programme de gestion de débordement. Le bit de débordement du registre d'état est relié directement sur la sortie de l'additionneur qui indique l’occurrence d'un débordement.

Le Stack Pointer et le Frame Pointer Ces deux registres sont utilisés pour gérer une pile, si le processeur en possède une. Pour rappel, le Stack Pointer stocke l'adresse du sommet de la pile. Tout processeur qui possède une pile en possède un. Par contre, le Frame Pointer est optionnel : il n'est présent que sur les processeurs qui gèrent des Stack Frames de taille variable. Ce registre stocke l'adresse à laquelle commence la Stack Frame située au sommet de la pile.
Registres entiers Certains registres sont spécialement conçus pour stocker des nombres entiers. On peut ainsi effectuer des instructions de calculs, des opérations logiques dessus.
Registres flottants Certains registres sont spécialement conçus pour stocker des nombres flottants. L’intérêt de placer les nombres flottants à part des nombres entiers, dans des registres différents peut se justifier par une remarque très simple : on ne calcule pas de la même façon avec des nombres flottants et avec des nombres entiers. La façon de gérer les nombres flottants par nos instructions étant différente de celle des entiers, certains processeurs placent les nombres flottants à part, dans des registres séparés.

On peut ainsi effectuer des instructions de calculs, des opérations logiques dessus.

Registres de constante Ces registres de constante contiennent des constantes assez souvent utilisées. Par exemple, certains processeurs possèdent des registres initialisés à zéro pour accélérer la comparaison avec zéro ou l'initialisation d'une variable à zéro. On peut aussi citer certains registres flottants qui stockent des nombres comme \pi, ou e pour faciliter l'implémentation des calculs trigonométriques).
Registres d'Index Autrefois, nos processeurs possédaient des registres d'Index, qui servait à calculer des adresses, afin de manipuler rapidement des données complexes comme les tableaux. Ces registres d'Index étaient utilisés pour effectuer des manipulations arithmétiques sur des adresses. Sans eux, accéder à des données placées à des adresses mémoires consécutives nécessitait souvent d'utiliser du code automodifiant : le programme devait être conçu pour se modifier lui-même en partie, ce qui n'était pas forcément idéal pour le programmeur.

Sur certains processeurs, certains des registres cités plus bas ne sont pas présents ! Il existe ainsi des processeurs qui se passent des registres chargés de gérer la pile : tout processeur n'utilisant pas de pile le peut. De même, il est possible de se passer du registre d'état, etc.

Registres généraux[modifier | modifier le wikicode]

Fournir des registres très spécialisés n'est pas très flexible. Prenons un exemple : j'ai un processeur disposant d'un Program Counter, de 4 registres entiers, de 4 registres d'Index pour calculer des adresses, et de 4 registres flottants. Si jamais j’exécute un morceau de programme qui manipule beaucoup de nombres entiers, mais qui ne manipule pas d'adresses ou de nombre flottants, j'utiliserais juste les 4 registres entiers. Une partie des registres du processeur sera inutilisé : tous les registres flottants et d'Index. Le problème vient juste du fait que ces registres ont une fonction bien fixée.

En réfléchissant, un registre est un registre, et il ne fait que stocker une suite de bits. Il peut tout stocker : adresses, flottants, entiers, etc. Pour plus de flexibilité, certains processeurs ne fournissent pas de registres spécialisés comme des registres entiers ou flottants, mais fournissent à la place des Les registres généraux utilisables pour tout et n'importe quoi. Ce sont des registres qui n'ont pas d'utilité particulière et qui peuvent stocker toute sorte d’information codée en binaire. Pour reprendre notre exemple du dessus, un processeur avec des registres généraux fournira un Program Counter et 12 registres généraux, qu'on peut utiliser sans vraiment de restrictions. On pourra s'en servir pour stocker 12 entiers, 10 entiers et 2 flottants, 7 adresses et 5 entiers, etc. Ce qui sera plus flexible et permettra de mieux utiliser les registres.

Méthodes hybrides[modifier | modifier le wikicode]

Dans la réalité, nos processeurs utilisent souvent une espèce de mélange entre les deux solutions. Généralement, une bonne partie des registres du processeur sont des registres généraux, à part quelques registres spécialisés, accessibles seulement à travers quelques instructions bien choisies. Certains processeurs sont très laxistes : tous les registres sont des registres généraux, même le Program Counter. Sur ces processeurs, on peut parfaitement lire ou écrire dans le Program Counter sans trop de problèmes. Ainsi, au lieu d'effectuer des branchements sur notre Program Counter, on peut simplement utiliser une instruction qui ira écrire l'adresse à laquelle brancher dans notre registre. On peut même faire des calculs sur le contenu du Program Counter : cela n'a pas toujours de sens, mais cela permet parfois d'implémenter facilement certains types de branchements avec des instructions arithmétiques usuelles.

Registres internes[modifier | modifier le wikicode]

Comme dit plus haut, certains registres sont des registres internes au processeur, auquel le programmeur n'a pas directement accès. Ces registres ne sont pas accessibles directement via un nom de registre ou une adresse et sont gérés en interne par le processeur. Leur présence permet souvent des optimisations assez intéressantes. Nous allons notamment voir comment ceux-ci permettent l'optimisation du fenêtrage de registres.

Fenêtrage de registres[modifier | modifier le wikicode]

Plus un processeur a de registres architecturaux, plus leur sauvegarde prend du temps. Pour limiter le temps de sauvegarde des registres, certains processeurs utilisent le fenêtrage de registres, une technique qui permet d'intégrer cette pile de registre directement dans les registres du processeur. Cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registre architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre qu'elle a réservé, mais pas les registres avec le même nom dans les autres fenêtres. Ainsi, pas besoin de sauvegarder les registres de cette fenêtre, vu qu'ils étaient vides de toute donnée. S'il ne reste pas de fenêtre inutilisée, on est obligé de sauvegarder les registres d'une fenêtre dans la pile.

Fenêtre de registres.