使用mmap / malloc / open等在64位linux系统上无法创建大于2GB的文件

时间:2014-04-12 22:57:24

标签: c linux memory 64-bit limits

好的,我之前已经知道过各种各样的问题并且我已经阅读了所有问题并尝试了所有建议但我仍然无法在64位系统上使用malloc创建一个超过2GB的文件,打开, lseek,blah blah在阳光下的每一招。

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

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

编辑: 我已经尝试过定义_LARGEFILE64_SOURCE& _FILE_OFFSET_BITS 64并没有区别。 我也正在为64位编译,即-m64

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

非常感谢任何帮助。

感谢。

编辑:我已经删除了很多完全不正确的bab呀声和我之后处理过的其他一些不重要的谣言。

我的2GB问题在于多种不同类型的可怕的交换。签名和未签名的混合是问题所在。基本上我传给lseek的3GB位置被解释/变成了-1GB的位置,显然lseek并不喜欢这样。所以我很糟糕。完全是愚蠢的。

我将改为使用posix_fallocate()作为p_l建议。虽然它确实删除了一个函数调用,即只需要posix_fallocate而不是lseek然后写入,对我来说这并不重要,事实上posix_fallocate正在完全按照我想要的方式执行lseek方法所不具备的功能。所以特别感谢p_l建议,并特别感谢NominalAnimal,他知道更好的持久性间接地让我意识到我无法计算哪个反过来让我接受posix_fallocate会起作用,所以改为使用它。

无论我使用的最终方法如何。 2GB的问题完全是我自己的垃圾编码,再次感谢EOF,chux,p_l和Jonathon Leffler,他们都提供了信息和建议,引导我解决了我为自己创造的问题。

我在答案中加入了较短版本。

4 个答案:

答案 0 :(得分:1)

我的2GB问题在于多种不同类型的可怕的交换。签名和未签名的混合是问题所在。基本上我传给lseek的3GB位置被解释/变成了-1GB的位置,显然lseek并不喜欢这样。所以我很糟糕。完全愚蠢的垃圾编码。

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

再次感谢p_l建议posix_fallocate(),特别感谢NominalAnimal,他知道更好的持久性间接地让我意识到我无法计算哪些反过来让我接受posix_fallocate会起作用所以改为使用它。

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

答案 1 :(得分:0)

首先,尝试:

//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();

之前

我建议保留定义,但是:)

答案 2 :(得分:0)

这是我创建的测试程序(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 <assert.h>标头未定义static_assert。当我用-m32编译时,静态断言被触发。

当我在具有1 GiB虚拟物理内存的Ubuntu 13.10 64位虚拟机上运行它(或者是同义反复?)时,我的输出并不令人惊讶:

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

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

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

你得到了什么? dd可以创建大文件吗?代码是否编译? (如果你的编译器没有C11支持,那么你需要用普通的'dynamic'断言替换静态断言,删除错误信息。)代码是否运行?你得到了什么结果。

答案 3 :(得分:0)

以下是一个示例程序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

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

du -h data.map

查看这样的稀疏文件实际占用多少磁盘,以及

hexdump -C data.map

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

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

  • '-DFILE_NAME="filename"'

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

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

    使用1024 3 = 1073741824字节映射而不是默认值3221225472.如果表达式包含shell将尝试评估的特殊字符,最好将其全部用单引号或双引号括起来。 / p>

  • -DFILE_ALLOCATE

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

  • -DFILE_FILL

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

  • -DFILE_ZERO

    将整个映射清除为零。

  • -DFILE_MIDDLE

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

  • -DFILE_KEEP

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


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

  1. 文件大小限制

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

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

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

  2. 内存限制

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

  3. 地址空间限制

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

    在64位Linux系统上,大型映射会消耗大量的RAM,即使映射内容本身尚未出现故障。通常,每个页面需要8个字节的元数据(&#34; page table在内存中输入&#34; ,或更多,取决于体系结构。使用4096字节的页面,这意味着最小开销为0.1953125%,并设置例如一个太字节映射只需要在页表结构中使用两千兆字节的RAM!

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

  4. 内核可能会对用户空间进程施加比资源可用性更严格的限制。运行bash -c 'ulimit -a'以查看当前施加的限制。 (详情请参见man bash-builtins中的ulimit部分。)