Chapitre 9 : Mémoire virtuelle Sujets abordés Généralités Chargement de page à la demande Création de processus Remplacement de pages Allocation des cadres Écroulement du SE Exemples de SE
Généralités Mémoire virtuelle – technique permettant de faire exécuter des processus qui ne se trouvent pas en totalité en mémoire ; ils peuvent donc utiliser un espace d’adressage logique supérieur à l’espace physique du système. Cette technique sépare la mémoire logique de l’utilisateur de la mémoire physique du système Simplifie le partage des espaces d’adressage. Simplifie la création des processus. Deux techniques d’implémentation : Pages à la demande : la technique la plus courante. Segment à la demande : plus compliquée et rarement utilisée (IBM OS2, Burroughs) Espace d’adressage virtuel : espace logique du processus, vu comme un plage continue d’adresses commençant à 0
Organisation de la mémoire virtuelle Page 0 Mémoire virtuelle d’un processus Page 1 Page 2 Page 3 Page 4 Page k Table de correspondance Page n Mémoire physique Mémoire externe
Espace d’adressage virtuel code données tas pile MAX
Partage de la mémoire Les processus peuvent se partager des procédures et des données Les zones partagées occupent dans la mémoire physique une place unique : la table des pages de chaque processus contient les correspondances entre les pages logiques et les pages physiques. Les zones partagées sont marquées « en lecture seule » Lorsqu’un processus se termine, on analyse la tables des pages actives. Les pages mémoire non partagées peuvent être libérées. La mémoire partagée peut aussi servir pour la communication entre les processus.
Espace d’adressage virtuel (suite) code données tas pile MAX Bibliothèque partagée MAX P2 code données tas pile
Implémentation : méthode des pages à la demande Les pages sont chargées en mémoire seulement quand elles sont nécessaires (a comparer avec la permutation qui est utilisée pour charger ou décharger l’ensemble de la mémoire occupée par un processus ). Moins d’opérations d’E/S Besoin réduit de mémoire Réponse accélérée Plus d’utilisateurs Le programme fait référence à une adresse logique : Référence invalide abort Référence absente de la mémoire chargement de la page Référence en mémoire : accès
Table des pages quand toutes les pages ne se trouvent pas en mémoire Le bit de validité V = page appartenant au processus et présente en mémoire I = page n’appartenant pas à l’espace virtuel (6 et 7) ou absente de la mémoire (1, 3 et 4). Si la page est absente de la mémoire, l’entrée de la table contient son adresse sur disque.
Bit de validité A chaque entrée de la table des pages on associe un bit de validité (1 en mémoire, 0 pas en mémoire) Le bit est initialisé à 0 pour toutes les entrées. Pendant le calcul de l’adresse, si le bit est à 0 le système génère une “erreur de page” No cadre Bit de validité 1 1 1 1 Table des pages
Erreur de page La première référence à une page génère automatiquement une “erreur de page” qui est détectée par un piège dans le SE. Le SE examine la table des pages du processus (qui fait partie du PCB) pour déterminer si : La référence est invalide (hors espace virtuel) abort. La référence est valide mais la page est absente de la mémoire. Cherche un cadre disponible (dans la liste des cadres disponibles). Charge la page dans le cadre. Modifie le bit de validité de la page et met à jour la table des pages. Redémarre le processus au point ou il a été interrompu L’expérience montre que les programmes on une forme de “localité” qui fait que les erreurs de page ne sont pas fréquentes par rapport aux adressages sans erreur.
Traitement de l’erreur de page
Equipement nécessaire Au lancement du processus, le SE construit le PCB (Process Control Block = descripteur du processus). Entre autres, ce descripteur contient une table ; pour chaque entrée de cette table on prévoit 2 données : le no. « logique » de la page dans l’espace virtuel du processus et l’adresse « physique » de la page sur disque Le défaut de page provoque une interruption qui est détectée par le matériel et traitée comme dans le schéma précédent. Si il n’y a pas de cadre disponible dans la mémoire vive, il faut en libérer, ce qui suppose une gestion spécifique des pages ainsi que des algorithmes de décision : quelle(s) page(s) sera(ont) enlevée(s) de la mémoire vive et que fait-on d’elle(s) ? Les pages enlevées de la mémoire sont généralement enregistrées sur un disque rapide, dans une zone spécialement destinée a la permutation (swapping).
Que se passe-t-il en cas d’erreur de page ? Si la mémoire physique a des cadres disponibles, on charge la page manquante Si la mémoire physique n’a pas de cadre libre, on remplace une page – elle ne doit pas être en cours d’utilisation Condition de performance – utilisation d’un algorithme conduisant a un nombre minimum d’erreurs de page. Problème : après le chargement de la page manquante (remplacement ou pas) il faut reprendre l’instruction qui a généré l’erreur de page. Ceci induit un temps perdu (l’instruction est exécuté deux fois) mais peut s’avérer plus coûteux et compliqué si c’est une instruction qui manipule plusieurs adresses à la fois (ex : une instruction qui permet de copier un bloc d’adresses d’un endroit à un autre dans la mémoire) : il faut aussi rétablir l’état précédent. Dans ce dernier cas il est possible que l’instruction fasse référence à plusieurs pages (ex : des instructions qui déplacent des blocs en mémoire)!
Opérations en cas de défaut de page. Activité no. 1 : gestion de l’interruption Interruption du SE (piège) Sauvegarde des registres et de l’état du processus Pk Identification de la cause de l’interruption Activité no. 2 : chargement de la page Vérification de la légalité de l’accès et calcul de la position sur disque de la page à charger Si un cadre libre existe, lance une lecture de la page sur disque sinon, libère une page (choix + écriture disque) Si l’UC a été affectée à un autre processus Pj pendant les opérations de sauvegarde / restauration disque (peuvent durer…), il faut l’interrompre et sauvegarder son état. Mise à jour des tables de gestion des pages Activité no. 3 : reprise du processus interrompu Attendre le tour du processus Pk Restauration des registres et de l’état processus Reprise au point d’interruption
Effet sur la performance du système Soit p la probabilité d’erreur de page : 0 p 1.0 si p = 0 pas d’erreurs si p = 1, toutes les références à la mémoire sont des erreurs Durée effective d’adressage (TAE = Temps Effectif d’Adressage) TAE = (1 – p) x temps_d’accès_à_ la_mémoire + p x (temps_de_traitement_de_l’erreur + temps de sauvegarde de la page + temps de restauration d’une page + surcharge due au redémarrage)
Exemple de calcul des performances Temps d’accès mémoire = 200 nanosecondes = 0,2 µs Dans 50% des cas, la page qui doit être remplacée a été “touchée”, donc il faut la sauvegarder. Permutation des pages = 8 ms (normal pour des disques durs) On fait abstraction de l’existence éventuelle d’une queue aux accès disque TAE = 200 x (1-p) +8 x 106 x p = 200 + 7999800 x p Le temps d’accès est donc pratiquement proportionnel à p : Soit p = 1/1000 : TAE = 8,199 microsecondes (soit 40 fois plus que le temps d’accès diret !) Si on veut imposer une dégradation limitée à 10%, il faut que 200+799800 x p < 200 + 20, ce qui conduit à p < 0,0000025 !
Gestion des défauts de pages Deux approches : La permutation totale : utilisation d’une zone dédiée sur un disque. On adresse cette zone par des procédures spéciales, plus performantes que celles utilisées dans le cadre de la gestion des fichiers, car on évite la surcharge nécessaire pour la gestion des arborescences et le partage. La permutation partielle : les pages du processus sont toujours chargées à partir du fichier qui les contient (et ou elles ne sont pas modifiées), mais on permute les informations périssables – la pile et les données temporaires stockées dans le « tas » (voir pages 4 et 6). La deuxième approche présente l’avantage de réduire l’espace nécessaire pour la permutation (SOLARIS et UNIX BSD)
Mémoire virtuelle et processus L’utilisation de la mémoire virtuelle apporte des avantages supplémentaires : Création des processus enfants : Initialement, les processus enfants peuvent partager les mêmes pages que le processus parent, ce qui évite une majeure partie de la procédure de création d’un nouveau processus (fork(), vfork() ). Seulement les pages qui ont été “touchées” par le parent ou un des enfants seront sauvegardées et copiées (COW = Copy On Write) Fichiers en mémoire (MMF = Memory Mapped Files) Le fichier est traité comme une succession de pages Une zone du fichier de la taille d’une page est lue et mise en mémoire Les opérations d’E/S sur le fichier sont ensuite traitées comme des accès mémoire, en évitant les procédures (plus lentes) READ et WRITE.
Exercices Exercice 1 Soit un système qui fournit aux utilisateurs un espace virtuel de 232 octets. L’ordinateur dispose d’une mémoire physique de 218 octets. La mémoire virtuelle est implémentée par pagination, avec des pages de 4096 octets. Montrer comment l’ordinateur établit l’adresse physique si le processus génère une référence à l’adresse 11123456. Exercice 2 Soit un système utilisant une mémoire paginée dont la table des pages est maintenue dans les registres. Il faut 8 ms pour répondre à un défaut de page si on trouve un cadre vide ou si la page que l’on remplace n’a pas été modifiée et 20 ms si la page a été modifiée. Le temps d’accès à la mémoire est de 100 ns. On suppose que les pages remplacées sont modifiées dans 70% des cas. Quel est le rapport acceptable de défauts de pages pour que le temps d’accès effectif ne dépase pas 200 ns ?
Réponse aux exercices Exercice 1 Exercice 2 Integer(11123456 / 4096) = 2715 donc l’adresse recherchée se trouve dans la page 2715 2715 * 4096 = 11120640 c’est l’adresse du début de la page L’adresse recherchée se trouve à l’adresse 11123456 – 11120640 =2816 de la page 2715 Exercice 2 En cas de défaut de page, 70% sont des pages modifiées et 30% des pages intactes. Soit p la probabilité de défaut de page ; le temps effectif d’accès Te se calcule avec la formule Te=Ta*(1 – p) + p * Tp Le temps de permutation n’est pas le même suivant que la page a été touchée ou non ; il faut donc considérer les 2 cas de figure : 0,7 * p * Tp1 et 0,3 * p * Tp2 , ou Tp1=20ms et TP2=8ms On en déduit que p =(Te – Ta) / (0,7 * Tp1 + 0,3 * Tp2 – Ta)
Problème de la sur - réservation Soit N processus composés de Mi pages chacun, mais dont Rj pages ne sont jamais utilisées Le chargement des pages à la demande présente l’avantage de ne charger que les pages nécessaires, soit P = Σ(Mi – Ri) Supposons que nous disposons de Q cadres ; il faut Q >=P Le nombre de processus qui peuvent être chargés et exécutés simultanément est donc supérieur à celui calculé sur la base du nombre total de pages des processus. Cela s’appelle de la « sur- réservation » et présente l’avantage de permettre à un plus grand nombre de processus de s ’exécuter dans le même laps de temps (throughput)
Remplacement des pages En cas de sur-réservation, que se passe-t-il si pour une raison quelconque, un processus demande une page qui n’est pas en mémoire ? Soit il y a un cadre disponible et la page peut être chargée Soit tous les cadres sont occupés, et il faut remplacer une page qui est allouée à un processus actif. On peut remplacer sans sauvegarde une page qui n’a pas été modifiée, mais pour ce faire il faut disposer d’une information indiquant l’état de la page : bit de modification (dirty bit) L’algorithme de traitement des erreurs de page doit être adapté. Le remplacement des pages affranchit le programmeur de toute dépendance envers la configuration de la machine, en ce qui concerne l’utilisation de la mémoire.
Le remplacement des pages
Le remplacement des pages : algorithme de base Calculer la position de la page souhaitée sur disque Trouver un cadre libre. Si trouvé, on l’utilise Sinon, utiliser l’algorithme de remplacement pour choisir une “victime” : ce sera la page à remplacer. Si on dispose du bit d’état de la page, on peut éviter d’effectuer deux transferts de pages : un pour sauvegarder la victime et l’autre pour charger l’élue… Chargement de la page demandée dans le cadre devenu libre et mise à jour des tables de pages et des autres tables. Redémarrage du processus
Le remplacement des pages : schéma
Le remplacement des pages : évaluation des algorithmes On souhaite le minimum d’erreurs de pages Pour évaluer l’algorithme, on utilise une liste de nombres qui représentent des références à la mémoire et on calcule le nombre d’erreurs de pages produites par la liste On se servira toujours de la même liste : 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5. Il semble logique qu’avec l’augmentation du nombre de cadres on réduise le nombre d’erreurs de pages.
Nombre d’erreurs de page par rapport au nombre de cadres
Algorithme FIFO (premier entré, premier sorti) (On note par N/T le no. N de la page remplacée au moment T ) Liste des références : 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5 3 cadres 4 cadres Cet algorithme présente, dans certains cas, une anomalie : l’anomalie de Belady 1 1/1 4/4 5/7 2 2/2 1/5 3/8 9 erreurs de page 3 3/3 2/6 4/9 1 1/1 5/7 4/11 2 2/2 1/8 5/12 10 erreurs de page ! 3 3/3 2/9 4/4 3/10 4
/* * * * MemVirt_FIFO démontre l'algorithme FIFO de remplacement de pages * et met en évidence l'anomalie de Belady * Pierre Dimo * version 2005 */ public class MemVirt_FIFO { public static int N; public static int Cadres[]; public static int Liste[] = {1,2,3,4,1,2,5,1,2,3,4,5}; public static void main() { for (N=1 ; N < Liste.length ; N++) { Cadres = new int[N]; int i, j, k; int p=0; for (j=0 ; j < N ; j++) // initialisation Cadres[j]=-1; for (i=0 ; i < Liste.length ; i++) for (j=0 ; j < N ; j++) // vérifier si déjà en mémoire if (Cadres[j]==Liste[i]) break; if (j==N) // pas en mémoire for (j=0 ; j < N ; j++) // vérifier si place if (Cadres[j] < 0) Cadres[j] = Liste[i]; p++ ; } if (j == N) // pas de place, il faut libérer la page la plus ancienne for (k=1 ; k < N ; k++) Cadres[k-1] = Cadres[k]; Cadres[N-1]=Liste[i]; System.out.println("Nombre de cadres = "+N+" Remplacements = "+p); }}
Algorithme optimal Remplacer la page qui sera la dernière sollicitée. Exemple avec 4 cadres 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5 Comment déterminer quelle est la page qui sera la dernière sollicitée ? Ce n’est pas possible… On utilise cette approche pour comparer des algorithmes proposés. 1/5 4/11 2/6 6 erreurs de page 3/3 4/4 5/7
Algorithme LRU (Least Recently Used = la page délaissée le plus longtemps) Série de référence : 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5 1/8 5/12 2/9 3/3 5/7 4/11 4/4 3/10 temps
LRU (Suite) Implémentation avec compteur Chaque entrée de la page possède un compteur dont le contenu est mis à jour avec l’horloge chaque fois que la page est référencée Quand il y a demande de page, on examine les compteurs pour établir celle qui a été délaissée le plus longtemps Implémentation avec pile : la page référencée peut se trouver ou ne pas se trouver déjà dans la pile ; si elle ne se trouve pas il faut la chercher On garde une pile des numéros de pages référencées sous la forme d’une liste doublement liée Lorsque la page est référencée, on déplace son numéro au sommet de la pile : le sommet est la dernière page référencée, la base la plus délaissée Pour accélérer le processus, on utilise des listes liées ce qui permet de réduire l’opération à la modification de 6 pointeurs de liaison On n’a pas besoin de chercher la page à changer
LRU – utilisation d’une pile
/* * MemVirt_LRU démontre l'algorithme LRU de remplacement de pages * Pierre Dimo * version 2005 * * */ public class MemVirt_LRU { public static int N; public static int Cadres[]; public static int Liste[] = {1,2,3,4,1,2,5,1,2,3,4,5}; public static void main() { for (N=1 ; N < Liste.length ; N++) { Cadres = new int[N]; int i, j, k; int p=0; for (j=0 ; j < N ; j++) // initialisation Cadres[j]=-1; for (i=0 ; i < Liste.length ; i++) for (j=0 ; j < N ; j++) // vérifier si déjà en mémoire if (Cadres[j]==Liste[i]) { // si oui, on la met en position "récente" for (k=0 ; k==j-1 ; k++) Cadres[k+1]=Cadres[k]; Cadres[0]=Liste[i]; break; } if (j==N) // pas en mémoire for (j=0 ; j < N ; j++) // vérifier si place if (Cadres[j] < 0) { Cadres[j] = Liste[i]; p++ ; break; } if (j == N) { // pas de place, il faut libérer une page for (k=0 ; k < N-1 ; k++) Cadres[k+1] = Cadres[k]; p++ ; } } System.out.println("Nombre de cadres = "+N+" Remplacements = "+p); }}}
Algorithme à base de compteurs Un compteur/page pour le nombre de références Algorithme LFU (least frequently used = la moins utilisée) : on remplace la page avec le moins d’utilisations Problème : certains processus utilisent intensément une page au début et la délaissent ensuite. On utilise le vieillissement pour ajuster le compteur. Algorithme MFU (most frequently used) : part de l’hypothèse que les pages peu utilisées ont été chargées récemment. Ces algorithmes donnent généralement des résultats qui s’éloignent de ce que produit l’agorithme optimal.
Allocation des cadres On peut établir qu’un processus, quel qu’il soit, doit disposer toujours d’un nombre minimum de pages. Ceci peut être imposé par l’architecture de l’ordinateur : Exemple: IBM 370 – 6 pages sont nécessaires pour exécuter l’instruction SS MOVE : L’instruction a 6 octets, elle peut s’étendre sur 2 pages. 2 pages sont nécessaires pour accéder au départ (from) 2 pages sont nécessaires pour accéder à la destination (to). Deux méthodes d’allocation Allocation fixe Allocation prioritaire
Allocation des cadres (suite) Allocation fixe On partage le nombre de cadres de manière égale entre les processus (ex : 100 cadres et 5 processus, chaque processus recevra 20 pages). Allocation proportionnelle : chaque processus reçoit un nombre de pages proportionnel à sa taille. Allocation prioritaire On utilise une allocation proportionnelle basée sur les priorités des processus ou sur une combinaison de la priorité et de la taille
Écroulement du système Si le processus ne dispose pas d’assez de pages, cela conduit à de nombreuses erreurs de page : Utilisation réduite de l’UC. Le SE en déduit qu’il doit augmenter le nombre de processus en mémoire…. Écroulement (Thrashing) un processus s’occupe uniquement de sauvegarder et restaurer des pages
Écroulement (Thrashing ) Pour éviter l’écroulement on peut utiliser un algorithme de remplacement « local » : Les processus fonctionnent par “localité” Les localités « isole » les processus (la machine ne s ’écroule pas brutalement) mais ne résout pas entièrement le problème car si un processus s’écroule il va accéder en permanence au mécanisme de demande de pages et donc va ralentir l’ensemble. Il faut fournir autant de pages que sont nécessaires On utilise la notion de « localité » : c’est l’espace d’adressage utilisé en priorité par un processus. Les tests montrent que les processus se « déplacent » de « localité » en « localité »
Le modèle de l’espace de travail (working set) On définit une « fenêtre » de l’espace de travail La fenêtre contient les Δ pages adressées dernièrement. Une page dont on ne se sert plus depuis Δ unités de temps est remplacée dans la fenêtre Exemple : 2 6 1 5 7 7 7 7 5 1 6 2 3 4 1 2 3 4 4 4 3 4 3 4 4 4 1 3 2 3 4 4 4 3 4 4 4 WS(t1)={1,2,5,6,7} WS(T1+16)={3,4} Le nombre de cadres nécessaires est la somme des nombres de pages de tous les WS actifs. Si le SE ne dispose pas d’assez de pages pour les satisfaire tous, il va interrompre un processus et redistribuer les cadres qu’il utilisait.
Exercices Soit un tableau à 2 dimensions A, dont le premier élément est stocké à l’adresse 200 d’un système paginé dont les pages ont la taille 200. Le processus qui trie ce tableau est stocké dans la page 0, entre les adresses 0 et 199. Faisons l’hypothèse que le système possède 3 cadres : combien d’erreurs de pages seront provoquées par les boucles d’initialisation de ce tableau (comparer algorithmes FIFO et LRU) : Int A[ ][ ]; for (int j=1; j < 100; j++) for (int i=1; i < 100 ; i++) A[i][j] = 0; for (int i=1; i < 100; i++) for (int j=1; j < 100 ; j++)
Mémoire virtuelle de Windows XP Pages à la demande + clustering (groupement, paquet, bouquet, ) La page demandée est chargée avec plusieurs pages qui la suivent A la création d’un processus on lui attribue un ensemble de travail (working set) minimum et un ensemble de travail maximum.(50 , 345) Le gestionnaire de la mémoire virtuelle maintien une liste des cadres libres et un valeur indiquant le seuil de disponibilité des cadres libres.
FIN chpt 9
FORK() et VFORK() FORK() est un appel système qui génère un processus « enfant » à partir d’un processus « parent ». Le processus enfant utilise le même espace d’adressage que le processus parent, ce qui, en l’absence de l’utilisation d’une mémoire virtuelle se traduit par la création d’un processus « copie » du processus qui appelle fork(). Si on dispose d’un système d’adressage virtuel, le processus enfant peut partager dans un premier temps les pages du processus parent et on ne copie que les pages partagées modifiées par l’enfant ou par le parent en cours d’exécution. Le SE copie la page partagée modifiée dans l’espace d’adressage du processus qui l’a modifié et qui sera par la suite le seul à l’adresser. Cette technique est appelée « copy on write » (copie si modifié). Les pages qui ne sont pas modifiables (ex : les pages qui contiennent le code) peuvent être partagées en permanence (Windows XP, Solaris, Linux). VFORK() est une variante de FORK(), qui crée un processus « enfant » et le lance immédiatement après la création après avoir stoppé le processus parent. Si VFORK() modifie des données dans l’espace virtuel d’adressage qu’il a hérité du parent, ce dernier trouvera son espace altéré lorsqu’il reprendra le contrôle. VFORK() est très efficace mais il faut l’utiliser à bon escient… Retour…
Fichiers en mémoire
Fichiers en mémoire La mise à jour du fichier ne se fait pas après chaque modification Périodiquement, quand le SE vérifie si des pages ont été modifiées Lors de la fermeture du fichier par un des processus qui l’a ouvert. Dans certains SE on dispose d’un appel système spécifique pour déclencher le « mapping » du fichier (Solaris : MMAP()) Lorsque le partage des fichiers « mappés » est autorisé, il faut gérer l’accès en écriture ; on se sert des procédures de type « exclusion mutuelle » étudiées antérieurement. Voir un exemple en Java à la page suivante.
Retour… import java.io.*; import java.nio.*; import java.nio.channels.*; public class MemoryMapReadOnly { // on prévoit un page de 4 KB public static final int PAGE_SIZE = 4096; public static void main(String[] args) throws java.io.IOException { if (args.length == 1) { RandomAccessFile inFile = null; try { inFile = new RandomAccessFile(args[0],"r"); } catch (FileNotFoundException fnfe) { System.err.println("Fichier " + args[0] + " inconnu !"); System.exit(0); FileChannel in = inFile.getChannel(); MappedByteBuffer mappedBuffer = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size()); System.out.println("La taille de l'image en mémoire est de " + in.size()+ " Kb"); long numPages = in.size() / (long)PAGE_SIZE; if (in.size() % PAGE_SIZE > 0) ++numPages; // on touche le premier octet de chaque page int position = 0; for (long i = 0; i < numPages; i++) { byte item = mappedBuffer.get(position); position += PAGE_SIZE; System.out.println("Le système a mappé le fichier dans " + numPages + " pages de 4 Kb"); //System.out.println(in.size() % PAGE_SIZE); // déterminer si le chargement est terminé if (!mappedBuffer.isLoaded()) { // forcer le chargement mappedBuffer.load(); in.close(); inFile.close(); } } } Retour…