wiki:SoclibCourseTp4

TP4 : Architectures multi-processeurs et périphériques

1 Objectif

Le but de ce quatrième TP est double :

D'un côté, on souhaite introduire les différents périphériques qui seront utilisés dans la suite de ces TPs, en insistant sur les mécanismes de communication entre les périphériques et le système d'exploitation.

D'un autre côté, on souhaite modéliser des architectures multi-processeurs génériques, telles qu'on puisse facilement faire varier le nombre de processeurs. .

2. Périphériques

Il existe deux types de périphériques:

  • Un périphérique orienté caractère (tel qu'un contrôleur TTY) supporte des requêtes de lecture ou d'écriture d'un petit nombre de caractères. Ce type de périphérique se comporte généralement comme une cible sur le bus, puisqu'il ne peut que recevoir des commandes provenant d'un processeur, et qu'il n'a pas la possibilité de lire ou d'écrire lui-même en mémoire. Le contrôleur de périphérique utilise une interruption pour signaler au système d'exploitation qu'un événement (tel qu'un caractère frappé au clavier) s'est produit du côté du périphérique.
  • Un périphérique orienté bloc, tel qu'un contrôleur de disque, doit tranférer de grosses quantités de données entre la mémoire et le disque. Les transferts se font par blocs (un bloc contenant généralement 512 octets), et ces périphériques ont généralement une capacité DMA : Ils sont à la fois maître et cible sur le bus, car ils reçoivent des commandes définissant le transfert à effectuer, mais peuvent ensuite directement lire ou écrire en mémoire. Le contrôleur de périphérique utilise une interruption pour signaler au système d'exploitation la fin du transfert.

2.1 composants matériels

Lorsque le nombre de périphériques augmente, le nombre de lignes d'interruption augmente également, et il faut un mécanisme permettant de concentrer plusieurs dizaines de requêtes d'interruption vers un seul signal IRQ connecté au processeur. C'est en interrogeant le composant matériel ICU (Interrupt Controler Unit) que le système d'exploitation (en pratique le gestionnaire d'interruption) peut obtenir le numéro de la ligne d'interruption active la plus prioritaire.

Le composant vci_icu est un contrôleur d'interruptions vectorisées. C'est une cible VCI dont vous trouverez la spécification fonctionnelle ici. Prenez le temps de la lire.

Le composant vci_multi_timer est également une cible VCI contenant un nombre queconque de timers programmables capables de générer des interruptions périodiques. On trouvera la spécification fonctionnelle de ce composant ici.

Le composant vci_block_device est un contrôleur de périphérique de stockage externe (disque ou mémoire flash). Ce composant IOC est à la fois un initiateur VCI, capable de lire et d'écrire dans la mémoire, et une cible qui peut recevoir des commandes de configuration. Puisque nous sommes en simulation, le composant IOC gère un unique fichier, stocké sur le disque de la station de travail qui exécute le simulateur. Le nom de ce fichier est un argument du constructeur. On trouvera la spécification fonctionnelle de ce composant ici.

Le composant vci_frame_buffer est un contrôleur d'écran graphique. C'est une cible VCI qui est vue comme un tampon mémoire directement adressable de M lignes de N pixels, dans lequel le logiciel peut lire ou écrire. Le contenu de ce buffer est parcouru périodiquement, et son contenu est affiché sur l'écran graphique externe. On trouvera la spécification fonctionnelle de ce composant ici.

Le composant vci_dma est un composant matériel qui peut être programmé par le système d'exploitation pour éffectuer de gros transferts de données, tel que la copie d'une image d'un tampon mémoire situé dans l'espace utilisateur vers le tampon mémoire du composant FBF. On trouvera la spécification fonctionnelle de ce composant DMA ici.

Les deux composants IOC et DMA étant à la fois initiateur et cible, on obtient finalement une architecture possédant trois initiateurs VCI (indexés de 0 à 2), et 9 cibles VCI (indexées de 0 à 8), conformément au schéma ci-dessous.

Les lignes d'interruptions ne passent pas par le réseau VCI : chaque ligne d'interruption d'un périphérique est directement connectée aux ports d'entrée p_irq_in[i] du composant ICU :

  • La ligne d'interruption du TIMER est connectée au port p_irq_in[0]
  • la ligne d'interruption du TTY est connectée au port p_irq_in[1]
  • la ligne d'interruption du contrôleur IOC est connectée au port p_irq_in[2]
  • la ligne d'interruption du contrôleur DMA est connectée au port p_irq_in[3]

Le système d'exploitation (GIET) associe à chaque ligne d'interruption une routine de traitement spécifique, appelée ISR (Interrupt Service Routine), qui est exécutée par le processeur lorsque la ligne d'interruption est activée par le périphérique, et que les interruptions ne sont pas masquées. Il s'agit donc pour le périphérique de "voler" quelques cycles du processeur pour lui permettre d'exécuter un peu de code. L'ISR permet généralement au périphérique de signaler un événement en allant écrire dans un emplacement prédéfini en mémoire.

2.2 Communication entre l'OS et le contrôleur TTY

Dans le TP3, le programme utilisateur utilise l'appel système tty_getc() pour lire un caracère. Cet appel système contient une boucle de scrutation dans laquelle, à chaque tour de boucle, le processeur effectue une transaction sur le bus pour lire la valeur du registre STATUS du terminal TTY concerné. On ne sort de cette boucle que lorsque la valeur lue indique qu'un caractère a effectivement été saisi au clavier.

Dans ce TP4, le programme utilisateur utilisera l'appel système tty_getc_irq() pour lire un caractère. Cet appel système utilise un tampon mémoire partagé _tty_get_buf, protégé par une variable de synchronisation _tty_get_full. Ces deux variables appartiennent au système d'exploitation et sont stockées en mémoire dans le segment seg_kunc, qui est à la fois protégé (non directement accessible par un programmes utilisateur) et non cachable. Plutôt que d'accéder directement au registre STATUS du contrôleur TTY, l'appel système tty_getc_irq() teste la variable _tty_get_full rangée en mémoire pour savoir si un caractère est disponible. C'est la routine d'interruption (ISR) associée au terminal TTY qui se charge d'écrire le code ASCII du caractère dans le tampon _tty_get_buf, et de forcer à 1 la variable de synchronsation _tty_get_full pour signaler que le tampon est plein. Cette variable de synchronisation est remise à 0 par l'appel système tty_getc_irq() lorsque le caractère est transféré du tampon système tty_get_buf vers le tampon mémoire défini par l'utilisateur.

Une architecture peut contenir plusieurs processeurs et chaque processeur peut exécuter plusieurs tâches (plusieurs programmes utilisateurs) en pseudo-paralléliseme, par multiplexage temporel. Le GIET supporte au plus 8 processeurs, et au plus 4 tâches par processeur, soit 32 tâches au maximum.

Comme chaque tâche possède son propre terminal écran/clavier, Il peut exister jusque 32 terminaux indépendants, qui sont tous contrôlés par le même contrôleur TTY : Chaque terminal possède donc son propre jeu de 4 registres, pour communiquer avec l'OS. On dit que le contrôleur TTY est un périphérique multi-canaux. Le GIET définit donc deux tableaux _tty_get_buf[32] et _tty_get_full[32], indexés par le numéro du terminal concerné.

Question : Comment les appels système tty_getc_irq() et tty_puts() calculent-ils l'index du terminal associé à la tâche qui effectue l'appel système ? La réponse se trouve dans le fichier sys/drivers.c.

Question : Comment le code de l'ISR associée à l'interruption générée par le TTY calcule-il l'index de l'entrée qui doit être modifiée dans les tableaux _tty_get_buf[32] et _tty_get_full[32] ? La réponse se trouve dans le fichier sys/irq_handler.c.

Question : Que fait la routine d'interruption ISR déclenchée par le périphérique TTY lorsqu'un caractère est frappé au clavier et que la variable _tty_get_full[i] vaut 1 (ce qui signifie que le tampon _tty_get_buf[i] est plein ? La réponse se trouve dans le fichier sys/irq_handler.c.

Question : Quel est l'avantage de ce type de communication par interruption, comparé au mécanisme de scrutation utilisé dans le TP3 ?

2.2 Communication entre l'OS et le contrôleur IOC

Les deux appels système ioc_read() et ioc_write() permettent à un programme utilisateur de demander au GIET de réaliser un transfert de données entre un tampon mémoire utilisateur et le périphérique de stockage externe. Tous les transferts se font par blocs de 512 octets.

Question : Editez le fichier stdio.c pour déterminer la signification des trois arguments de ces appels système. Editez le fichier drivers.c pour voir ce que font réellement les deux fonctions système _ioc_read() et _ioc_write() associées. Quand ces fonctions rendent-elle la main au programme utilisateur?

L'appel système ioc_completed(), qui appelle lui-même la fonction système _ioc_completed() permet au programme utilisateur de se mettre en attente sur la fin d'un transfert. C'est donc une fonction bloquante qui ne rend la main au programme utilisateur que lorsque le transfert est effectivement terminé.

Question : Le contrôleur de disque utilisé ici ne peut effectuer qu'un seul transfert de données à la fois. Ce n'est donc pas un périphérique multi-canaux. Si deux programmes utilisateurs s'exécutant en parallèle sur deux processeurs différents demandent simultanément à utiliser le contrôleur de disque, comment le GIET peut-il garantir que le périphérique ne sera alloué qu'à un seul des deux demandeurs?

Question: Expliquez précisément le rôle des deux variables _ioc_lock et _ioc_done utilisées par le GIET pour contrôler l'accès au contrôleur de disque.

3 Modélisation de l'architecture matérielle

L'archive soclib_tp4.tgz contient différents fichiers dont vous aurez besoin pour ce TP. Créez un répertoire de travail spécifique TP4, recopiez l'archive dans ce répertoire, et décompressez-la:

$ tar xzvf soclib_tp4.tgz

Outre les fichiers qui permettent de générer le simulateur de l'architecture matérielle, cette archive contient également le sous-répertoire soft qui est utilisé pour la génération du logiciel embarqué.

Question : Complêtez le fichier tp4_top.cpp pour définir les adresses de base et les tailles des segments associés aux composants TTY, GCD, ICU, TIMER, IOC, FBF et DMA, et pour introduire ces 7 segments dans la table des segments. Ces segments sont-ils cachables ou non cachables? Les adresses de base des segments sont définis dans le fichier soft/seg.ld. Les tailles minimales des segments sont définies dans la documentation des composants matèriels sur le site SoCLib.

Question : Complétez le fichier tp4_top.cpp pour définir les arguments des constructeurs des composants ICU, TIMER, IOC, FBF, DMA et BUS. Pour le composant IOC on fera en sorte que le cheminom désignant le fichier externe représentant le disque puisse être redéfini par un paramètre sur la ligne de commande au lancement du simulateur. Pour le composant FBF, on choisira une taille d'écran de 128 lignes de 128 pixels.

Question : Complétez la net-list dans le fichier tp4_top.cpp pour connecter sur les 4 premières entrées du composant ICU les 4 lignes d'interruption irq_tim, irq_tty, irq_ioc, irq_dma utilisées dans cette architecture, dans cet ordre.

Question Complétez la net-list pour connecter sur le bus les 3 initiateurs et les 9 cibles.

Question : Complétez le fichier tp4_top.desc pour pouvoir utiliser soclib-cc, et utilisez le Makefile pour générer le simulateur.

4. Logiciel embarqué

Le répertoire soft contient les sept fichiers app.ld, sys.ld, seg.ld, config.h, reset.s, main.c, et Makefile, permettant de générer le logiciel embarqué.

4.1 Code de boot

Puisqu'on utilise des interruptions, le code de boot, défini dans le fichier soft/reset.s, doit initialiser le vecteur d'interruption (c'est à dire le tableau indexé par le numéro d'interruption, et contenant les adresses des différentes routines d'interruption). Il doit également initialiser le composant ICU, pour démasquer les interruptions qu'on veut autoriser.

Question : En ouvrant le fichier sys/irq_handler.c, déterminez les nom des quatre ISRs associées aux composants TIMER, TTY, IOC et DMA. Modifiez le fichier reset.s pour initialiser les 4 premières entrées du vecteur d'interruption dans cet ordre.

Question : Le fichier sys/hwr_mapping.h contient la carte des registres adressables des périphériques supportés par le GIET. Ouvrez ce fichier pour déterminer l'offset du registre ICU_MASK_SET, et complétez le fichier reset.s pour configurer le composant ICU de façon à autoriser les quatre lignes d'interruption utilisées : irq_in[0], irq_in[1], irq_in[2], irq_in[3].

4.2 Activation du TIMER

On va commencer par exécuter un programme très simple, qui se contente d'activer la génération d'interruptions périodiques par le TIMER. Consultez le fichier app/stdio.c pour déterminer quels sont les deux appels système qui permettent de définir la période et d'autoriser le TIMER à générer les interruptions périodiques.

Question : modifiez le fichier main.c pour que le TIMER génère des interruptions avec une période de 500000 cycles, compilez le logiciel embarqué, et lancez la simulation.

N'oubliez pas de modifier préalablement la variable d'environnement PATH de la fenêtre dans laquelle vous lancez le simulateur, en exécutant la commande:

$ source /users/outil/soc/env_soclib.sh

4.3 Utilisation du contrôleur TTY

On veut maintenant utiliser le contrôleur TTY en écrivant un petit interprêteur de commandes, qui exécute une boucle infinie dans laquelle il lit des commandes tapées au clavier (un seul caractère à la fois) et les exécute. On traitera au minimum les trois commandes suivantes:

  • a : activation des interruptions générées par le TIMER
  • d : desactivation des interruptions générées par le TIMER
  • q : sortie de l'interprêteur par l'appel système exit()

Question : Complétez le fichier main.c pour coder cet interprêteur de commandes comme une boucle infinie contenant les appels système tty_getc_irq() et tty_puts(). Compilez en utilisant le Makefile du répertoire soft, et exécutez ce programme interactif sur le simulateur.

4.4 Contrôleur IOC et contrôleur d'écran graphique

On veut utiliser le contrôleur IOC pour charger dans un tampon mémoire du programme utilisateur une séquence d'images stockées dans le fichier images.raw, avant d'afficher ces images sur l'écran graphique contrôlé par le composant FBF.

Le fichier attaché images.tgz, que vous pouvez télécharger ici, est une version compressée du fichier images.raw. Ce fichier conient une séquence d'images de 128 lignes de 128 pixels, codées en 256 niveaux de gris (un octet par pixel). Vous devez décompresser le fichier images.tgz en utilisant la commande :

$ tar -xvfz images.tgz

Question : Quel est l'encombrement d'une image en nombre de blocs?

Question : Ecrivez un nouveau programme dans un fichier main_display.c. Vous utiliserez les appels système ioc_read() et ioc_completed() pour charger une image dans un tampon mémoire de 128*128 octets tab[128][128], déclaré dans la fonction main_display(). Utilisez l'appel système fb_write() pour afficher cette image sur l'écran graphique.

N'oubliez pas de tester systématiquement la valeur du code de retour chaque fois que vous utilisez un appel système. Modifiez le Makefile pour utiliser main_display.c au lieu de main.c, compilez le logiciel embarqué, et exécutez-le sur le simulateur.

Question : Dans quel segment sera rangéé ce tableau tab[128][128]?

Question : Complétez le fichier main_display.c pour encapsuler cette séquence chargement / affichage dans une boucle de façon à afficher successivement les images du fichier images.raw. On pourra utiliser l'appel système tty_getc_irq() pour rendre interactif le passage à l'image suivante.

5. Architecture multi-processeurs générique

On veut maintenant modéliser une architecture matérielle multi-processeur générique, où le nombre de processeurs peut varier entre 1 et 4. Ce paramètre NPROCS sera défini sur la ligne de commande.

5.1 architecture matérielle

Vous devez modifier le fichier tp4_top.cpp pour créer un nouveau fichier tp4_top_multi.cpp, en suivant les recommandations suivantes. Lisez toutes les recommandations avant de commencer à coder...

  • On ajoutera le paramètre NPROCS dans la liste des paramètres qu'on peut modifier sur la ligne de commande, en vérifiant que le paramètre NPROCS est plus grand que 0 et inférieur ou égal à 4.
  • Pour les composants répliqués PROC[i] ainsi que pour les signaux connectés à ces composants, vous définirez des tableaux de pointeurs indexés par l'index du processeurs (proc_id).
  • Vous utiliserez une boucle indexée par l'index du processeur pour les constructeurs des composants PROC[i].
  • Chaque processeur a besoin de son propre concentrateur d'interruption. Le composant matériel VciIcu est un composant mono-canal, qui ne peut servir qu'un seul processeur. Il faut donc utiliser le composant matériel multi-canaux VciMultiIcu, qui peut servir jusqu'à 8 processeurs. Vous pouvez consulter la documentation ici.
  • De même, pour ce qui concerne le contrôleur DMA, on souhaite avoir un canal DMA indépendant pour chaque processeur. Il faut donc utiliser le composant matériel multi-canaux VciMultiDma dont vous pouvez consulter la documentation ici. Attention: Le composant VciMultiDma utilise le champs VCI TRDID, qui doit avoir au moins 4 bits. Il faudra donc modifier la valeur du champs trdid_size dans les deux fichier tp4_top.cpp et tp4_top.desc.
  • Pour ce qui concerne les timers, on souhaite avoir un timer indépendant pour chaque processeur, mais puisque le composant VciTimer peut contenir jusque 256 timers indépendants, on utilisera un seul composant VciTimer.
  • Puisque chaque processeur exécutera une seule tâche, et que chaque tâche doit disposer de son propre terminal écran/clavier, le nombre de terminaux controlés par le composant VciMultiTty est égal à NPROCS.
  • Pour ce qui concerne l'accès au disque, on aura un seul contrôleur de disque VciBlockDevice partagé par toutes les applications.
  • Pour les périphériques multi-canaux qui génèrent des interruptions (TTY, TIMER, DMA,ICU), le nombre de de lignes d'interruption dépend du nombre de processeurs. Il faut donc utiliser des tableaux de signaux.
  • Pour les tableaux de signaux, vous utiliserez le constructeur générique de tableaux alloc_elems<type>(name, size). Ce constructeur est défini dans le fichier alloc_elems.h, qu'il ne faut pas oublier d'inclure dans la top-cell.
  • Pour l'index des initiateurs (SRCID), vous utiliserez les valeurs 0 à (NPROCS-1) pour les processeurs, et les valeurs NPROCS et (NPROCS+1) pour le composant IOC et pour le composant DMA respectivement.
  • Pour la net-list, vous utiliserez des boucles indexées par l'index du processeur pour connecter les composants répliqués et signaux répliqués.

  • Puisque l'architecture contient maintenant plusieurs périphériques multi-canaux (ICU, TTY, TIMER, DMA) les longueurs des segments alloués à ces périphériques doivent être ajustées en conséquence.
  • Pour ce qui concerne le cablage des interruptions, vous utiliserez un composant ICU possédant 32 lignes d'interruption entrantes. L'interruption IRQ_IOC sera connectée à l'entrée p_irq_in[0] du composant ICU. Les 4 interruptions IRQ_DMA[0:3] seront connectée aux entrées p_irq_in[4:7]. Les 4 interruptions IRQ_TIM[0:3] seront connectées aux entrées p_irq_in[8:11]. Les 4 interruptions IRQ_TTY[0:3] seront connectées aux entrées p_irq_in[12:15]. Les ports d'entrée p_irq_in[i] non utilisés devront impérativement être connectés à un signal ayant la valeur false...

Question : Pour quelle raison les valeurs stockées dans les 4 registres de masque ICU_MASK[k] associés aux 4 canaux du composant VciMultiIcu doivent-elles être non-recouvrantes?

Question : Puisqu'il n'existe qu'un seul contrôleur de disque, qui peut - en principe - être utilisé par toutes les applications, comment le GIET doit-il gèrer les accès concurrents?

Question : Modifiez le fichier tp4_top.desc pour créer un fichier tp4_top_multi.desc. * Puisque deux nouveaux composants (VciMultiDma, et VciMultiIcu) ont été introduits dans l'architecture, modifiez le fichier le fichier tp4_top.desc pour créez un fichier tp4_top_multi.desc. Modifiez le fichier Makefile en conséquence pour générer un simulateur générique simulator_multi.x.

5.2 Code de boot multi-processeurs

Un vrai système d'exploitation tel que LINUX permet de lancer dynamiquement de nouvelles tâches (i.e. de nouveaux programmes utilisateurs) alors que la machine est déjà démarrée (mécanisme pthread_create() pour les threads POSIX, ou mécanisme fork/exec pour les processus UNIX). Le GIET n'est pas un vrai système d'exploitation, car il ne supporte pas la création ou la destruction dynamique de tâches : Les tâches doivent être créées une fois pour toute au démarrage de la machine, c'est à dire dans le code de boot.

On va utiliser ici un mécanisme simple où chaque processeur n'exécute qu'une seule tâche, qui lui est assignée une fois pour toutes dans le code de boot.

Le mécanisme général est le suivant :

  • Tous les processeurs exécutent le même code de boot, mais l'exécution de ce code dépend de l'index du processeur. L'index du processeur (proc_id) étant stocké dans le registre système ($15, 1) du processeur MIPS32, sa valeur peut être testée par le logiciel. Remarquez que la valeur de ce registre est initialisée par le constructeur C++, ce qui modélise une valeur cablée lors de la fabrication de la puce.
  • Les tâches s'exécutant en parallèle, chaque tâche (et donc chaque processeur) doit disposer de sa propre pile d'exécution. On définit un seul segment pour les différentes piles d'exécution, mais les pointeurs de pile des différents processeurs doivent être initialisés à des valeurs différentes en fonction du proc_id.
  • Chaque processeur exécutant un programme utilisateur différent, le points d'entrée du programme (registre EPC) doit être initialisé à une valeur dépendant du proc_id.

Pour cela, on demande à GCC de construire une table de sauts au début du segment seg_data, contenant les adressesdes 4 points d'entrée dans les 4 tâches allouées aux 4 processeurs.

5.3 Exécution parallèle sur architecture bi-processeurs

On commence par générer le code binaire pour une architecture comportant seulement 2 processeurs, de façon à exécuter en parallèle les deux programmes définis par les deux fonctions main() et main_display() (utilisées au début de ce TP) sur les deux processeurs P0 et P1.

Créez un nouveau répertoire soft_multi, et recopiez dans ce répertoire les fichiers contenus dans le répertoire soft, car plusieurs fichiers doivent être modifiés.

Question : Modifiez le fichier reset.s pour initialiser le pointeur de pile ($29 dans le cas du MIPS32) à une valeur dépendant du proc_id. Chaque tâche disposera d'une pile de 64 Koctets. Quelle doit être la longueur du segment de pile défini dans la table des segments de la top-cell pour 2 processeurs? Pour 4 processeurs?

Question : Modifiez le fichier reset.s pour que chaque processor PROC[i] initialize le registre de masque du composant ICU[i] qui lui est associé. A la fois la valeur du masque, et les adresses des registres dépendent du canaL: On fera en sorte que le processeur [i] reçoive les interruptions IRQ_TTY[i], IRQ_TIM[i], IRQ_DMA[i]. Le processeur [0] recevra en plus l'interruption IRQ_IOC.

Question : Modifiez le fichier reset.s pour que chaque processeur initialise son registre EPC à une valeur dépendant du proc_id.

Question : Modifiez le fichier reset.s pour que tous les processeurs initialisent le vecteur d'interruption pour toutes les lignes d'interruptions existant dans l'architecture. Pourquoi a-ton choisi que cette initialisation soit réalisée par tous les processeurs, ce qui peut sembler redondant ? Question : Modifiez le fichier Makefile du répertoire soft pour que le code des deux fonctions main() et main_display() soient intégré dans le même fichier app.bin. Vérifiez dans le fichier app.bin.txt que les adresses des deux fonctions main() et main_display() sont bien rangées au début du segment seg_data.

Question : Modifiez dans le fichier config.h la valeur de la variable qui informe le système d'exploitation du nombre de processeurs présents dans l'architecture matérielle.

Question : Compilez le logiciel embarqué, puis lancez l'exécution sur le simulateur simulator_multi.x, avec -NPROCS = 2 processeurs.

5.4 Exécution parallèle sur architecture quadri-processeurs

On souhaite maintenant exécuter 4 programmes en parallèle sur 4 processeurs.

Question : Ecrivez deux autres petits programmes qui s'exécuteront sur les deux autres processeurs. Si vous manquez d'imagination, vous pouvez vous contenter de répliquer le code du programme main.c. Si ces programmes utilisent les interruptions générées par le timer, n'oubliez pas que chaque processeur ne doit configurer que le timer qui lui appartient.

Question : Modifiez les fichiers reset.s, config.h, et Makefile du répertoire pour les adapter au cas de 4 processeurs, compilez le logiciel embarqué puis lancez l'exécution sur le simulateur simulator_multi.x, avec 4 processeurs.

Question : Le GIET supporte des architectures contenant jusque 8 processeurs. Nous avons cependant limité l'architecture générique à 4 processeurs. Quel est le facteur limitant qui justifie cette restriction?

6 Compte-rendu

Vous devez rédiger un compte-rendu résumant les étapes et répondant aux, vous pourrez faire une démonstration de votre simulateur la semaine prochaine (ou un peu plus tard, donc organisez bien vos répertoires de TP...).

Last modified 4 years ago Last modified on Oct 23, 2020, 2:54:54 AM

Attachments (3)

Download all attachments as: .zip