c++ - 如何让写时复制在 Linux 上的共享内存上工作

标签 c++ linux memory memory-management shared-memory

我尝试编写一个小应用程序来熟悉用户空间中写时复制的概念。我已通读 answer by MSalters并认为只有从 mmap 开始才会起作用'ed 文件来存储我的数据。由于我不需要基于文件的持久性,我尝试对共享内存做同样的事情。首先我mmap 'ed 并初始化了一个 shm fd,然后我用 MAP_PRIVATE 映射了第二个拷贝并再次阅读。然而,仅仅读取它会导致内核复制整个内容,花费更多的时间并消耗两倍的内存。为什么它不做 COW?

这是我想出的用于说明行为的程序:

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

static const size_t ARRAYSIZE = 1UL<<30;

void init(int* A)
{
    for (size_t i = 0; i < ARRAYSIZE; ++i)
        A[i] = i;
}

size_t agg(const int* A)
{
    size_t sum = 0;
    for (size_t i = 0; i < ARRAYSIZE; ++i)
        sum += A[i];
    return sum;
}

int main()
{
    assert(sizeof(int) == 4);
    shm_unlink("/cowtest");
    printf("ARRAYSIZE: %lu\n", ARRAYSIZE);
    int fd = shm_open("/cowtest", O_RDWR | O_CREAT | O_TRUNC, 0);
    if (fd == -1)
    {
        perror("Error allocating fd\n");
        return 1;
    }
    if (ftruncate(fd, sizeof(int) * ARRAYSIZE) == -1)
    {
        perror("Error ftruncate\n");
        return 1;
    }
    /* Open shm */
    int* A= (int*)mmap(NULL, sizeof(int) * ARRAYSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (A == (int*)-1)
    {
        perror("Error mapping A to memory\n");
        return 1;
    }
    init(A);

    /* Create cow copy */
    int* Acopy = (int*)mmap(NULL, sizeof(int) * ARRAYSIZE, PROT_READ, MAP_PRIVATE, fd, 0);
    if (Acopy == (int*)-1)
    {
        printf("Error mapping copy from file\n");
        return 1;
    }

    /* Aggregate over A */
    size_t sumA = agg(A);
    size_t expected = (ARRAYSIZE * (ARRAYSIZE - 1)) >> 1;
    assert(expected == sumA);

    /* Aggregate over Acopy */
    size_t sumCopy = agg(Acopy);
    assert(expected == sumCopy);


    shm_unlink("/cowtest");
    printf("Enter to exit\n");
    getchar();
    return 0;
}

我用 g++ -O3 -mtune=native -march=native -o shm-min shm-min.cpp -lrt 编译它.

它创建的数组包含 4GB 的整数值。然而,在终止程序之前分配 8GB 共享内存,并在 /proc/<pid>/smaps您可以看到它实际上在只读操作期间进行了完整复制。我不知道为什么会这样。这是内核错误吗?还是我错过了什么?

非常感谢您提供任何见解。拉尔斯

编辑 这里是/proc/<pid>/smaps的相关内容在 Ubuntu 14.04 (3.13.0-24) 上:

7f3b9b4ae000-7f3c9b4ae000 r--p 00000000 00:14 168154                     /run/shm/cowtest (deleted)
Size:            4194304 kB
Rss:             4194304 kB
Pss:             2097152 kB
Shared_Clean:          0 kB
Shared_Dirty:    4194304 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:      4194304 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd mr mw me sd
7f3c9b4ae000-7f3d9b4ae000 rw-s 00000000 00:14 168154                     /run/shm/cowtest (deleted)
Size:            4194304 kB
Rss:             4194304 kB
Pss:             2097152 kB
Shared_Clean:          0 kB
Shared_Dirty:    4194304 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:      4194304 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr sh mr mw me ms sd

最佳答案

没有复制。 smaps 文件有一个提示:

Size:            4194304 kB
Rss:             4194304 kB
Pss:             2097152 kB

看到 Pss 是映射区域实际大小的一半吗?那是因为它除以两个用法(Pss = 比例共享大小)。也就是说,您将同一个文件两次映射到不同范围的虚拟内存,但两次映射的底层物理页面是相同的。

要找出相关页面的物理地址,您可以使用 a tool here .另存为page-types.c , 运行 make page-types然后 ./page-types -p <pid> -l -N .您将看到不同的虚拟地址(在第一列中)映射到相同的物理页面(在第二列中)。

如果您添加 PROT_WRITE第二个映射的权限位,并调用 init(Acopy) ,你会看到Pss跳转到了4GB,对应的page的物理地址也不再一样了。

TL;DR COW 有效。

关于c++ - 如何让写时复制在 Linux 上的共享内存上工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24409261/

相关文章:

c++ - 在具有透明度的图像中创建边界框

c++ - 如何使用 range v3 库将字符串拆分为由规则定义的序列?

linux - 了解 'top' CPU 使用率

linux - 打开文件中的每个路径并在每个文件中搜索模式

linux - 期望从不匹配模式

c++ - Glibc 错误 : double free or corruption

c++ - 寻找适用于 C++ 的设计模式

c++ - 如何在C++中实现R的 "optimize"函数?

c++ - 通过线程句柄获取线程的 TIB/TEB (2015)

performance - 什么是数据的低延迟访问?