Fonctionnement d'un ordinateur/Communication avec les entrées-sorties

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

Dans ce chapitre, on va voir comment nos périphériques vont faire pour communiquer efficacement avec notre processeur ou notre mémoire. On sait déjà que nos entrées-sorties (et donc nos périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Mais communiquer avec un périphérique n'est pas aussi simple que ça, comme ce chapitre va vous le montrer.

Interfaçage entrées-sorties[modifier | modifier le wikicode]

Dans la façon la plus simple de procéder, le processeur se connecte au bus et envoie sur le bus l'adresse et les données et commandes à envoyer à l'entrée-sortie ou au périphérique. Ensuite, le processeur va devoir attendre et rester connecté au bus tant que le périphérique n'a pas traité sa demande correctement, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique.

Registres d’interfaçage[modifier | modifier le wikicode]

Pour résoudre ce problème, il suffit d'intercaler des registres d'interfaçage entre le processeur et les entrées-sorties. Une fois que le processeur a écrit les informations à transmettre dans ces registres, il peut faire autre chose dans son coin : le registre se charge de maintenir/mémoriser les informations à transmettre. Le processeur doit vérifier ces registres d’interfaçage régulièrement pour voir si un périphérique lui a envoyé quelque chose, mais il peut prendre quelques cycles pour faire son travail en attendant que le périphérique traite sa commande. Ces registres peuvent contenir des données tout ce qu'il y a de plus normales ou des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées.

Registres d'interfaçage.

Les commandes sont traitées par un contrôleur de périphérique, qui va lire les commandes envoyées par le processeur, les interpréter, et piloter le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Certains contrôleurs de périphérique peuvent permettre au processeur de communiquer avec plusieurs périphériques en même temps. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. Si le contrôleur de périphérique peut très bien être séparé du périphérique qu'il commande, certains périphériques intègrent en leur sein ce contrôleur : les disques durs IDE, par exemple. Certains de ces contrôleurs intègrent un registre d'état, lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent à signaler des erreurs de configuration ou des pannes touchant un périphérique.

Contrôleur de périphérique.

Lorsqu'un ordinateur utilise un système d'exploitation, celui-ci ne connait pas toujours le fonctionnement d'un périphérique ou de son contrôleur : il faut installer un programme qui va s'exécuter quand on souhaite communiquer avec le périphérique, et qui s'occupera de tout ce qui est nécessaire pour le transfert des données, l'adressage du périphérique, etc. Ce petit programme est appelé un driver ou pilote de périphérique. La « programmation » d'un contrôleur de périphérique est très simple : il suffit de savoir quoi mettre dans les registres pour paramétrer le contrôleur.

Interruptions[modifier | modifier le wikicode]

Déroulement d'une interruption.

La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les interruptions. Il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). L'interruption va effectuer un petit traitement (ici, communiquer avec un périphérique), réalisé par un programme nommé routine d'interruption. Avec ces interruptions, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail : il suffit que le contrôleur de périphérique prévienne le processeur avec une interruption. Lorsqu'un processeur exécute une interruption, celui-ci :

  • arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
  • exécute la routine d'interruption ;
  • restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.

L'appel d'une routine d'interruption est similaire à un appel de fonction, ce qui fait qu'il faut sauvegarder les registres du processeur, vu que la fonction ou la routine peuvent écraser des données dans les registres. Cette sauvegarde n'est pas toujours faite automatiquement par notre processeur : c'est parfois le programmeur qui doit coder lui-même la sauvegarde de ces registres dans la routine d'interruption elle-même. Certains processeurs fournissent des registres spécialement dédiés aux interruptions, qui ne sont accessibles que par les routines d'interruptions : cela évite d'avoir à sauvegarder les registres généraux. D'autres utilisent le fenêtrage de registres, avec une fenêtre pour les interruption et une autre pour les programmes. Pour savoir s'il est dans une interruption, le processeur utilise une bascule.

Vecteur d'interruptions[modifier | modifier le wikicode]

Devant la multiplicité des périphériques, on se doute bien qu'il n'existe pas d'interruption à tout faire : il va de soi qu'un programme envoyant un ordre au disque dur sera différent d'un programme agissant sur une carte graphique. On a donc besoin de plusieurs routines d'interruption : au moins une par périphérique. Certains ordinateurs utilisent une partie de leur mémoire pour stocker les adresses de chaque routine : cette portion de mémoire s'appelle le vecteur d'interruption. Lorsqu'une interruption a lieu, le processeur va automatiquement aller chercher son adresse dans ce vecteur d'interruption. Une autre solution est simplement de déléguer cette gestion du choix de l’interruption au système d'exploitation : l'OS devra alors traiter l'interruption tout seul. Dans ce cas, le processeur contient un registre qui stockera des bits qui permettront à l'OS de déterminer la cause de l'interruption : est-ce le disque dur qui fait des siennes, une erreur de calcul dans l'ALU, une touche appuyée sur le clavier, etc.

Masquage d'interruptions[modifier | modifier le wikicode]

Quand deux interruptions de déclenchent en même temps, on ne peut exécuter qu'une seule interruption. Le truc, c'est que certaines interruptions seront prioritaires sur les autres : par exemple, l'interruption qui gère l'horloge système est plus prioritaire qu'une interruption en provenance de périphériques lents comme le disque dur ou une clé USB. Quand deux interruptions souhaitent s'exécuter en même temps, on choisit d'exécuter celle qui est la plus prioritaire. L'autre interruption n'est pas exécutée, et est mise en attente : on parle de masquage d'interruption. Le masquage d'interruption permet de bloquer des interruptions temporairement, et de les exécuter ultérieurement, une fois le masquage d'interruption levé. Il faut noter qu'il est parfois possible de masquer à l'avance certaines interruptions : on peut ainsi désactiver temporairement l’exécution des interruptions, quelle qu’en soit la raison. Certaines interruptions ne sont pas masquables. Il s'agit généralement d'erreurs matérielles importantes, comme une erreur de protection mémoire, une division par zéro au niveau du processeur, une surchauffe du processeur, etc. Ces interruptions sont systématiquement exécutées en priorité, et ne sont jamais masquées.

Types d'interruptions[modifier | modifier le wikicode]

Il y a trois méthodes pour déclencher une interruption :

  • Les interruptions logicielles sont déclenchées par un programme en cours d'exécution, via une instruction d'interruption. Un programmeur peut donc décider d'utiliser des interruptions à un certain moment de ce programme, pour des raisons particulières. Ces interruptions logicielles sont surtout utilisées par les pilotes de périphériques ou les systèmes d'exploitation.
  • Une exception matérielle est aussi une interruption, mais qui a pour raison un évènement interne au processeur, par exemple une erreur d'adressage, une division par zéro… Pour pouvoir exécuter des exceptions matérielles, notre processeur doit pouvoir déclencher une interruption lorsqu'une erreur particulière survient dans le traitement d'une instruction. Il faut donc que ce processeur intègre des circuits dédiés à cette tâche. Lorsqu'une exception matérielle survient, il faut trouver un moyen de corriger l'erreur qui a été la cause de l'exception matérielle : la routine exécutée va donc servir à corriger celle-ci. Bien sûr, une exception matérielle peut avoir plusieurs causes. On a donc plusieurs routines.
  • Les IRQ sont des interruptions déclenchées par un périphérique. Dans une implémentation simple des IRQ, chaque périphérique envoie ses interruptions au processeur via une entrée IRQ : la mise à 1 de cette entrée déclenche une interruption bien précise au cycle suivant. Pour économiser des entrées, on a inventé le contrôleur d'interruptions, un circuit sur lequel on connecte tous les fils d'IRQ. Ce contrôleur va gérer les priorités et les masquages. Ce contrôleur envoie un signal d'interruption IRQ global au processeur et un numéro qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Parfois, ce numéro n'est pas envoyé au processeur directement, mais stocké dans un registre, accessible via le bus de données.
Contrôleur d'interruptions IRQ

Direct memory access[modifier | modifier le wikicode]

Avec nos interruptions, seul le processeur gère l'adressage de la mémoire. Impossible par exemple, de permettre à un périphérique d'adresser la mémoire RAM ou un autre périphérique. Il doit donc forcément passer par le processeur, et le monopoliser durant un temps assez long, au lieu de laisser notre processeur exécuter son programme tranquille. Pour éviter cela, on a inventé le bus mastering. Grâce au bus mastering, le périphérique adresse la mémoire directement. Il est capable d'écrire ou lire des données directement sur les différents bus. Ainsi, un périphérique peut accéder à la mémoire, ou communiquer avec d’autres périphériques directement, sans passer par le processeur. Le direct memory access est une technologie de bus mastering qui permet aux périphériques d'accéder à la RAM sans passer par le processeur. Elle peut même servir à transférer des données de la mémoire vers la mémoire, pour effectuer des copies de très grosses données.

Avec la technologie DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial, intégré au périphérique et relié au bus mémoire : le contrôleur DMA. Ce contrôleur DMA est capable de transférer un gros bloc de mémoire entre un périphérique et la mémoire. Il contient des registres dans lesquels le processeur pour initialiser un transfert de données. Ces registres contiennent, au miminum un registre pour l'adresse du segment de la mémoire, un autre pour la longueur du segment de mémoire. Le travail du contrôleur est assez simple. Celui-ci doit se contenter de placer les bonnes valeurs sur les bus, pour effectuer le transfert. Il va donc initialiser le bus d'adresse à l'adresse du début du bloc de mémoire. Puis, à chaque fois qu'une donnée est lue ou écrite sur le périphérique, il va augmenter l'adresse de ce qu'il faut pour sélectionner le bloc de mémoire suivant. Le transfert peut aller dans les deux sens : du périphérique vers la RAM, ou de la RAM vers le périphérique. Le sens du transfert, ainsi que les informations sur le bloc de mémoire à transférer, sont précisés dans des registres interne au contrôleur DMA. On trouve aussi parfois un ou plusieurs registres de contrôle. Ces registres de contrôle peuvent contenir beaucoup de choses : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore. Lorsqu'un périphérique souhaite accéder à la mémoire ou qu'un programme veut envoyer des données à un périphérique, il déclenche l'exécution d'une interruption et configure le contrôleur DMA avec les données nécessaires pour démarrer le transfert de donnée.

Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode block, le mode cycle stealing, et le mode transparent.

  • Dans le mode block, le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
  • Dans le mode cycle stealing, on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
  • Et enfin, on trouve le mode transparent, dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.

Adressage des périphériques[modifier | modifier le wikicode]

Pour accéder aux registres du contrôleur de périphérique, il existe trois méthodes :

  • la connexion directe ;
  • l'espace d'adressage séparé ;
  • les entrées-sorties mappées en mémoire.

Connexion directe[modifier | modifier le wikicode]

Dans le cas le plus simple, le contrôleur est directement relié au processeur par un bus d'entrées-sorties. Si le contrôleur est unique, il n'a pas d'adresse qui permettrait de l’identifier : le bus d'entrées-sorties se réduit donc à un bus de données couplé à un bus de commande. Les problèmes commencent quand il faut câbler plusieurs contrôleurs de périphérique. Pour éviter tout problème, les contrôleurs de périphériques se partagent le même bus d'entrées-sorties. Avec cette solution, chaque contrôleur de périphérique se voit attribuer une adresse, utilisée pour l'identifier et le sélectionner. Cette adresse a deux buts : adresser le contrôleur de périphérique, et préciser dans quel registre du contrôleur il faut aller lire ou écrire. D'ordinaire, certains bits de l'adresse indiquent quel contrôleur de périphérique est le destinataire, les autres indiquant le registre de destination. Cette adresse peut être fixée une bonne fois pour toute dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.

L'adressage des périphériques peut se faire de deux manières différentes. Première solution : chaque composant branché sur le bus vérifie si l'adresse envoyée par le processeur est bien la sienne : si c'est le cas, il va se connecter sur le bus (les autres composants restants déconnectés). En conséquence, chaque contrôleur contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus.

Seconde solution : utiliser un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera aconnecter activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour cela, chaque contrôleur possède une entrée CS, qui désactive le contrôleur de périphérique : si ce bit est à zéro, le contrôleur de périphérique ne prend absolument pas en compte ce qu'on trouve sur ses entrées, et ses sorties sont déconnectées. Ce bit est identique au bit CS : des mémoires RAM et ROM. Pour éviter les conflits sur le bus, un seul contrôleur de périphérique doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse.

Circuit de décodage d'adresse.

Espace d’adressage séparé[modifier | modifier le wikicode]

Pour économiser des fils et des broches sur le processeur, il est possible de mutualiser le bus d'adresse entre le bus d'entrées-sorties et le bus mémoire. En faisant cela, un mot mémoire et un registre du contrôleur de périphérique peuvent avoir la même adresse.

Bit IO[modifier | modifier le wikicode]

Dans le cas le plus simple, mémoire et entrées-sorties sont adressées séparément : une même adresse peut adresser soit une entrée-sortie, soit une case mémoire. On doit donc indiquer la destination de l'adresse, périphérique ou mémoire, via un bit IO sur le bus d'adresse. Pour faire la sélection entre périphérique et mémoire, le bit IO est envoyé au circuit de décodage partiel d'adresse, qui commande les bits CS des périphériques et de la mémoire. Si le bit IO vaut zéro, le bit CS de la mémoire est mis à zéro, déconnectant celle-ci du bus. Pour positionner le bit IO à la bonne valeur, le processeur utilise des instructions différentes pour communiquer avec le contrôleur de périphérique et la mémoire. Suivant l'instruction utilisée, le bit IO sera positionné à la bonne valeur.

Registre de banque.

Registre de banque[modifier | modifier le wikicode]

Exemple de Bank switching.

Certains processeurs améliorent ce principe, en utilisant plusieurs bits IO : cela donne la technique du bank switching. Cette technique consiste à utiliser un bus d'adresse réel plus grand que celui du processeur, les bits manquants étant reliés à un registre configurable par le processeur : le registre de banque. L'espace mémoire du processeur est présent en plusieurs exemplaires, sélectionnés non pas par un bit IO, mais par la valeur du registre de banque. On peut changer de banque en changeant le contenu de ce registre : le processeur dispose souvent d'instructions spécialisées qui en sont capables. Chaque exemplaire de l'ensemble des adresses du processeur s'appelle une banque. Le processeur sera limité par son bus d'adresse pour adresser les données dans une banque, pas pour l'ensemble de la mémoire. Cette technique s'appelle la commutation de banque. En répartissant les données utiles dans différentes banques, le processeur peut donc adresser beaucoup plus de mémoire. Par exemple, supposons que j'ai besoin d'adresser une mémoire ROM de 4 kibioctets, une RAM de 8 kibioctets, et divers périphériques. Le processeur a un bus d'adresse de 12 bits, ce qui limite à 4kibioctets. Dans ce cas, je peux réserver 4 banques : une pour la ROM, une pour les périphériques, et deux banques qui contiennent chacune la moitié de la RAM.

Banque mémoire.

Entrées-sorties mappées en mémoire[modifier | modifier le wikicode]

Partager le bus d'adresse complexifie pas mal la conception d'un processeur, vu qu'il faut ajouter des instructions spécialisées. Pour éviter ce genre de désagrément, on a trouvé une autre solution : mapper les entrées-sorties en mémoire. Avec cette technique, périphériques et mémoire sont connectées au processeur par le même bus, qui est intégralement mutualisé. Certaines adresses mémoires sont redirigées automatiquement vers les périphériques, ce qui fait que le périphérique se retrouve inclus dans l'ensemble des adresses utilisées pour manipuler la mémoire. Cette technique peut aussi s'appliquer pour les mémoires rom, ou dans les systèmes qui utilisent plusieurs mémoires.

Certains mots mémoire renverront donc vers un périphérique, cette redirection s'effectuant par décodage partiel d'adresse : le circuit de décodage partiel d'adresse va ainsi placer le bit CS de la mémoire à 1 pour les adresses invalidées, l’empêchant de répondre à ces adresses. Évidemment, les adresses utilisées pour les périphériques ne sont plus disponibles pour la mémoire RAM. C'est ce qui fait que certaines personnes installent 4 gigaoctets de mémoire sur leur ordinateur et se retrouvent avec « seulement » 3,5 à 3,8 gigaoctets de mémoire, les périphériques prenant le reste. Ce « bug » apparait sur les processeurs x86 quand on utilise un système d'exploitation 32 bits. On remarque ainsi le défaut inhérent à cette technique : on ne peut plus adresser autant de mémoire qu'avant. Et mine de rien, quand on a une carte graphique avec 512 mégaoctets de mémoire intégrée, une carte son, une carte réseau PCI, des ports USB, un port parallèle, un port série, des bus PCI Express ou AGP, et un BIOS à stocker dans une EEPROM/Flash, ça part assez vite.