wiki:SujetTD8

ALMO TD n°8 - Périphériques à capacité DMA

Préambule

L'objectif de ce TD est d'analyser le comportement des périphériques orientés blocs, c'est-à-dire des périphériques qui manipulent de gros volumes de données. Ces composants matériels ont souvent la capacité de lire et/ou d'écrire en mémoire, au même titre que le processeur. On parle alors de périphériques ayant une capacité DMA (Direct Memory Access). On trouve dans cette catégorie les contrôleurs de disques (appelés aussi "contrôleurs d'entrées/sorties"), les contrôleurs réseau, ainsi que les coprocesseurs graphiques. On s'intéresse ici aux mécanismes de communication permettant à une application logicielle utilisateur d'utiliser ces périphériques, en s'appuyant sur les services proposés par le système d'exploitation.

Plateforme matérielle

La plateforme matérielle considérée est toujours mono-processeur, simulable avec le simulateur simul_almo_generic. Comme illustré ci-dessus, on y a cependant introduit 3 nouveaux composants matériels, dont les fonctionnalités sont décrites ci-dessous.

  • Le composant DMA est utilisé pour déplacer des données, d'un tampon mémoire source vers un tampon mémoire destination. C'est un maître sur le bus puisqu'il est capable de lire et d'écrire à n'importe quelle adresse dans l'espace d'adressage du système. C'est également une cible, puisqu'il contient un (petit) nombre de registres de configuration adressables dans lesquels le système d'exploitation peut écrire, pour définir les caractéristiques du transfert à effectuer (typiquement l'adresse du tampon source, l'adresse du tampon destination et le nombre d'octets à transférer). Une fois configuré, ce composant travaille en parallèle avec le processeur, et signale la fin du transfert en activant une ligne d'interruption spécifique.
  • Le composant IOC (Input/Ouput Controller) est un contrôleur de disque. Il est utilisé pour transférer des données entre un tampon mémoire et un fichier situé sur un composant de stockage externe tel qu'un disque magnétique. Tous les transferts se font par blocs de 512 octets (appelés secteurs dans le cas des disques). Le contrôleur de disque est aussi un maître sur le bus puisqu'il est capable de lire et d'écrire en mémoire à n'importe quelle adresse dans l'espace d'adressage. C'est également une cible, puisqu'il contient un (petit) nombre de registres de configuration adressables dans lesquels le système d'exploitation peut écrire, pour définir le transfert à effectuer (typiquement, l'adresse du tampon en mémoire, la désignation du premier bloc dans le fichier et le nombre de blocs à transférer). Une fois configuré, ce composant travaille en parallèle avec le processeur, et signale la fin du transfert en activant une ligne d'interruption spécifique.
  • Le composant FBF (Frame Buffer) est un contrôleur vidéo permettant d'afficher des images sur un écran graphique. Ce composant contient une mémoire vidéo permettant de stocker une image de dimension prédéfinie. On s'intéresse dans ce TP à des images en niveaux de gris. Chaque pixel est codé sur un octet : il y a donc 256 niveaux de gris, sachant que la valeur 0 correspond à un pixel noir. Pour avoir des temps d'exécution raisonnables (nous sommes en simulation), on se limite dans ce TP à des images de 128 lignes de 128 pixels. Dans un PC, la capacité de la mémoire vidéo atteint au moins 4 Moctets (1024 lignes de 1024 bits, chaque pixel étant codé sur 4 octets pour l'affichage couleur). Cette mémoire vidéo est parcourue à une fréquence fixe (typiquement 25 fois par seconde) de façon à générer le signal vidéo qui est envoyé vers l'écran graphique. Cette mémoire vidéo (ou frame buffer) est généralement implantée dans la partie de l'espace d'adressage réservé au système d'exploitation. Le frame buffer est un composant cible sur le bus.

1. Fonctions d'accès au contrôleur I/O (IOC)

Le GIET fournit trois appels système permettant à une application logicielle utilisateur d'accéder au fichier sur le disque, en lecture ou en écriture. La signification des arguments est expliquée dans les fichiers stdio.c et stdio.h.

/* Block device related functions */
unsigned int ioc_read(unsigned int lba, void *buffer, unsigned int count);
unsigned int ioc_write(unsigned int lba, void *buffer, unsigned int count);
unsigned int ioc_completed();

Les deux fonctions ioc_read() et ioc_write() permettent de donner au contrôleur I/O un ordre de transfert de données du fichier sur disque vers la mémoire, ou de la mémoire vers le fichier. La fonction ioc_completed() est une fonction bloquante, qui ne rend la main que lorsque le transfert demandé est terminé.

Remarque : comme le GIET ne fournit pas un véritable système de fichiers, notre contrôleur I/O est très simplifié : il ne contient qu'un seul fichier, et on n'a pas besoin d'un appel système de type open() pour sélectionner et ouvrir le fichier, puisqu'il n'y en a qu'un seul.

Question 1.1 Donner trois raisons justifiant l'utilisation d'appels systèmes pour accéder au disque. Quelles vérifications doivent faire les deux fonctions ioc_read() et ioc_write() ?

Question 1.2 Les deux fonctions ioc_read() et ioc_write() ne sont pas bloquantes : elles rendent généralement la main à l'application utilisateur bien avant que le contrôleur I/O ait terminé le transfert, ce qui permet à l'application et au contrôleur I/O de fonctionner en parallèle. En analysant le code des fonctions systèmes _ioc_read() et _ioc_completed() (fichier drivers.c) ainsi que de l'ISR _isr_ioc (fichier irq_handler.c), décrivez précisément le mécanisme de synchronisation entre l'application utilisateur et le contrôleur I/0.

Question 1.3 Dans l'implémentation proposée, la fonction _ioc_completed() contient une boucle de scrutation, dans laquelle le processeur attend que la variable _ioc_done passe à 1. Quel est l'inconvénient de cette approche ? Proposez un mécanisme plus efficace dans le cas où le système d'exploitation supporte le fonctionnement multi-tâches pré-emptif, par multiplexage temporel.

2. Fonctions d'accès au Frame Buffer (FBF)

Le GIET fournit cinq appels systèmes permettant à une application utilisateur de transférer des données depuis un tampon mémoire de l'espace utilisateur vers le Frame Buffer (ou l'inverse). La signification des arguments est expliquée dans les fichiers stdio.c et stdio.h.

/* Frame buffer device related functions */
unsigned int fb_sync_read(unsigned int offset, void *buffer, unsigned int length);
unsigned int fb_sync_write(unsigned int offset, void *buffer, unsigned int length);
unsigned int fb_read(unsigned int offset, void *buffer, unsigned int length);
unsigned int fb_write(unsigned int offset, void *buffer, unsigned int length);
unsigned int fb_completed();

Les deux fonctions fb_sync_write() et fb_sync_read() effectuent le transfert de façon purement logicielle : chaque octet du tampon source est chargé par le processeur dans un registre interne, puis écrit par le processeur dans le tampon destination (utilisation de la fonction logicielle memcpy()). Ces fonctions sont bloquantes et ne rendent la main que lorsque le transfert est terminé : on dit que le transfert est synchrone. Cette technique est simple, mais elle est lente.

Les deux fonctions fb_write() et fb_read() utilisent le contrôleur matériel DMA présent dans la plateforme pour accélérer le transfert. Le mécanisme est très similaire à celui utilisé par le contrôleur I/O. Ces deux fonctions sont non bloquantes : elles configurent le contrôleur DMA pour que celui-ci effectue le transfert et rendent immédiatement la main à l'application utilisateur. Plus tard, le contrôleur DMA active une interruption matérielle pour signaler la fin du transfert. Comme dans le cas du contrôleur I/O, la fonction fb_completed() est une fonction bloquante qui ne rend la main que lorsque le transfert est terminé.

Question 2.1 La plupart des transferts effectués par l'application utilisateur sont évidemment des écritures (pour l'affichage). Pourquoi l'application utilisateur a-t-elle besoin d'être informée de la fin du transfert ?

Question 2.2 Quel risque présente l'utilisation du contrôleur DMA ? En déduire la raison pour laquelle le lancement d'un transfert DMA passe toujours par un appel système.

3. Problèmes de cohérence des caches

Il y a en pratique trois maîtres sur le bus système de la plateforme matérielle considérée, qui peuvent fonctionner en parallèle et modifier les valeurs stockées en mémoire indépendamment les uns des autres : le processeur MIPS32, le contrôleur IOC et le contrôleur DMA. Ceci peut créer des problèmes de cohérence entre les caches L1 et la mémoire.

Question 3.1 Après avoir fait les vérifications concernant l'implantation du tampon mémoire cible en espace utilisateur, et après avoir déclenché le transfert des données du disque vers la mémoire, l'appel système _ioc_read() exécute la fonction _dcache_buf_invalidate(), qui garantit que toutes les lignes du cache L1 de données correspondantes au tampon mémoire cible sont éliminées du cache. Pourquoi cette précaution est-elle indispensable ?

Question 3.2 La variable _ioc_done permet la synchronisation entre le contrôleur I/O et l'application qui souhaite accéder au disque. De la même façon, la variable _dma_busy permet la synchronisation entre le contrôleur DMA et l'application qui souhaite effectuer un transfert DMA. Ces deux variables sont stockées dans un segment mémoire particulier nommé seg_kunc, implanté dans l'espace adressable du système, et qui a la particularité d'être non-cachable. Expliquez pourquoi ces variables de synchronisation sont dans la zone protégée de la mémoire. Pourquoi ces variables sont-elles rangées dans un segment non-cachable ?

4. Estimations des temps de calcul et de communication

Dans le TP associé à ce TD, on va chercher à afficher sur l'écran graphique des images carrées de 128 lignes de 128 pixels, après une étape de filtrage. Les images sont initialement stockées sur le disque. Chaque pixel est codé sur 1 octet (256 niveaux de gris). Le traitement d'une image se décompose donc en trois étapes :

  • Phase Load : lecture d'une image à partir du disque, et chargement de cette image dans un premier tampon mémoire nommé buf_in.
  • Phase Modif : filtrage de l'image consistant à remplacer tous les pixels dont la valeur est supérieure à un certain seuil par la valeur maximale 255. L'image ainsi modifiée est stockée dans un second tampon mémoire nommé buf_out.
  • Phase Display : transfert de l'image du tampon buf_out vers la mémoire vidéo (frame buffer) pour affichage.

Question 4.1 Selon les hypothèses données, quelle est la taille d'une image (en nombre d'octets) ? Combien de secteurs occupe une image sur le disque ?

Question 4.2 Dans la phase Load, le contrôleur I/O effectue le transfert entre le disque et la mémoire en construisant des rafales de 32 mots consécutifs. Quelle est la durée minimale de transfert d'une image complète entre le disque et la mémoire (sans prendre en compte le temps d'accès au disque, qui peut représenter plusieurs centaines de milliers de cycles) ?

Question 4.3 Dans la phase Modif, l'application exécute une boucle qui traite l'image octet par octet. On suppose que la durée d'exécution de cette boucle est d'une dizaine de cycles quand il n'y a pas de MISS sur le cache de données. Quelle est la durée minimale de la modification d'une image complète ? On prendra en compte le coût des MISS de cache, avec l'hypothèse que la largeur d'une ligne cache est de 16 octets, et que le coût moyen du MISS est de 10 cycles.

Question 4.4 Si on utilise le contrôleur DMA dans la phase Display, celui-ci fabrique des rafales de 32 mots consécutifs. Il faut deux rafales pour transférer 128 octets : d'abord lecture dans le tampon mémoire utilisateur, puis écriture dans la mémoire vidéo. Quel est le nombre de cycles minimal pour transférer une image de la mémoire vers le frame buffer ?

Last modified 8 years ago Last modified on Aug 31, 2016, 3:59:10 PM