问题 当长度大于4GB时,mmap失败


(正确的代码在'Update 5'中)

我尝试在这个示例C代码中映射从0x100000000到0x200000000的内存范围:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>

int main(void)
{ 
    uint64_t* rr_addr = 0;
    uint64_t i = 17179869184;

    printf("\nsizeof(size_t): %llu\n", sizeof(size_t));

    printf("(uint64_t)0x100000000: %llx\n", (uint64_t)0x100000000);
    printf("1L << 33: %llx\n", 1L << 33);
    rr_addr = mmap((void*)i, (1UL << 33), PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
    printf("rr_addr: %p, %llu \n", rr_addr, rr_addr);
    if (rr_addr == MAP_FAILED) {
        perror("mmap error");
    }

    return 0;
}

在不同的系统(Linux,gcc)上,我得到了不同的结果:

结果1:

sizeof(size_t): 8
(uint64_t)0x100000000: 100000000
1L << 33: 200000000
rr_addr: 0xffffffffffffffff, 18446744073709551615 
mmap error: Cannot allocate memory

系统信息(Fedora 14):

Linux localhost.localdomain 2.6.35.10-74.fc14.x86_64 #1 SMP Thu Dec 23 16:04:50 UTC 2010 x86_64 x86_64 x86_64 GNU/Linux

gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4)

glibc: 2.12.90-21

结果2:

sizeof(size_t): 8
(uint64_t)0x100000000: 100000000
1L << 33: 200000000
rr_addr: 0x400000000, 17179869184 

系统信息(Fedora 12):

Linux wiles 2.6.32.13 #2 SMP Fri Sep 10 01:29:43 HKT 2010 x86_64 x86_64 x86_64 GNU/Linux

gcc (GCC) 4.4.4 20100630 (Red Hat 4.4.4-10)

glibc verison: 2.11.2-1

我期待“结果2”。也许我的代码有问题。

请帮帮我。

更新1:如果mmap失败,则打印出errno。

更新3:将mmap调用更改为以下行后:

char *cmd[20]; 

sprintf(cmd, "pmap -x %i", getpid()); 
printf("%s\n", cmd);
system(cmd);

rr_addr = mmap((void*)i, (1UL << 33), PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);

printf("%s\n", cmd);
system(cmd);

结果:

sizeof(size_t): 8
(uint64_t)0x100000000: 100000000
1L << 33: 200000000
pmap -x 5618
5618:   ./test
Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       4       4       0 r-x--  test
0000000000600000       4       4       4 rw---  test
00007f1cc941e000    1640     280       0 r-x--  libc-2.12.90.so
00007f1cc95b8000    2044       0       0 -----  libc-2.12.90.so
00007f1cc97b7000      16      16      16 r----  libc-2.12.90.so
00007f1cc97bb000       4       4       4 rw---  libc-2.12.90.so
00007f1cc97bc000      24      16      16 rw---    [ anon ]
00007f1cc97c2000     132     108       0 r-x--  ld-2.12.90.so
00007f1cc99c6000      12      12      12 rw---    [ anon ]
00007f1cc99e0000       8       8       8 rw---    [ anon ]
00007f1cc99e2000       4       4       4 r----  ld-2.12.90.so
00007f1cc99e3000       4       4       4 rw---  ld-2.12.90.so
00007f1cc99e4000       4       4       4 rw---    [ anon ]
00007fffa0da8000     132       8       8 rw---    [ stack ]
00007fffa0dff000       4       4       0 r-x--    [ anon ]
ffffffffff600000       4       0       0 r-x--    [ anon ]
----------------  ------  ------  ------
total kB            4040     476      80
pmap -x 5618
5618:   ./test
Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       4       4       0 r-x--  test
0000000000600000       4       4       4 rw---  test
00007f1cc941e000    1640     280       0 r-x--  libc-2.12.90.so
00007f1cc95b8000    2044       0       0 -----  libc-2.12.90.so
00007f1cc97b7000      16      16      16 r----  libc-2.12.90.so
00007f1cc97bb000       4       4       4 rw---  libc-2.12.90.so
00007f1cc97bc000      24      16      16 rw---    [ anon ]
00007f1cc97c2000     132     108       0 r-x--  ld-2.12.90.so
00007f1cc99c6000      12      12      12 rw---    [ anon ]
00007f1cc99e0000       8       8       8 rw---    [ anon ]
00007f1cc99e2000       4       4       4 r----  ld-2.12.90.so
00007f1cc99e3000       4       4       4 rw---  ld-2.12.90.so
00007f1cc99e4000       4       4       4 rw---    [ anon ]
00007fffa0da8000     132       8       8 rw---    [ stack ]
00007fffa0dff000       4       4       0 r-x--    [ anon ]
ffffffffff600000       4       0       0 r-x--    [ anon ]
----------------  ------  ------  ------
total kB            4040     476      80
rr_addr: 0xffffffffffffffff, 18446744073709551615 
mmap error: Cannot allocate memory

更新4:添加“system(”ulimit -m -v“);”在调用mmap之前: ulimit的输出是:

max memory size         (kbytes, -m) unlimited
virtual memory          (kbytes, -v) unlimited

除了pid之外,其他输出与“Update 3”(仍然失败)相同。

更新5:更新的代码适用于两个系统:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>

int main(void)
{ 
    uint64_t* rr_addr = 0;
    uint64_t i = 17179869184;
    uint64_t len = 0;

    char cmd[20]; 

    printf("\nsizeof(size_t): %llu\n", sizeof(size_t));

    len = (1UL << 32);
    printf("len: %llx\n", len);

    snprintf(cmd, sizeof cmd, "pmap -x %i", getpid()); 
    printf("%s\n", cmd);
    system(cmd);

    system("ulimit -m -v");

    rr_addr = mmap((void*)i, len, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE|MAP_NORESERVE, -1, 0);

    printf("%s\n", cmd);
    system(cmd);

    printf("rr_addr: %p, %llu \n", rr_addr, rr_addr);
    if (rr_addr == MAP_FAILED) {
        perror("mmap error");
    }

    return 0;
}

正确的答案由@caf给出:向mmap添加MAP_NORESERVE标志解决了这个问题。详细的原因在于caf的回答。非常感谢咖啡馆,所有这些都给予了很多帮助!


2083
2018-01-26 09:49


起源

阅读手册页:“失败-1,错误设置”。它可能会或可能不会告诉你任何有用的东西,但你应该在要求其他人调查之前将其打印出来。 - Tony Delroy
你为什么要自己指定基地址? - sarnold
@Tony:谢谢!我打印了错误信息,上面写着“无法分配内存”。 - ericzma
@sarnold:我们想在汇编中编写一些代码来读/写特定范围内的内存。我知道普通应用程序有点奇怪。 - ericzma
@Zhiqiang Ma,哈哈!谢谢:)我想知道这是不是 地址空间布局随机化 每次跑步都会给你不同的“自由空间”? - sarnold


答案:


如果您实际上没有配置超过8G的交换,则该大型映射可能会失败。

你可以添加 MAP_NORESERVE 国旗 mmap() 告诉它不要为前面的映射保留任何交换空间。


7
2018-01-26 12:16



太好了,谢谢!添加此标志后,mmap将成功。实际上我只有2GB内存+ 2GB交换。 - ericzma
你应该知道 MAP_NORESERVE 在为严格提交费用配置的系统上完全被忽略(没有过度使用)。 - R..


有多少物理内存? Linux有两种不同的地址空间分配模式:写入时的内存分配(即过度使用模式)或地址空间分配时的内存分配。您可以通过读取procfs中的两个文件来检查:

cat /proc/sys/vm/overcommit_memory
cat /proc/sys/vm/overcommit_ratio

如果overcommit_memory 不是 0,然后每个地址空间分配必须由物理内存(RAM +交换空间)支持,如果overcommit_memory  0,然后内存过度使用,即内核将愉快地分发地址空间,但只有在将数据写入分配的地址空间时才会分配内存。然后,内存不会分配给完整的保留地址空间,而是仅针对那些被触摸的页面。这有点像预订机票:航空公司通常卖的票数多于飞机上的座位,预计并非所有预订的乘客都会出现。现在你可能想知道,如果所有程序都使用了整个空间会发生什么......那么一些令人讨厌的事情就会爆发:Linux Out Of Memory Killer会对你的系统造成严重破坏,很可能会杀死你最需要的进程,原因是这是一个神秘的启发式方法。

overcommit_ratio告诉内核

  • 在物理存储器中,物理存储器可能被过度使用的过度使用模式,即可以分配多少地址空间。

  • 在非过度使用模式下要保留多少备用内存

因此,过度使用模式可能在系统之间有所不同。


3
2018-01-26 11:21



Linux VM会计实际上有三种模式:启发式过度使用(0),始终过度使用(1)和从不过度使用(2)。 - caf
非常感谢!在两个系统上,overcommit_ratio为50,而overcommit_memory为0.我只有2GB内存+ 2GB交换。按照@caf的建议添加MAP_NORESERVE标志后,它可以mmap 8GB内存。 - ericzma


只需在Fedora 13上运行您的代码,它就会产生结果2。

当mmap()返回MAP_FAILED(-1)时检查errno。你也可以在mmap调用之前和之后粘贴以下行,看看你是否在进程的虚拟地址空间中有空格 一个4GB的区域:

system("pmap -x $$");

更新: 以上实际上打印了子进程的映射。正确的代码:

char buf[0x100];
snprintf(buf, sizeof buf, "pmap -x %u", (unsigned)getpid());
system(buf);

2
2018-01-26 10:08



非常聪明的技巧:)谢谢! - sarnold
@Maxim谢谢!我在mmap调用之前和之后添加了系统(“pmap -x $$”)。我已发布更新的结果(更新2)。我不确定我是否以正确的方式使用它。请帮我看一下。 - ericzma
请使用更新的代码。 - Maxim Egorushkin
哎呀,傻傻的我;该 $$ 得到了 贝壳 PID。该 pmap 正在倾倒错误的地图。更换 system("pmap -x $$") 有类似的东西 char pid[10]; sprintf(pid, "%i", getpid()); execlp("pmap",pid, (char *)NULL); - sarnold
@sarnold:我更改了代码并打印了新结果(Update 3)。我生成正确的命令并通过调用系统来执行它。您提供的代码似乎缺少'-x'参数。我认为根据pmap的输出不使用范围(0x100000000到0x200000000)。 - ericzma


由于您尝试映射到特定地址,因此它将取决于您呼叫时进程的当前内存布局 mmap。满足请求的策略取决于系统,linux手册页说明了一些“提示”。

因此,在第一种情况下,您的进程的虚拟地址空间中没有足够的空间来满足请求,因为在该范围内已经存在另一种映射方式。

检查这是否与此相关的一个好主意是检查你是否成功,当你不给予 addr 暗示。


1
2018-01-26 10:28



谢谢你的建议!我在mmap调用中将addr更改为NULL,但它仍然失败并给出“无法分配内存”错误消息。 - ericzma


也许你遇到了资源限制?尝试添加 system("ulimit -m -v"); 打印出可能分配的内存和地址空间。

编辑:嗯,我没有想法。抱歉。清理代码中的错误和警告后,我有这个来源:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>

int main(void)
{
    uint64_t* rr_addr = 0;
    uint64_t i = 17179869184;

    printf("\nsizeof(size_t): %lu\n", sizeof(size_t));

    printf("(uint64_t)0x100000000: %lx\n", (uint64_t)0x100000000);
    printf("1L << 33: %lx\n", 1L << 33);

    char cmd[20];

    sprintf(cmd, "pmap -x %i", getpid());
    printf("%s\n", cmd);
    system(cmd);

    rr_addr = mmap((void*)i, (1UL << 33), PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);

    printf("%s\n", cmd);
    system(cmd);


    printf("rr_addr: %p, %lu \n", rr_addr, rr_addr);
    if (rr_addr == MAP_FAILED) {
        perror("mmap error");
    }

    return 0;
}

而这个输出:

sizeof(size_t): 8
(uint64_t)0x100000000: 100000000
1L << 33: 200000000
pmap -x 23819
23819:   ./zhiqiang
Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       0       4       0 r-x--  zhiqiang
0000000000600000       0       4       4 r----  zhiqiang
0000000000601000       0       4       4 rw---  zhiqiang
00007f37b3c27000       0     260       0 r-x--  libc-2.12.1.so
00007f37b3da1000       0       0       0 -----  libc-2.12.1.so
00007f37b3fa0000       0      16      16 r----  libc-2.12.1.so
00007f37b3fa4000       0       4       4 rw---  libc-2.12.1.so
00007f37b3fa5000       0      12      12 rw---    [ anon ]
00007f37b3faa000       0     108       0 r-x--  ld-2.12.1.so
00007f37b41aa000       0      12      12 rw---    [ anon ]
00007f37b41c7000       0      12      12 rw---    [ anon ]
00007f37b41ca000       0       4       4 r----  ld-2.12.1.so
00007f37b41cb000       0       4       4 rw---  ld-2.12.1.so
00007f37b41cc000       0       4       4 rw---    [ anon ]
00007fff70cf8000       0      12      12 rw---    [ stack ]
00007fff70dff000       0       4       0 r-x--    [ anon ]
ffffffffff600000       0       0       0 r-x--    [ anon ]
----------------  ------  ------  ------
total kB            3912     464      88
pmap -x 23819
23819:   ./zhiqiang
Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       0       4       0 r-x--  zhiqiang
0000000000600000       0       4       4 r----  zhiqiang
0000000000601000       0       4       4 rw---  zhiqiang   
0000000400000000       0       0       0 rw---    [ anon ]
00007f37b3c27000       0     260       0 r-x--  libc-2.12.1.so
00007f37b3da1000       0       0       0 -----  libc-2.12.1.so
00007f37b3fa0000       0      16      16 r----  libc-2.12.1.so
00007f37b3fa4000       0       4       4 rw---  libc-2.12.1.so
00007f37b3fa5000       0      12      12 rw---    [ anon ]
00007f37b3faa000       0     108       0 r-x--  ld-2.12.1.so
00007f37b41aa000       0      12      12 rw---    [ anon ]
00007f37b41c7000       0      12      12 rw---    [ anon ]
00007f37b41ca000       0       4       4 r----  ld-2.12.1.so
00007f37b41cb000       0       4       4 rw---  ld-2.12.1.so
00007f37b41cc000       0       4       4 rw---    [ anon ]
00007fff70cf8000       0      12      12 rw---    [ stack ]
00007fff70dff000       0       4       0 r-x--    [ anon ]
ffffffffff600000       0       0       0 r-x--    [ anon ]
----------------  ------  ------  ------
total kB         8392520     464      88
rr_addr: 0x400000000, 17179869184

我的系统的细节:

Linux haig 2.6.35-24-generic #42-Ubuntu SMP Thu Dec 2 02:41:37 UTC 2010 x86_64 GNU/Linux
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)
GNU C Library (Ubuntu EGLIBC 2.12.1-0ubuntu10.1) stable release version 2.12.1, by Roland McGrath et al.

1
2018-01-26 10:58



看起来像:ENOMEM没有可用的内存,或者超出了进程的最大映射数。 - Maxim Egorushkin
我在调用mmap之前添加了这个命令。输出显示在“Update 4”中。它显示“最大内存大小”和“虚拟内存”的“无限制”。 - ericzma
非常感谢您的帮助!我还尝试了不同版本的kernel / gcc / libc的不同系统。只有在配备Fedora 14(系统1)的笔记本电脑上,此程序才会失败。 - ericzma
@Zhiqiang Ma,如果@datenwolf没有正确的答案,我认为是时候用Fedora提交bug了:) - sarnold
caf's提供了正确的解决方案。当我的计算机没有时,mmap会尝试为8GB内存保留空间。添加MAP_NORESERVE标志将避免保留空间。这不是Fedora的错误,这是我代码的错误。但很高兴找到它。 - ericzma