Mmap () весь большой файл

Я пытаюсь выполнить «mmap» двоичного файла (~ 8 ГБ), используя следующий код (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 и системных вызовов. Что не так с моим кодом?


person Emer    schedule 28.08.2011    source источник
comment
вам нужно открыть файл с флагом O_LARGEFILE. проверьте руководство. не уверен, что ›файлы размером 4 ГБ можно mmap редактировать.   -  person phoxis    schedule 28.08.2011
comment
Не могу воспроизвести здесь. Ваш код отлично работает с файлом 9G. Сколько (RAM + SWAP) у вас? Какова ваша текущая политика / proc / sys / vm / overcommit_memory?   -  person Mat    schedule 28.08.2011
comment
@Mat $ free -m #Mem {total: 1984, used: 1923, free 60} Swap {total: 2021, used: 0, free: 2021} $ cat / proc / sys / vm / overcommit_memory #returns 0   -  person Emer    schedule 28.08.2011
comment
@phoxis этот флаг потребуется от 32-битной машины. Ссылка   -  person Emer    schedule 28.08.2011
comment
@Emer: извините, я только что заметил x86_64   -  person phoxis    schedule 28.08.2011
comment
Похоже, у вас есть ответ на свою конкретную проблему, но эта ошибка также может быть вызвана тем, что ulimit -v установлен слишком низким для объема памяти, который вы запрашиваете, независимо от того, сколько памяти / свопа у вас есть.   -  person Chris Dodd    schedule 28.08.2011
comment
Вы пробовали запустить strace, чтобы проверить, не наткнулись ли вы на ENOMEM?   -  person susmits    schedule 29.08.2011


Ответы (3)


MAP_PRIVATE отображения требуют резервирования памяти, так как запись на эти страницы может привести к выделению ресурсов для копирования при записи. Это означает, что вы не можете сопоставить что-то намного большее, чем ваш физический RAM + своп. Попробуйте вместо этого использовать MAP_SHARED сопоставление. Это означает, что записи в сопоставление будут отражаться на диске - как таковое, ядро ​​знает, что всегда может освободить память, выполнив обратную запись, поэтому это не будет ограничивать вас.

Я также отмечаю, что вы отображаете с помощью PROT_WRITE, но затем вы продолжаете читать из сопоставления памяти. Вы также открыли файл с помощью O_RDONLY - это само по себе может быть для вас другой проблемой; вы должны указать O_RDWR, если хотите использовать PROT_WRITE с MAP_SHARED.

Что касается только PROT_WRITE, это работает на x86, потому что x86 не поддерживает сопоставления только для записи, но может вызвать сбои на других платформах. Запросите 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. Это позволит выполнить запрос сопоставления; однако имейте в виду, что если вы действительно попытаетесь использовать все 8 ГБ памяти COW, ваша программа (или какая-либо другая программа!) будет убита убийцей OOM.

person bdonlan    schedule 28.08.2011
comment
хм .. Мат сумел сопоставить 8 * 3G (23G) с 16G виртуального пространства. Так что на самом деле он не резервирует память .. - person Karoly Horvath; 28.08.2011
comment
@yi_H, ядро ​​linux по умолчанию позволяет вам превышать физический + своп на определенный процент: opsmonkey.blogspot.com/2007/01/linux-memory-overcommit.html Однако, в зависимости от конфигурации вашей системы, вы можете установить более строгую конфигурацию. Или, если у вас, скажем, всего 4G, вы можете превысить лимит. - person bdonlan; 28.08.2011
comment
Я в курсе, проверьте, что он написал. - person Karoly Horvath; 28.08.2011
comment
Да, я заметил ошибки O_RDONLY и PROT_WRITE. Однако мне нужно поменять местами байты сопоставленного файла, не изменяя его. Я пробовал с PROT_READ и MAP_PRIVATE, но ничего не вышло. Спасибо за ответ - person Emer; 28.08.2011
comment
@Emer, см. Мою правку. Если вам нужно заменить 8 ГБ файла, вам понадобится 8 ГБ памяти + swap для сохранения результата. - person bdonlan; 28.08.2011
comment
@yi_H: У Мэта, вероятно, ядро ​​настроено на эвристическое чрезмерное фиксирование, и в этом случае очень большие (очевидно плохие) сопоставления могут потерпеть неудачу. - person caf; 29.08.2011
comment
Спасибо, возникла та же проблема: только 1 ГиБ ОЗУ на машине amd64, поэтому я хотел использовать mmap файла для доступа к 32 ГиБ виртуальной памяти без замены машины до смерти, и оказалось, что MAP_SHARED был ключом. - person mirabilos; 23.11.2013

У вас недостаточно виртуальной памяти для обработки этого сопоставления.

В качестве примера у меня есть машина с 8 ГБ ОЗУ и ~ 8 ГБ подкачки (так что общая доступная виртуальная память 16 ГБ).

Если я запустил ваш код на снимке VirtualBox размером ~ 8 ГБ, он будет работать нормально:

$ 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 

Теперь, если я откажусь от свопа, у меня останется 8 ГБ общей памяти. (Не запускайте это на активном сервере.) И результат:

$ sudo swapoff -a
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256 
mmap: Cannot allocate memory

Поэтому убедитесь, что у вас достаточно виртуальной памяти для хранения этого сопоставления (даже если вы коснетесь только нескольких страниц в этом файле).

person Mat    schedule 28.08.2011
comment
Можете ли вы проверить, можете ли вы сопоставить два файла 8G с виртуальным пространством 8 + 8? звучит немного странно, что ему нужно место, хотя некоторые страницы могут никогда не быть загружены или изменены (в этом случае их можно просто отбросить). - person Karoly Horvath; 28.08.2011
comment
Я могу mmap много файлов размером менее 16 Гбайт с доступной виртуальной памятью (8 Гбайт + 8 Гбайт) (увеличилось до ~ 23 Гбайт с тремя файлами). Я не могу сопоставить один файл ›8G только с 8G. - person Mat; 28.08.2011
comment
Спасибо за тестирование. Есть ли способ рассчитать точный объем виртуальной памяти, необходимой для сопоставления файла? или это будет зависеть от многих факторов? - person Emer; 28.08.2011
comment
@Mat, 0 - это не «всегда чрезмерно»; 1 это - person bdonlan; 28.08.2011
comment
@bdonlan: вы правы, извините - весь этот комментарий был неправильным - person Mat; 28.08.2011

В Linux (и, по-видимому, в некоторых других системах UNIX) есть флаг MAP_NORESERVE для mmap ( 2), который можно использовать для явного включения избыточного выделения пространства подкачки. Это может быть полезно, если вы хотите отобразить файл, размер которого превышает объем свободной памяти, доступной в вашей системе.

Это особенно удобно, когда используется с MAP_PRIVATE и предназначено только для записи в небольшую часть отображаемого диапазона памяти, так как в противном случае это вызвало бы резервирование пространства подкачки для всего файла (или заставило бы систему вернуть ENOMEM, если чрезмерное выделение хэш-значения в масштабе всей системы '' t был включен, и вы превышаете свободную память системы).

Проблема, на которую следует обратить внимание, заключается в том, что если вы действительно выполняете запись в большую часть этой памяти, ленивое резервирование пространства подкачки может привести к тому, что ваше приложение будет использовать всю свободную оперативную память и подкачку в системе, в конечном итоге вызывая убийцу OOM (Linux) или заставляя ваше приложение получать SIGSEGV.

person dcoles    schedule 04.12.2012