Cours "système d`exploitation" 1ère année IUT de Caen
Transcription
Cours "système d`exploitation" 1ère année IUT de Caen
Cours "système d'exploitation" 1ère année IUT de Caen, Département d'Informatique (François Bourdon) Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 1 Chapitre 5 (partie 2) Gestion de la mémoire en « C » Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 2 5 Gestion de la mémoire d'un processus Plan 5.1 Allocation dynamique de la mémoire 5.2 Manipulation de blocs mémoire Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 3 5.1 Allocation dynamique de la mémoire en « C » Les variables d'un programme « C » peuvent être allouées de différentes manières : • les variables globales1 ou les variables statiques au sein des fonctions sont allouées une fois pour toutes lors du chargement du programme dans les zones « data » et « bss ». • Les variables locales et les arguments des fonctions sont réservées sur la pile (zone « stack ») lors de l'invocation des blocs ou fonctions. • Les variables dynamiques dynamiquement/explicitement sont via allouées des fonctions spécifiques (malloc, …) à travers des pointeurs sur les zones réservées sur le tas (zone « heap »). (1) Les variables globales sont automatiquement initialisées à 0 (y compris les pointeurs, avec tous les bits à 0). Les variables locales (sur la pile) ne sont pas initialisées; leur valeur dépend du contenu de la pile et donc de l'exécution précédente du programme. Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 4 3 Go env Environnement stack Pile variables automatiques heap Tas variables dynamiques _end bss variables globales non initialisées _edata data variables globales et statiques initialisées _etext text Code exécutable et routines des bibliothèques partagées 0 Sur un ordinateur 32 bits un processus dispose d'un adressage de 232 octets soit 4 Go, dont 1Go pour le noyau, d'où les 3 Go, sur une machine 64 bits l'espace d'adressage va jusqu'à 128 To pour les processus et autant pour le noyau. Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 5 Pourquoi utiliser les variables dynamiques ? • • • Si l'on ne connaît pas la taille de la variable lors de la compilation (par exemple un nombre variable d'éléments d'une liste chaînée). Si l 'on veut allouer une zone mémoire de très grande taille; qui plus est si cette allocation se fait dans une fonction (donc sur la pile) récursive. Si l'on veut optimiser la gestion de la mémoire en utilisant des organisations de données telles que la liste chaînée, l'arbre binaire ou encore la table de hachage. ATTENTION aux problèmes de fuite mémoire. Lorsque le noyau augmente la taille du segment de données d'un processus, il n'a pas pour autant réservé physiquement de la place dans la mémoire du système. Le principe de la mémoire virtuelle est de ne réserver effectivement une place demandée, que lorsque le processus tente d'y accéder. Il est donc possible de demander plus de mémoire que le système ne peut en fournir. Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 6 Pour allouer dynamiquement une zone mémoire on utilise « malloc » : #include <stdlib.h> void * malloc (size_t taille); • • « taille » correspond à la taille, en octets, de la zone contigüe désirée; « size_t » est redéfini en un type entier « int », « malloc » renvoie un pointeur (void *) sur cette zone, ou le pointeur NULL si la mémoire disponible ne permet pas de faire cette allocation. ma_struct_t * ma_struct; if ((ma_struct = malloc(sizeof(ma_struct_t))) == NULL) { fprintf(stderr, "Pas assez de mémoire pour la structure\n"); exit(EXIT_FAILURE); } Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 7 Pour allouer dynamiquement des tableaux, initialisés à 0, on utilise « calloc » : #include <stdlib.h> void * calloc (size_t nb_elements, size_t taille_element); • • • «nb_elements» correspond au nombre d'éléments à allouer, «taille_element» correspond à la taille de chaque élément,. « calloc » renvoie un pointeur (void *) sur cette zone, ou le pointeur NULL si la mémoire disponible ne permet pas de faire cette allocation. int * fonction (int nb_valeurs) { int * table = NULL; int cpt; if ((table = calloc(nb_valeurs, sizeof(int))) == NULL) { fprintf(stderr, "Pas assez de mémoire\n"); exit(EXIT_FAILURE); } for (cpt=0; cpt < nb_valeurs; cpt++) table[cpt]=cpt; return table; } int main ( ) { int * table; int nb_valeurs; scanf("%i", &nb_valeurs); table = fonction ( nb_valeurs); free (table); return EXIT_SUCCESS; } Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 8 Pour modifier dynamiquement une table déjà allouée par malloc ou calloc, on utilise « realloc » : #include <stdlib.h> void * realloc (void * ancien, size_t taille); • • • «ancien» correspond à l'adresse de l'ancienne zone à agrandir, «taille» correspond à la taille de la nouvelle zone, « realloc » renvoie un pointeur (void *) sur la nouvelle zone, ou le pointeur NULL si l'allocation échoue. En cas de réussite l'ancien pointeur n'est plus utilisable et le contenu de l'ancienne zone se retrouve au début de la nouvelle zone. En cas d'échec (NULL) l'ancienne zone n'est pas touchée. void * nouveau ; nouveau = realloc(bloc_de_donnees, nouvelle_taille) if ( nouveau!= NULL) bloc_de_donnees = nouveau; else fprintf( stderr, "Pas assez de mémoire \n"); Une réduction de la zone libère de la mémoire et tronque les données. Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 9 Pour libérer une zone déjà allouée par malloc, un calloc ou un realloc, on utilise «free» : #include <stdlib.h> void free (void * pointeur); • «pointeur» correspond à l'adresse de la zone à libérer et qui a été allouée dynamiquement. Une fois la zone libérée, ne jamais réutiliser le pointeur d'accès à cette zone, ni relibérer cette zone. Quelques règles pour éviter les fuites mémoire : • initialisez avec NULL tout pointeur déclaré pour une allocation dynamique, • avant toute allocation dynamique vérifiez que le pointeur utilisé est bien NULL, • avant de libérer un pointeur vérifiez qu'il n'est pas NULL. Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 10 5.2 Manipulation de blocs mémoire Fonctions mem*, déclarées dans <string.h>. Ici on accède au contenu de la mémoire octet par octet, dont le contenu pourra être comparé à un entier compris entre 0 et 255 : prompt> cat initmem.c #include <stdio.h> #include <string.h> #include <stdlib.h> // a compiler avec -DTAILLE_BLOC=valeur int main(){ int i; unsigned char * bloc; if((bloc = malloc(TAILLE_BLOC)) == NULL) { perror("malloc"); exit(EXIT_FAILURE); } for (i=0; i< TAILLE_BLOC; i++){ bloc[i]= i; printf("%i\n",bloc[i]); } return 12 ; } prompt> gcc initmem.c -DTAILLE_BLOC=4 -o initmem -Wall prompt> initmem 0 1 2 3 prompt> echo $? 12 prompt> Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 11 Pour initialiser un bloc mémoire avec une valeur donnée, on utilise «memset» : #include <string.h> void * memset (void * bloc, int valeur, size_t longueur); • • «bloc» pointe sur le bloc à remplir, « valeur » est la valeur entière convertie en « unsigned char », qui servira à remplir le nombre « longueur » d'octets choisis. Cette fonction renvoie la valeur du pointeur « bloc ». Exemple : memset (bloc, 0, longueur); Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 12 Pour copier des blocs de mémoire disjoints, on utilise «memcpy» : #include <string.h> void * memcpy (void * destination, void * origine, size_t longueur); Cette fonction renvoie le pointeur sur la destination (destination) après y avoir recopié la longueur désirée (longueur) de la chaîne originale (origine). Cette fonction est très utile pour recopier tous les champs d'une structure : void ma_fonction (struct ma_structure * originale) { { struct ma_structure copie_de_travail; memcpy (&copie_de_travail, originale, sizeof(struct ma_struct)); … } Elle ne peut travailler que sur des blocs de mémoire disjoints, sinon il existe un risque de recouvrement. Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 13 Voici le résultat espéré après la copie suivante : memcpy (Destination, Source, 11); Source A Destination (avant copie) B C D Source A E F G H I J K L M N O E F G H I J K Destination (après copie) B C D A B C D Une fois les 4 premiers octets copiés, la fonction va lire les 4 suivants qui coincident avec la chaîne de destination. Au lieu de trouver EFGH elle trouvera ABCD, … avec les 4 suivants. Source A Destination (avant copie) B C D Source A B C D I J K L M N O A B C D M N O A B C D A B C Destination (après copie) B C D Source A A A B C D Destination (avant copie) B C D A B C D Dans ce cas, il faut utiliser la fonction « memmove ». Elle se comporte comme « memcpy », mais assure que la copie finale sera exactement une image de la source originale au début de l'appel. Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 14 On peut effectuer une copie jusqu'à la longueur désirée, ou jusqu'à avoir rencontré un caractère donné dans le bloc original en utilisant la fonction « memccpy » : #include <string.h> void * memccpy (void * destination, void * source, int octet, size_t longueur); On peut comparer deux blocs de mémoire en utilisant la fonction « memcmp » : #include <string.h> int memcmp (const void * bloc1, cont, void * bloc2, , size_t taille); Cette fonction renvoie le pointeur sur la destination (destination) après y avoir recopié la longueur désirée (longueur) de la chaîne originale (origine). Cours Systèmes d'exploitation, François Bourdon, IUT département informatique, Caen. 15