【问题标题】:Can I do a copy-on-write memcpy in Linux?我可以在 Linux 中进行写时复制 memcpy 吗?
【发布时间】:2010-12-06 14:46:51
【问题描述】:

我有一些代码经常复制一大块内存,通常是在对其进行非常小的更改之后。

我已经实现了一个跟踪更改的系统,但我认为它可能会很好,如果可能的话,告诉操作系统对内存进行“写时复制”,并让它只处理复制那些变化的部分。然而,虽然 Linux 确实会进行写时复制,例如在 fork() 时,我找不到控制它并自己做的方法。

【问题讨论】:

  • 你要复制什么样的数据?写时复制可能不是唯一的解决方案。

标签: c linux memory-management


【解决方案1】:

您最好的机会可能是 mmap() 将原始数据归档,然后再次使用 MAP_PRIVATE mmap() 相同的文件。

【讨论】:

  • 请注意,您需要创建两个MAP_PRIVATE 映射 - COW 语义要求所有用户都拥有 COW 副本,没有人使用“主”副本。不幸的是,文件本身似乎是必要的。
  • 为什么?假设 master 是 AA,而 COW 的原因是你想要一个可以更改为 AB 的副本。没有理由原来的AA 需要是私有映射,因为没有人打算写信给它。它只是一个模板。
  • 我的评论是基于“原始”副本也可能被写入的可能性,在这种情况下,这些更改是否反映在 COW 副本中将是未指定的。顺便说一句,很遗憾mmap 没有为此提供固有的支持 - 我可能会尝试向mmap 添加支持以复制现有映射并看看它是如何进行的。
  • 我支持 MSalters:没有“标准”的 COW 语义集。将一个映射作为“真实”文件,一个作为私有副本似乎完全合理。很明显,有些应用需要可写快照之类的东西,但不是全部。
  • memfd_create 可用于解决创建文件的需要,但您仍需要分配原始数据 memfd 支持的内存来处理它。
【解决方案2】:

根据您要复制的具体内容,persistent data structure 可能是您问题的解决方案。

【讨论】:

    【解决方案3】:

    使用面向对象的语言(如 c++)更容易实现写时复制。例如,Qt 中的大多数容器类都是写时复制的。

    但是,如果您当然也可以在 C 中做到这一点,那只是需要做更多的工作。当你想将你的数据分配给一个新的数据块时,你不做一个复制,而是你只复制一个指针到你的数据周围的包装器 strcut 中。您需要在数据块中跟踪数据的状态。如果您现在更改新数据块中的某些内容,则制作“真实”副本并更改状态。 您当然不能再使用像“=”这样的简单运算符进行赋值,而是需要有函数(在 C++ 中,您只需进行运算符重载)。

    一个更健壮的实现应该使用引用计数器而不是一个简单的标志,我把它留给你。

    一个快速而肮脏的例子: 如果你有一个

    struct big {
    //lots of data
        int data[BIG_NUMBER];
    }
    

    您必须自己实现分配函数和 getter/setter。

    // assume you want to implent cow for a struct big of some kind
    // now instead of
    struct big a, b;
    a = b;
    a.data[12345] = 6789;
    
    // you need to use
    struct cow_big a,b;
    assign(&a, b);   //only pointers get copied
    set_some_data(a, 12345, 6789); // now the stuff gets really copied
    
    
    //the basic implementation could look like 
    struct cow_big {
        struct big *data;
        int needs_copy;
    }
    
    // shallow copy, only sets a pointer. 
    void assign(struct cow_big* dst, struct cow_big src) {
        dst->data = src.data;
        dst->needs_copy = true;
    }
    
    // change some data in struct big. if it hasn't made a deep copy yet, do it here.
    void set_some_data(struct cow_big* dst, int index, int data } {
        if (dst->needs_copy) {
            struct big* src = dst->data;
            dst->data = malloc(sizeof(big));
            *(dst->data) = src->data;   // now here is the deep copy
           dst->needs_copy = false;
       }
       dst->data[index] = data;
    }
    

    您还需要编写构造函数和析构函数。我真的为此推荐 c++。

    【讨论】:

    • 这不会生成我想要的 COW 语义,如果操作系统这样做了,它只会复制(至少在 Mac OS X 上)已更改的 4k 页面,而剩下的(其他 10 或 100 MB)数据结构仍然是 COW。当然,我可以并且已经实现了我真正想要的东西,但如果我能让操作系统为我做这件事,那就太好了。
    • 较新版本的 linux 内核可能会自动完成,内核 2.6.32+ 具有使用写时复制链接 lwn.net/Articles/353501 替换重复页面的代码,但 ksm 子系统不是很好成熟,直到现在它的工作方式相反:页面在复制后被扫描,如果相同则替换。如果您希望它从用户空间控制它,您可能需要查看 linux/mm/ksm.c 并进行所需的更改。
    • 发布的解决方案根本不是“CoW”,它是一种软件模拟,它通过间接层强制所有“写入”操作。我相信 Chris 是专门要求使用 MMU 硬件的内存级解决方案。 FWIW:您不需要新版本的 Linux 内核。 BSD mmap() 已经支持 MAP_PRIVATE 几十年了——它从一开始就是 POSIX 的一部分。
    【解决方案4】:

    采用的写时复制机制,例如by fork() 是 MMU(内存管理单元)的一个特性,它为内核处理内存分页。访问 MMU 是一项特权操作,即不能由用户空间应用程序完成。我也不知道有任何写入时复制 API 导出到用户空间。

    (再说一次,我并不是 Linux API 的专家,所以其他人可能会指出我错过的相关 API 调用。)

    编辑: 瞧,MSalters 应运而生。 ;-)

    【讨论】:

      【解决方案5】:

      您应该能够通过 /proc/$PID/mem 打开自己的内​​存,然后使用 MAP_PRIVATE 将其中有趣的部分 mmap() 到其他地方。

      【讨论】:

      • 这将不起作用,因为 /proc.../mem 不支持 mmap。另请参阅here
      【解决方案6】:

      这是一个工作示例:

      #include <stdio.h>
      #include <stdlib.h>
      #include <sys/mman.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      
      #define SIZE 4096
      
      int main(void) {
        int fd = shm_open("/tmpmem", O_RDWR | O_CREAT, 0666);
        int r = ftruncate(fd, SIZE);
        printf("fd: %i, r: %i\n", fd, r);
        char *buf = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
            MAP_SHARED, fd, 0);
        printf("debug 0\n");
        buf[SIZE - 2] = 41;
        buf[SIZE - 1] = 42;
        printf("debug 1\n");
      
        // don't know why this is needed, or working
        //r = mmap(buf, SIZE, PROT_READ | PROT_WRITE,
        //  MAP_FIXED, fd, 0);
        //printf("r: %i\n", r);
      
        char *buf2 = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
          MAP_PRIVATE, fd, 0);
        printf("buf2: %i\n", buf2);
        buf2[SIZE - 1] = 43;
        buf[SIZE - 2] = 40;
        printf("buf[-2]: %i, buf[-1]: %i, buf2[-2]: %i, buf2[-1]: %i\n",
            buf[SIZE - 2],
            buf[SIZE - 1],
            buf2[SIZE - 2],
            buf2[SIZE - 1]);
      
        unlink(fd);
        return EXIT_SUCCESS;
      }
      

      为了安全起见,我有点不确定是否需要启用注释掉的部分。

      【讨论】:

      • 对我来说,这在第二次调用 mmap 时崩溃。我很想知道您是否随后使用了此代码或它的改进版本,因为我对 C 代码中的写时复制有类似的要求? (附注:对 unlink 的调用看起来是错误的(unlink 需要一个字符串,而不是文件描述符))。
      猜你喜欢
      • 2023-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-27
      • 1970-01-01
      • 2023-03-04
      • 2019-11-09
      相关资源
      最近更新 更多