Systèmes d'entrée-sortie



- Système d'Entrée-Sortie
- Bus E/S
- E/S Programmé
- Interruptions
- Accès direct à la mémoire (ADM, ou "DMA" en anglais)
- Processeurs E/S

Système d'entrée-sortie :

Le système d'entrée-sortie donne les mécanismes nécessaire pour que le processeur puisse communiquer avec le monde extérieur.  En général, le monde intérieur est ce qui se trouve entre le processeur et la mémoire principale (inclus).
Ce système doit s'occuper des différentes exigences des périphériques:
  -  Selection (en général il y a plus d'un périphérique, qui peuvent avoir plusieurs choses à dire)
  -  Taux de trasfert (en général très faible par rapport à la vitesse du processeur)
  -  Taille des trasferts (octet pour un clavier, blocs ou secteurs pour un disque)
  -  Synchronisation

Exemple:  L'accès à un disque (Figure 8.1, page 378).  Après avoir lancé la lecture, il y a un million de cycles avant que la première donnée arrive, puis ensuite les données du bloc arrivent une après l'autre avec un espacement de moins de 10 cycles.  Il faut pouvoir tranferer ces données au bons moments.

Synchronisation :
En général il n'y a pas d'horloge commune entre le processeur et les périphériques (opérations asynchrones).  Le système d'entrée-sortie doit donc faire une certaine synchronisation entre le CPU et ces périphériques qui ont des vitesses très différentes.
Il doit y avoir une indication de la disponibilité des données (entrée) ou que le périphérique est prêt à reçevoir d'autres données (sortie) :  bits de statut du périphérique.
Il y a d'autres bits de de statut pour les erreurs et autre.
On regroupe ces bits dans un "registre" qui est manipulable (+ ou -) comme un mot mémoire.  (c'est bien un registre mais il n'est pas dans le CPU)

Seulement avec ces bits, on peut faire un système simple et utiliser les entrees-sorties programmées :  lors d'une lecture, on fait une boucle qui attend que le bit de statut dise qu'il y a une donnée, puis on va chercher la donnée (même chose pour les écritures).


Bus E/S :  indépendant ou partagé avec la mémoire

Voir Figure 8.2, page 379.
a) bus indépendants (isolées) :  peut s'adapter à n'importe quoi, mais il y a beaucoup de connexions.
b) adresse et données communes :  instructions spécifiques pour les E/S (comme en a), moins flexible, moins de connexions.
c) bus partagé (ce qu'on appel "memory-mapped I/O") :  le plus simple (du point de vue du bus et du processeur), minimum de connexions, plus lent.

E/S dans l'espace d'adresses mémoire ("memory-mapped I/O") :  Voir Figure 8.3.
Le CPU ne fait aucune différence entre un accès mémoire et un accès E/S, c'est le programmeur qui doit savoir quelles adresses sont en mémoire, et quelles sont des E/S.


E/S programmées :

La Figure 8.4, page 381, montre un système simple permettant les E/S programmées, comme décrites plus haut. (attente sur un certain bit, avec une boucle, avant de faire l'E/S).
Le même système peut être utilisé sur les trois types de bus décrits dans la section précédente.
Chaque périphérique a son décodeur d'adresses et un ensemble de registres pour le statut et avoir un tampon entre les éléments asynchrones (processeur - périphérique).  Le périphérique réagit lorsque l'adresse présentée à son décodeur est une des adresses lui appartenant.

Exemple de décodeur d'adresses :  Figure 8.5.

Protocole synchrone vs. asynchrone :  Figure 8.7.
Même si les E/S sont asynchrones, le bus peut utiliser un protocole synchrone puisqu'il y a des registres tampons entre les deux.

Exemple d'interface de sortie de caractères :  Figure 8.6.
Il y a deux registres, un en lecture, pour le statut, et l'autre en écriture, pour dire le caractère.
Registre de statut :  bit 31 = Ready,  les autres sont inutilisés
Registre de données :  bits 7..0 = Caractère,  les autres sont inutilisés

Fragment de programme pour la sortie de caractères avec cette interface :  (Figure 8.8)
(on doit définir COSTAT et COUT, comme étant l'adresse du registre de statut et de données, respectivement)

      lar  r3, Wait    ; Adresse de la boucle d'attente
      ldr  r2, Char    ; Le caractère à sortir
Wait: ld   r1, COSTAT  ; Regarder le statut
      brpl r3, r1      ; Si le bit 31 est 0, on boucle
      st   r2, COUT    ; On met le caractère en sortie

Une machine SRC de 10 MIPS exécuterait 10000 instructions (5000 itérations de la boucle d'attente), en attendant une imprimante qe 1000 caractères/secondes.
Pour imprimer une ligne, on fait une boucle sur l'impression d'un caractère (Figure 8.9).  Ils ont aussi ajouter un registre de commande, pour dire à l'imprimente de commencer à imprimer la ligne (il peut y avoir un tampon dans l'imprimante).

Gestion de plusieurs périphériques en parallèle :  Figure 8.10
Il y a 32 périphériques lents, comme des claviers (~10 caractères/seconde).
Chaque périphérique a un registre de statut :  le pilote d'E/S examine répétitivement ("polling") les bits Ready (de tous les périphériques).
Chaque périphérique a un registre de données.

Analyse du pilote pour les 32 entrées :  (avec un CPU de 10 MIPS)
Si tous les périphériques ont un caractère :  32 octets sont entrés en 547 intructions, donc 585 KB/s (32*10M/547).
Si un seul périphérique n'est pas prêt, 538 instructions sont exécutées avant de retrouner vérifier le périphérique (délais de 53.8 us implique un max de 18.6 Kcar/s par périphérique pour ne pas perdre de données).


E/S sur interruptions :

Plutôt que d'attendre le périphérique de manière active (en perdant son temps à regarder le statut), c'est son iterface qui demande un interruption lorsqu'il est prêt.  Le périphérique donne au processeur une indication de la source de l'interruption, de manière à ce qu'il exécute la bonne routine.  Le CPU indique par un signal d'acquittement quand l'interface doit envoyer cette information. (Certains processeurs n'ont qu'un type d'interruption et c'est la routine qui doit demander aux périphériques lequel en est la cause)
On peut inhiber les interruptions de chaque interface périphérique individuellement (bit dans le registre de commande ou de statut).

Chaînage d'interruptions :  (Figure 8.13, page 393)
Plusieurs interfaces sont sur la même ligne d'interruption (ligne "ou" câblé, "wired-logic").  Chaque périphérique reçoit le signal "iack" que le périphérique précédent a filtré ("daisy-chaining").  Lorsqu'un périphérique reçoit un "iack" il le passe au prochain périphérique seulement s'il n'avait pas demandé d'interruption.  Le signal "ireq" sera actif tant que tous les périphériques n'ont pas étés servis.  Puisque le processeur inhibe les interruptions pendant qu'il traite une autre interruption, les périphériques seront servis un après l'autre (du plus proche au plus loin).

Exemple d'interface avec interruptions :  Figure 8.14.

Fonctions de la procédure de service d'interruptions :
1 -  Sauver l'état du programme interrompu
2 -  Faire les opérations comme avec les E/S progammées (mais sans l'attente)
3 -  Redémarrer ou arrêter le périphérique
4 -  Restaurer l'état du programme interrompu, autoriser les interruptions et retourner au programme interrompu

Exemple de lecture de caractères par interruption :
Dans ex.asm il y a un exemple "correct" de lecture du clavier sur interruption.  La figure 8.15 du livre montre aussi un exemple de lecture sur interruption, mais il faudrait normalement mettre la routine à un autre endroit mémoire puisque chaqu'on a seulement 16 octets pour chaque routine dans la table d'interruption.

Interruptions imbriquées :
Certains périphériques ont besoin d'une réponse rapide du processeur lorsqu'ils demandent une interruption.  Si le processeur exécute déjà une routine d'interruption, il se peut que de terminer l'exécution de cette routine avant de répondre au périphérique donne un délais trop long.  Il faut alors permettre les interruptions imbriquées et avoir un système de priorité des interruption.  La routine de service d'une interruption doit maintenant faire :
1 -  Sauver l'état changé par l'interruption (état du programme interrompu + masque d'interruption + IPC et II)
2 -  Inhiber les interruptions moins prioritaires (changer le masque)
3 -  Autoriser les interruptions
4 -  Asservir le périphérique
5 -  Inhiber les interruptions
6 -  Restaurer le masque d'interruption (réautoriser les interruptions  moins prioritaires)
7 -  Restaurer l'état du début de l'interruption
8 -  Retourner au programme interrompu et autoriser les interruptions.

Parfois il y a des petites sections de code où il ne faut pas qu'il y ait d'interruption.  On peut inhiber les interruptions pendant ces sections, qu'on appel sections critiques, mais il ne faut rester trop longtemps sans permettre les interruptions, sinon certaines interruptions pourraient ne pas être traitées à temps.

Le traitement des interruptions imbriquées vient surtout du logiciel, mais pour qu'il soit efficace il faut que les opérations effectuées sur le masque d'iterruption le soient.  Si on utilise le bit pour permettre les interruption, qui se trouve dans l'interface de chaque périphérique, comme masque, ça serait beaucoup trop lent.  À la place, on va donner un niveau d'interruption (un petit entier) à chaque périphérique, plusieurs périphérique pouvant avoir le même niveau.  Si le nombre de niveau est petit, on peut avoir un bit pour autoriser ou inhiber les interruption de chaque niveau, et regrouper ces bits dans un registre (en général cette méthode ne sera pas utilisée si le nombre de niveau est plus grand que la taille des mots du processeur).  Sinon, on peut simplement avoir un registre qui contient le niveau de l'interruption courante, et le système d'interruption inhibe les interruptions qui ont un niveaux moins prioritaire.  (Figure 8.17, page 398)


Accès direct à la mémoire :  (ADM, ou "DMA")

Certains périphériques n'auraient pas réellement besoin de l'attention du processeur pour tranférer les données si on leut permettaient d'accéder à la mémoire.  Par exemple, souvent, quand on écrit sur un disque, c'est un bloc qui se trouve en mémoire principale qu'on veut sauver.  Dans ce cas, la routine d'interruption ne ferait qu'incrémenter un pointeur et fournir les données de la mémoire au disque.  On peut libérer le processeur de ce travail simple, mais qui peut être très demandant si le taux de transfert est élevé, en mettant une interface d'ADM.

Avec l'interface d'ADM (Figure 8.18, page 400), le processeur exécute des instructions pour partir le transfert, en spécifiant l'adresse mémoire et la longueur (au système d'ADM) en plus de la commande (au periphérique).  Le périphérique fait ensuite les demandes à l'interface d'ADM plutot qu'au processeur pour écrire ou lire la prochaine donnée en mémoire.  Une fois que le transfert est fini, une interruption est générée, pour avertir le processeur.

Lorsque l'interface d'ADM est séparée de l'interface du périphérique et que plusieurs pourraient utiliser la même interface d'ADM, on parle de canaux d'ADM ("DMA channels").  Il se peut qu'un seul périphérique puisse transférer à la fois, dans ce cas on parle de sélecteur, et lorsque plusieurs peuvent transférer "en même temps", on parle de multiplexeur (Figure 8.19).

Certaines architectures de bus permettent aux interfaces des périphériques de faire directement des demandes aux autres interfaces, incluant la mémoire.  Dans ce cas on parle de "bus mastering", et il faut un contrôleur de bus pour donner la parole aux interfaces sans qu'il n'y ait de chicane.


Processeurs E/S :

Pour décharger le processeur des tâches d'E/S, on peut ajouter un autre processeur.  Les processeurs ont accès à la même mémoire et peuvent aussi communiquer avec les procédés d'E/S décrits avant.  En fait l'interface d'ADM peut être vue comme étant un processeur très simple, mais si on veut plus que simplement incrémenter une adresse, on peut utiliser un processeur plus puissant.



Présentation en classe (.PDF)