c语言 - 为什么我们可以分配一个 1 PB (10^15) 数组并访问最后一个元素,但不能释放它?



众所周知:http://linux.die.net/man/3/malloc

默认情况下,Linux 遵循乐观的内存分配策略。 这意味着当 malloc(( 返回非 NULL 时,无法保证 内存确实可用。万一事实证明 系统内存不足,一个或多个进程将被 OOM 杀手。

我们可以通过使用 malloc(petabyte); 成功分配 1 PB 的 VMA(虚拟内存区域(:http://ideone.com/1yskmB

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    long long int petabyte = 1024LL * 1024LL * 1024LL * 1024LL * 1024LL;    // 2^50
    printf("petabyte %lld n", petabyte);
    volatile char *ptr = (volatile char *)malloc(petabyte);
    printf("malloc() - success, ptr = %p n", ptr);
    ptr[petabyte - 1LL] = 10;
    printf("ptr[petabyte - 1] = 10; - success n");
    printf("ptr[petabyte - 1] = %d n", (int)(ptr[petabyte - 1LL]));
    free((void*)ptr);   // why the error is here?
    //printf("free() - success n");
    return 0;
}

结果:

Error   time: 0 memory: 2292 signal:6
petabyte 1125899906842624 
malloc() - success, ptr = 0x823e008 
ptr[petabyte - 1] = 10; - success 
ptr[petabyte - 1] = 10 

我们可以成功访问(存储/加载(PB的最后一个成员,但是为什么我们在free((void*)ptr);上出现错误?

注:https://en.wikipedia.org/wiki/Petabyte

  • 1000^5 PB PB
  • 1024^5 PiB pebibyte - 我使用它

因此,实际上,如果我们想分配超过 RAM + 交换并绕过overcommit_memory限制,那么我们可以通过在 Windows 上使用VirtualAllocEx()或在 Linux 上使用mmap()来分配内存,例如:

    16
  • TiB(16 * 2^40字节(,那么我们可以使用名义动物答案中的示例:https://stackoverflow.com/a/38574719/1558037
  • 127
  • TiB(127 * 2^40 字节(那么我们可以将mmap()与标志一起使用 MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUSfd=-1 : http://coliru.stacked-crooked.com/a/c69ce8ad7fbe4560
我相信

你的问题是malloc()不以long long int为论据。这需要size_t.

更改代码以将petabyte定义为size_t后,程序不再从 malloc 返回指针。它反而失败了。

我认为您的数组访问设置 PBBYTE-1 到 10 的写入远远超出了返回的数组 malloc。这就是崩溃。

调用函数时始终使用正确的数据类型。

使用此代码查看发生了什么:

long long int petabyte = 1024LL * 1024LL * 1024LL * 1024LL * 1024LL;
size_t ptest = petabyte;
printf("petabyte %lld %lun", petabyte, ptest);

如果我以 64 位模式编译,它无法 malloc 1 PB。如果我以 32 位模式编译,它会成功 malloc 0 字节,然后尝试在其数组和段错误之外写入。

(这不是一个答案,而是关于在Linux中使用大型数据集的人的重要说明(

这不是你在Linux中使用非常大的 - 大约TB级或更多 - 数据集的方式。

当您使用 malloc()mmap()(GNU C 库无论如何都会在内部使用 mmap() 进行大量分配(来分配私有内存时,内核将大小限制为(理论上(可用 RAM 和 SWAP 的大小乘以过度使用因子。

简而言之,我们知道可能必须换出大于 RAM 的数据集,因此当前交换的大小将影响允许的分配大小。

为了解决这个问题,我们创建一个用作数据"交换"的文件,并使用MAP_NORESERVE标志对其进行映射。这告诉内核我们不想对此映射使用标准交换。(这也意味着,如果由于任何原因,内核无法获得新的支持页面,应用程序将获得SIGSEGV信号并死亡。

Linux 中的大多数文件系统都支持稀疏文件。这意味着您可以拥有一个TB大小的文件,如果它的大部分内容没有写入(因此是零(,则只需要几千字节的实际磁盘空间。(创建稀疏文件很容易;您只需跳过长时间的零。打孔更困难,因为写入零确实使用正常的磁盘空间,需要使用其他方法代替。

下面是一个可用于探索的示例程序 mapfile.c

#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
    const char    *filename;
    size_t         page, size;
    int            fd, result;
    unsigned char *data;
    char           dummy;
    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "n");
        fprintf(stderr, "Usage: %s [ -h | --help ]n", argv[0]);
        fprintf(stderr, "       %s MAPFILE BYTESn", argv[0]);
        fprintf(stderr, "n");
        return EXIT_FAILURE;
    }
    page = sysconf(_SC_PAGESIZE);
    if (page < 1) {
        fprintf(stderr, "Unknown page size.n");
        return EXIT_FAILURE;
    }
    filename = argv[1];
    if (!filename || !*filename) {
        fprintf(stderr, "No map file name specified.n");
        return EXIT_FAILURE;
    }
    if (sscanf(argv[2], " %zu %c", &size, &dummy) != 1 || size < 3) {
        fprintf(stderr, "%s: Invalid size in bytes.n", argv[2]);
        return EXIT_FAILURE;
    }
    if (size % page) {
        /* Round up to next multiple of page */
        size += page - (size % page);
        fprintf(stderr, "Adjusted to %zu pages (%zu bytes)n", size / page, size);
    }
    do {
        fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1) {
        fprintf(stderr, "Cannot create %s: %s.n", filename, strerror(errno));
        return EXIT_FAILURE;
    }
    do {
        result = ftruncate(fd, (off_t)size);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        fprintf(stderr, "Cannot resize %s: %s.n", filename, strerror(errno));
        unlink(filename);
        close(fd);
        return EXIT_FAILURE;
    }
    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0);
    if ((void *)data == MAP_FAILED) {
        fprintf(stderr, "Mapping failed: %s.n", strerror(errno));
        unlink(filename);
        close(fd);
        return EXIT_FAILURE;
    }
    fprintf(stderr, "Created file '%s' to back a %zu-byte mapping at %p successfully.n", filename, size, (void *)data);
    fflush(stdout);
    fflush(stderr);
    data[0] = 1U;
    data[1] = 255U;
    data[size-2] = 254U;
    data[size-1] = 127U;
    fprintf(stderr, "Mapping accessed successfully.n");
    munmap(data, size);
    unlink(filename);
    close(fd);
    fprintf(stderr, "All done.n");
    return EXIT_SUCCESS;
}

编译它

,例如
gcc -Wall -O2 mapfile.c -o mapfile

并在没有参数的情况下运行它以查看使用情况。

该程序只需设置一个映射(调整为当前页面大小的倍数(,并访问映射的前两个和后两个字节。

在我的机器上,在 x86-64 上运行 4.2.0-42-通用 #49~14.04.1-Ubuntu SMP 内核,在 ext4 文件系统上,我无法映射完整的 PB。最大值似乎约为 17,592,186,040,320 字节 (244-4096( -- 16 TiB - 4 KiB --,即 4,294,967,296 页 4096 字节(232 页,每页 212 字节(。看起来限制是由 ext4 文件系统施加的,因为失败发生在ftruncate()调用中(甚至在尝试映射之前(。

(在 tmpfs 上,我最多可以获得大约 140,187,732,541,440 字节或 127.5 TiB,但这只是一个噱头,因为 tmpfs 由 RAM 和交换支持,而不是实际的存储设备。因此,这不是真正的大数据工作的选择。我似乎记得 xfs 适用于非常大的文件,但我懒得格式化分区甚至查找规格;我认为没有人会真正阅读这篇文章,尽管这里的信息在过去十年左右的时间里对我非常有用。

以下是该示例运行在我的机器上的外观(使用 Bash shell(:

$ ./mapfile datafile $[(1<<44)-4096]
Created file 'datafile' to back a 17592186040320-byte mapping at 0x6f3d3e717000 successfully.
Mapping accessed successfully.
All done.

.

相关内容

  • 没有找到相关文章

最新更新