copy-on-write如何在fork()中工作?

时间:2014-11-27 00:52:43

标签: c linux unix fork copy-on-write

我想知道如何在fork()中发生copy-on-write。

假设我们有一个具有动态int数组的进程A:

int *array = malloc(1000000*sizeof(int));

数组中的元素被初始化为一些有意义的值。 然后,我们使用fork()创建子进程,即B. B将迭代数组并进行一些计算:

for(a in array){
    a = a+1;
}
  1. 我知道B不会立即复制整个数组,但是什么时候B会为数组分配内存?在fork()?
  2. 期间
  3. 是一次性分配整个数组,还是a = a+1只分配一个整数?
  4. a = a+1;这是怎么发生的? B是否从A读取数据并将新数据写入其自己的阵列?
  5. 我写了一些代码来探索COW是如何工作的。我的环境:ubuntu 14.04,gcc4.8.2

    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/sysinfo.h>
    
    void printMemStat(){
        struct sysinfo si;
        sysinfo(&si);
        printf("===\n");
        printf("Total: %llu\n", si.totalram);
        printf("Free: %llu\n", si.freeram);
    }
    
    int main(){
        long len = 200000000;
        long *array = malloc(len*sizeof(long));
        long i = 0;
        for(; i<len; i++){
            array[i] = i;
        }
    
        printMemStat();
        if(fork()==0){
            /*child*/
            printMemStat();
    
            i = 0;
            for(; i<len/2; i++){
                array[i] = i+1;
            }
    
            printMemStat();
    
            i = 0;
            for(; i<len; i++){
                array[i] = i+1;
            }
    
            printMemStat();
    
        }else{
            /*parent*/
            int times=10;
            while(times-- > 0){
                sleep(1);
            }
        }
        return 0;
    }
    

    在fork()之后,子进程修改数组中一半的数字,然后修改整个数组。输出是:

    ===
    Total: 16694571008
    Free: 2129162240
    ===
    Total: 16694571008
    Free: 2126106624
    ===
    Total: 16694571008
    Free: 1325101056
    ===
    Total: 16694571008
    Free: 533794816
    

    似乎数组未作为整体分配。如果我稍微将第一个修改阶段改为:

    i = 0;
    for(; i<len/2; i++){
        array[i*2] = i+1;
    }
    

    输出将是:

    ===
    Total: 16694571008
    Free: 2129924096
    ===
    Total: 16694571008
    Free: 2126868480
    ===
    Total: 16694571008
    Free: 526987264
    ===
    Total: 16694571008
    Free: 526987264
    

2 个答案:

答案 0 :(得分:8)

取决于操作系统,硬件架构和libc。但是,对于最近使用MMU的Linux,fork(2)将使用copy-on-write。它只会(分配和)复制一些系统结构和页面表,但堆页面实际上指向父页面,直到写入。

可以通过clone(2)调用对此进行更多控制。并且vfork(2)是一种特殊的变体,它不期望使用这些页面。这通常在exec()之前使用。

至于分配:malloc()在请求的内存块(地址和大小)上有元信息,C变量是指针(在进程内存堆和堆栈中都有)。对于孩子来说,这两个看起来是一样的(相同的值,因为在两个进程的地址空间中看到相同的底层内存页)。因此,从C程序的角度来看,数组已经被分配,并且当进程存在时变量被初始化。然而,底层内存页面指向父进程的原始物理页面,因此在修改它们之前不需要额外的内存页面。

如果子节点分配一个新数组,它取决于它是否适合已经存在的堆页面,或者是否需要增加该进程的brk。在这两种情况下,只复制修改后的页面,并且仅为子项分配新页面。

这也意味着物理内存可能会在malloc()之后耗尽。 (由于程序无法检查“随机代码行中的操作”的错误返回代码,这是不好的)。某些操作系统不允许这种形式的过度使用:因此,如果你分叉一个进程,它将不会分配页面,但它要求它们在那个时候可用(保留它们)以防万一。在Linux中,这是configurable and called overcommit-accounting

答案 1 :(得分:4)

有些系统最初有系统调用vfork() 设计为fork()的低开销版本。以来 fork()涉及复制流程的整个地址空间, 因此,vfork()功能非常昂贵 介绍(在3.0BSD中)。

然而,自从vfork()被引入以来, fork()的实施已经大大改善,最值得注意的是 随着'copy-on-write'的引入,在哪里复制了 通过允许两个进程透明地伪造进程地址空间 在它们中的任何一个修改之前引用相同的物理内存 它。这在很大程度上消除了vfork();的理由,a 大部分系统现在缺乏原有的功能 完全vfork()。但是,为了兼容性,可能仍然存在 存在vfork()来电,只需拨打fork()即可 试图模仿所有vfork()语义。

因此,实际使用任何一个是非常不明智的 fork()vfork()之间的差异。的确是 除非你确切知道,否则完全使用vfork()可能是不明智的 为什么你想要。

两者之间的基本区别在于,当使用vfork()创建新进程时,父进程将暂时挂起,子进程可能会借用父进程的地址空间。这种奇怪的状态一直持续到子进程退出或调用execve(),此时父进程继续。

这意味着vfork()的子进程必须小心 避免意外修改父进程的变量。在 特别是,子进程不能从函数返回 包含vfork()电话,但不得拨打电话 exit()(如果需要退出,则应使用_exit(); 实际上,对于正常fork()的孩子来说也是如此。