C语言 使用ENOMEM将大型文件Map为只读失败

3htmauhk  于 2023-06-21  发布在  其他
关注(0)|答案(2)|浏览(115)

我正在运行以下(最小复制)代码:

#include <stdio.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void main() {
        int fd = open("file.data", O_RDONLY);
        void* ptr = mmap(0, (size_t)240 * 1024 * 1024 * 1024, PROT_READ, MAP_SHARED, fd, 0);
        printf("Result = %p\n", ptr);
        printf("Errno = %d\n", errno);
}

它输出(用gcc test.c && ./a.out编译和运行):

Result = 0xffffffffffffffff
Errno = 9

file.data是一个243GiB的文件:

$ stat file.data 
  File: file.data
  Size: 260165023654    Blocks: 508135088  IO Block: 4096   regular file
Device: 801h/2049d      Inode: 6815790     Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1001/  user)   Gid: ( 1001/  user)
Access: 2023-05-08 09:22:07.314477587 -0400
Modify: 2023-06-16 07:53:12.275187040 -0400
Change: 2023-06-16 07:53:12.275187040 -0400
 Birth: -

其他配置(debian stretch,Linux 5.2.21):

$ sysctl vm.overcommit_memory
vm.overcommit_memory = 1

$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 768178
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 768178
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

$ free -m
              total        used        free      shared  buff/cache   available
Mem:         192105         671      189213           9        2220      190314
Swap:             0           0           0

我已经遵循的建议:

根据我的理解,我应该能够mmap这个文件。我将它Map为只读,因此内核可以在需要时自由地将其全部交换回磁盘。应该有足够的连续内存,因为这是一个64位系统。
如何使mmap调用工作?

编辑:/proc/*/maps程序输出:

2aaaaaa000-2aaaaab000 r-xp 00000000 08:01 6815754                        /home/<username>/a.out
2aaacaa000-2aaacab000 r--p 00000000 08:01 6815754                        /home/<username>/a.out
2aaacab000-2aaacac000 rw-p 00001000 08:01 6815754                        /home/<username>/a.out
3ff7a3a000-3ff7bcf000 r-xp 00000000 08:01 5767499                        /lib/x86_64-linux-gnu/libc-2.24.so
3ff7bcf000-3ff7dcf000 ---p 00195000 08:01 5767499                        /lib/x86_64-linux-gnu/libc-2.24.so
3ff7dcf000-3ff7dd3000 r--p 00195000 08:01 5767499                        /lib/x86_64-linux-gnu/libc-2.24.so
3ff7dd3000-3ff7dd5000 rw-p 00199000 08:01 5767499                        /lib/x86_64-linux-gnu/libc-2.24.so
3ff7dd5000-3ff7dd9000 rw-p 00000000 00:00 0 
3ff7dd9000-3ff7dfc000 r-xp 00000000 08:01 5767249                        /lib/x86_64-linux-gnu/ld-2.24.so
3ff7fe8000-3ff7fea000 rw-p 00000000 00:00 0 
3ff7ff8000-3ff7ffb000 r--p 00000000 00:00 0                              [vvar]
3ff7ffb000-3ff7ffc000 r-xp 00000000 00:00 0                              [vdso]
3ff7ffc000-3ff7ffd000 r--p 00023000 08:01 5767249                        /lib/x86_64-linux-gnu/ld-2.24.so
3ff7ffd000-3ff7ffe000 rw-p 00024000 08:01 5767249                        /lib/x86_64-linux-gnu/ld-2.24.so
3ff7ffe000-3ff7fff000 rw-p 00000000 00:00 0 
3ffffde000-3ffffff000 rw-p 00000000 00:00 0                              [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
i7uaboj4

i7uaboj41#

我无法复制你的问题

我修改了您的程序,添加了一个选项来生成示例/测试文件:
1.它可以 * 只是 * 做一个truncate来创建一个大文件。这只需要几分之一秒。
1.然后,它可以用真实的数据填充它。在我的系统上创建一个243 GB的文件大约需要10分钟。
1.结果在任一模式下都是相同的。所以,IMO,快速模式是足够的(即,锉刀有孔)。换句话说,任何人都可以在几秒钟内在他们的系统上运行该程序。
我尝试了我能想到的所有组合和其他选择。在任何情况下,我都不能复制。下面是我的系统和你的系统的比较。
在阅读下面的内容后,如果你能想到任何其他的想法,我很乐意在我的系统上尝试一下,重现你的失败。
以下是修改后的程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>

#define GBSIZE(_gb)         (size_t) _gb * 1024 * 1024 * 1024
#define GBOF(_siz)          (double) _siz / (1024 * 1024 * 1024)

int opt_f;
int opt_G;
int opt_v;

const char *file;
char pagebuf[64 * 1024];

#define ONERR(_expr,_reason) \
    do { \
        if (_expr) { \
            printf("ONERR: " #_expr " -- %s\n",strerror(errno)); \
            exit(1); \
        } \
    } while (0)

void genfile(void);
void mapshow(void);

int
main(int argc,char **argv)
{
    int fd;
    int err;

    setlinebuf(stdout);

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        char *cp = *argv;
        if (*cp != '-')
            break;

        cp += 2;
        switch (cp[-1]) {
        case 'f':
            opt_f = ! opt_f;
            break;

        case 'G':
            opt_G = (*cp != 0) ? strtol(cp,&cp,10) : 243;
            break;

        case 'v':
            opt_v = ! opt_v;
            break;
        }
    }

    if (argc == 1)
        file = *argv;
    else
        file = "tmp";
    printf("file='%s'\n",file);

    if (opt_G) {
        genfile();
        exit(0);
    }

    fd = open(file,O_RDONLY);
    ONERR(fd < 0,"open/RDONLY");

    struct stat st;
    err = fstat(fd,&st);
    ONERR(err < 0,"fstat");

    size_t fsize = st.st_size;
    size_t mapsize = fsize - GBSIZE(3);
    printf("main: st.st_size=%zu/%.3f mapsize=%zu/%.3F\n",
        fsize,GBOF(fsize),mapsize,GBOF(mapsize));

    errno = 0;
    void *ptr = mmap(0, mapsize, PROT_READ, MAP_SHARED, fd, 0);
    printf("Result = %p -- errno=%d %s\n", ptr, errno, strerror(errno));

    mapshow();

    if (ptr != MAP_FAILED)
        munmap(ptr,mapsize);
    close(fd);

    // remove the temp file
#if 0
    unlink(file);
#endif

    return 0;
}

void
genfile(void)
{
    int fd;
    int err;

    // get desired file size
    size_t mksize = GBSIZE(opt_G);

    printf("genfile: unlink ...\n");
    unlink(file);

    printf("genfile: G=%d mksize=%zu\n",opt_G,mksize);

    // create the file
    printf("genfile: open ...\n");
    fd = open(file,O_WRONLY | O_CREAT,0644);
    ONERR(fd < 0,"open/WRONLY");

    // truncate
    printf("genfile: ftruncate ...\n");
    err = ftruncate(fd,mksize);
    ONERR(err < 0,"ftruncate");

    close(fd);

    struct stat st;
    err = stat(file,&st);
    ONERR(err < 0,"stat");

    printf("genfile: st_size=%zu\n",(size_t) st.st_size);
    errno = 0;
    ONERR(st.st_size != mksize,"st_size");

    // fill the file with real data -- not really necessary
    if (opt_f) {
        printf("genfile: memset ...\n");
        fd = open(file, O_RDWR);
        ONERR(fd < 0,"open/RDWR");

        size_t curlen;
        size_t remlen = mksize;
        size_t outsize = 0;
        int val = 0;
        time_t todbeg = time(NULL);
        time_t todold = todbeg;
        for (;  remlen > 0;  remlen -= curlen, outsize += curlen, ++val) {
            curlen = remlen;
            if (curlen > sizeof(pagebuf))
                curlen = sizeof(pagebuf);

            memset(pagebuf,val,sizeof(pagebuf));

            ssize_t xlen = write(fd,pagebuf,curlen);
            ONERR(xlen < 0,"write");

            time_t todnow = time(NULL);
            if ((todnow - todold) >= 1) {
                todold = todnow;

                double pct = outsize;
                pct /= mksize;
                pct *= 100;

                printf("\rELAPSED: %ld %.3f/%.3f %.3f%%",
                    todnow - todbeg,GBOF(outsize),GBOF(mksize),pct);
                fflush(stdout);
            }
        }

        printf("\n");

        close(fd);
    }
}

void
mapshow(void)
{
    char file[100];
    char buf[1000];

    printf("\n");

    sprintf(file,"/proc/%d/maps",getpid());

    FILE *xfsrc = fopen(file,"r");
    ONERR(xfsrc == NULL,"fopen/maps");

    while (1) {
        if (fgets(buf,sizeof(buf),xfsrc) == NULL)
            break;
        fputs(buf,stdout);
    }

    fclose(xfsrc);
}

下面是我的配置:

COMMAND: uname -r
5.3.11-100.fc29.x86_64

COMMAND: sysctl vm.overcommit_memory
vm.overcommit_memory = 0

COMMAND: ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 47763
max locked memory       (kbytes, -l) 16384
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 47763
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

COMMAND: free -m
              total        used        free      shared  buff/cache   available
Mem:          11972        3744         750          68        7477        7842
Swap:        122879        1147      121732

细微差别:
1.你有192 GB的RAM。但是,我只有12 GB的RAM。这种差异应该对你有利。但事实并非如此。该程序在我的系统上工作,该系统的RAM量不到1/10。
1.我有一个128 GB的交换磁盘。但是,我在执行swapoff -a后重新运行了该程序,以禁用所有交换磁盘。程序运行中没有差异。

  1. vm.overcommit_memory为0。但是,我将其设置为1,并且程序操作中没有 * 任何 * 差异。
    1.在我的vm.mmap_min_addr上是65536(参见下面的TASK_SIZE
    1.我的电脑系统已经用了十多年了。
    1.我运行的是一个更老的内核版本。
    在测试的时候,我有:
    1.几个gnome-terminal窗口
  2. firefox,页面位于SO上
  3. thunderbird
    1.一些背景shell程序[我自己设计的]。
    由于我的内存小得多,我不得不质疑 neo-jgrec 的答案:
    在x86(64位)系统上,TASK_SIZE可以是:
    1.正常系统:1ul << 47 131,072 GB(128 TB)
    1.启用5级分页:1ul << 56 67,108,864 GB(65,536 TB)
    即使使用更小的地址值,我们也显然不会超过TASK_SIZE
    我在过去对许多100+GB的文件做过mmap,没有问题。例如,看看我的答案:read line by line in the most efficient way platform specific
    下面是文件的状态:
File: tmp
  Size: 260919263232    Blocks: 509608032  IO Block: 4096   regular file
Device: 901h/2305d  Inode: 180624922   Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/     user)   Gid: ( 1000/     user)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2023-06-18 15:39:51.253702772 -0400
Modify: 2023-06-18 15:58:43.512226035 -0400
Change: 2023-06-18 15:58:43.512226035 -0400
 Birth: -

下面是程序输出:

file='tmp'
main: st.st_size=260919263232/243.000 mapsize=257698037760/240.000
Result = 0x7edf00cf9000 -- errno=0 Success

00400000-00401000 r--p 00000000 09:01 180624914                          /home/user/bigmmap/orig
00401000-00402000 r-xp 00001000 09:01 180624914                          /home/user/bigmmap/orig
00402000-00403000 r--p 00002000 09:01 180624914                          /home/user/bigmmap/orig
00403000-00404000 r--p 00002000 09:01 180624914                          /home/user/bigmmap/orig
00404000-00405000 rw-p 00003000 09:01 180624914                          /home/user/bigmmap/orig
00405000-00415000 rw-p 00000000 00:00 0
013bb000-013dc000 rw-p 00000000 00:00 0                                  [heap]
7edf00cf9000-7f1b00cf9000 r--s 00000000 09:01 180624922                  /home/user/bigmmap/tmp
7f1b00cf9000-7f1b00d1b000 r--p 00000000 09:00 1202975                    /usr/lib64/libc-2.28.so
7f1b00d1b000-7f1b00e68000 r-xp 00022000 09:00 1202975                    /usr/lib64/libc-2.28.so
7f1b00e68000-7f1b00eb4000 r--p 0016f000 09:00 1202975                    /usr/lib64/libc-2.28.so
7f1b00eb4000-7f1b00eb5000 ---p 001bb000 09:00 1202975                    /usr/lib64/libc-2.28.so
7f1b00eb5000-7f1b00eb9000 r--p 001bb000 09:00 1202975                    /usr/lib64/libc-2.28.so
7f1b00eb9000-7f1b00ebb000 rw-p 001bf000 09:00 1202975                    /usr/lib64/libc-2.28.so
7f1b00ebb000-7f1b00ec1000 rw-p 00000000 00:00 0
7f1b00f16000-7f1b00f17000 r--p 00000000 09:00 1182318                    /usr/lib64/ld-2.28.so
7f1b00f17000-7f1b00f37000 r-xp 00001000 09:00 1182318                    /usr/lib64/ld-2.28.so
7f1b00f37000-7f1b00f3f000 r--p 00021000 09:00 1182318                    /usr/lib64/ld-2.28.so
7f1b00f3f000-7f1b00f40000 r--p 00028000 09:00 1182318                    /usr/lib64/ld-2.28.so
7f1b00f40000-7f1b00f41000 rw-p 00029000 09:00 1182318                    /usr/lib64/ld-2.28.so
7f1b00f41000-7f1b00f42000 rw-p 00000000 00:00 0
7fff0d6d7000-7fff0d6f8000 rw-p 00000000 00:00 0                          [stack]
7fff0d75a000-7fff0d75d000 r--p 00000000 00:00 0                          [vvar]
7fff0d75d000-7fff0d75e000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
d7v8vwbk

d7v8vwbk2#

您遇到的问题来自这样一个事实,即即使您的系统是64位系统,内核仍然有寻址限制,这取决于您的系统架构。
默认情况下,Linux将一半的可寻址内存空间分配给内核,另一半分配给用户。因此,对于一个64位系统,这将是2^63字节的内核和相同数量的用户空间。
但是,内核并没有使用整个空间。内核使用一个可寻址内存范围进行内存Map,即从mmap_min_addrTASK_SIZETASK_SIZE通常在内核中设置为某个值,具体取决于系统的体系结构,该值可能小于最大可寻址空间。
您的mmap请求可能会失败,因为它试图分配比系统的TASK_SIZE更多的内存。如果您尝试一次mmap 240GiB,它可能会超过系统上的TASK_SIZE
一种解决方案是在循环中Map文件的较小块,直到Map完整个文件。下面是一个如何做到这一点的例子:

#include <stdio.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define CHUNK_SIZE ((size_t)64 * 1024 * 1024 * 1024) //64GiB

int main() {
    int fd = open("file.data", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return EXIT_FAILURE;
    }
    
    struct stat statbuf;
    if (fstat(fd, &statbuf) < 0) {
        perror("fstat");
        return EXIT_FAILURE;
    }
    
    size_t file_size = statbuf.st_size;
    size_t offset = 0;
    
    while (offset < file_size) {
        size_t size = (file_size - offset > CHUNK_SIZE) ? CHUNK_SIZE : file_size - offset;
        void* ptr = mmap(0, size, PROT_READ, MAP_SHARED, fd, offset);
        
        if (ptr == MAP_FAILED) {
            perror("mmap");
            return EXIT_FAILURE;
        }
        
        // do something with the memory here
        
        munmap(ptr, size);
        offset += size;
    }
    
    close(fd);
    
    return 0;
}

这段代码以64GiB为单位读取文件。最好根据您的具体情况调整块大小。您应该始终检查系统调用的返回值以处理任何错误。错误消息将更具描述性和信息性。
记住,在处理完文件的一个部分之后,在进入下一个部分之前,调用munmap()

相关问题