Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sde:laboratoare:07_fr_python [2020/04/01 13:13]
iuliana.marin
— (current)
Line 1: Line 1:
-====== TP 07 - Gestion de la mémoire ====== 
-===== Gestion de la mémoire ===== 
- 
-Sle système de gestion de la mémoire d'un système d'​exploitation est utilisé par tous les autres sous-systèmes:​ planificateur,​ E / S, système de fichiers, gestion des processus, mise en réseau. La mémoire est une ressource importante, c'est pourquoi des algorithmes efficaces pour son utilisation et sa gestion sont nécessaires. 
- 
-Le rôle du sous-système de gestion de la mémoire est de: 
-  * garder une trace des zones de mémoire physique (occupée ou libre) 
-  * pour fournir aux processus ou autres sous-systèmes un accès à la mémoire 
-  * mapper les pages de mémoire virtuelle d'un processus (pages) sur les pages physiques (frames). 
- 
-Le noyau du système d'​exploitation propose un ensemble d'​interfaces (appels système) qui permettent l'​allocation / partage de mémoire, le mappage de régions de mémoire virtuelle sur des fichiers, le partage de zones de mémoire. 
- 
-Malheureusement,​ le niveau limité de compréhension de ces interfaces et des actions qui se produisent derrière elles conduit à un certain nombre de problèmes qui sont couramment rencontrés dans les applications logicielles:​ fuites de mémoire, accès invalides, écrasement,​ débordement de tampon, corruption de zone mémoire. 
- 
-Il est donc essentiel de connaître le contexte dans lequel le sous-système de gestion de la mémoire fonctionne et de comprendre l'​interface fournie au programmeur par le système d'​exploitation. 
- 
-===== L'​espace d'​adressage d'un processus ===== 
- 
-{{ so:​laboratoare-2013:​process_address_space.jpg|L'​espace d'​adressage d'un processus}} 
- 
-L'​espace d'​adressage d'un processus ou, mieux encore, l'​espace d'​adressage virtuel d'un processus est la zone de mémoire virtuelle qu'un processus peut utiliser. Chaque processus a son propre espace d'​adressage. Même dans les situations où deux processus partagent une zone mémoire, l'​espace virtuel est distinct, mais il est mappé sur la même zone mémoire physique. 
- 
-La figure suivante montre un espace d'​adressage typique pour un processus. Dans les systèmes d'​exploitation modernes, dans l'​espace virtuel de chaque processus, la mémoire du noyau est mappée, elle peut être mappée au début ou à la fin de l'​espace d'​adressage. Ensuite, nous ferons uniquement référence à l'​espace d'​adressage dans l'​espace utilisateur pour un processus. 
- 
-Les 4 zones importantes de l'​espace d'​adressage du processus sont la zone de données, la zone de code, la pile et le tas. Comme le montre la figure, la pile et le tas sont les zones qui peuvent croître. En fait, ces deux domaines sont dynamiques et n'ont de sens que dans le contexte d'un processus. De l'​autre côté, les données dans la zone de données et la zone de code sont décrites dans l'​exécutable. 
- 
-<note info> 
-Parce que Python est un langage interprété,​ toutes les variables sont affectées sur le tas. Pour pouvoir analyser la mémoire allouée, nous devons utiliser un langage compilé. Par conséquent,​ dans ce laboratoire,​ nous utiliserons des exemples en langage C. 
-</​note>​ 
- 
- 
-==== Zone de code ==== 
- 
-Le segment de code (également appelé «segment de texte») représente les instructions en langage machine du programme. Le registre du pointeur d'​instruction (IP) fera référence aux adresses dans la zone de code. Lisez l'​instruction indiquée par l'IP, décodez-la et interprétez-la,​ puis incrémentez le compteur de programme et passez à l'​instruction suivante. 
-La zone de code est généralement une zone en lecture seule afin que le processus ne puisse pas modifier ses instructions en utilisant le mauvais pointeur. La zone de code est partagée entre tous les processus exécutant le même programme. Ainsi, une seule copie du code est mappée à l'​espace d'​adressage virtuel de tous les processus. 
- 
-==== Zones de données ==== 
- 
-Les zones de données contiennent les variables globales définies dans un programme et les variables en lecture seule. Il existe plusieurs sous-types de zones de données selon le type de données. 
- 
-=== .data === 
- 
-La zone ''​ .data ''​ contient les variables globales et les variables statiques __initialized__ aux valeurs nulles d'un programme. Par exemple: 
- 
-<code c> 
-static int a = 3; 
-char b = '​a';​ 
-</​code>​ 
- 
-=== .bss === 
- 
-La zone ''​ .bss ''​ contient les variables globales et statiques __initialized__ d'un programme. Avant l'​exécution du code, ce segment est initialisé avec 0. Par exemple: 
- 
-<code c> 
-static int a; 
-char b; 
-</​code>​ 
- 
-En général, ces variables ne seront pas préallouées dans l'​exécutable,​ mais lors de la création du processus. L'​allocation de la zone ''​ .bss ''​ se fait sur des pages mises à zéro. 
- 
-=== .rodata === 
- 
-La zone ''​ .rodata ''​ contient des informations qui ne peuvent être lues que et non modifiées. Voici les __literals__ stockés: 
-<code c> 
-"​Hello,​ World!"​ 
-"En Taro Adun!" 
-</​code>​ 
- 
-et __contenu__. Toutes les variables globales déclarées avec le mot-clé ''​ const ''​ seront placées dans les ''​ données ''​. Les variables locales déclarées comme "​const"​ seront placées sur la pile, donc dans une zone mémoire non marquée comme // en lecture seule // et peuvent être modifiées par un pointeur sur elles. Un cas particulier est représenté par les variables locales constantes déclarées avec le mot-clé ''​ statique ''​ qui seront mises dans le ''​ .data '':​ 
-<code c> 
-const int a;           /* în .rodata */ 
-const char ptr[]; ​     /* în .rodata */ 
- 
-void myfunc(void) 
-{ 
-   int x;              /* pe stivă */ 
-   const int y;        /* pe stivă */ 
-    
-   ​static const int z; /* în .rodata */ 
-    
-   ​static int p = 8;   /* în .data */ 
-   ​static int q;       /* în .bss */ 
-   ... 
-</​code>​ 
- 
-==== Pile ==== 
- 
-La pile est une région dynamique au sein d'un processus, gérée automatiquement par le compilateur. 
- 
-La pile est utilisée pour stocker des "​cadres de pile". Un nouveau "cadre de pile" sera créé pour chaque appel de fonction. 
- 
-Un "cadre de pile" contient: 
-   * variables locales 
-   * arguments de fonction 
-   * adresse de retour 
- 
-Sur la grande majorité des architectures modernes, la pile croît (des grandes adresses aux petites adresses) et le tas augmente. La pile augmente à chaque appel de fonction et diminue à chaque retour de la fonction. 
- 
-La figure ci-dessous montre une vue conceptuelle sur la pile lors de l'​appel d'une fonction. 
- 
-{{ so:​laboratoare-2013:​call_stack.png }} 
- 
-==== Tas (Heap) ==== 
- 
-Le tas est la zone de mémoire dédiée à l'​allocation dynamique de mémoire. Le segment de mémoire est utilisé pour allouer des régions de mémoire dont la taille est déterminée lors de l'​exécution. 
- 
-Comme la pile, le tas est une région dynamique qui change sa taille. Contrairement à la pile, cependant, le tas n'est pas géré par le compilateur. Il est du devoir du programmeur de savoir combien de mémoire allouer et de se rappeler combien allouer et quand allouer. Les problèmes courants dans la plupart des programmes sont la perte de références aux zones allouées (fuites de mémoire) ou la référence de zones non allouées ou insuffisamment allouées (accès invalides). 
- 
-Dans des langages comme Java, Lisp, etc. là où il n'y a pas de "​liberté de pointeur",​ l'​effacement de l'​espace alloué se fait automatiquement via un garbage collector. Sur ces systèmes, le problème de la perte de références est évité, mais le problème de référence de zones non allouées reste actif. 
-  
-  
- 
-===== Allocation / affectation de mémoire ===== 
- 
-** L'​allocation de mémoire ** est effectuée statiquement par le compilateur ou dynamiquement pendant l'​exécution. // L'​allocation statique // est effectuée dans des segments de données pour les variables globales ou pour les littéraux. 
- 
-Lors de l'​exécution,​ les variables sont allouées sur la pile ou dans le tas. L'​allocation de pile est automatiquement effectuée par le compilateur pour les variables locales d'une fonction (à l'​exception des variables locales préfixées par l'​identifiant ** statique **). 
- 
-// L'​allocation dynamique // se fait en tas. L'​allocation dynamique se produit quand on ne sait pas, au moment de la compilation,​ combien de mémoire sera nécessaire pour une variable, une structure, un vecteur. Si, au moment de la compilation,​ on sait combien d'​espace une variable occupera, son allocation statique est recommandée,​ afin d'​éviter les erreurs qui se produisent fréquemment dans le contexte de l'​allocation dynamique. 
- 
-Afin de fragmenter le moins possible l'​espace d'​adressage du processus, à la suite d'​allocations et de désallocations de zones de différentes tailles, l'​allocateur de mémoire organisera le segment de données alloué dynamiquement en // tas //, d'où le nom du segment. 
- 
-** Débordement de mémoire ** signifie libérer de la zone de mémoire précédemment allouée (elle est marquée comme libre). 
- 
-Si le verrouillage d'une zone mémoire est omis, il restera alloué pendant toute la durée d'​exécution du processus. Chaque fois qu'une zone mémoire n'est plus nécessaire,​ elle doit être compensée pour l'​efficacité d'​utilisation de l'​espace mémoire. 
- 
-Il n'est pas nécessaire de traiter différents domaines avant un appel [[http://​linux.die.net/​man/​3/​exit|exit]] ou avant la fin du programme car ils sont automatiquement libérés par le système d'​exploitation. 
- 
-<​note>​Dans la plupart des cas, des problèmes surviennent si vous essayez de colocaliser deux fois la même zone de mémoire. En effet, les données de gestion interne des zones allouées sont corrompues. Habituellement,​ le noyau signale le problème avec une interruption synchrone.</​note>​ 
- 
-==== Allocation de mémoire sous Linux ==== 
- 
-Sous Linux, l'​allocation de mémoire pour les processus utilisateur se fait via les fonctions de bibliothèque [[http://​linux.die.net/​man/​3/​malloc|malloc]],​ [[http://​linux.die.net/​man/​3/​calloc|calloc]] et [[http://​linux.die.net/​man/​3/​realloc|realloc]],​ et sa collocation à travers la fonction [[http://​linux.die.net/​man/​3/​free|free]]. Ces fonctions représentent les appels de bibliothèque et résolvent, dans la mesure du possible, l'​allocation de mémoire et les demandes d'​allocation dans l'​espace utilisateur. 
- 
-<spoiler Implémentation de la fonction malloc> 
-\\  
-Implémentation de la fonction [[http://​linux.die.net/​man/​3/​malloc|malloc]] dépend du système d'​exploitation. 
- 
-Il existe des implémentations qui conservent des tables qui spécifient les zones de mémoire allouées au tas. S'il y a des zones libres sur le tas, appelez [[http://​linux.die.net/​man/​3/​malloc|malloc]] cil nécessite une zone de mémoire qui peut être encadrée dans une zone libre du tas sera satisfaite immédiatement,​ marquant la zone dans la table comme étant allouée et renvoyant au programme appelant un pointeur vers celle-ci. 
- 
-Si, au lieu de cela, une zone est demandée qui ne rentre dans aucune zone sans segment de mémoire, [[http://​linux.die.net/​man/​3/​malloc|malloc]] va tenter d'​étendre le tas par appel système [[http://​linux.die.net/​man/​2/​brk|brk]] ou [[http://​linux.die.net/​man/​3/​mmap|mmap]]. 
- 
-{{so:​laboratoare-2013:​heap_bounds.gif|}} 
- 
-Il existe des implémentations qui, pour chaque zone de mémoire requise avec [[http://​linux.die.net/​man/​3/​malloc|malloc]] ajoute un en-tête qui contient des informations utiles - taille d'une zone, pointeur vers la zone suivante, que la zone ait été effacée ou non. 
- 
-{{so:​laboratoare-2013:​heap_chunks.gif|}} 
- 
-Plus de détails sur malloc et comment organiser le tas [[http://​academicearth.org/​lectures/​heap-management|ici]] et [[https://​docs.google.com/​viewer?​url=http://​wiki-prog.kh405.net/​images/​0/​04/​Malloc_tutorial.pdf|ici]]. 
-</​spoiler>​ 
- 
-\\  
-<code c> 
-void *malloc(size_t size); 
-void *calloc(size_t nmemb, size_t size); 
-void *realloc(void *ptr, size_t size); 
-void free(void *ptr); 
-</​code>​ 
- 
-Toujours relâcher ([[http://​linux.die.net/​man/​3/​free|free]]) mémoire allouée. La mémoire allouée au processus est automatiquement libérée à la fin du processus, mais, par exemple, dans le cas d'un processus serveur qui s'​exécute depuis longtemps et ne libère pas la mémoire allouée, il finira par occuper toute la mémoire disponible dans le système, ce qui aura des conséquences néfastes. 
- 
-** Avertissement! ** Ne libérez pas deux fois la même zone de mémoire, car cela entraînerait la corruption des tables [[http://​linux.die.net/​man/​3/​malloc|malloc]] qui aura à nouveau des conséquences néfastes. Depuis la fonction [[http://​linux.die.net/​man/​3/​free|free]] renvoie immédiatement s'il reçoit en paramètre un pointeur ''​ NULL '',​ il est recommandé qu'​après un appel [[http://​linux.die.net/​man/​3/​free|free]],​ le pointeur est réinitialisé sur ''​ NULL ''​. 
- 
-Voici quelques exemples d'​utilisation de l'​allocation de mémoire [[http://​linux.die.net/​man/​3/​malloc|malloc]]:​ 
- 
-<code c> 
-int n = atoi(argv[1]);​ 
-char *str; 
- 
-/* généralement malloc reçoit l'​argument taille comme: 
-   ​num_elements * size_of_element */ 
-str = malloc((n + 1) * sizeof(char));​ 
-if (NULL == str) { 
-perror("​malloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-[...] 
- 
-free(str); 
-str = NULL; 
- 
-</​code>​ 
- 
-<code c> 
- 
-/* Création d'un tableau de références aux arguments reçus par un programme */ 
-char **argv_no_exec;​ 
- 
-/* allouer de l'​espace pour le tableau */ 
-argv_no_exec = malloc((argc - 1) * sizeof(char*));​ 
-if (NULL == argv_no_exec) { 
-perror("​malloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-/* définir des références aux arguments du programme */ 
-for (i = 1; i < argc; i++) 
-argv_no_exec[i-1] = argv[i]; 
- 
-[...] 
- 
-free(argv_no_exec);​ 
-argv_no_exec = NULL; 
-</​code>​ 
- 
-L'​appel [[http://​linux.die.net/​man/​3/​realloc|realloc]] est utilisé pour changer l'​espace mémoire alloué par un appel [[http://​linux.die.net/​man/​3/​malloc|malloc]] ou [[http://​linux.die.net/​man/​3/​calloc|calloc]]:​ 
- 
-<code c> 
-int *p; 
- 
-p = malloc(n * sizeof(int));​ 
-if (NULL == p) { 
-perror("​malloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-[...] 
- 
-p = realloc(p, (n + extra) * sizeof(int));​ 
- 
-[...] 
- 
-free(p); 
-p = NULL; 
-</​code>​ 
- 
-L'​appel [[http://​linux.die.net/​man/​3/​calloc|calloc]] est utilisé pour allouer des zones de mémoire dont le contenu est nul (plein de valeurs nulles). Contrairement à [[http://​linux.die.net/​man/​3/​malloc|malloc]],​ l'​appel recevra deux arguments: le nombre d'​éléments et la taille d'un élément. 
- 
-<code c> 
-list_t *list_v; /* list_t could be any C type ( except void ) */ 
- 
-list_v = calloc(n, sizeof(list_t));​ 
-if (NULL == list_v) { 
-perror("​calloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-[...] 
- 
-free(list_v);​ 
-list_v = NULL; 
-</​code>​ 
- 
-** Attention ** Selon la norme C, il est redondant (et considéré comme une mauvaise pratique) de faire // cast // à la valeur retournée par [[http://​linux.die.net/​man/​3/​malloc|malloc]]. 
-<code C> 
-int *p = (int *)malloc(10 * sizeof(int));​ 
-</​code>​ 
-malloc renvoie // void * // qui en C est automatiquement converti en le type souhaité. De plus, si le cast est fait et que l'​en-tête ''​ stdlib.h ''​ requis pour la fonction malloc n'est pas inclus, aucune erreur ne sera générée! Sur certaines architectures,​ ce cas peut conduire à un comportement indéfini. Contrairement à C, en C ++, vous avez besoin d'un cast. La discussion est élaborée [[http://​www.cprogramming.com/​faq/​cgi-bin/​smartfaq.cgi?​answer=1047673478&​id=1043284351 | ici ]]. 
- 
-Vous trouverez plus d'​informations sur les fonctions d'​allocation dans [[http://​www.gnu.org/​software/​libc/​manual/​html_node/​Unconstrained-Allocation.html#​Unconstrained-Allocationl| le manuel de bibliothèque standard C]] et dans la page de manuel **man malloc**. 
- 
-===== Travailler avec la mémoire - Problèmes ===== 
- 
-Travailler avec le tas est l'une des principales causes de l'​émergence de problèmes de programmation. Travailler avec des pointeurs, la nécessité d'​utiliser les appels système / bibliothèque pour l'​allocation / concession, peut conduire à un certain nombre de problèmes qui affectent le fonctionnement du programme (souvent fatal). 
- 
-Les problèmes les plus fréquemment rencontrés lors de l'​utilisation de la mémoire sont les suivants: 
-   * accès mémoire invalide - qui implique l'​accès à des zones qui n'ont pas été allouées ou qui ont été libérées. 
-   * fuites de mémoire - situations où la référence à une zone précédemment allouée est perdue. Cette zone restera occupée jusqu'​à la fin du procès. 
- 
-Les problèmes et les utilitaires qui peuvent être utilisés pour les combattre seront présentés ci-dessous. 
- 
-==== Accès invalide ==== 
- 
-En règle générale, l'​accès à une zone de mémoire non valide entraîne une erreur de page et l'​achèvement du processus (sous Unix, cela signifie l'​envoi du signal SIGSEGV -> affichage du message ''​ Erreur de segmentation ''​). Toutefois, si l'​erreur apparaît à une adresse non valide, mais sur une page valide, le matériel et le système d'​exploitation ne pourront pas signaler l'​action comme non valide. En effet, l'​allocation de mémoire se fait au niveau de la page. Par exemple, il peut y avoir des situations où seule la moitié de la page est utilisée. Bien que l'​autre moitié contienne des adresses non valides, le système d'​exploitation ne pourra pas détecter un accès non valide à cette zone. 
- 
-Ces accès peuvent entraîner une corruption de tas et une perte de cohérence de la mémoire allouée. Comme nous le verrons ci-dessous, il existe des utilitaires qui aident à détecter ces situations. 
- 
-Un type spécial d'​accès non valide est [[http://​en.wikipedia.org/​wiki/​Buffer_overflow|buffer overflow]]. Ce type d'​attaque implique de référencer des régions valides dans l'​espace d'​adressage du processus au moyen d'une variable qui ne devrait pas pouvoir référencer ces adresses. En règle générale, une attaque par dépassement de tampon entraîne l'​exécution de code non sécurisé. La protection contre les attaques par débordement de tampon est obtenue en vérifiant les limites d'un tampon / vecteur à la compilation ou au moment de l'​exécution. 
- 
-==== Fuites de mémoire ==== 
- 
-Une [[http://​en.wikipedia.org/​wiki/​Memory_leak|fuite de mémoire]] apparaît dans deux situations: 
-   * un programme omet d'​effacer une zone mémoire 
-   * un programme perd la référence à une zone mémoire allouée et, par conséquent,​ ne peut pas la libérer 
- 
-Les fuites de mémoire ont pour effet de réduire la quantité de mémoire existante dans le système. On peut atteindre, dans des situations extrêmes, la consommation de toute la mémoire du système et l'​impossibilité de fonctionnement de ses différentes applications. 
- 
-Comme pour le problème d'​accès à la mémoire invalide, [[#​Valgrind|Utilitaire Valgrind]] il est très utile pour détecter les fuites de mémoire d'un programme. 
- 
-==== Accord double ==== 
- 
-Le nom "​accord double"​ offre une bonne intuition sur la cause: libérer deux fois le même espace mémoire. La double désallocation peut avoir des effets négatifs car elle affecte les structures internes utilisées pour gérer la mémoire occupée. 
- 
-Dans les dernières versions de la bibliothèque C standard, les cas de double désallocation sont automatiquement détectés. Prenez l'​exemple ci-dessous: 
- 
-<code c dubla_dealocare.c>​ 
-#include <​stdlib.h>​ 
- 
-int main(void) 
-{ 
-char *p; 
- 
-p = malloc(10); 
-free(p); 
-free(p); 
- 
-return 0; 
-} 
-</​code>​ 
- 
-L'​exécution de l'​exécutable obtenu à partir du programme ci-dessus entraîne l'​affichage d'un message de libération double glibc spécifique d'une région de mémoire et l'​arrêt du programme: 
- 
-<code bash> 
- ​so@spook$ make 
- cc -Wall -g    dfree.c ​  -o dfree 
- ​so@spook$ ./​dfree ​ 
-  *** glibc detected *** ./dfree: double free or corruption (fasttop): 0x0000000000601010 *** 
- ​======= Backtrace: ========= 
- /​lib/​libc.so.6[0x2b675fdd502a] 
- /​lib/​libc.so.6(cfree+0x8c)[0x2b675fdd8bbc] 
- ​./​dfree[0x400510] 
- /​lib/​libc.so.6(__libc_start_main+0xf4)[0x2b675fd7f1c4] 
- ​./​dfree[0x400459] 
- 
-</​code>​ 
- 
-Des situations de double transaction sont également détectées par Valgrind. 
- 
-==== Autres utilitaires pour résoudre les problèmes de travail avec la mémoire ==== 
- 
-Les utilitaires présentés ci-dessus ne sont pas les seuls utilisés pour détecter les problèmes dans [[http://​en.wikipedia.org/​wiki/​Category:​Memory_management_software|travailler avec la mémoire]]. Les autres utilitaires sont: 
- 
-  *[[http://​dmalloc.com/​|dmalloc]] 
-  *[[http://​mpatrol.sourceforge.net/​|mpatrol]] 
-  *[[http://​duma.sourceforge.net|DUMA]] 
-  *[[http://​perens.com/​works/​software/​ElectricFence/​|Electric Fence]] 
- 
-===== Mémoire virtuelle ===== 
- 
-Mecanismul de memorie virtuală este folosit de către nucleul sistemului de operare pentru a implementa o politică eficientă de gestiune a memoriei. Astfel, cu toate că aplicațiile folosesc în mod curent memoria virtuală, ele nu fac acest lucru în mod explicit. Există însă câteva cazuri în care aplicațiile folosesc memoria virtuală în mod explicit. 
- 
-Sistemul de operare oferă primitive de mapare a fișierelor,​ a memoriei sau a dispozitivelor în spațiul de adresă al unui proces. 
-  ***Maparea fișierelor** în memorie este folosită în unele sisteme de operare pentru a implementa mecanisme de memorie partajată. De asemenea, acest mecanism face posibilă implementarea paginării la cerere și a bibliotecilor partajate. 
-  ***Maparea memoriei** în spațiul de adresă este folositoare atunci când un proces dorește să aloce o cantitate mare de memorie. 
-  ***Maparea dispozitivelor** este folositoare atunci când un proces dorește să folosească direct memoria unui dispozitiv (precum placa video). 
- 
-==== Concepte teoretice ====  ​ 
- 
-Dimensiunea spațiului de adresă virtual al unui proces depinde de dimensiunea registrelor procesorului. Astfel, pe un sistem de 32 biți un proces va putea accesa 2^32 = 4GB spațiu de memorie (pe de altă parte, pe un sistem de 64 biți va accesa teoretic 2^64 B). Spațiul de memorie al procesului este împărțit în spațiu rezervat pentru adresele virtuale de kernel - acest spațiu este comun tuturor proceselor - și spațiul virtual (propriu) de adrese al procesului. De cele mai multe ori, împărțirea între cele două este de 3/1 (3GB user space vs 1GB kernel space). 
- 
-Memoria fizică (RAM) este împărțită între procesele active în momentul respectiv și sistemul de operare. Astfel că, în funcție de câtă memorie avem pe mașina fizică, este posibil să epuizăm toate resursele și să nu mai putem porni un proces nou. Pentru a evita acest scenariu s-a introdus mecanismul de memorie virtuală. În felul acesta, chiar dacă spațiul virtual (compus din segmentul de text, data, heap, stivă) al unui proces este mai mare decât memoria fizică disponibilă pe sistem, procesul va putea rula încărcându-și în memorie doar paginile de care are nevoie în timpul execuției (on demand paging). 
- 
-Spațiul virtual de adrese este împărțit în //pagini virtuale// (page). Corespondentul pentru memoria fizică este //pagina fizică// (frame). Dimensiunea unei pagini virtuale este egală cu cea a unei pagini fizice. Dimensiunea este dată de hardware (în majoritatea cazurilor o pagină are 4KB pe un sistem de 32 biți sau 64 biți). 
- 
-Atât timp cât un proces în timpul rulării accesează numai pagini rezidente în memorie, se execută ca și când ar avea tot spațiul mapat în memoria fizică. În momentul în care un proces va dori să acceseze o anumită pagină virtuală, care nu este mapată în memorie, se va genera un //page fault//, iar în urma acestui page fault pagina virtuală va fi mapată la o pagină fizică. Două procese diferite au spațiu virtual diferit, însă anumite pagini virtuale din aceste procese se pot mapa la aceeași pagină fizică. Astfel că, două procese diferite pot partaja o aceeași pagină fizică, dar nu partajează pagini virtuale. 
- 
-=== malloc === 
- 
-Așa cum am aflat in sectiunea [[#Alocarea memoriei în Linux|Alocarea memoriei în Linux]], ''​malloc''​ alocă memorie pe heap, deci în spațiul virtual al procesului. ​ 
- 
-Alocarea memoriei virtuale se face la nivel de pagină, astfel că ''​malloc''​ va aloca de fapt cel mai mic număr de pagini virtuale ce cuprinde spațiul de memorie cerut. Fie următorul cod:<​code c> 
-char *p = malloc(4150);​ 
-DIE(p == NULL, "​malloc failed"​);​ 
-</​code>​ 
- 
-Considerând că o pagină virtuală are 4KB = 4096 octeți, atunci apelul ''​malloc''​ va aloca 4096 octeți + 54 octeți = 4KB + 54 octeți, spațiu care nu este cuprins într-o singură pagină virtuală, astfel că se vor aloca 2 pagini virtuale. În momentul alocării cu ''​malloc''​ nu se vor aloca (tot timpul) și pagini fizice; acestea vor fi alocate doar atunci când sunt accesate datele din zona de memorie alocată cu ''​malloc''​. De exemplu, în momentul accesării unui element din p se va genera un //page fault//, iar pagina virtuală ce cuprinde acel element va fi mapată la o pagină fizică. 
- 
-<​note>​ 
-În general, la apelul malloc de dimensiuni mici (când se apelează în spate apelul de sistem ''​brk''​) biblioteca standard C parcurge paginile alocate, se generează page fault-uri, iar la revenirea din apel paginile fizice vor fi deja alocate. Putem spune că pentru dimensiuni mici, apelul ''​malloc'',​ așa cum este văzut el din aplicație (din afara bibliotecii standard C), alocă și pagini fizice și pagini virtuale. 
- 
-Mai mult, alocarea efectivă de pagini virtuale și fizice are loc în momentul apelului de sistem ''​brk''​. Acesta prealocă un spațiu mai mare, iar viitoarele apeluri ''​malloc''​ vor folosi acest spațiu. În acest fel, următoarele apeluri ''​malloc''​ vor fi eficiente: nu vor face apel de sistem, nu vor face alocare efectivă de spațiu virtual sau fizic, nu vor genera //page fault-uri//​. 
- 
-Apelul ''​malloc''​ este mai eficient decât apelul ''​calloc''​ pentru că nu parcurge spațiul alocat pentru a-l umple cu zero-uri. Acest lucru înseamnă că ''​malloc''​ va întoarce zona alocată cu informațiile de acolo; în anumite situații, acest lucru poate fi un risc de securitate - dacă datele de acolo sunt private. 
-</​note>​ 
- 
-==== Maparea fișierelor ==== 
- 
-În urma mapării unui fișier în spațiul de adresă al unui proces, accesul la acest fișier se poate face similar cu accesarea datelor dintr-un vector. Eficiența metodei vine din faptul că zona de memorie este gestionată similar cu memoria virtuală, supunându-se regulilor de evacuare pe disc atunci când memoria devine insuficientă (în felul acesta se poate lucra cu mapări care depășesc dimensiunea efectivă a memoriei fizice). Mai mult, partea de ''​I/​O''​ este realizată de către kernel, programatorul scriind cod care doar preia/​stochează valori din/în regiunea mapată. Astfel nu se mai apelează ''​read'',​ ''​write'',​ ''​lseek''​ - ceea ce adesea simplifică scrierea codului. 
- 
-<note important>​ Nu orice descriptor de fișier poate fi mapat în memorie. Socket-urile,​ pipe-urile, dispozitivele care nu permit decât accesul secvențial (ex. char device) sunt incompatibile cu conceptele de mapare. Există cazuri în care fișiere obișnuite nu pot fi mapate (spre exemplu, dacă nu au fost deschise pentru a putea fi citite; pentru mai multe informații:​ **man mmap**). </​note>​ 
- 
-=== mmap === 
- 
-Prototipul funcției [[http://​linux.die.net/​man/​2/​mmap|mmap]] ce permite maparea unui fișier în spațiul de adresă al unui proces este următorul: 
- 
-<code cpp> 
-void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 
-</​code>​ 
- 
-Funcția va întoarce în caz de eroare ''​MAP_FAILED''​. Dacă maparea s-a făcut cu succes, va întoarce un pointer spre o zonă de memorie din spațiul de adresă al procesului, zonă în care a fost mapat fișierul descris de descriptorul ''​fd'',​ începând cu offset-ul ''​offset''​. Folosirea parametrului ''​start''​ permite propunerea unei anumite zone de memorie la care să se facă maparea. Folosirea valorii ''​NULL''​ pentru parametrul ''​start''​ indică lipsa vreunei preferințe în ceea ce privește zona în care se va face alocarea. Adresa precizată prin parametrul ''​start''​ trebuie să fie multiplu de //​dimensiunea unei pagini//. Dacă sistemul de operare nu poate să mapeze fișierul la adresa cerută, atunci îl va mapa la o adresă apropiată și multiplu de dimensiunea unei pagini. Cea mai corespunzatoare adresa este si intoarsa. ​ 
- 
-Parametrul ''​prot''​ specifică tipul de acces care se dorește: ​ 
-  *''​PROT_READ''​ (citire) 
-  *''​PROT_WRITE''​ (scriere) ​ 
-  *''​PROT_EXEC''​ (execuție) 
-  *''​PROT_NONE''​. ​ 
-Cand zona e folosită altfel decât s-a declarat, este generat un semnal ''​SIGSEGV''​. 
- 
-Parametrul ''​flags''​ permite stabilirea tipului de mapare ce se dorește; poate lua următoarele valori (combinate prin SAU pe biți; trebuie să existe cel puțin ''​MAP_PRIVATE''​ sau ''​MAP_SHARED''​):​ 
-  *''​MAP_PRIVATE'' ​  - Se folosește o politică de tip //​copy-on-write//​. Zona va conține inițial o copie a fișierului,​ dar scrierile nu sunt făcute în fișier. Modificările //nu vor fi vizibile în alte procese//, dacă există mai multe procese care au făcut ''​mmap''​ pe aceeași zonă din același fișier. 
-  *''​MAP_SHARED'' ​   - Scrierile sunt actualizate imediat în toate mapările existente. In acest fel toate procesele care au realizat mapări vor vedea modificările. Lucru datorat faptului că mapările ''​MAP_SHARED''​ se fac peste paginile fizice din page cache, iar apelurile r/w folosesc paginile fizice din page cache pentru a reduce numărul de citiri/​scrieri de pe disc. Actualizările pe disc vor avea loc la un moment de timp ulterior, nespecificat. 
-  *''​MAP_FIXED'' ​    - Dacă nu se poate face alocarea la adresa specificată de ''​start'',​ apelul va eșua. 
-  *''​MAP_LOCKED'' ​   - Se va bloca paginarea pe această zonă, în maniera [[http://​linux.die.net/​man/​2/​mlock|mlock]]. 
-  *''​MAP_ANONYMOUS''​ - Se mapează memorie RAM (argumentele ''​fd''​ și ''​offset''​ sunt ignorate). 
- 
- Este de remarcat faptul că folosirea ''​MAP_SHARED''​ permite partajarea memoriei între procese care nu sunt înrudite. În acest caz, conținutul fișierului devine conținutul inițial al memoriei partajate și orice modificare făcută de procese în această zonă este copiată apoi în fișier, asigurând persistență prin sistemul de fișiere.  ​ 
- 
-=== msync === 
- 
-Pentru a declanșa în mod explicit sincronizarea fișierului cu maparea din memorie este disponibilă funcția [[http://​linux.die.net/​man/​2/​msync|msync]]:​ 
- 
-<code c> 
-int msync(void *start, size_t length, int flags); 
-</​code>​ 
- 
-unde ''​flags''​ poate fi: 
-  *''​MS_SYNC''​ - Datele vor fi scrise în fișier și se așteaptă până se termină. 
-  *''​MS_ASYNC''​ - Este inițiată secvența de salvare, dar nu se așteaptă terminarea ei. 
-  *''​MS_INVALIDATE''​ - Se invalidează mapările zonei din alte procese, astfel incât procesele își vor face update cu datele noi înscrise. 
- 
-Apelul msync este util pentru a face scrierea paginilor modificate din page cache pe disc, cu scopul de a evita pierderea modificărilor în cazul unei căderi a sistemului. 
-==== Alocare de memorie în spațiul de adresă al procesului ==== 
- 
-În UNIX, tradițional,​ pentru alocarea //memoriei dinamice//, se folosește apelul de sistem [[http://​linux.die.net/​man/​2/​brk|brk]]. Acest apel crește sau descrește zona de heap asociată procesului. Odată cu oferirea către aplicații a unor apeluri de sistem de gestiune a memoriei virtuale ([[http://​linux.die.net/​man/​2/​mmap|mmap]]),​ a existat posibilitatea ca procesele să aloce memorie folosind aceste noi apeluri de sistem. Practic, procesele pot mapa memorie în spațiul de adresă, nu fișiere. 
- 
-Procesele pot cere alocarea unei zone de memorie de la o anumită adresă din spațiul de adresare, chiar și cu o anumită politică de acces (citire, scriere sau execuție). În UNIX, acest lucru se face tot prin intermediul funcției [[http://​linux.die.net/​man/​2/​mmap|mmap]]. Pentru acest lucru parametrul ''​flags''​ trebuie să conțină flag-ul ''​MAP_ANONYMOUS''​. 
- 
-==== Maparea dispozitivelor ==== 
- 
-Există chiar și posibilitatea ca aplicațiile să mapeze în spațiul de adresă al unui proces un dispozitiv de intrare-ieșire. Acest lucru este util, de exemplu, pentru plăcile video: o aplicație poate mapa în spațiul de adresă memoria fizica a plăcii video. În UNIX, dispozitivele fiind reprezentate prin fișiere, pentru a realiza acest lucru nu trebuie decât să deschidem fișierul asociat dispozitivului și să-l folosim într-un apel ''​mmap''​. 
- 
-<note important>​ Nu toate dispozitivele pot fi mapate în memorie, însă atunci când pot fi mapate, semnificația acestei mapări depinde strict de dispozitiv. </​note>​ 
- 
-Un alt exemplu de dispozitiv care poate fi mapat este chiar memoria. În Linux se poate folosi fișierul ''/​dev/​zero''​ pentru a face mapări de memorie, ca și când s-ar folosi flag-ul ''​MAP_ANONYMOUS''​. 
- 
-==== Demaparea unei zone din spațiul de adresă ==== 
- 
-Dacă se dorește demaparea unei zone din spațiul de adresă al procesului se poate folosi funcția [[http://​linux.die.net/​man/​3/​munmap|munmap]]:​ 
- 
-<code c> 
- int munmap(void *start, size_t length); 
-</​code>​ 
- 
-''​start''​ reprezintă adresa primei pagini ce va fi demapată (trebuie să fie multiplu de //​dimensiunea unei pagini//). Dacă ''​length''​ nu este o dimensiune care reprezintă un număr întreg de pagini, va fi rotunjit superior. Zona poate să conțină bucăți deja demapate. Se pot astfel demapa mai multe zone în același timp. 
- 
-==== Redimensionarea unei zone mapate ==== 
- 
-Pentru a executa operații de redimensionare a zonei mapate se poate utiliza funcția [[http://​linux.die.net/​man/​2/​mremap|mremap]]:​ 
- 
-<code c> 
-void *mremap(void *old_address,​ size_t old_size, size_t new_size, unsigned long flags); 
-</​code>​ 
- 
-Zona pe care ''​old_address''​ și ''​old_size''​ o descriu trebuie să aparțină unei singure mapări. O singură opțiune este disponibilă pentru ''​flags'':​ ''​MREMAP_MAYMOVE''​ care arată că este în regulă ca pentru obținerea noii mapări să se realizeze o nouă mapare într-o altă zonă de memorie (vechea zona fiind dealocată). 
-==== Schimbarea protecției unei zone mapate ==== 
- 
-Uneori este nevoie ca modul (drepturile de acces) în care a fost mapată o zonă să fie schimbat. Pentru acest lucru se poate folosi funcția [[http://​linux.die.net/​man/​2/​mprotect|mprotect]]:​ 
- 
-<code c> 
-int mprotect(const void *addr, size_t len, int prot); 
-</​code>​ 
- 
-Funcția primește ca parametri intervalul de adrese [''​addr'',​ ''​addr''​ + ''​len''​ - 1] și noile drepturi de access (''​PROT_READ'',​ ''​PROT_WRITE'',​ ''​PROT_EXEC'',​ ''​PROT_NONE''​). Ca și  la [[http://​linux.die.net/​man/​2/​munmap|munmap]],​ ''​addr''​ trebuie să fie multiplu de //​dimensiunea unei pagini//. Funcția va schimba protecția pentru toate paginile care conțin cel puțin un octet în intervalul specificat. 
- 
-==== Exemplu ==== 
-<code cpp> 
-int fd = open("​fisier",​ O_RDWR); 
-void *p = mmap(NULL, 2*getpagesize(),​ PROT_NONE, MAP_SHARED, fd, 0); 
-// *(char*)p = '​a';​ // segv fault 
-mprotect(p, 2*getpagesize(),​ PROT_WRITE);​ 
-*(char*)p = '​a';​ 
-munmap(p, 2*getpagesize());​ 
-</​code>​ 
- 
-Apelul ''​getpagesize''​ va returna dimensiunea unei pagini in bytes. 
- 
-====== Exerciții ====== 
- 
-==== Exercițiul 1 - Zone de stocare a variabilelor ==== 
- 
-Intrați în directorul ''​1-counter''​ și analizati functia ''​inc''​ care întoarce de fiecare dată un întreg reprezentând numărul de apeluri până în momentul respectiv. Compilati programul ruland comanda ''​make''​ si inspectati unde a fost alocata memoria pentru variabila ''​cnt''​. 
- 
-Folositi urmatoarele comenzi: 
-<code bash> 
-make 
-objdump -t counter | grep cnt 
-</​code>​ 
- 
-==== Exercițiul 2 - Spațiul de adresă al unui proces ==== 
-  
-Intrați în directorul ''​2-adr_space''​ și deschideți sursa ''​adr_space.c''​. În alt terminal compilați și rulați programul. Observați zonele de memorie din executabil în care sunt salvate variabilele,​ folosind comanda:<​code bash> 
-objdump -t adr_space | grep var 
-</​code>​ 
- 
-<note tip> 
-Observați că unele variabile apar în tabela de simboluri (variabilele globale și cele locale statice - așa cum arată și flagurile ''​g''​ (global) și ''​l''​ (local) ​ din dreptul acestora; ​ ''​man objdump''​ pentru mai multe informații),​ iar altele nu. Variabilele care nu apar în tabelă se află pe stivă. 
-</​note>​ 
- 
-Afișați conținutul zonei ''​.rodata''​ folosind utilitarul ''​readelf''​ 
- 
-<note tip> 
-**Hint**: Trebuie să afișați hex dump-ul secțiunii ''​.rodata''​ a executabilului ''​adr_space''​. Consultați pagina de manual a ''​readelf''​ după parametrul potrivit. 
- 
-Nu uitați să adăugați și numele fișierului executabil ca parametru al comenzii ''​readelf''​. 
-</​note>​ 
- 
-==== Exercițiul 3 - Alocarea si utilizarea memoriei ==== 
-  
-Intrați în directorul ''​3-alloc''​ și deschideți sursa ''​alloc.c''​. Pe rand, decomentati cate una din liniile care incep cu ''​TODO x''​. 
-<note warning> 
-Doar cate una din cele 3 linii trebuie sa fie decomentata inainte de compilarea programului. 
-</​note>​ 
-În alt terminal compilați programul. Observați zonele de memorie din executabil în care sunt salvate variabilele si dimensiunea acestora, dupa care afisati dimensiunea executabilului folosind comenzile:<​code bash> 
-make 
-objdump -t alloc | grep var 
-ls -lh alloc 
-</​code>​ 
- 
-==== Exercițiul 4 - Investigarea mapărilor folosind pmap ==== 
- 
-Intrați în directorul ''​4-intro''​ și compilați sursa ''​intro.c''​. Rulați programul ''​intro'':<​code>​ 
-./intro 
-</​code>​ 
- 
-Într-o altă consolă, folosiți comanda [[http://​linux.die.net/​man/​1/​pmap | pmap]].:<​code bash> watch -d pmap $(pidof intro) 
-</​code>​ pentru a urmări modificările asupra memoriei procesului. ​ 
- 
-În prima consolă, folosiți ''​ENTER''​ pentru a continua programul. În cea de-a doua consolă urmăriți modificările care apar în urma diferitelor tipuri de mapare din cod. 
- 
-Analizați mapările făcute de procesul ''​init''​ folosind comanda:<​code bash>​sudo pmap 1 
-</​code>​ 
- 
-<note tip> 
-Puteți observa că pentru bibliotecile partajate (de exemplu, ''​libc''​) sunt mapate trei zone: zona de cod (read-execute),​ zona .rodata (read-only) și zona .data (read-write). 
-</​note>​ 
- 
-==== Exercițiul 5 - Scrierea în fișier - write vs. mmap ==== 
- 
-Intrați în directorul ''​6-compare''​ și inspectați sursele ''​write.c''​ și ''​mmap.c'',​ apoi compilați. Obțineți timpul de execuție al celor două programe folosind comanda ''​time'':​ <code bash> ​ 
-time ./write; time ./mmap 
-</​code>​ 
- 
-Observăm că varianta cu ''​mmap''​ este mai rapidă decât varianta cu ''​write''​. Vom folosi [[http://​linux.die.net/​man/​1/​strace|strace]] pentru a vedea ce apeluri de sistem se realizează pentru rularea fiecărui program: 
-<code bash> 
-strace -c ./write 
-strace -c ./mmap 
-</​code>​ 
- 
-Din output-ul ''​strace''​ observăm că programul ''​write''​ face foarte multe (100000) de apeluri ''​write''​ și din această cauză este mai lent decât programul ''​mmap''​. 
- 
-În continuare vom analiza cele două moduri de mapare a fișierelor:​ ''​MAP_SHARED''​ și ''​MAP_PRIVATE''​. Observați că fișierul ''​test_mmap''​ (creat de programul ''​mmap''​ cu ''​MAP_SHARED''​) conține 100000 de linii: 
-<code bash> 
-cat test_mmap | wc -l 
-</​code>​ 
- 
-În programul ''​mmap.c''​ schimbați flagul de creare al memoriei partajate din ''​MAP_SHARED''​ în ''​MAP_PRIVATE'',​ compilați și rulați din nou: 
-<code bash> 
-./mmap 
-cat test_mmap | wc -l 
-</​code>​ 
- 
-<note tip>​Modificările aduse unei zone de memorie mapată cu ''​MAP_PRIVATE''​ nu vor fi vizible nici altor procese și nici nu vor ajunge în fișierul mapat de pe disc.</​note>​ 
- 
-==== Exercițiul 6 - Page fault-uri ==== 
- 
-Intrați în directorul ''​7-faults''​ și urmăriți conținutul fișierului ''​fork-faults.c''​. 
- 
-Vom folosi utilitarul ''​pidstat''​ ([[http://​www.cyberciti.biz/​open-source/​command-line-hacks/​linux-monitor-process-using-pidstat| tutorial pidstat]]) din pachetul ''​sysstat''​ pentru a monitoriza page fault-urile făcute de un proces. 
- 
-<note important>​Dacă întâmpinați probleme în instalarea pachetului ''​sysstat'',​ descărcați-l de [[http://​ro.archive.ubuntu.com/​ubuntu/​pool/​main/​s/​sysstat/​sysstat_11.2.0-1_i386.deb|aici]] și instalați-l folosind comanda ''​dpkg''​. 
-<​code>​ 
-student@spook:​~$ wget http://​ro.archive.ubuntu.com/​ubuntu/​pool/​main/​s/​sysstat/​sysstat_11.2.0-1_i386.deb 
-student@spook:​~$ sudo dpkg -i sysstat_11.2.0-1_i386.deb ​ 
-</​code>​ 
-</​note>​ 
- 
-Rulați programul ''​fork-faults''​. Într-o altă consolă executați comanda ​ 
- 
-<code bash>​pidstat -r -T ALL -p $(pidof fork-faults) 5</​code>​ 
- 
-pentru a urmări page fault-urile. Comanda de mai sus vă afișează câte un mesaj la fiecare 5 secunde; ne interesează valorile ''​minflt-nr''​. 
- 
-Pe rând, apăsați tasta ENTER în consola unde ați rulat programul ''​fork-faults''​ și observați output-ul comenzii ''​pidstat''​. Urmăriți evoluția numărului de page fault-uri pentru cele două procese: părinte și copil. Page fault-urile care apar în cazul unui copy-on-write în procesul copil vor fi vizibile ulterior și în procesul părinte (după ce procesul copil își încheie execuția). 
- 
-<​note>​Pachetul ''​sysstat''​ mai conține și utilitarul ''​sar''​ prin care puteți colecta și realiza rapoarte despre activitatea sistemului. Pentru a activa salvarea datelor, trebuie setat flag-ul ''​ENABLED''​ din ''/​etc/​default/​sysstat''​. ​ 
-Cu ajutorul utilitarului ''​sar''​ puteți monitoriza informații precum încărcarea CPU-ului, utilizarea memoriei și a paginilor, operațiile de I/O, activitatea proceselor. Detalii puteți afla din [[http://​www.cyberciti.biz/​tips/​identifying-linux-bottlenecks-sar-graphs-with-ksar.html| tutorial sar]]. </​note>​ 
- 
- 
- 
- 
  
sde/laboratoare/07_fr_python.1585735987.txt.gz · Last modified: 2020/04/01 13:13 by iuliana.marin
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0