How does copy-on-write work in fork ()?

I want to know how copy-on-write happens in fork ().

Assuming we have a process A that has a dynamic int array:

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

Elements in the array are initialized with some significant values. Then we use fork () to create a child process, namely B. B will iterate over the array and perform some calculations:

 for(a in array){ a = a+1; } 
  • I know that B will not copy the entire array at once, but when does child B allocate memory for the array? during fork ()?
  • Does it select the entire array at once or only one integer for a = a+1 ?
  • a = a+1; How does this happen? Does B read data from A and write new data to its own array?

I wrote code to learn how COW works. My environment: 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; } 

After fork (), the child process changes half the numbers in the array, and then modifies the entire array. Outputs:

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

It seems that the array is not allocated as a whole. If I slightly changed the first phase of the modification to:

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

The outputs will be:

 === Total: 16694571008 Free: 2129924096 === Total: 16694571008 Free: 2126868480 === Total: 16694571008 Free: 526987264 === Total: 16694571008 Free: 526987264 
+7
c linux unix fork
source share
2 answers

Depends on operating system, hardware architecture, and libc. But yes, in the case of recent Linux with MMU, fork (2) will work with copy to write. It will (select and) copy several system structures and the page table, but the heap pages actually point to the parent elements until they are written.

Additional control over this can be done with clone (2) . And vfork (2) is a special option that does not expect pages to be used. This is usually used before exec ().

As for the distribution: malloc () has meta-information on the requested blocks of memory (address and size), and the variable C is a pointer (both in the memory heap and in the packets). These two look the same for the child (the same values ​​as the same memory page as in the address space of both processes). Thus, from program point C, the array is already allocated and the variable is initialized when the process occurs. However, the main pages of memory point to the original physical parent process, so additional pages of memory are not required until they are changed.

If the child element allocates a new array, it depends on whether it fits into existing heap pages or if you want to increase the level of the process. In both cases, only modified pages are copied, and new pages are allocated only to the child.

It also means that physical memory may end after malloc (). (Which is bad, because the program cannot check the error return code "operation in a random line of code"). Some operating systems will not allow this form of overcommit: therefore, if you unlock the process, it will not allocate pages, but this requires that they be available at this moment (for example, reserves them) just in case. On Linux, this is customizable and called overcommit-accounting .

+7
source share

Some systems have the vfork() system call, which was originally designed as a lower version of fork() . since fork() meant copying the entire address space of the process, and therefore was quite expensive, the vfork() function was (in version 3.0BSD).

However, since vfork() was introduced, the fork() implementation has improved significantly, especially with the introduction of "copy-on-write" , where copying the process address space is transparently tampered with, allowing both processes to access the same physical memory, while none of them will not change that. This pretty much eliminates the vfork(); excuse vfork(); Indeed, most systems now do not have the original vfork() functionality completely. However, for compatibility, there could still be a vfork() , which simply calls fork() without trying to emulate all the semantics of vfork() .

As a result, it is very unwise to actually use any of the differences between fork() and vfork() . In fact, it is probably unwise to use vfork() at all unless you know exactly why you want to.

The main difference between the two is that when a new process is created using vfork() parent process is temporarily suspended, and the child process can take up the parent address space. This strange state continues until the child process exits or calls execve() , after which the parent process continues.

This means that the vfork() child process must be careful to avoid unexpected changes to the variables of the parent process. In particular, the child process should not return from the function containing the vfork() call, and it should not call exit() (if it needs to exit, it should use _exit(); in fact, this is also true for the normal fork() child )

+3
source share

All Articles