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

我想知道在fork()中如何写入copy。

假设我们有一个具有dynamicint数组的进程A:

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

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

 for(a in array){ a = a+1; } 
  1. 我知道B不会立即复制整个数组,但是什么时候子B为数组分配内存? 在fork()?
  2. 它是一次分配整个数组,还是只分配一个整数a = a+1
  3. a = a+1; 这是怎么发生的? B是否从A读取数据并将新数据写入自己的数组?

我写了一些代码来探索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()之后,subprocess修改数组中的一半数字,然后修改整个数组。 输出是:

 === 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 

Solutions Collecting From Web of "fork()中的copy-on-write工作如何?"

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

通过克隆(2)调用可以实现更多的控制权。 而vfork(2)是一个特殊的变体,它不希望页面被使用。 这通常在exec()之前使用。

至于分配:malloc()具有超过请求的内存块(地址和大小)的元信息,C变量是一个指针(在进程内存堆和堆栈中)。 这两个看起来相同的孩子(相同的值,因为在这两个进程的地址空间中看到相同的底层内存页面)。 所以从C程序的角度来看,这个数组已经被分配了,当这个进程出现的时候,这个变量被初始化了。 但底层内存页面却指向父进程的原始物理内存页面,所以在修改之前不需要额外的内存页面。

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

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

有些系统有一个系统调用vfork() ,最初设计为fork()的低开销版本。 由于fork()涉及复制进程的整个地址空间,因此非常昂贵,引入了vfork()函数(在3.0BSD中)。

但是,由于引入了vfork()fork()的实现得到了极大的改善,最引人注目的是引入了“copy-on-write”, 通过允许这两个进程参照进程地址空间的复制是透明伪造的到相同的物理内存,直到其中任何一个修改它。 这在很大程度上消除了vfork();的理由vfork(); 实际上,大部分系统现在完全没有vfork()的原始功能。 尽管为了兼容性,仍然可能存在一个vfork()调用,它只是简单地调用fork()而不尝试模拟所有的vfork()语义。

因此,实际上使用fork()vfork()之间的任何区别是非常不明智的。 的确,使用vfork()根本不明智,除非你确切地知道你为什么vfork()

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

这意味着vfork()的子进程必须小心,以避免意外地修改父进程的变量。 特别是,子进程不能从包含vfork()调用的函数返回,并且不能调用exit() (如果需要退出,应该使用_exit();实际上,对于孩子来说也是如此一个普通的fork() )。