【问题标题】:can't create a file larger than 2GB on 64 bit linux system with mmap/malloc/open etc无法使用 mmap/malloc/open 等在 64 位 linux 系统上创建大于 2GB 的文件
【发布时间】:2014-04-12 22:57:23
【问题描述】:

好的,我知道以前曾以各种形式提出过这样的问题,我已经阅读了所有这些问题并尝试了所有建议的方法,但我仍然无法在 64 位系统上使用 malloc、open、 lseek,blah blah 在阳光下的每一个技巧。

显然我在这里写 c。我正在运行 Fedora 20,我实际上是在尝试映射文件,但这不是它失败的地方,我最初的方法是使用 open(),然后 lseek 到文件应该结束的位置,在这种情况下是3GB,编辑:然后在文件结束位置写一个字节来实际创建那个大小的文件,然后mmap文件。我无法超过 2GB。我也不能 malloc 超过 2GB。 ulimit -a 等都显示无限制,/etc/security/limits.conf 什么都不显示,....

当我尝试 lseek 超过 2GB 时,我得到 EINVAL for errno 并且 lseek 的 ret val 是 -1.edit:lseek 的 size 参数是 off_t 类型,它被定义为 long int(64 位有符号),而不是 size_t正如我之前所说的。

编辑: 我已经尝试定义 _LARGEFILE64_SOURCE 和 _FILE_OFFSET_BITS 64 并没有什么区别。 我还专门为 64 位编译,即 -m64

我迷路了。我不知道为什么我不能这样做。

任何帮助将不胜感激。

谢谢。

编辑:我已经删除了很多完全不正确的喋喋不休以及其他一些稍后处理的不重要的胡言乱语。

我的 2GB 问题在于多种不同类型的可怕草率交换。有符号和无符号的混合是问题。本质上,我传递给 lseek 的 3GB 位置被解释/转换为 -1GB 的位置,显然 lseek 不喜欢那样。所以我的坏。完全愚蠢。

我将按照 p_l 的建议改为使用 posix_fallocate()。虽然它确实删除了一个函数调用,即只需要 posix_fallocate 而不是 lseek 然后写入,但对我来说这并不重要,事实上 posix_fallocate 正在做我想要的,而 lseek 方法没有。所以特别感谢 p_l 的建议,特别感谢 NominalAnimal 的坚持,他知道他更好地间接地让我意识到我无法数数,这反过来又让我接受了 posix_fallocate 可以工作,因此改用它。

无论我使用哪种结束方法。 2GB 的问题完全是我自己的废话编码,再次感谢 EOF、chux、p_l 和 Jonathon Leffler,他们提供的信息和建议让我找到了我自己创造的问题。

我在答案中包含了一个较短的版本。

【问题讨论】:

  • 什么文件系统?一些例如vfat 有一个硬性限制。
  • 我认为off_t 默认只有32位。
  • 我认为没有“lseek 的大小参数”。有一个“偏移”参数,我希望它是 off_t 类型,如 off_t lseek(int fildes, off_t offset, int whence);
  • 您是在创建 32 位还是 64 位可执行文件?如果它是 32 位(-m32;它可能是默认值,也可能不是默认值),除非您使用最旧(仅?)答案中描述的选项(#define 语句),否则您所描述的是合理的行为。如果它是 64 位(-m64,但可能是默认值),则该行为不太合理。
  • @Chux 就我而言,用词不当。你是对的,没有大小参数,它是一个偏移量,是的,它是 off_t 类型,在我的系统上是一个 unsigned long int。不知道这有什么不同?除非您指的是与 EOF 相同的东西?

标签: c linux memory 64-bit limits


【解决方案1】:

我的 2GB 问题在于多种不同类型的互换非常草率。有符号和无符号的混合是问题。本质上,我传递给 lseek 的 3GB 位置被解释/转换为 -1GB 的位置,显然 lseek 不喜欢那样。所以我的坏。完全愚蠢的废话编码。

再次感谢 EOF、chux、p_l 和 Jonathon Leffler,他们提供的信息和建议使我找到了我所创建的问题及其解决方案。

再次感谢 p_l 提出 posix_fallocate() 的建议,并特别感谢 NominalAnimal 的坚持,他更了解他的坚持,间接地让我意识到我无法数数,这反过来又让我接受 posix_fallocate 可以工作,因此改为使用它。

@p_l 虽然我的实际问题的解决方案不在你的答案中,但我仍然会投票赞成你建议使用 posix_fallocate 的答案,但我没有足够的分数来做到这一点。

【讨论】:

    【解决方案2】:

    首先,尝试:

    //Before any includes:
    #define  _LARGEFILE64_SOURCE
    #define  _FILE_OFFSET_BITS 64
    

    如果这不起作用,请像这样将lseek 更改为lseek64

    lseek64(fd, 3221225472, SEEK_SET);
    

    lseek 更好的选择可能是posix_fallocate()

    posix_fallocate(fd, 0, 3221225472);
    

    在调用 mmap() 之前;

    不过,我建议保留定义 :)

    【讨论】:

    • 我尝试设置那些#defines,但没有任何改变(我不得不承认,在 64 位操作系统上,我有点不明白为什么它们是必要的)。但我还没有尝试过 lseek64 或 posix_fallocate。我会调查那些。非常感谢。
    【解决方案3】:

    这是我创建的一个测试程序 (a2b.c):

    #include <assert.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <inttypes.h>
    #include <stdarg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    static void err_exit(const char *fmt, ...);
    
    int main(void)
    {
        char const filename[] = "big.file";
        int fd = open(filename, O_RDONLY);
        if (fd < 0)
            err_exit("Failed to open file %s for reading", filename);
        struct stat sb;
        fstat(fd, &sb);
        uint64_t size = sb.st_size;
        printf("File: %s; size %" PRIu64 "\n", filename, size);
        assert(size > UINT64_C(3) * 1024 * 1024 * 1024);
        off_t offset = UINT64_C(3) * 1024 * 1024 * 1024;
        if (lseek(fd, offset, SEEK_SET) < 0)
            err_exit("lseek failed");
        close(fd);
        _Static_assert(sizeof(size_t) > 4, "sizeof(size_t) is too small");
        size = UINT64_C(3) * 1024 * 1024 * 1024;
        void *space = malloc(size);
        if (space == 0)
            err_exit("failed to malloc %zu bytes", size);
        *((char *)space + size - 1) = '\xFF';
        printf("All OK\n");
        return 0;
    }
    
    static void err_exit(const char *fmt, ...)
    {
        int errnum = errno;
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
        if (errnum != 0)
            fprintf(stderr, ": (%d) %s", errnum, strerror(errnum));
        putc('\n', stderr);
        exit(1);
    }
    

    在 Mac 上编译和运行时(Mac OS X 10.9.2 Mavericks、GCC 4.8.2、16 GiB 物理 RAM),使用命令行:

    gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
        -Wold-style-definition -Werror a2b.c -o a2b
    

    并通过以下方式创建了big.file

    dd if=/dev/zero of=big.file bs=1048576 count=5000
    

    我得到了令人放心的输出:

    File: big.file; size 5242880000
    All OK
    

    我不得不使用_Static_assert 而不是static_assert,因为Mac 的&lt;assert.h&gt; 标头没有定义static_assert。当我使用-m32 编译时,触发了静态断言。

    当我在具有 1 GiB 虚拟物理内存的 Ubuntu 13.10 64 位 VM 上运行它时(或者这是重言式的?),我得到输出并不令人惊讶:

    File: big.file; size 5242880000
    failed to malloc 3221225472 bytes: (12) Cannot allocate memory
    

    我使用完全相同的命令行来编译代码;它在 Linux 上用static_assert 代替_Static_assert 编译OK。 ulimit -a 的输出表明最大内存大小是无限的,但这意味着“没有小于机器上虚拟内存量的限制”,而不是任何更大的限制。

    请注意,我的编译没有明确包含 -m64,但它们自动是 64 位编译。

    你得到了什么? dd 可以创建大文件吗?代码是否编译? (如果您的编译器中没有 C11 支持,那么您需要将静态断言替换为正常的“动态”断言,从而删除错误消息。)代码是否运行?你会得到什么结果。

    【讨论】:

      【解决方案4】:

      这是一个示例程序,example.c

      /* Not required on 64-bit architectures; recommended anyway. */
      #define  _FILE_OFFSET_BITS 64
      
      /* Tell the compiler we do need POSIX.1-2001 features. */
      #define  _POSIX_C_SOURCE 200112L
      
      /* Needed to get MAP_NORESERVE. */
      #define  _GNU_SOURCE
      
      #include <unistd.h>
      #include <sys/mman.h>
      #include <fcntl.h>
      #include <errno.h>
      
      #include <stdlib.h>
      #include <string.h>
      #include <stdio.h>
      
      #ifndef   FILE_NAME
      #define   FILE_NAME   "data.map"
      #endif
      
      #ifndef   FILE_SIZE
      #define   FILE_SIZE   3221225472UL
      #endif
      
      int main(void)
      {
          const size_t      size = FILE_SIZE;
          const char *const file = FILE_NAME;
      
          size_t            page;
          unsigned char    *data;
      
          int               descriptor;
          int               result;
      
          /* First, obtain the normal page size. */
          page = (size_t)sysconf(_SC_PAGESIZE);
          if (page < 1) {
              fprintf(stderr, "BUG: sysconf(_SC_PAGESIZE) returned an invalid value!\n");
              return EXIT_FAILURE;
          }
      
          /* Verify the map size is a multiple of page size. */
          if (size % page) {
              fprintf(stderr, "Map size (%lu) is not a multiple of page size (%lu)!\n",
                      (unsigned long)size, (unsigned long)page);
              return EXIT_FAILURE;
          }
      
          /* Create backing file. */
          do {
              descriptor = open(file, O_RDWR | O_CREAT | O_EXCL, 0600);
          } while (descriptor == -1 && errno == EINTR);
          if (descriptor == -1) {
              fprintf(stderr, "Cannot create backing file '%s': %s.\n", file, strerror(errno));
              return EXIT_FAILURE;
          }
      
      #ifdef FILE_ALLOCATE
      
          /* Allocate disk space for backing file. */
          do {
              result = posix_fallocate(descriptor, (off_t)0, (off_t)size);
          } while (result == -1 && errno == EINTR);
          if (result == -1) {
              fprintf(stderr, "Cannot resize and allocate %lu bytes for backing file '%s': %s.\n",
                      (unsigned long)size, file, strerror(errno));
              unlink(file);
              return EXIT_FAILURE;
          }
      
      #else
      
          /* Backing file is sparse; disk space is not allocated. */
          do {
              result = ftruncate(descriptor, (off_t)size);
          } while (result == -1 && errno == EINTR);
          if (result == -1) {
              fprintf(stderr, "Cannot resize backing file '%s' to %lu bytes: %s.\n",
                      file, (unsigned long)size, strerror(errno));
              unlink(file);
              return EXIT_FAILURE;
          }
      
      #endif
      
          /* Map the file.
           * If MAP_NORESERVE is not used, then the mapping size is limited
           * to the amount of available RAM and swap combined in Linux.
           * MAP_NORESERVE means that no swap is allocated for the mapping;
           * the file itself acts as the backing store. That's why MAP_SHARED
           * is also used. */
          do {
              data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE,
                          descriptor, (off_t)0);
          } while ((void *)data == MAP_FAILED && errno == EINTR);
          if ((void *)data == MAP_FAILED) {
              fprintf(stderr, "Cannot map file '%s': %s.\n", file, strerror(errno));
              unlink(file);
              return EXIT_FAILURE;
          }
      
          /* Notify of success. */
          fprintf(stdout, "Mapped %lu bytes of file '%s'.\n", (unsigned long)size, file);
          fflush(stdout);
      
      #if defined(FILE_FILL)
          memset(data, ~0UL, size);
      #elif defined(FILE_ZERO)
          memset(data, 0, size);
      #elif defined(FILE_MIDDLE)
          data[size/2] = 1; /* One byte in the middle set to one. */
      #else
      
          /*
           * Do something with the mapping, data[0] .. data[size-1]
          */
      
      #endif
      
          /* Unmap. */
          do {
              result = munmap(data, size);
          } while (result == -1 && errno == EINTR);
          if (result == -1)
              fprintf(stderr, "munmap(): %s.\n", strerror(errno));
      
          /* Close the backing file. */
          result = close(descriptor);
          if (result)
              fprintf(stderr, "close(): %s.\n", strerror(errno));
      
      #ifndef FILE_KEEP
      
          /* Remove the backing file. */
          result = unlink(file);
          if (result)
              fprintf(stderr, "unlink(): %s.\n", strerror(errno));
      
      #endif
      
          /* We keep the file. */
          fprintf(stdout, "Done.\n");
          fflush(stdout);
      
          return EXIT_SUCCESS;
      }
      

      要编译和运行,请使用例如

      gcc -W -Wall -O3 -DFILE_KEEP -DFILE_MIDDLE example.c -o example
      ./example
      

      上面将创建一个3GB(10243)的稀疏文件data.map,并将其中的中间字节设置为1\x01)。文件中的所有其他字节保持为零。然后就可以运行了

      du -h data.map
      

      查看这样一个稀疏文件在磁盘上实际占用了多少空间,以及

      hexdump -C data.map
      

      如果您希望验证文件内容是我声称的内容。

      您可以使用一些编译时标志(宏)来更改示例程序的行为方式:

      • '-DFILE_NAME="filename"'

        使用文件名filename 而不是data.map。请注意,整个值是在单引号内定义的,因此 shell 不会解析双引号。 (双引号是宏值的一部分。)

      • '-DFILE_SIZE=(1024*1024*1024)'

        使用 10243 = 1073741824 字节映射而不是默认的 3221225472。如果表达式包含 shell 会尝试计算的特殊字符,最好将其全部括在单引号或双引号中。

      • -DFILE_ALLOCATE

        为整个映射分配实际磁盘空间。默认情况下,使用稀疏文件代替。

      • -DFILE_FILL

        (unsigned char)(~0UL) 填充整个映射,通常为 255。

      • -DFILE_ZERO

        将整个映射清零。

      • -DFILE_MIDDLE

        将映射中的中间字节设置为1。其他所有字节不变。

      • -DFILE_KEEP

        不要删除数据文件。这对于探索映射在磁盘上实际需要多少数据很有用;使用例如du -h data.map.


      在 Linux 中使用内存映射文件时需要考虑三个主要限制:

      1. 文件大小限制

        FAT (MS-DOS) 等旧文件系统不支持大文件或稀疏文件。如果数据集是稀疏的(包含大洞),稀疏文件很有用;在这种情况下,未设置的部分不会存储在磁盘上,而是简单地读取为零。

        由于许多文件系统存在大于 231-1 字节(2147483647 字节)的读写问题,当前的 Linux 内核内部将每个单个操作限制为 231- 1 个字节。读取或写入调用不会失败,它只是返回一个短计数。我不知道有任何类似限制llseek() 系统调用的文件系统,但是由于C 库负责将lseek()/lseek64() 函数映射到正确的系统调用,因此C 库(而不是内核)很可能会限制功能. (对于 GNU C 库和嵌入式 GNU C 库,这种系统调用映射取决于编译时标志。例如,请参阅 man 7 feature_test_macrosman 2 lseekman 3 lseek64

        最后,文件位置处理在大多数 Linux 内核中都不是原子的。 (补丁在上游,但我不确定哪些版本包含它们。)这意味着如果多个线程以修改文件位置的方式使用相同的描述符,则文件位置可能会完全乱码。

      2. 内存限制

        默认情况下,文件支持的内存映射仍受可用内存和交换限制的约束。也就是说,默认mmap() 行为是假设在内存压力下,脏页被交换,而不是刷新到磁盘。您需要使用特定于 Linux 的 MAP_NORESERVE 标志来避免这些限制。

      3. 地址空间限制

        在 32 位 Linux 系统上,用户空间进程可用的地址空间通常小于 4 GiB;它是一个内核编译时选项。

        在 64 位 Linux 系统上,大型映射会消耗大量 RAM,即使映射内容本身还没有出错。通常,每个单页需要 8 个字节的元数据(“页表条目”) 在内存中,或者更多,取决于架构。使用 4096 字节的页面,这意味着 0.1953125% 的最小开销,并设置例如仅在页表结构中,TB 映射需要 2 GB 的 RAM!

        Linux 中的许多 64 位系统支持大页面以避免这种开销。在大多数情况下,由于configuration and tweaking and limitations,大页面的使用有限。内核也可能对进程对大页面映射的操作有限制。一个健壮的应用程序需要彻底回退到正常的页面映射。

      内核可能会对用户空间进程施加比资源可用性更严格的限制。运行 bash -c 'ulimit -a' 以查看当前施加的限制。 (详细信息可在man bash-builtinsulimit 部分中找到。)

      【讨论】:

      • 哇,要证明一个观点需要付出难以置信的努力。谢邀。不幸的是,其中大部分是完全无关的。然而。编译和运行你的代码确实让我意识到我已经熬夜太晚了。我希望你从一开始就说的是“嘿,伙计,你的计算是错误的,off_t 可以解决 2^(64-1) 文件,这是任何人都想要的,而不是你疯狂的 4GB 限制喋喋不休”
      • @gpuMonster:不,我没有证明这一点。许多高性能计算数据后端使用愚蠢的方法进行编程,在缓存层之上添加缓存层,需要指数数量的硬件来实现必要的数据速率(例如,参见 Cern/LHC 后端)。我的担心,也是我努力的原因,是那些后来阅读这些线程的人。这里的“专家”很少承认自己的错误;每个人都只是“继续前进”。否则这可能没问题,但事实是,这些线程仍然存在,很容易被谷歌搜索,导致未来的程序员误入歧途。
      • @gpuMonster:就我个人而言,我只是没有想到将您的问题视为简单的拼写错误或计算错误。实际上,如果您找到一种方法来简单地表达您的错误估计发生的方式,并将其添加到您的问题中,我认为这将比该线程中提供的任何其他内容更能帮助遇到类似问题的其他人。 . 毕竟,我们从错误中学到的最多。我只是想确保错误和糟糕的选择被清楚地标记出来,以便那些以我们为基础的人会变得比我们更好。
      • @gpuMonster:64 位体系结构上的内存映射技术是一个很好的例子,展示了非常非常尖锐的优势:你可以实现很多,但也有很多缺点(比如SIGSEGV空间不足时发出信号而不是 ENOSPC 错误,以及可用于获得最佳性能的可调参数数量(msync()madvise() 等)。一个经过适当调整但简单的内存映射数据集服务器可以使用相同的资源将任何用户空间缓存系统吹走。但那,我担心,又是一场烈火盛会……:)
      • 我对高性能 CPU 代码的兴趣为零。我为在 CPU 上运行而编写的代码只是从 GPU 获取数据的必需品。查找、打开、创建、读取和写入的基本文件操作。我从事的 GPU 工作可能需要一组高端 GPU 的天数来计算,因此 CPU 代码所需的秒数或分钟数之间的差异并不重要。可调参数;不需要它们,一个信号与另一个信号没有区别。 CPU 的东西是单线程的,有数百 TB 的磁盘和数百 GB 的内存,保证。
      猜你喜欢
      • 1970-01-01
      • 2021-11-18
      • 2010-09-20
      • 2012-12-02
      • 1970-01-01
      • 1970-01-01
      • 2020-08-16
      • 2016-10-27
      相关资源
      最近更新 更多