Mmap() весь большой файл
Я пытаюсь "mmap" двоичный файл (~ 8Gb), используя следующий код (test.c).
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[])
{
const char *memblock;
int fd;
struct stat sb;
fd = open(argv[1], O_RDONLY);
fstat(fd, &sb);
printf("Size: %lu\n", (uint64_t)sb.st_size);
memblock = mmap(NULL, sb.st_size, PROT_WRITE, MAP_PRIVATE, fd, 0);
if (memblock == MAP_FAILED) handle_error("mmap");
for(uint64_t i = 0; i < 10; i++)
{
printf("[%lu]=%X ", i, memblock[i]);
}
printf("\n");
return 0;
}
test.c скомпилирован с использованием gcc -std=c99 test.c -o test
и file
результатов теста: test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped
Хотя это отлично подходит для небольших файлов, я получаю ошибку сегментации, когда я пытаюсь загрузить большую. Программа фактически возвращает:
Size: 8274324021
mmap: Cannot allocate memory
Мне удалось отобразить весь файл с помощью boost:: iostreams:: mapped_file, но я хочу сделать это с помощью C и системных вызовов. Что не так с моим кодом?
Ответы
Ответ 1
MAP_PRIVATE
для сопоставления требуется резервирование памяти, так как запись на эти страницы может привести к распределению по копиям на запись. Это означает, что вы не можете сопоставить что-то слишком большое, чем ваш физический ram + swap. Вместо этого попробуйте использовать сопоставление MAP_SHARED
. Это означает, что запись на отображение будет отражена на диске - как таковая, ядро знает, что он всегда может освобождать память, делая обратную запись, поэтому она не будет ограничивать вас.
Я также отмечаю, что вы сопоставляете с PROT_WRITE
, но затем продолжаете читать и читаете из памяти. Вы также открыли файл с помощью O_RDONLY
- это может быть другой проблемой для вас; вы должны указать O_RDWR
, если вы хотите использовать PROT_WRITE
с MAP_SHARED
.
Только для PROT_WRITE
это работает на x86, потому что x86 не поддерживает сопоставления только для записи, но может вызывать segfaults на других платформах. Запросить PROT_READ|PROT_WRITE
- или, если вам нужно только прочитать, PROT_READ
.
В моей системе (VPS с ОЗУ 676 МБ, своп с 256 МБ) я воспроизвел вашу проблему; изменение на MAP_SHARED
приводит к ошибке EPERM
(так как мне не разрешено записывать файл резервной копии, открытый с помощью O_RDONLY
). Переход на PROT_READ
и MAP_SHARED
позволяет успешному отображению.
Если вам нужно изменить байты в файле, одним из вариантов было бы сделать частным только диапазоны файла, в который вы собираетесь писать. То есть munmap
и переназначить с MAP_PRIVATE
те области, которые вы намереваетесь записать. Конечно, если вы собираетесь писать весь файл, вам понадобится 8 ГБ памяти.
В качестве альтернативы вы можете написать 1
в /proc/sys/vm/overcommit_memory
. Это позволит выполнить запрос на отображение; однако имейте в виду, что если вы на самом деле пытаетесь использовать полную память COW на 8 ГБ, ваша программа (или какая-то другая программа!) будет убита убийцей OOM.
Ответ 2
У вас недостаточно виртуальной памяти для обработки этого сопоставления.
В качестве примера у меня есть машина с 8G RAM и ~ 8G swap (поэтому доступна общая виртуальная память 16G).
Если я запустил ваш код в моментальном снимке VirtualBox, который равен ~ 8G, он отлично работает:
$ ls -lh /media/vms/.../snap.vdi
-rw------- 1 me users 9.2G Aug 6 16:02 /media/vms/.../snap.vdi
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256
[0]=3C [1]=3C [2]=3C [3]=20 [4]=4F [5]=72 [6]=61 [7]=63 [8]=6C [9]=65
Теперь, если я опустил своп, я остался с общей памятью 8G. ( Не выполнять на активном сервере.) И результат:
$ sudo swapoff -a
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256
mmap: Cannot allocate memory
Поэтому убедитесь, что у вас достаточно виртуальной памяти для хранения этого сопоставления (даже если вы касаетесь только нескольких страниц в этом файле).
Ответ 3
Linux (и, видимо, несколько других UNIX-систем) имеет флаг MAP_NORESERVE
для mmap (2), который может быть использован для явного включить переопределение пространства подкачки. Это может быть полезно, если вы хотите сопоставить файл размером, превышающим объем свободной памяти, доступный в вашей системе.
Это особенно удобно при использовании с MAP_PRIVATE
и предназначено только для записи на небольшую часть диапазона с отображением памяти, поскольку в противном случае это обеспечило бы резервное копирование всего файла (или заставить систему возвращать ENOMEM
, если системная избыточность не была включена, и вы превысите свободную память системы).
Проблема, о которой следует помнить, заключается в том, что если вы пишете большую часть этой памяти, ленивое резервирование пространства подкачки может привести к тому, что ваше приложение будет потреблять всю свободную оперативную память и свопиться в систему, в конечном счете вызывая убийцу OOM ( Linux) или заставить ваше приложение получать SIGSEGV
.