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