c - 是否可以将现有缓冲区映射到新文件?

标签 c linux memory-management posix mmap

这个想法相对简单,但是我发现实现有些复杂,所以我想知道现在是否有可能。

  • 我想做的一个例子是在
    缓冲区,然后将此缓冲区的内容映射到文件。代替
    将内存空间虚拟地填充了
    文件,我想传输原始缓冲区的内容
    到系统缓存(应该是零复制操作),并且
    立即弄脏(这会将数据刷新到磁盘
    最终)。

  • 当然,我提到的问题是缓冲区应该被释放并取消映射(因为数据现在由系统缓存负责),我也不知道该怎么做。

    重要方面是:
  • 该程序可以控制何时创建文件链接。
  • 不需要程序预测文件的大小,也不需要随着数据集的增长重新映射文​​件。相反,它可以重新分配初始缓冲区(为此使用高效的内存分配器),直到满意为止(它确定知道数据集将不再增长),然后再将其最终映射到文件。
  • 即使将数据映射到文件后,仍然可以通过相同的虚拟内存地址对其进行访问,但仍然没有单个内存内副本。

  • 一种假设是:
  • 我们可以使用任意的内存分配器(或一般的内存管理方案)来管理动态缓冲区,而mmap/mremap可以更有效地管理动态缓冲区,因为后者必须管理文件系统以增大/缩小文件,这总是比较慢。

  • 因此,(1)这些要求是否受到约束? (2)这个假设正确吗?

    PS:我不得不为这个问题随意选择标签,但是我也对听到BSD和Windows如何做到这一点感兴趣。当然,如果POSIX API允许这样做,那就太好了。

    更新:我将缓冲区称为在主内存中分配的私有(private)内存空间(在任何具有正常VMM的操作系统中,进程/任务都是私有(private)的)。高层目标涉及使用另一个输入(在我的情况下为网络)生成任意大小的数据集,然后一旦生成,使其可长时间访问(对网络和流程本身),然后保存到磁盘的过程中。
  • 如果我将数据集保留在私有(private)内存中并正常写出,则它们只在操作系统需要空间时才被交换,这有点愚蠢,因为它们已经在磁盘上了。
  • 如果我映射另一个区域,则必须将缓冲区的内容复制到该区域(位于系统高速缓存中),这又是一个小问题,因为在此之后我将不再使用该缓冲区。

  • 我看到的另一种选择是写入或使用成熟的userland缓存读取和写入磁盘本身,以确保(a)页面不会被无用地换出,并且(b)该进程不会占用太多内存就其本身而言,这永远不可能实现最佳化(最好让内核来完成工作),而我认为这绝对不是一条值得走的路(太复杂而得不到 yield )。

    更新:考虑到名义动物的答案,要求2和3不是问题。当然,这意味着假设是不正确的,因为他证明了这种情况(开销很小)。我还放宽了要求1,O_TMPFILE确实是完美的选择。

    更新: A recent article on LWN在中间提到:“这可以通过实际上不会引起I/O的特殊写操作,或将物理页转移到页缓存中的系统调用来完成”。这表明确实(至少2014年4月),至少对于Linux(可能还有其他操作系统),目前尚无办法,而使用标准API的情况则更少。这篇文章是关于PostgreSQL的,但是所讨论的问题是相同的,除了可能对此问题的特定要求没有在本文中定义。

    最佳答案

    对于这个问题,这不是令人满意的答案。是更多评论链的延续。

    这是一个测试程序,可以用来测量使用文件支持的内存映射而不是匿名内存映射的开销。

    请注意,列出的work()函数仅用随机数据填充内存映射。为了更加现实,它应该至少模拟实际使用中预期的访问模式。

    #define  _POSIX_C_SOURCE 200809L
    #define  _GNU_SOURCE
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <time.h>
    #include <stdint.h>
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>
    
    /* Xorshift random number generator.
    */
    
    static uint32_t xorshift_state[4] = {
        123456789U,
        362436069U,
        521288629U,
        88675123U
    };
    
    static int xorshift_setseed(const void *const data, const size_t len)
    {
        uint32_t state[4] = { 0 };
    
        if (len < 1)
            return ENOENT;
        else
        if (len < sizeof state)
            memcpy(state, data, len);
        else
            memcpy(state, data, sizeof state);
    
        if (state[0] || state[1] || state[2] || state[3]) {
            xorshift_state[0] = state[0];
            xorshift_state[1] = state[1];
            xorshift_state[2] = state[2];
            xorshift_state[3] = state[3];
            return 0;
        }
    
        return EINVAL;
    }
    
    static uint32_t xorshift_u32(void)
    {
        const uint32_t temp = xorshift_state[0] ^ (xorshift_state[0] << 11U);
        xorshift_state[0] = xorshift_state[1];
        xorshift_state[1] = xorshift_state[2];
        xorshift_state[2] = xorshift_state[3];
        return xorshift_state[3] ^= (temp >> 8U) ^ temp ^ (xorshift_state[3] >> 19U);
    }
    
    /* Wallclock timing functions.
    */
    
    static struct timespec wallclock_started;
    
    static void wallclock_start(void)
    {
        clock_gettime(CLOCK_REALTIME, &wallclock_started);
    }
    
    static double wallclock_stop(void)
    {
        struct timespec wallclock_stopped;
        clock_gettime(CLOCK_REALTIME, &wallclock_stopped);
        return difftime(wallclock_stopped.tv_sec, wallclock_started.tv_sec)
             + (double)(wallclock_stopped.tv_nsec - wallclock_started.tv_nsec) / 1000000000.0;
    }
    
    /* Accessor function. This needs to read/modify/write the mapping,
     * simulating the actual work done onto the mapping.
    */
    static void work(void *const area, size_t const length)
    {
        uint32_t *const data = (uint32_t *)area;
        size_t          size = length / sizeof data[0];
        size_t          i;
    
        /* Add xorshift data. */
        for (i = 0; i < size; i++)
            data[i] += xorshift_u32();
    }
    
    int main(int argc, char *argv[])
    {
        long   page, size, delta, maxsize, steps;
        int    fd, result;
        void  *map, *old;
        char   dummy;
        double seconds;
    
        page = sysconf(_SC_PAGESIZE);
    
        if (argc < 5 || argc > 6 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            fprintf(stderr, "\n");
            fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
            fprintf(stderr, "       %s MAPFILE SIZE DELTA MAXSIZE [ SEEDSTRING ]\n", argv[0]);
            fprintf(stderr, "Where:\n");
            fprintf(stderr, "       MAPFILE     backing file, '-' for none\n");
            fprintf(stderr, "       SIZE        initial map size\n");
            fprintf(stderr, "       DELTA       map size change\n");
            fprintf(stderr, "       MAXSIZE     final size of the map\n");
            fprintf(stderr, "       SEEDSTRING  seeds the Xorshift PRNG\n");
            fprintf(stderr, "Note: sizes must be page aligned, each page being %ld bytes.\n", (long)page);
            fprintf(stderr, "\n");
            return 1;
        }
    
        if (argc >= 6) {
            if (xorshift_setseed(argv[5], strlen(argv[5]))) {
                fprintf(stderr, "%s: Invalid seed string for the Xorshift generator.\n", argv[5]);
                return 1;
            } else {
                fprintf(stderr, "Xorshift initialized with { %lu, %lu, %lu, %lu }.\n",
                                (unsigned long)xorshift_state[0],
                                (unsigned long)xorshift_state[1],
                                (unsigned long)xorshift_state[2],
                                (unsigned long)xorshift_state[3]);
                fflush(stderr);
            }
        }
    
        if (sscanf(argv[2], " %ld %c", &size, &dummy) != 1) {
            fprintf(stderr, "%s: Invalid map size.\n", argv[2]);
            return 1;
        } else
        if (size < page || size % page) {
            fprintf(stderr, "%s: Map size must be a multiple of page size (%ld).\n", argv[2], page);
            return 1;
        }
    
        if (sscanf(argv[3], " %ld %c", &delta, &dummy) != 1) {
            fprintf(stderr, "%s: Invalid map size change.\n", argv[2]);
            return 1;
        } else
        if (delta % page) {
            fprintf(stderr, "%s: Map size change must be a multiple of page size (%ld).\n", argv[3], page);
            return 1;
        }
    
        if (delta) {
            if (sscanf(argv[4], " %ld %c", &maxsize, &dummy) != 1) {
                fprintf(stderr, "%s: Invalid final map size.\n", argv[3]);
                return 1;
            } else
            if (maxsize < page || maxsize % page) {
                fprintf(stderr, "%s: Final map size must be a multiple of page size (%ld).\n", argv[4], page);
                return 1;
            }
    
            steps = (maxsize - size) / delta;
            if (steps < 0L)
                steps = -steps;
    
        } else {
            maxsize = size;
            steps = 0L;
        }
    
        /* Time measurement includes the file open etc. overheads.
        */
        wallclock_start();
    
        if (strlen(argv[1]) < 1 || !strcmp(argv[1], "-"))
            fd = -1;
        else {
            do {
                fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, 0600);
            } while (fd == -1 && errno == EINTR);
            if (fd == -1) {
                fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
                return 1;
            }
    
            do {
                result = ftruncate(fd, (off_t)size);
            } while (result == -1 && errno == EINTR);
            if (result == -1) {
                fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
                unlink(argv[1]);
                do {
                    result = close(fd);
                } while (result == -1 && errno == EINTR);
                return 1;
            }
    
            result = posix_fadvise(fd, 0, size, POSIX_FADV_RANDOM);
        }
    
        /* Initial mapping. */
        if (fd == -1)
            map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, fd, 0);
        else
            map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0);
        if (map == MAP_FAILED) {
            fprintf(stderr, "Memory map failed: %s.\n", strerror(errno));
            if (fd != -1) {
                unlink(argv[1]);
                do {
                    result = close(fd);
                } while (result == -1 && errno == EINTR);
            }
            return 1;
        }
        result = posix_madvise(map, size, POSIX_MADV_RANDOM);
    
        work(map, size);
    
        while (steps-->0L) {
    
            if (fd != -1) {
                do {
                    result = ftruncate(fd, (off_t)(size + delta));
                } while (result == -1 && errno == EINTR);
                if (result == -1) {
                    fprintf(stderr, "%s: Cannot grow file: %s.\n", argv[1], strerror(errno));
                    unlink(argv[1]);
                    do {
                        result = close(fd);
                    } while (result == -1 && errno == EINTR);
                    return 1;
                }
                result = posix_fadvise(fd, 0, size, POSIX_FADV_RANDOM);
            }
    
            old = map;
            map = mremap(map, size, size + delta, MREMAP_MAYMOVE);
            if (map == MAP_FAILED) {
                fprintf(stderr, "Cannot remap memory map: %s.\n", strerror(errno));
                munmap(old, size);
                if (fd != -1) {
                    unlink(argv[1]);
                    do {
                        result = close(fd);
                    } while (result == -1 && errno == EINTR);
                }
                return 1;
            }
            size += delta;
            result = posix_madvise(map, size, POSIX_MADV_RANDOM);
    
            work(map, size);
        }
    
        /* Timing does not include file renaming.
        */
        seconds = wallclock_stop();
    
        munmap(map, size);
        if (fd != -1) {
            unlink(argv[1]);
            do {
                result = close(fd);
            } while (result == -1 && errno == EINTR);
        }
    
        printf("%.9f seconds elapsed.\n", seconds);
        return 0;
    }
    

    如果将以上内容另存为bench.c,则可以使用进行编译
    gcc -W -Wall -O3 bench.c -lrt -o bench
    

    在不带参数的情况下运行它以查看用法。

    在我的机器上ext4文件系统上,运行测试
    ./bench - 4096 4096 4096000
    ./bench testfile 4096 4096 4096000
    

    匿名存储映射的挂钟时间为1.307秒,文件支持的存储映射的时间为1.343秒,这意味着文件支持的映射慢了约2.75%。

    该测试从一页内存映射开始,然后将其放大一千倍。对于4096000 4096 8192000这样的测试,差异甚至更小。测量的时间确实包括构造初始文件(以及使用posix_fallocate()为文件分配磁盘上的块)。

    在同一台机器上的tmpfs,swRAID0上的ext4以及swRAID1上的ext4上运行测试似乎不会影响结果。所有的差异都消失了。

    尽管我希望在进行任何清除语句之前先在多台机器和内核版本上进行测试,但我确实了解内核如何管理这些内存映射。因此,基于以上和我的经验,我将提出以下主张:

    与匿名内存映射相比,甚至与malloc()/realloc()/free()相比,使用文件支持的内存映射都不会导致明显的速度下降。我希望在所有实际使用案例中,差异不会超过5%,而在典型的实际使用案例中,差异最大为1%;如果调整大小与访问 map 的频率相比很少,则调整较少。

    对于user2266481来说,上面的意思是仅在目标文件系统上创建一个临时文件来保存内存映射应该是可以接受的。 (请注意,可以在不允许任何人访问临时文件的情况下创建临时文件,方式0,因为仅在打开文件时才检查访问方式。)当内容为最终格式时,使用ftruncate()msync()内容,然后进行硬编码-使用link()将最终文件链接到临时文件。最后,取消链接临时文件并关闭临时文件描述符,任务应以接近最佳的效率完成。

    关于c - 是否可以将现有缓冲区映射到新文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18214009/

    相关文章:

    PHP 垃圾收集器统计

    c++ - 将双端队列与 C 库一起使用

    c - 在 C 中实现无输入大小的链表

    c - 如何在 C 程序中读取一行并拆分为字符串和浮点值?

    c - 如何在 C 中的结构内、 union 内使用结构?

    c - Linux编程。如何使用消息队列发送数组?

    c - 如何计算fork进程的位置

    c - OS X 上的 malloc 问题

    java - Netbeans 8.2 无法启动

    objective-c - Objective-C ,iOS : not calling alloc or init on a subview