Année scolaire 2016-2017 Isabelle Puaut Systèmes d’exploitation, gestion de processus (SGP) Travaux dirigés Préambule Vous remarquerez que la plupart des périphériques utilisés lors des travaux dirigés sont anciens tant au niveau de leur mode de fonctionnement que de leur vitesse d’exécution. Les raisons motivant ce choix sont les suivantes : Le mode de fonctionnement de ces périphériques est simple, ce qui permet de les appréhender pendant la durée d’une séance de TD, ce qui serait quasiment impossible avec des systèmes plus actuels Les problèmes de synchronisation se posant avec ces périphériques simples sont identiques à ceux qui se posent avec des systèmes plus complexes La technologie évoluant très rapidement, il est illusoire d’avoir des informations temporelles exactes Merci de signaler lors des TD ou par mail à [email protected] toute erreur ou texte confus dans les sujets de TD. 2 Petits exercices sur les sémaphores Objectifs L'objectif de ce TD est d'acquérir une maîtrise de l'utilisation des sémaphores à compteurs via la programmation de problèmes de synchronisation très simples, mais néanmoins très courants. Questions 1. Deux programmes P1 et P2 se partagent deux variables a et b. Ecrire le code d’entrée et sortie de section critique permettant de protéger ces deux variables. Quel est l’effet d’un P bloquant à l’intérieur d’une des sections critiques ? P1: P2: if (a >0) a = a-1; b = a+4; b = b*2; 2. Soient deux processus P1 et P2 dont le code est le suivant : P1 P2 : : ………. ………. A1 A2 …………. …………. Ecrire le code de P1 et de P2 pour que P2 attende, au point A2, que P1 soit au point A1, et uniquement quand c’est nécessaire. 3. En reprenant l'exemple précédent, écrire le code des processus P1 et P2 pour établir un rendez-vous entre P1 et P2 juste après passage aux points A1 et A2 (attente mutuelle des deux processus quand c’est nécessaire). 4. Ecrire le code pour maintenant réaliser un rendez-vous entre trois processus P1, P2 et P3. Généraliser la solution pour un nombre n de processus en utilisant un tableau de n sémaphores. 3 5. Donner une solution à la question précédente qui minimise le nombre de sémaphores utilisés, par exemple en utilisant un compteur de processus arrivés au rendez-vous. 6. Soit un parking à N places, initialement vide. Chaque voiture désirant entrer dans le parking est représentée par un processus. Ecrire le code de synchronisation bloquant les voitures quand le parking est plein : • en n’utilisant pas d’attente active • en utilisant une variable partagée comptant le nombre de places libres dans le parking Processus voiture : <entrer dans le parking, avec attente d’une place libre> <rester dans le parking (durée quelconque inconnue a priori)> <sortir du parking> Est-il possible de réaliser ce problème de synchronisation sans variable partagée, en utilisant exclusivement des sémaphores ? Si oui, donner le code correspondant. 4 Utilisation de l'instruction XCHG pour réaliser une exclusion mutuelle Objectif L'objectif de ce TD est de bien comprendre les problèmes de réalisation de l'exclusion mutuelle en système monoprocesseur et multiprocesseur. Description du système On se place dans le cadre d'un ordinateur monoprocesseur possédant une instruction XCHG dont le fonctionnement est le suivant : XCHG - reg,mem Echange les contenus du registre reg et de la case mémoire d'adresse mem Ne positionne pas les codes condition du processeur Comme toute instruction, non interruptible Pas par défaut d’exclusivité d'accès au bus pendant l'exécution de l'instruction Questions 1. Programmer (en assembleur ou pseudo-code) le code d'entrée et de sortie d'une section critique 2. Démontrer, en vous appuyant sur des diagrammes temporels d’exécution, que : 5 • Le code fonctionne toujours sur un monoprocesseur • Le code ne fonctionne pas sur un multiprocesseur • En supposant l'existence d'un préfixe d'instruction LOCK, de syntaxe LOCK instruction, qui permet d'avoir un accès exclusif au bus pendant toute la durée d'une instruction, modifier les codes d'entrée et de sortie de section critique pour que l'exclusion mutuelle soit respectée dans le cas multiprocesseur et montrer que ce code fonctionne. 6 Application des sémaphores à deux problèmes de type général Objectifs L'objectif de ce TD est d'acquérir la maîtrise de deux problèmes de synchronisation très courants. Producteur-consommateur Deux processus (le producteur et le consommateur) se partagent un tableau de N éléments. Le producteur produit de nouveaux éléments dans le tableau, que le consommateur est chargé de consommer. Le producteur produit les éléments un par un. Le consommateur consomme les éléments également un par un. L'échange d'informations entre producteur et consommateur suit le schéma suivant, dans lequel les ??? représentent du code de synchronisation : Producteur : Consommateur : while (1) { while (1) { < bâtir une information > ??? ??? < recevoir une information > < la communiquer au consommateur > ??? ??? < exploiter l'information > } } Le producteur produit les informations dans un tableau, géré de manière circulaire. Le producteur utilise un indice de production nommé iplein (indice de la dernière case remplie), tandis que le consommateur utilise un indice de consommation nommé ivide (indice de la dernière case vidée). 1. Compléter le code des processus producteur et consommateur (gestion des indices et synchronisations à l’aide de sémaphores). 2. Si les processus ne se trouvent pas dans les sections de code entre les ???, on a e(splein) + e(svide) = N. Le démontrer en utilisant la propriété des sémaphores e(s) = e0(s) + nb_V(s) - nb_P(s). 3. Si l'on voulait avoir plusieurs producteurs et plusieurs consommateurs, quelles modifications faudrait-il apporter au 7 schéma précédent ? On supposera que les producteurs produisent pour n'importe quel consommateur. 4. On revient à un seul producteur et un seul consommateur. Si le producteur est actif, il continue tant qu'il y a une case vide ; si le producteur est bloqué, il n'est réveillé que s'il y a X cases vides (X > 1). Ecrire les nouvelles synchronisations pour le producteur et le consommateur. Lecteur-rédacteur Une donnée (par exemple un fichier) est partagée par deux types de processus : - Les processus qui la consultent (processus lecteurs) Les processus qui la modifient (processus rédacteurs). Les lectures et les écritures n’utilisent pas de temps processeur, donc en l’absence de synchronisation s’exécutent en parallèle. On veut assurer un maximum de parallélisme entre les différents processus tout en assurant que les informations lues aient un sens et que les modifications se fassent correctement. Pour que les informations lues aient un sens, on ne veut pas avoir un lecteur et un écrivain en même temps, ou deux écrivains simultanément sur la même donnée. 5. Donner le code des processus lecteur et rédacteur basé sur le principe suivant : - Un processus rédacteur se bloque s'il existe un processus lecteur ou écrivain en cours d'exécution Un processus lecteur se bloque uniquement s'il existe un processus écrivain en cours d'exécution 6. Que se passe t'il si on a les arrivées suivantes ? Dessiner le chronogramme correspondant au code écrit dans la question précédente, en supposant que les lectures peuvent s’effectuer en parallèle car elles n’utilisent pas le processeur (ex : lecture sur un périphérique). Date d'arrivée 1 2 3 5 Nature Lecteur 1 Rédacteur 2 Lecteur 2 Lecteur 3 Durée 3 2 3 3 7. Reprendre la première question en mettant une « priorité » égale entre les processus lecteurs et rédacteurs. Dès qu'un processus rédacteur arrive, tous les processus attendent, quelle que soit leur catégorie. 8 Décomposition en processus Objectif L'objectif de cet exercice est de mettre en application votre connaissance de la synchronisation par sémaphores (producteur-consommateur) sur un exemple complet, tout en restant simple. L'exercice permettra aussi de se convaincre des gains en performances que l'on peut obtenir en décomposant un système en processus. Le système utilisé est volontairement générique (débarrassé de toute considération de bas niveau). Le système considéré Dans le système on peut distinguer trois parties : entrée, calcul, sortie. Ces trois parties échangent de l'information à travers deux types de tampons TE et TS. Les tampons de même nature (TE, TS) sont chainés circulairement. • Entrée(TE) symbolise le remplissage du tampon TE. Cette partie dure 20 ms et se décompose en une phase d'initialisation d'1 ms, une phase d'attente sur le périphérique d'entrée et une phase terminale qui utilise également 1 ms de temps processeur. L’attente sur le périphérique d’entrée est non active (i.e. le processus en attente est bloqué sur un sémaphore pendant la durée de la lecture). • Calcul(TE,TS) utilise les informations contenues dans TE pour remplir le tampon TS. Cette opération dure 21 ms et ne consomme que du temps processeur. • Sortie(TS) symbolise le transfert sur le périphérique de sortie du contenu du tampon TS. Cette partie dure 25 ms et se décompose en une phase d'initialisation de 2 ms, une phase d'attente (non active) de 22 ms et une phase terminale d'1 ms. Comme pour l’entrée, le processus qui attend la fin de Sortie est bloqué sur un sémaphore pendant la durée de l’écriture. On regroupe ces 3 parties dans une boucle : while (1) { entrée(TE); calcul(TE,TS); sortie(TS); } 9 Questions 1. Evaluer la durée d'un cycle, ainsi que le taux d'occupation du processeur. Afin d'améliorer les performances, on envisage d'autres organisations. On suppose que les entrées et les sorties se font sur des périphériques indépendants et que l’ordonnançeur ne réalise la commutation de processus que sur l'appel à une primitive P bloquante (pas de changement de processus lors d’un V). Les schémas temporels mettront bien en évidence à chaque instant le processus qui s’exécute sur le processeur. On utilisera la notation x = suivant(x) pour effectuer un changement de tampon dans un ensemble x de tampons gérés circulairement. 2. On met la partie entrée dans un processus et on regroupe calcul et sortie dans un autre. • Combien de tampons TE et TS faudra-t-il déclarer pour avoir un bon parallélisme ? • Donner le texte des deux processus avec leur synchronisation par sémaphores. • Montrer sur un schéma temporel le fonctionnement de l'ensemble et indiquer la durée du cycle lorsque le régime permanent est atteint. 3. On regroupe maintenant entrée et calcul dans un processus, tandis que sortie est mis dans un autre. Reprendre les trois points de la question précédente avec cette nouvelle hypothèse. 4. On décompose le travail en trois processus : entrée, calcul, sortie. 10 • Quelle valeur de la durée du cycle espère-t-on ainsi atteindre ? • Donner le texte des trois processus. • Montrer sur le début d'un schéma temporel que l’ordonnançeur va avoir à choisir entre plusieurs processus. Quelle règle de choix proposez-vous (ordre de passage à prêt, priorité, -) ? • Donner alors un schéma temporel complet. Programmation des entrées/sorties caractère Objectif L'objectif de cet exercice est de programmer un pilote de périphérique caractère par attente active puis demande d'interruptions. Le contrôleur de périphérique est volontairement très simple pour se libérer des contraintes d'accès aux registres du contrôleur. Spécification du matériel Une ligne série est utilisée pour communiquer entre deux ordinateurs. Le matériel fonctionne en "full-duplex", ce qui signifie que l'envoi et la réception d'un caractère sont deux opérations indépendantes pouvant se dérouler en parallèle. Le contrôleur d'entrées/sorties comprend quatre registres internes : - Un registre d'entrée pour la réception d'un caractère Un registre de sortie pour l'émission d'un caractère Un registre d'état Un registre de contrôle (commande) Chaque voie du contrôleur (émission ou réception) peut être gérée en mode "test d'état" (attente active), ou avec demande d'interruption. On suppose disposer des primitives suivantes pour la programmation du contrôleur : - - 11 void lire(char *c) : range dans c le contenu du registre d'entrée void écrire(char c) : range c dans le registre de sortie char état_sortie : rend vrai quand le registre de sortie est vide char état_entrée : rend vrai quand le registre d'entrée est plein void autoriser_it_s() : une interruption sur le niveau it_s est postée par le contrôleur dès que le registre de sortie devient vide. L'acquittement de cette interruption s'effectue en écrivant un caractère dans le registre de sortie void autoriser_it_e() : une interruption sur le niveau it_e est postée par le contrôleur dès que le registre d'entrée devient plein. L'acquittement de cette interruption s'effectue en lisant un caractère. - void interdire_it_e() : le contrôleur ne poste pas d'interruption sur le niveau it_e - void interdire_it_s() : le contrôleur ne poste pas d'interruption sur le niveau it_s - void initialiser() : Initialise le contrôleur. Le contrôleur ne poste pas d'interruption sur les niveaux it_e et it_s. Les niveaux d'interruption it_e et it_s sont distincts ; le niveau it_e est plus prioritaire que le niveau it_s. Questions Dans un premier temps, on suppose qu'un seul processus utilise la ligne série. On souhaite réaliser les trois primitives suivantes : - void tty_init(void) void tty_em_ligne (char *l); : émet la ligne de longueur lg située dans le tableau l. Une ligne se termine par un zéro. void tty_rec_ligne (int *lg ; char *l) : reçoit une ligne de caractères terminée par 0 et retourne sa longueur dans lg. 1. Ecrire les trois opérations tty_init, tty_em_ligne et tty_rec_ligne, le contrôleur étant programmé en mode test d'état (attente active). L'émission et la réception sont des opérations synchrones. 2. Ecrire de nouveau les trois opérations en programmant maintenant le contrôleur avec demande d'interruption en entrée et en sortie. La voie de réception est gérée avec anticipation : les caractères reçus sont mémorisés dans un tampon de réception jusqu'à leur consommation (appel à tty_rec_ligne). Un tampon est également affecté à la voie émission. Dès que la ligne à émettre est recopiée dans le tampon émission, le processus suppose que l'entrée/sortie est terminée. Vous écrirez également les traitants d'interruption associés aux niveaux it_e et it_s. 3. Le partage de la ligne série entre plusieurs processus avec le code des deux questions précédentes pose t'il des problèmes de cohérence des lignes émises/reçues ? Si oui, modifier le code. 12 Entrées/sorties disque Objectif L'objectif de cet exercice est d'étudier la programmation de pilotes d'entrées/sorties dans le cas où ces dernières sont prises en charge par un processeur différent de celui sur lequel s'exécutent les programmes utilisateur (par exemple, processeur intégré au contrôleur disque). On s'intéressera en particulier à l'analyse des problèmes de synchronisation entre les processus utilisateurs et l'interface matérielle d'entrées/sorties. Le système considéré A. Appel aux procédures d'entrée/sortie par les processus utilisateur Un processus utilisateur qui désire lancer une entrée/sortie appelle une procédure système d'entrée/sortie (read ou write selon le sens du transfert). Cette procédure détermine le disque utilisé et construit un bloc d'entrée/sortie : IOCB (Input Output Control Block). IOCB IOCB.sema IOCB.posit IOCB.transf Zone réservée au chaînage Sémaphore privé pour synchronisation Commande de positionnement Commande de transfert la Ce bloc est ensuite chaîné dans une file d'attente exploitée par le processus driver. On pourra utiliser les procédures indivisibles : - in_queue(f_a,IOCB) pour placer le bloc IOCB en queue de la file d'attente f_a out_queue(f_b,IOCB) pour extraire le premier bloc de la file d'attente f_b et le recopier dans la zone locale IOCB. B. Processeur embarqué dans le contrôleur disque Le système peut comporter plusieurs disques, gérés par plusieurs contrôleurs disque. Les échanges entre la mémoire et les disques sont réalisés par un processeur embarqué dans le contrôleur disque, que nous nommons par la suite unité d'échange, ou UE, pour le différencier du processeur principal. Ce processeur traite des mots de commande élémentaires (positionnement, transfert). Il ne sait 13 traiter qu'une commande à la fois (il est non multiplexé). On suppose écrite la routine SIO(SIO a, b, @) (pour Start Input Output) qui transmet une commande à l'UE : où a désigne le numéro de l'unité d'échange b désigne le numéro du disque @ désigne l'adresse du mot de commande L'unité d'échange reconnaît deux commandes : - positionnement du bras sur la piste. Cette commande est transmise au disque par l'UE et celle-ci redevient libre à la fin du SIO transfert. Cette commande est transmise au disque, mais l'UE ne redevient libre qu'à la fin du transfert (c'est elle qui prend en charge les transferts mémoire). ex : a = 0; b = 1; Par exemple, SIO(a, b, &IOCB.posit) transmet au disque 1 de l'unité d'échange 0 la commande de positionnement préparée dans le mot IOCB.posit C. Le disque Lorsqu'un disque a fini une opération (positionnement ou transfert), l’opération V(sema(a,b)) est effectuée sur un sémaphore matériel. sema(a,b) est le sémaphore associé au disque b de l'UE a. Ce sémaphore est connu du logiciel et du matériel. Il est initialisé à 0 à la mise sous tension de la machine. Caractéristiques du disque : - Temps moyen de positionnement du bras : 60 ms, temps de rotation : 16ms Nombre de secteurs par piste : 8, longueur d'un transfert : 1 secteur Configuration machine ne comportant qu'un seul disque 1. Donner le squelette de la procédure d'entrée/sortie sans détailler le remplissage de l'IOCB 2. Donner le squelette du processus driver disque associé, qui exécute les SIO 3. Calculer la durée moyenne entre prise en compte d'une commande d'entrée/sortie par le driver et son achèvement. En déduire un pourcentage moyen d'occupation de l'unité d'échange. 14 Configuration machine à huit disques et une seule unité d'échange Dans cette partie, une unité d'échange unique gère l'ensemble des disques. 4. Quel intérêt voyez vous à définir un processus driver par disque ? 5. Modifier la procédure d'entrée/sortie 6. Décrire l'un des processus driver disque Configuration avec huit disques et deux unités d'échange Dans cette partie, l'UE 0 gère les disques 0 à 3, alors que l'UE 1 gère les disques 4 à 7. 7. Quelles sont les modifications à apporter au code écrit avant pour supporter cette nouvelle configuration ? Configuration avec huit disques à double accès On peut utiliser indifféremment l'une ou l'autre des unités d'échange pour transmettre une commande au disque. Pour un même disque la commande de positionnement et la commande de transfert ne sont pas obligées de transiter par la même UE. 8. Quel(s) intérêt(s) voyez-vous à cette configuration par rapport à la configuration précédente ? 9. On peut profiter de cette configuration pour spécialiser une UE en positionnement et l'autre en transfert. Citer les avantages et inconvénients d'une telle approche. 10. Si au contraire les deux UE ne sont pas spécialisées, modifier les processus disques pour utiliser au mieux les unités d'échange. 15 Etude d'un d’impression serveur Objectif Un serveur d’impression, autrefois appelé Spooler, de l'anglais SPOOL, abréviation de Simultaneous Peripheral Operation On-Line, est un programme qui se charge de récupérer les données envoyées vers un périphérique, qui les met dans file d'attente servant aussi de tampon et ne les envoie que lorsque que le périphérique est prêt. Cette technique est essentiellement utilisée pour les imprimantes. L'objectif de cet exercice est de réaliser un serveur d’impression, pour imprimer le contenu d'un fichier en parallèle avec une autre utilisation du processeur. On examinera aussi l'utilisation d'un DMA pour accélérer les transferts de données vers l'imprimante. L’imprimante utilisée (imprimante caractère que vous n’avez vue que dans le grenier de vos parents) n’est pas là uniquement par pure nostalgie de son doux crépitement, mais aussi parce que son temps variable d’impression d’un caractère introduit des phénomènes intéressants d’un point de vue synchronisation. Description du system Caractéristiques du disque - temps de changement de piste : 30 ms vitesse de rotation : 160 ms/tour 32 secteurs par piste 128 octets par secteur La primitive void lire_secteur(int nb; char *tampon,int *nblu) demande à transférer le contenu de nb secteur(s) dans la zone tampon. En sortie, nblu indique le nombre des secteurs qui ont effectivement été transférés. Cette primitive est bloquante ; elle utilise 1 ms de temps processeur quel que soit le nombre de tampons transférés. A chaque appel de lire-secteur on suppose qu'il y a positionnement du bras car le disque a pu être utilisé par d'autres processus. Caractéristiques de l'imprimante Pendant la phase de transfert d'une ligne vers l'imprimante, celle-ci impose un délai de 200 micro-secondes pour mémoriser un caractère. La phase d'impression 16 proprement dite est déclenchée par le transfert du caractère '\n' et dure 300 ms (en plus des 200 micro-secondes). L'imprimante apparaît au processeur à travers trois registres : donnée, contrôle et état. Lorsque l'imprimante est prête à échanger un caractère, ceci est noté dans son registre d'état et peut donner lieu à une interruption. Lorsque la valeur "it-interdite" est placée dans le registre de contrôle, l'imprimante n'émet aucune interruption. Si c'est par contre la valeur "it-permise", l'imprimante maintient une demande d'IT tant qu'elle est dans l'état prêt. Caractéristiques du fichier Pour les applications numériques, on considérera que le serveur d’impressions travaille sur un fichier de 50000 octets qui forment 1000 lignes. Une ligne est une séquence de 0 à 127 caractères suivie du caractère '\n'. Sauf indication contraire, les octets forment une chaîne continue qui ignore les frontières de secteurs. Le caractère '\0' servira à remplir les secteurs incomplets. Son transfert vers l'imprimante occasionnera un délai de 200 micro-secondes comme les autres caractères mais il sera ignoré lors de la phase d'impression. Caractéristiques du processeur La durée de la protection du contexte ou de sa restauration est de 30 microsecondes. Par contre, on considérera que dans la routine d'IT le temps des instructions est négligeable, de même dans le serveur d’impression seule la primitive lire-secteur consomme du temps processeur. A) Version simple du serveur d’impression On considère qu'il se compose d'une routine d'IT et d'un processus : it-imprimante : Processus serveur d’impression donnée = *p; while (1) { compte = compte-1; lire_secteur(1,tampon,&nblu); if (compte == 0) { if (nblu==0) break; controle="it-interdite"; p=tampon; V(sema); compte = 128; } controle = "it-permise"; else p = p+1; P(sema); retour d'IT } Le pointeur p et la variable compte sont des variables globales partagées entre la routine d'IT et le processus serveur d’impression. 1. Quelle doit être la valeur initiale de sema ? A quoi servent les deux instructions modifiant le contenu du registre contrôle ? 17 2. Quel intervalle de temps sépare l'envoi sur l’imprimante de deux caractères consécutifs d'une même ligne, stockés dans le même secteur et déjà chargés en mémoire ? 3. Donner le schéma temporel moyen (ligne de taille "moyenne") qui correspond à un tour de boucle du processus serveur d’impression, et donner la durée totale du transfert. 4. Pour effectuer le transfert du fichier, quel est le temps d'occupation du disque, de l'imprimante, du processeur ? Quel est le temps total ? En déduire le taux d'occupation disque, imprimante, processeur. B) Modification de l'organisation disque On décide de modifier les conventions du système de fichiers afin de ne mettre qu'une ligne par secteur. 5. Modifier la routine it-imprimante pour tirer profit de cette nouvelle convention. 6. Donner le nouveau schéma temporel 7. Reprendre la question 4 avec cette nouvelle convention. C) Version rapide du serveur d’impression Devant le volume disque impliqué par la convention du B) on revient à l'organisation initiale. 8. Montrer par un schéma temporel que l'on peut quand même conserver les performances de la partie B) à condition de prévoir deux tampons. 9. Décrire la routine d'IT et le processus serveur d’impression correspondant. 10. L'utilisation du disque par les autres processus peut provoquer une attente supplémentaire dans la procédure lire-secteur. Donner la valeur moyenne de l'attente qui peut être supportée sans que l'imprimante en soit ralentie. D) Augmentation des capacités d'impression Afin de faire face au besoin d'impression, on décide de doter le système de deux imprimantes connectées à travers un D.M.A. (Direct Memory Access) selon le schéma suivant : 18 Imprimante 1 UC Imprimante 2 DMA Mémoire Bus d'adresse Bus de données Le D.M.A. possède trois registres : adresse, nombre d'octets, état-dma. Lorsque le mot de contrôle d'une imprimante est mis à "transfert avec dma", celle-ci demande l'octet suivant au D.M.A. chaque fois qu'elle est prête ; par contre, elle n'émet plus d'interruption. Lorsque le registre nombre d'octets du D.M.A. passe à zéro, ceci est noté dans état-dma et génère simultanément une interruption. La lecture de ce mot d'état supprime l'interruption et la marque de passage par zéro. Les deux imprimantes et le D.M.A. sont connectés sur le même niveau d'interruption. 11. Avec quelle valeur doit être chargé le registre nombre d'octets pour aboutir à une bonne utilisation du D.M.A. ? 12. Quelles seront les valeurs successives mises dans le registre de contrôle d'une imprimante ? Quelles seront les causes logiques de ces modifications ? 13. Décrire la routine d'IT. 14. Décrire l'un des processus du serveur d’impression. 15. Comparer cette solution avec celle qui correspond à la duplication de la solution de la partie C. 19 Sauvegarde d'un disque Objectif L'objectif dans cet exercice est de réaliser un système de gestion d'entrées/sorties complexe : impliquant plusieurs périphériques, les contrôleurs associés étant dotés de tampons internes, et les transferts vers ces tampons étant effectués par DMA. Architecture du système Convention : Afin de simplifier les calculs et pour éviter toute ambiguïté, on adopte les conventions suivantes, 1 Ko=1024 octets, 1 Mo= 1000 Ko et 1 Go=1000 Mo. L'ordinateur utilisé possède la configuration ci-dessous. Bande disque RAM UC adresse e compteur fonction Le DMA n'est pas multiplexé, c'est à dire qu'il ne peut prendre en charge qu'un seul échange à la fois entre un périphérique et la RAM. Il apparaît à travers trois registres : dma.adresse, dma.compteur, dma.fonction. Le contenu du registre fonction comporte deux champs : {sens, périphérique}. Ainsi pour utiliser le DMA lors d'une lecture sur la bande, écrira-t-on dma.fonction ={lecture, bande}. Lorsque le compteur du DMA passe par 0, celui-ci émet une interruption et devient disponible pour être affecté à un autre transfert. Pour un fonctionnement correct, l'initialisation du DMA doit précéder celle du périphérique mis en jeu dans l'échange. Processus et ordonnancement Les outils de synchronisation sont les sémaphores (sans message). Chaque processus a sa priorité, et deux processus n'ont pas la même priorité. La 20 programmation de l’ordonnançeur, des primitives P et V, du retour d'IT, est faite pour que ce soit toujours le processus prêt de plus haute priorité qui soit actif. Les routines d'IT ne sont pas des processus, les IT utilisées sont au même niveau. il n'y a pas d'interruption horloge. Sauf indication contraire on négligera tous les temps de calcul sur le processeur. Lors de l'opération de sauvegarde il n'y a pas d'autre utilisation de l'ordinateur. Disque Le disque comporte 8 faces, et donc 8 têtes de lecture. Chaque piste est formée de 192 secteurs de 512 octets, et il y a 5000 cylindres. Le disque tourne à 10000 tr/mn. Il contient également une mémoire interne de 512 Ko, appelée cache par la suite. Le contrôleur est suffisamment évolué pour qu'il n'y ait pas besoin de programmer explicitement les déplacements du bras. Il apparaît à travers trois registres : - disc.adresse : numéro du premier secteur à transférer, disc.nbSecteur : nombre de secteurs consécutifs à transférer, disc.fonction : lecture, écriture, ainsi qu'une autre fonction décrite plus loin. Une demande de transfert se fait en initialisant les registres adresse, nbSecteur et fonction, dans cet ordre. Le contrôleur décompose une fonction lecture en deux phases, il lit d'abord toute l'information demandée sur le disque proprement dit et la met dans son cache, puis il utilise le DMA pour transférer cette information du cache vers la RAM. Au cours de l'exécution de cette fonction le contrôleur n'émet aucune IT. Dans tout le problème on considérera que lors d'un transfert entre le disque et son cache, il faut inclure un temps de positionnement en rotation, ou temps de latence, égal à 1/2 tour, et si besoin un temps de déplacement du bras dont la durée sera précisée dans la suite du texte. Le transfert à travers le DMA se fait à 20 Mo/s. Lecteur de bande Le lecteur de bande (nom classique même s'il sert aussi en écriture) est architecturé autour d'un tampon de 1024 Ko géré circulairement. Son fonctionnement peut se décomposer en deux activités indépendantes pouvant s'exécuter en parallèle : - le transfert entre le tampon et la RAM au travers du DMA, le transfert entre le tampon et la bande. Le transfert à travers le DMA est limité à 4 Mo/s, mais il peut être plus lent. Le transfert entre le tampon et la bande peut se faire selon trois vitesses : 600, 450 ou 300 Ko/s. La vitesse utilisée devra être définie au début de la sauvegarde. 21 Le contrôleur apparaît à travers deux registres : - bande.longueur : nombre d'octets à transférer à travers le DMA, bande.fonction : écriture, lecture. Une écriture sur bande s'initialise en chargeant le registre longueur puis la valeur écriture dans le registre fonction. Ceci lance en parallèle une séquence d'initialisation de la bande qui dure 1 s, et le remplissage du tampon du nombre d'octets demandés. Le transfert entre le tampon et la bande ne débute qu'à la fin de la séquence d'initialisation. Si les ordres d'écriture se succèdent assez rapidement le tampon ne se vide pas complètement, la bande défile en continu et donc la séquence d'initialisation n'a pas lieu. Si un ordre d'écriture arrive alors que le tampon est presque plein, c'est le transfert à travers le DMA qui se ralentit, pour s'adapter à la vitesse de libération du tampon par l'écriture sur la bande. Par contre si les ordres d'écriture ne sont pas assez fréquents le tampon peut devenir vide, dans ces conditions la bande s'arrête. Pour ce problème on conviendra que la solution n'est pas acceptable quand la bande s'arrête au cours de la sauvegarde. Sauvegarde physique Dans une première version on réalise une recopie systématique, piste par piste, de tous les secteurs du disque. 1. Quelle est la capacité du disque ? 2. En détaillant votre calcul, indiquez le temps qui s'écoule entre l'initialisation du disque et la disponibilité de l'information en RAM lors du transfert d'une des pistes situées sous une tête de lecture (pas de déplacement du bras) ? Pour les transferts piste par piste, le déplacement du bras prend 1 ms, quel est le temps nécessaire pour lire tout le contenu du disque mais sans effectuer de sauvegarde ? 3. Quel est le temps minimum pour transférer dans le tampon du lecteur de bande un volume équivalent à celui d'une piste, puis pour le transférer sur la bande à 600 Ko/s ? En déduire la durée du transfert sur la bande d'un volume équivalent à celui du disque ? 4. Donner le code des deux procédures bloquantes disc.lire(ad_mem, ad_disque, nb_secteurs) et bande.écrire(ad_mem, nb_octets) ainsi que la routine d'IT associée. Les codes devront être suffisamment généraux pour pouvoir être utilisés dans plusieurs processus. Par la suite les échanges ne se feront qu'au travers de ces deux fonctions. 22 5. En justifiant votre réponse, indiquez si la sauvegarde peut se programmer sous la forme d'un seul processus enchaînant la lecture d'une piste puis l'écriture sur bande, ou s'il faut utiliser un producteur-consommateur. Où se fera l'adaptation des vitesses ? Sauvegarde logique Afin de faciliter une restauration partielle du disque on envisage maintenant une seconde version faisant la recopie fichier par fichier et non plus piste par piste. Le système de fichier fait des allocations par granule, ensemble de secteurs consécutifs. Pour simplifier on ne se préoccupera pas de la gestion de la valeur de adresse_disque dans les appels à disc.lire(). On peut alors symboliser la lecture par : char tamp[TAILLE_GRANULE] ; /* tampon de la taille d'un granule */ for /* tous les fichiers */ { for /* tous les granules */ { disc.lire(& tamp, xxx, taille_granule); } } Afin de pouvoir facilement comparer avec la version précédente on considérera que le volume sauvegardé reste quand même celui de tout le disque. Cette manière de faire implique des accès plus désordonnés que dans la version précédente et le temps de positionnement moyen du bras sera celui fourni par le constructeur, 8 ms. 6. Calculez le temps moyen de transfert par disc.lire() d'un granule lorsque celui-ci fait 1 secteur et que tous les granules d'un fichier sont répartis aléatoirement sur le disque. En déduire le temps de lecture de tout le disque. 7. Calculez le temps de transfert d'un granule, puis celui de tout le disque lorsque le granule fait 8 Ko. 8. Donnez le code de l'application sous la forme d'un producteur- consommateur bande.écrire(). utilisant les procédures disc.lire() et 9. En rapprochant la durée de la lecture du disque de celle de l'écriture sur bande on décide d'utiliser la bande à 600 Ko/s et de prendre deux tampons en mémoire. Montrez par un schéma temporel que cette solution n'est pas satisfaisante. Pourquoi ? 23 10. En justifiant votre réponse, choisissez la vitesse de la bande, le nombre de tampons en mémoire et la priorité des processus. En lisant plus attentivement les caractéristiques du contrôleur du disque, on s'aperçoit qu'il existe aussi la fonction préchargement qui se limite à transférer les secteurs demandés dans le cache avec émission par le contrôleur d'une interruption en fin de transfert. Lorsque par la suite, le contrôleur reçoit un ordre de lecture portant sur des secteurs déjà dans le cache, il n'a plus qu'à réaliser le transfert à travers le DMA. 11. Modifiez la procédure disc.lire(…, nb_secteurs) pour tirer partie du préchargement de nb_secteurs et au besoin la routine d'IT. 12. En quoi cela modifie-t-il les choix faits dans la question 10. Donnez un schéma temporel pour le début de la sauvegarde, puis un autre quand le régime permanent est atteint. Compression Afin de pouvoir utiliser des cartouches de bande magnétique plus petites ou des disques ayant un plus grand nombre de faces on envisage maintenant de compresser les données lorsqu'elles sont en RAM, entre la lecture et l'écriture. L'algorithme de compression s'adapte au contenu de chaque fichier et pour cela il travaille en deux phases : il génère d'abord des statistiques sur tout le fichier (supposé en mémoire), puis il le comprime bloc par bloc en fonction des propriétés statistiques de ce fichier. La procédure stat(ad_mem, nbOctets, tabStat) permet de remplir le tableau tabStat en analysant la chaîne d'octets commençant en ad_mem. Cette procédure prend 200 ms par Ko analysé. La procédure comprime(adSource, adCible, tabStat) prend un bloc de 8 Ko et le comprime sous la forme d'un bloc de 4 Ko en utilisant les statistiques contenues dans tabStat. Cette procédure prend 100 ms par Ko produit. Des mesures faites lors de l'utilisation de la version utilisant le préchargement ont montré que le système de fichiers savait allouer les granules dans des cylindres suffisamment proches pour que le temps moyen de déplacement du bras soit de 4 ms. Pour rajouter la compression, on repart de la version avec préchargement. Il faut donc décider de la place de chacune des procédures stat et comprime : dans le processus de lecture, dans celui d'écriture, dans un processus indépendant. Il faut également choisir la priorité des processus, la vitesse de la bande ainsi que les caractéristiques des tampons en mémoire. 13. Commencez par faire ces choix en supposant que tous les fichiers font 8 Ko, 14. Ces choix restent-ils valides lorsque les fichiers font tous 1 Mo ? 24