Impossible de créer un fichier de plus de 2 Go sur un système Linux 64 bits avec mmap / malloc / open, etc.

OK, je sais que des questions de ce type ont déjà été posées sous différentes formes. Je les ai toutes lues et j’ai tout essayé. Je ne peux toujours pas créer un fichier de plus de 2 Go sur un système 64 bits avec malloc, open, lseek, blah. bla chaque tour sous le soleil.

Clairement, j’écris c ici. J’exécute Fedora 20, j’essaie en fait de mapper le fichier, mais ce n’est pas le cas, ma méthode originale consistait à utiliser open (), puis lseek à la position où le fichier devrait se terminer, ce qui est dans ce cas à 3GB, éditez: puis écrivez un octet à la fin du fichier pour créer le fichier de cette taille, puis mmapez le fichier. Je ne peux pas lire au-delà de 2 Go. Je ne peux pas malloc plus de 2 Go non plus. ulimit -a etc tout affichera illimité, /etc/security/limits.conf ne montre rien, ….

Quand j’essaie de lseek après 2 Go, j’obtiens EINVAL pour errno et la valeur de retour de lseek est -1.edit: Le paramètre de taille de lseek est de type off_t, défini comme un long int (signé avec 64 bits), pas size_t comme je l’ai dit précédemment.

edit: j’ai déjà essayé de définir _LARGEFILE64_SOURCE & _FILE_OFFSET_BITS 64 et cela ne faisait aucune différence. Je comstack aussi spécifiquement pour 64bit ie -m64

Je suis perdu. Je ne sais pas pourquoi je ne peux pas faire ça.

Toute aide serait grandement appréciée.

Merci.

edit: J’ai supprimé beaucoup de babillages complètement incorrects de ma part et quelques autres divagations sans importance qui ont été traitées ultérieurement.

Mon problème de 2 Go était dans l’interchangement horriblement bâclé de plusieurs types différents. Mélange de signé et non signé étant le problème. Essentiellement, la position de 3 Go que je passais à lseek était interprétée / transformée en une position de -1 Go et lseek ne l’a clairement pas aimé. Donc mon mauvais. Totalement stupide.

Je vais maintenant utiliser posix_fallocate () comme suggéré par p_l. Bien que cela supprime un appel de fonction, c’est-à-dire qu’il faut seulement posix_fallocate au lieu d’un lseek, puis d’une écriture, pour moi, cela n’a aucune importance, c’est le fait que posix_fallocate fait exactement ce que je veux, contrairement à la méthode lseek. Donc, merci en particulier à p_l de l’avoir suggéré, et un merci spécial à NominalAnimal, dont la persistance qu’il connaissait mieux m’a indirectement conduit à la réalisation que je ne peux pas compter, ce qui m’a amené à accepter que posix_fallocate fonctionnerait et changerait pour l’utiliser.

Peu importe la méthode finale que j’ai utilisée. Le problème de 2 Go était entièrement mon propre code de merde et merci encore à EOF, chux, p_l et Jonathon Leffler qui ont tous fourni des informations et des suggestions qui m’ont conduit au problème que j’avais créé pour moi-même.

J’ai inclus une version plus courte de ceci dans une réponse.

Mon problème de 2 Go était dans l’interchangement horriblement bâclé de plusieurs types différents. Mélange de signé et non signé étant le problème. Essentiellement, la position de 3 Go que je passais à lseek était interprétée / transformée en une position de -1 Go et lseek ne l’a clairement pas aimé. Donc mon mauvais. Codage de merde totalement stupide.

Merci encore à EOF, chux, p_l et Jonathon Leffler qui ont tous fourni des informations et des suggestions qui m’ont conduit au problème que j’avais créé et à sa solution.

Merci encore à p_l pour avoir suggéré posix_fallocate (), et un merci spécial à NominalAnimal dont la persistance qu’il connaissait mieux m’a indirectement conduit à la réalisation que je ne compte pas, ce qui m’a amené à accepter que posix_fallocate fonctionnerait et changerait pour l’utiliser.

@p_l Bien que la solution à mon problème actuel ne soit pas dans votre réponse, je voterais quand même votre réponse suggérant d’utiliser posix_fallocate mais je n’ai pas assez de points pour le faire.

Tout d’abord, essayez:

 //Before any includes: #define _LARGEFILE64_SOURCE #define _FILE_OFFSET_BITS 64 

Si cela ne fonctionne pas, changez lseek en lseek64 comme ceci

 lseek64(fd, 3221225472, SEEK_SET); 

Une meilleure option que lseek pourrait être posix_fallocate() :

 posix_fallocate(fd, 0, 3221225472); 

avant l’appel à mmap ();

Je recommande de garder les définit, cependant 🙂

C’est un programme de test que j’ai créé ( a2b.c ):

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  static void err_exit(const char *fmt, ...); int main(void) { char const filename[] = "big.file"; int fd = open(filename, O_RDONLY); if (fd < 0) err_exit("Failed to open file %s for reading", filename); struct stat sb; fstat(fd, &sb); uint64_t size = sb.st_size; printf("File: %s; size %" PRIu64 "\n", filename, size); assert(size > UINT64_C(3) * 1024 * 1024 * 1024); off_t offset = UINT64_C(3) * 1024 * 1024 * 1024; if (lseek(fd, offset, SEEK_SET) < 0) err_exit("lseek failed"); close(fd); _Static_assert(sizeof(size_t) > 4, "sizeof(size_t) is too small"); size = UINT64_C(3) * 1024 * 1024 * 1024; void *space = malloc(size); if (space == 0) err_exit("failed to malloc %zu bytes", size); *((char *)space + size - 1) = '\xFF'; printf("All OK\n"); return 0; } static void err_exit(const char *fmt, ...) { int errnum = errno; va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); if (errnum != 0) fprintf(stderr, ": (%d) %s", errnum, strerror(errnum)); putc('\n', stderr); exit(1); } 

Une fois compilé et exécuté sur un Mac (Mac OS X 10.9.2 Mavericks, GCC 4.8.2, RAM physique de 16 Go), avec une ligne de commande:

 gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wssortingct-prototypes \ -Wold-style-definition -Werror a2b.c -o a2b 

et ayant créé big.file avec:

 dd if=/dev/zero of=big.file bs=1048576 count=5000 

J’ai eu la sortie rassurante:

 File: big.file; size 5242880000 All OK 

J’ai dû utiliser _Static_assert plutôt que static_assert car l’en-tête Mac ne définit pas static_assert . Lorsque j’ai compilé avec -m32 , l’ -m32 statique s’est déclenchée.

Lorsque je l’ai exécuté sur une machine virtuelle Ubuntu 13.10 64 bits avec une mémoire physique virtuelle de 1 Go (ou est-ce une tautologie?), Je ne suis pas très surpris du résultat:

 File: big.file; size 5242880000 failed to malloc 3221225472 bytes: (12) Cannot allocate memory 

J’ai utilisé exactement la même ligne de commande pour comstackr le code; il a compilé OK sous Linux avec static_assert à la place de _Static_assert . La sortie de ulimit -a indique que la taille maximale de la mémoire est illimitée, mais que cela signifie «aucune limite inférieure à celle imposée par la quantité de mémoire virtuelle de la machine» plutôt que toute taille supérieure.

Notez que mes compilations n’incluaient pas explicitement -m64 mais qu’elles étaient automatiquement des compilations 64 bits.

Qu’est ce que tu obtiens? dd peut-il créer le gros fichier? Le code est-il compilé? (Si vous ne disposez pas de la prise en charge de C11 dans votre compilateur, vous devrez remplacer l’assertion statique par une assertion «dynamic» normale, en supprimant le message d’erreur.) Le code est-il exécuté? Quel résultat obtenez-vous.

Voici un exemple de programme, example.c :

 /* Not required on 64-bit architectures; recommended anyway. */ #define _FILE_OFFSET_BITS 64 /* Tell the comstackr we do need POSIX.1-2001 features. */ #define _POSIX_C_SOURCE 200112L /* Needed to get MAP_NORESERVE. */ #define _GNU_SOURCE #include  #include  #include  #include  #include  #include  #include  #ifndef FILE_NAME #define FILE_NAME "data.map" #endif #ifndef FILE_SIZE #define FILE_SIZE 3221225472UL #endif int main(void) { const size_t size = FILE_SIZE; const char *const file = FILE_NAME; size_t page; unsigned char *data; int descriptor; int result; /* First, obtain the normal page size. */ page = (size_t)sysconf(_SC_PAGESIZE); if (page < 1) { fprintf(stderr, "BUG: sysconf(_SC_PAGESIZE) returned an invalid value!\n"); return EXIT_FAILURE; } /* Verify the map size is a multiple of page size. */ if (size % page) { fprintf(stderr, "Map size (%lu) is not a multiple of page size (%lu)!\n", (unsigned long)size, (unsigned long)page); return EXIT_FAILURE; } /* Create backing file. */ do { descriptor = open(file, O_RDWR | O_CREAT | O_EXCL, 0600); } while (descriptor == -1 && errno == EINTR); if (descriptor == -1) { fprintf(stderr, "Cannot create backing file '%s': %s.\n", file, strerror(errno)); return EXIT_FAILURE; } #ifdef FILE_ALLOCATE /* Allocate disk space for backing file. */ do { result = posix_fallocate(descriptor, (off_t)0, (off_t)size); } while (result == -1 && errno == EINTR); if (result == -1) { fprintf(stderr, "Cannot resize and allocate %lu bytes for backing file '%s': %s.\n", (unsigned long)size, file, strerror(errno)); unlink(file); return EXIT_FAILURE; } #else /* Backing file is sparse; disk space is not allocated. */ do { result = ftruncate(descriptor, (off_t)size); } while (result == -1 && errno == EINTR); if (result == -1) { fprintf(stderr, "Cannot resize backing file '%s' to %lu bytes: %s.\n", file, (unsigned long)size, strerror(errno)); unlink(file); return EXIT_FAILURE; } #endif /* Map the file. * If MAP_NORESERVE is not used, then the mapping size is limited * to the amount of available RAM and swap combined in Linux. * MAP_NORESERVE means that no swap is allocated for the mapping; * the file itself acts as the backing store. That's why MAP_SHARED * is also used. */ do { data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, descriptor, (off_t)0); } while ((void *)data == MAP_FAILED && errno == EINTR); if ((void *)data == MAP_FAILED) { fprintf(stderr, "Cannot map file '%s': %s.\n", file, strerror(errno)); unlink(file); return EXIT_FAILURE; } /* Notify of success. */ fprintf(stdout, "Mapped %lu bytes of file '%s'.\n", (unsigned long)size, file); fflush(stdout); #if defined(FILE_FILL) memset(data, ~0UL, size); #elif defined(FILE_ZERO) memset(data, 0, size); #elif defined(FILE_MIDDLE) data[size/2] = 1; /* One byte in the middle set to one. */ #else /* * Do something with the mapping, data[0] .. data[size-1] */ #endif /* Unmap. */ do { result = munmap(data, size); } while (result == -1 && errno == EINTR); if (result == -1) fprintf(stderr, "munmap(): %s.\n", strerror(errno)); /* Close the backing file. */ result = close(descriptor); if (result) fprintf(stderr, "close(): %s.\n", strerror(errno)); #ifndef FILE_KEEP /* Remove the backing file. */ result = unlink(file); if (result) fprintf(stderr, "unlink(): %s.\n", strerror(errno)); #endif /* We keep the file. */ fprintf(stdout, "Done.\n"); fflush(stdout); return EXIT_SUCCESS; } 

Pour comstackr et exécuter, utilisez par exemple

 gcc -W -Wall -O3 -DFILE_KEEP -DFILE_MIDDLE example.c -o example ./example 

Ce qui précède créera un fichier data.map trois gigaoctets (1024 3 ) et définira l’octet du milieu à 1 ( \x01 ). Tous les autres octets du fichier restnt à zéro. Vous pouvez alors courir

 du -h data.map 

pour voir combien un tel fichier fragmenté prend réellement sur le disque, et

 hexdump -C data.map 

si vous souhaitez vérifier que le contenu du fichier correspond à ce que je prétends être.

Il existe quelques indicateurs de compilation (macros) que vous pouvez utiliser pour modifier le comportement du programme exemple:

  • '-DFILE_NAME="filename"'

    Utilisez le nom de filename au lieu de data.map . Notez que la valeur entière est définie entre guillemets simples, de sorte que le shell n'parsing pas les guillemets doubles. (Les guillemets font partie de la valeur de la macro.)

  • '-DFILE_SIZE=(1024*1024*1024)'

    Utilisez 1024 3 = 1073741824 mappage d'octets au lieu du 3221225472 par défaut. Si l'expression contient des caractères spéciaux que le shell essaiera d'évaluer, il est préférable de tout mettre entre guillemets simples ou doubles.

  • -DFILE_ALLOCATE

    Allouez l'espace disque réel pour l'ensemble du mappage. Par défaut, un fichier fragmenté est utilisé à la place.

  • -DFILE_FILL

    Remplissez le mappage entier avec (unsigned char)(~0UL) , généralement 255.

  • -DFILE_ZERO

    Efface tout le mappage à zéro.

  • -DFILE_MIDDLE

    Définissez l'octet du milieu dans le mappage sur 1. Tous les autres octets sont inchangés.

  • -DFILE_KEEP

    Ne supprimez pas le fichier de données. Cela est utile pour explorer la quantité de données que le mappage nécessite réellement sur le disque; utilisez par exemple du -h data.map .


Il existe trois limitations principales à prendre en compte lors de l'utilisation de fichiers mappés en mémoire sous Linux:

  1. Taille limite du fichier

    Les systèmes de fichiers plus anciens tels que FAT (MS-DOS) ne prennent pas en charge les fichiers volumineux ou fragmentés. Les fichiers fragmentés sont utiles si le jeu de données est fragmenté (contient de grands trous); dans ce cas, les parties non définies ne sont pas stockées sur le disque et se lisent simplement comme des zéros.

    Étant donné que de nombreux systèmes de fichiers ont des problèmes de lecture et d'écriture supérieurs à 2 31 -1 octets (2147483647 octets), les kernelx Linux actuels limitent en interne chaque opération à 2 31 -1 octets. L'appel en lecture ou en écriture n'échoue pas, il renvoie simplement un compte court . Je ne suis au courant d'aucun système de fichiers limitant de manière similaire le llseek() , mais comme la bibliothèque C est chargée de mapper les fonctions lseek()/lseek64() sur les appels syscall appropriés, il est fort possible que la bibliothèque C (et non le kernel ) limite la fonctionnalité. (Dans le cas de la bibliothèque GNU C et de la bibliothèque Embedded GNU C, un tel mappage d'appel système dépend des indicateurs de compilation. Par exemple, voir man 7 feature_test_macros , man 2 lseek et man 3 lseek64 .

    Enfin, la gestion de la position des fichiers n’est pas atomique dans la plupart des kernelx Linux. (Les correctifs sont en amont, mais je ne suis pas sûr des versions qui les contiennent.) Cela signifie que si plusieurs threads utilisent le même descripteur de manière à modifier la position du fichier, il est possible que la position du fichier soit complètement tronquée.

  2. Limites de la mémoire

    Par défaut, les mappages de mémoire sauvegardés sur fichier sont toujours soumis aux limites de mémoire disponible et de permutation. En d’autres mmap() comportement par défaut de mmap() consiste à supposer qu’à la pression de la mémoire, les pages modifiées sont échangées et non vidées sur le disque. Vous devrez utiliser l'indicateur MAP_NORESERVE spécifique à MAP_NORESERVE pour éviter ces limites.

  3. Limites d'espace d'adressage

    Sur les systèmes Linux 32 bits, l'espace d'adressage disponible pour un processus en espace utilisateur est généralement inférieur à 4 Go; c'est une option de compilation du kernel.

    Sur les systèmes Linux 64 bits, les mappages volumineux consumnt une quantité importante de RAM, même si le contenu du mappage n’est pas encore défectueux. En règle générale, chaque page nécessite au moins 8 octets de métadonnées ( "entrée de table de page" ) en mémoire. en fonction de l'architecture. L'utilisation de pages de 4 096 octets représente une surcharge minimale de 0,195125%. Par exemple, la configuration d'une carte en téraoctets nécessite deux gigaoctets de RAM uniquement dans les structures de table de page!

    De nombreux systèmes Linux 64 bits prennent en charge des pages très volumineuses pour éviter cette surcharge. Dans la plupart des cas, les pages volumineuses ont une utilisation limitée en raison de la configuration, des modifications et des limitations . Les kernelx peuvent également avoir des limites sur ce qu'un processus peut faire avec un mappage de page énorme; une application robuste aurait besoin de solutions de rechange aux mappages de pages normaux.

Le kernel peut imposer des limites plus ssortingctes que la disponibilité des ressources aux processus de l'espace utilisateur. Exécutez bash -c 'ulimit -a' pour voir les limites actuellement imposées. (Les détails sont disponibles dans la section ulimit dans man bash-builtins .)