COW 는 Copy on Write 의 약자입니다.
간단히 설명하면 시스템에서 fork 시에 모든 것을 생성하지 않고, 
실제로 쓰기 동작이 일어날때 새로운 영역을 할당받는 다는 것입니다.
이게 어떤 의미이며 어떻게 동작할수 있는지 살펴 보겠습니다.
이 아이디어는 fork 시에 부모가 갖고 있는 모든 것을 복제하기에는 시스템 자원의 소모가 크고 
참조만 이루어지는 공간의 경우 복제할 필요가 없기 대문에 공유가 가능하다는 것입니다.
최초 fork 시에 부모로부터 상속받은 data, stack, heap 의 주소를 포함한 자료만을 복사해 오게 됩니다. 
이후 실제 쓰기 동작이 일어날때 read-only 로 표시된 영역에 접근하기 때문에 mmu 에서 에러가 발생하고 
그때 새로운 영역을 할당해주는 방식을 택하게 됩니다.
ARM MMU 시스템은 memory 에 대한 access control 을 할수 있으며 domain 에 따라 조절할수 있습니다.
이 기능을 이용해서 COW 를 구현하였는데 실제 쓰기 동작이 일어날때 비로소 새로운 영역을 할당 받게 됩니다.
MMU table 을 분석했던 지난번 응용을 이용하여 어떻게 달라지는지 확인해 보겠습니다.

커널에서 mmu table 은 task_struct 에서 가리키는 mm_struct 멤버중에 mm 이 있습니다.
mm_struct 에는 pgd_t *pgd 가 있는데 이것이 해당 task 의 mmu table 을 가리킵니다.
이제 어플리케이션에서 fork 를 시키는데 fork 후에 execl 이 실행되기 전과 실행된 후의 pid 를 살펴보고 mmu table 에서 
applcation 이 차지하고 있는 메모리 공간을 살펴 보도록 하겠습니다.
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <fcntl.h>
 6 #include <dirent.h>
 7 #include <unistd.h>
 8 #include <assert.h>
 9 #include <sys/vfs.h>
10 #include <sys/wait.h>
11 
12 static pthread_t    threadid;
13 
14 void main(void)
15 {
16     int ret, status;
17 
18     pid_t forkid;
19     char syscmd[128];
20     
21     memset(syscmd, 0, sizeof(syscmd));
22     forkid=fork();  
23     if( forkid<0 ) {
24         printf(" fork error\n");
25     } else if(forkid==0) {
26         printf("sleep at fork\n");
27         sleep(60); 
28             
29         sprintf(syscmd, "/mnt/nfs/appsleep.out");
30         execl(syscmd, NULL);
31         
32         printf("sleep at fork\n");
33         sleep(60);
34         
35         exit(1);
36     } else if(forkid>0) {
37         printf(" success start %d\n", forkid);
38     }   
39         
40     while(1) {
41         sleep(10);
42     
43         if( waitpid(forkid, &status, WNOHANG)<0 ) {
44             if( WIFEXITED(status) ) {
45                 printf(" WIFEXITED=0x%x\n", WEXITSTATUS(status));
46             } else if( WIFSIGNALED(status) ) {
47                 printf(" WIFSIGNALED=0x%x\n", WTERMSIG(status));
48             }   else if( WIFSTOPPED(status) ) {
49                 printf(" WIFSTOPPED=0x%x", WSTOPSIG(status));
50             }   
51             printf("process %s expired\n", syscmd);
52             return 1;
53         }       
54             
55     }       
56 }   

샘플 어필리케이션에서는 fork 를 하는데 fork 직후에 아직 execl 을 실행하기 전에 지연을 갖고 그 시간동안에 mmu 정보를 얻습니다.
그리고 나서 execl 을 수행후에 mmu 정보를 얻어 그 결과를 확인합니다.
mmu 정보는 지난시간에 얻는 것중에 app 영역에 해당하는 0x10000000 이전번지대역만 보여 드리겠습니다.
PAGE SEARCH START
=================
*** a.out [1395] mm c4b06ae0 pgd c30b8000
L1 entry 0xc30b8000
0x00000000 0x55847031  page
L2 entry 0xc5847000
0x00000008 0x5256782E 0x00008000 0x52567000
0x00000010 0x524DF83F 0x00010000 0x524DF000

*** a.out [1396] mm c4b06660 pgd c30a8000
L1 entry 0xc30a8000
0x00000000 0x5589A031  page
L2 entry 0xc589a000
0x00000008 0x5256782E 0x00008000 0x52567000
0x00000010 0x524C883F 0x00010000 0x524C8000


*** a.out [1395] mm c4b06ae0 pgd c30b8000
L1 entry 0xc30b8000
0x00000000 0x55847031  page
L2 entry 0xc5847000
0x00000008 0x5256782E 0x00008000 0x52567000
0x00000010 0x524DF83F 0x00010000 0x524DF000

*** appsleep.out [1396] mm c4b06360 pgd c3424000
L1 entry 0xc3424000
0x00000000 0x53430031  page
L2 entry 0xc3430000
0x00000008 0x525A582E 0x00008000 0x525A5000
0x00000010 0x525A083F 0x00010000 0x525A0000

네개의 정보가 보이는데 a.out 이라는 pid 1395 의 mmu table 과 pid 1396 의 mmu table 은 fork 된 시점이기 때문에
이미 복사되어 다른곳을 가리키고 있습니다만... 아직 모든 것을 원래의 실행어플과 동일하기 때문에 물리적으로는 동일한 위치를 가리키고 있습니다.
pid, pgd 가 모두 틀린데도 불구하고 0x00008000 은 동일한 물리번지를 가리킵니다.
그리고 중복되지 않은 공간은 0x00010000 은 위치는 서로 다른 공간을 가리키고 있습니다.
자 그럼 아래의 두개쌍을 살펴보면 이제 execl 이 실행되어 완전히 독립적인 어플리케이션이 동작하게 되고 별도의 공간을 갖게 됩니다.
실제로 코드중간에 mmu 이런 COW 의 동작이 발생하는 포인트를 정확히 잡아내기는 매우 어렵습니다.
여기서 다 보여드리진 못했지만 data 와 heap 영역 또한 생성된 어플리케이션이 사용하는 heap 이나 메모리가 없을 경우 code 를 제외한 heap 영역등
모든 영역은 부모 프로세스와 동일한 mmu table 을 가지고 있습니다.
실제 사용하는 영역에서만 메모리를 새롭게 할당함으로써 시스템의 성능과 효율을 높일수 있습니다.
다음번에는 fork 후에 프로세스 공간에서 메모리 할당과 해제시에 mmu table 이 바뀌는 것에 대한 것을 확인해 보도록 하겠습니다.