[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Non-Officiels
> Programmation
> C
> Les fichiers mappés en mémoires (UNIX)
> Lecture du tutoriel
Les fichiers mappés en mémoires (UNIX)
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Ce petit tuto a pour but de vous apprendre le fonctionnement des fichiers 'mappés' en mémoire.
C'est bien beau tout ça, mais en quoi ça consiste en quoi exacement ?
mapper un fichier consiste tout simplement à charger celui-ci en mémoire et réaliser des opérations dessus.
Il y a donc un gain de performance par rapport à des opérations standard.
PS : Ce tuto s'adresse aux personnes sous systèmes basés sur UNIX, donc tous les utilisateurs de unix / linux / OS X peuvent le suivre sans problème.
a) Création du fichier
On va donc commencer par créer un fichier texte que nous appellerons tout bêtement
test.txt
Ajoutons-y les 10 premières lettres de l'alphabet par exemple.
ABCDEFGHIJ
b) Ouverture du fichier
Nous allons maintenant ouvrir ce fichier dans notre programme.
Ici j'ai appelé mon fichier C :
memory_mapping.c
Pour ouvrir notre fichier nous allons faire appel à une fonction qui s'appelle
open() et dont le prototype est le suivant :
Code : C1 | int open(const char* path, int oflag, ...);
|
Il ne faudra pas oublier d'inclure :
Code : C
Les paramètres sont les suivants :
- const char* path : Une chaîne de caractères contenant le chemin où se situe votre fichier.
- int oflag : Les flags pour le mode d'ouverture du fichier
Un flag, mais qu'est kesako ?
Un flag est une constante qui va nous servir à spécifier le mode d'ouverture du fichier, dans notre exemple nous voulons lire et écrire dans le fichier, le flag que nous utiliserons est donc :
O_RDWR
RD pour read et WR pour write.
Ici le choix du flag est très important, si par exemple vous ouvrez le fichier en lecture uniquement (O_RDONLY), la modification en mémoire sera impossible!
open() fonction renvoie -1 si elle échoue, sinon elle renvoie un entier qui est ce que l'on appelle un
file descriptor sur le fichier.
M@teo21 nous a dit que pour ouvrir un fichier il fallait se servir de fopen()
Cette fois ci nous n'avons pas le choix que d'employer cette fonction, car sa valeur de retour est nécessaire par la suite pour la fonction qui mappera le fichier en mémoire.
Voici donc le code avec la détection d'erreur :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | #include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
int fd; // Notre File Descriptor
char chemin_fichier[200]; // buffer pour le chemin du fichier
memset(chemin_fichier, 0x00, 200); // Fonction qui remplit chemin_fichier de '0', pour initialiser
printf("Chemin du fichier à mapper : ");
scanf("%s", chemin_fichier); // Saisie du chemin du fichier
if (-1 == (fd = open(chemin_fichier, O_RDWR))) // Ouverture en lecture et écriture
{
perror("open() "); // perror affichera un message indiquant la cause de l'erreur
exit(EXIT_FAILURE); // sortir du programme
}
return EXIT_SUCCESS;
}
|
Voilà, si tout c'est bien passé notre fichier est ouvert, nous allons donc récupérer sa taille.
Nous avons besoin de connaître la taille du fichier pour savoir combien de mémoire il faut allouer pour mappper le fichier.
Là encore nous allons utiliser une fonction nécessitant des
#include supplémentaires.
Code : C1
2
3 | #include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
|
Le prototype de la fonction est le suivant :
Code : C1 | int stat (const char* restrict path, struct stat* restrict buf);
|
Voici une petite description des paramètres :
- const char* restrict path : Vous l'avez deviné, c'est le chemin du fichier dont vous souhaitez connaître la taille.
- struct stat* restrict buf : Un pointeur sur une structure
stat qui possède entre autre un attribut stockant la taille
Comme
open(),
stat() renvoie -1 en cas d'erreur, 0 en cas de réussite, et les informations sur notre fichier stockées dans la structure.
Voici donc le code :
Code : C1
2
3
4
5
6
7
8 | ...
struct stat buf;
if (-1 == stat(nom_fichier, &buf)) // Obtention de la taille, on passe l'adresse de buf car la fonction attend un pointeur!
{
perror("stat() ");
close(fd); // Fermeture du fichier
exit(EXIT_FAILURE); // Si erreur on sort
}
|
Notez un appel à une fonction
close() qui comme fclose() ferme un fichier ouvert en prenant comme paramètre un entier, le file descriptor.
En effet si la fonction stat() échoue, avant de sortir du programme il faut penser à fermer notre fichier!
Pour les curieux voici la définition de la structure stat :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | struct stat
{
dev_t st_dev;
ino_t st_ino;
mode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
struct timespec st_atimespec;
struct timespec st_mtimespec;
struct timespec st_ctimespec;
off_t st_size;
quad_t st_blocks;
u_long st_blksize;
u_long st_flags;
u_long st_gen;
};
|
Mais qu'est ce donc que tous ces types bizarres ?
Il s'agit pour la plupart de types standard qui ont été redéfinis par des
typedef dans le fichier
types.h,
ce qui explique le :
Code : C
L'attribut qui nous intéresse, en l'occurence celui qui contient la taille du fichier (en octets), est celui-ci :
off_t st_size;
</code>
Voilà, nous avons donc ouvert le fichier et nous connaissons sa taille, nous pouvons donc passer à la suite, à savoir mapper le fichier !
Ca y est nous y voilà, nous allons maintenant mapper le fichier en mémoire!
Pour ça un dernier
include
Code : C
La fonction que nous allons utiliser s'appelle
mmap(), pour
Memory
Map.
Elle ne comporte rien moins que 6 paramètres

mais ne vous inquiétez pas, rien de bien sorcier.
Voici donc son prototype :
Code : C1 | void* mmap(void* addr, size_t len, int prot, int flags, int fildes, off_t offset);
|
Une petite description des paramètres s'impose
- void* addr : un pointeur sur
void pour spécifier l'adresse où commencer à mapper le fichier, nous prendrons 0.
- size_t len : La taille à allouer, size_t peut être considéré comme un
unsigned int, donc la taille de notre fichier.
- int prot : Un entier pour spécifier quel type d'opérations on souhaite réaliser, sous formes de constantes symboliques, nous aurons besoin de 2 constantes :
PROT_READ et
PROT_WRITE.
- int flags : Un entier pour spécifier le type de mapping utilisé, nous utiliserons la constante
MAP_SHARED pour indiquer que la zone de mémoire est partagée aux autres processus utilisant l'objet.
- int fildes : Le file descriptor associé au fichier, (la valeur de retour de
open()).
- off_t offset : Position à laquelle le mapping va commencer, là aussi nous prendrons 0.
La fonction renvoie un pointeur universel, ce qui signifie en clair que la valeur de retour peut être de n'importe quel type.
Voici donc le code :
Code : C1
2
3
4
5
6
7
8
9 | char* buffer;
buffer = (char*)mmap(0, (int)buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // Mappage
if ((char*)-1 == buffer)
{
perror("mmap() ");
close(fd); // Fermeture du fichier
exit(EXIT_FAILURE);
}
close(fd); // Fermeture du fichier
|
Maintenant que notre fichier est mappé en mémoire, nous pouvons faire des opération dessus.
Je vous propose de modifier son contenu et de remplacer les 10 lettres par 10 chiffres, de 0 à 9.
Vous l'aurez deviné, une simple boucle
for() suffit
Voici donc le code :
Code : C1
2
3 | unsigned short i;
for (i = 0 ; i < (int)buf.st_size ; i++)
buffer[i] = i+'0'; // Remplacement (+'0' pour obtenir les chiffres ASCII)
|
Une fois que vous avez fini, il est très important de libérer la mémoire que nous avons allouée, il faut donc pour ça faire un appel à :
Code : C1 | int munmap(void* start, size_t length);
|
Des infos plus précises sont disponibles sur la même page que mmap()
Les 2 paramètres sont donc notre 'buffer' et la taille du fichier.
Code : C1 | munmap(buffer, (int)buf.st_size); // Desallocation
|
Cette ligne est très importante, car dans le cas de l'utilisation de la constante MAP_SHARED, l'appel à munmap() permet la sauvegarde des modifications!
Il ne vous reste plus qu'à compiler puis exécuter votre programme!
Code : Console | gcc memory_mapping.c -o ESSAI
./ESSAI
Chemin du fichier a mapper : test.txt
Contenu du fichier avant modification : ABCDEFGHIJ
Contenu du fichier apres modification : 0123456789 |
Pour les sceptiques, ouvrez manuellement votre fichier et vous constaterez que le fichier a bien été modifié.
Voici le code source complet du programme memory_mapping.c
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 | #include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
typedef unsigned short ushort;
int main(int argc, char* const argv[])
{
if (argc != 2)
{
printf("Usage : %s <fichier>\n", argv[0]);
exit(EXIT_FAILURE);
}
int fd; // File descriptor
ushort i; // Compteur
char* buffer; // Contenu fichier en memoire
struct stat buf; // Pour la taille du fichier
if (-1 == (fd = open(argv[1], O_RDWR))) // Ouverture en lecture et ecriture
{
perror("open() ");
exit(EXIT_FAILURE);
}
if (-1 == stat(argv[1], &buf)) // Obtention de la taille
{
perror("stat() ");
close(fd); // Fermeture fichier!
exit(EXIT_FAILURE);
}
buffer = (char*)mmap(0, (int)buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // Mappage
if ((char*)-1 == buffer)
{
perror("mmap() ");
close(fd); // fermeture fichier!
exit(EXIT_FAILURE);
}
close(fd); // Fermeture du fichier
printf("Contenu du fichier avant modification : %s\n", buffer);
/* OPERATIONS A FAIRE ICI */
for (i = 0 ; i < (int)buf.st_size ; i++)
buffer[i] = i+'0'; // Remplacement (+'0' pour les chiffres ASCII)
printf("Contenu du fichier apres modification : %s\n", buffer);
munmap(buffer, (int)buf.st_size); // Desallocation & sauvegarde modifs
return EXIT_SUCCESS;
}
|
Vous savez maintenant comment modifier un fichier autrement que par les fonctions de la bibliothèque standard fopen(), fread(), fwrite()
Pour des opérations sur des petits fichiers textes comme nous l'avons fait, le gain de performance est négligeable, mais sur des fichiers un peu plus gros il peut s'avérer utile.
Ce n'est pas une raison pour charger des très gros fichiers en mémoire hein