Почему COW mmap не работает с ENOMEM для (разреженных) файлов размером более 4 ГБ?
Это происходит в ядре Linux 2.6.26-2-amd64 при попытке отобразить файл размером 5 ГБ с семантикой копирования при записи ( PROT_READ | PROT_WRITE и MAP_PRIVATE). Сопоставление файлов размером менее 4 ГБ или использование только PROT_READ работает нормально. Это не мягкий вопрос ограничения ресурса, как сообщается в этом вопросе; размер виртуального лимита не ограничен.
Вот код, который воспроизводит проблему (фактический код является частью Boost.Interprocess).
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
main()
{
struct stat b;
void *base;
int fd = open("foo.bin", O_RDWR);
fstat(fd, &b);
base = mmap(0, b.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (base == MAP_FAILED) {
perror("mmap");
return 1;
}
return 0;
}
и вот что происходит:
dd if=/dev/zero of=foo.bin bs=1M seek=5000 count=1
./test-mmap
mmap: Cannot allocate memory
Вот соответствующий вывод strace (только что скомпилированный 4.5.20), как указано в nos.
open("foo.bin", O_RDWR) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=5243928576, ...}) = 0
mmap(NULL, 5243928576, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = -1 ENOMEM (Cannot allocate memory)
dup(2) = 4
[...]
write(4, "mmap: Cannot allocate memory\n", 29mmap: Cannot allocate memory
) = 29
2 ответа
Попробуйте пройти MAP_NORESERVE
в flags
поле как это:
mmap(NULL, b.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_NORESERVE, fd, 0);
Вероятно, комбинация вашей подкачки и физической памяти меньше, чем запрошенные 5 ГБ.
В качестве альтернативы вы можете сделать это в целях тестирования, если это работает, вы можете внести изменения в код выше:
# echo 0 > /proc/sys/vm/overcommit_memory
Ниже приведены соответствующие выдержки из страниц справочника.
ММАП (2):
MAP_NORESERVE
Do not reserve swap space for this mapping. When swap space is
reserved, one has the guarantee that it is possible to modify
the mapping. When swap space is not reserved one might get
SIGSEGV upon a write if no physical memory is available. See
also the discussion of the file /proc/sys/vm/overcommit_memory
in proc(5). In kernels before 2.6, this flag only had effect
for private writable mappings.
Proc(5):
/proc/sys/vm/overcommit_memory
This file contains the kernel virtual memory accounting mode.
Values are:
0: heuristic overcommit (this is the default)
1: always overcommit, never check
2: always check, never overcommit
In mode 0, calls of mmap(2) with MAP_NORESERVE are not checked,
and the default check is very weak, leading to the risk of get‐
ting a process "OOM-killed". Under Linux 2.4 any non-zero value
implies mode 1. In mode 2 (available since Linux 2.6), the
total virtual address space on the system is limited to (SS +
RAM*(r/100)), where SS is the size of the swap space, and RAM is
the size of the physical memory, and r is the contents of the
file /proc/sys/vm/overcommit_ratio.
Цитируя вашу память, размер подкачки и настройки overcommit из вашего комментария:
MemTotal: 4063428 kB SwapTotal: 514072 kB
$ cat /proc/sys/vm/overcommit_memory
0
$ cat /proc/sys/vm/overcommit_ratio
50
С overcommit_memory
При значении 0 ("эвристическое превышение") вы не можете создать частное сопоставляемое для записи отображение, которое больше текущей свободной памяти и общего объема подкачки - ясно, поскольку у вас есть только 4,5 ГБ памяти + подкачка, что никогда не может быть правдой.
Ваши варианты либо использовать MAP_NORESERVE
(как предполагает Мэтт Джойнер), если вы уверены, что вы никогда не испачкаете (напишите) больше страниц в отображении, чем у вас есть свободная память и обмен местами; или значительно увеличить размер вашего пространства подкачки.