Copy-on-Write(CoW)는 여러 프로세스가 같은 물리 페이지를 공유할 때,
쓰기 시점에만 페이지를 복사하여 분리하는 메커니즘이다.
Copy-on-Write가 생긴 이유는 아주 단순하다.
”공유는 유지하고, 쓰기는 격리하기” 위해서이다.
Memory-Mapped File과 Page Cache 구조에서는,
여러 프로세스 (Process)의 가상 주소 (Virtual Memory)가
같은 Page Cache 페이지를 가리킬 수 있다.
이로 인해, 두 프로세스가 같은 Page Cache에 접근하는 일도 발생할 것이다.
이때 Page Cache를 통해 읽는 페이지는 실제 파일의 내용이며,
동시에 여러 프로세스가 읽고 있는 상태이다.
만약 이 상황에서 한 프로세스가 해당 페이지에 쓰기를 시도하면 어떻게 될까?
이때, 커널은 다음 2가지 규칙을 지켜야 한다:
- Page Cache의 내용은 파일의 실제 내용과 일치해야 한다.
- 한 프로세스의 쓰기가 다른 프로세스나 파일을 오염시켜선 안된다.
만약 프로세스의 해당 페이지에 대한 쓰기를 허용하면 2번 규칙이 깨지고,
그렇다고 쓰기를 제한하면 프로세스는 파일에 쓰기를 할 수 없는 상황이 발생한다.
이 문제를 해결하기 위해, Copy-on-Write라는 개념이 생겨났다.
즉, “읽는 것은 공유하고, 쓰는 것은 따로 하기”가 Copy-on-Write의 의미인 것이다.
이제 Copy-on-Write (CoW)가 어떻게 동작하는지 살펴보자.
Memory-Mapped File (MMF)을 생성할 때, 2가지의 flag가 CoW의 사용 여부를 결정한다.
-
MAP_SHARED: “데이터의 변경 내용을 공유하겠다” 라는 뜻으로,
이 flag를 가지고 MMF를 생성할 경우, 직접 쓰기가 가능하다. -
MAP_PRIVATE: “데이터의 변경 내용을 공유하지 않겠다” 라는 뜻으로,
이 flag를 가지고 MMF를 생성할 경우, 쓰기 시도 시 CoW가 발생한다.
CoW의 동작을 알아보기 위해, 프로세스 A와 B에서
MMF를 MAP_PRIVATE로 매핑했다고 가정해보자.
이때 커널은 파일의 Page Cache 페이지를 읽기 전용으로
각 프로세스의 PTE (Page Table Entry (PTE))에 매핑하게 된다.
이 상황에서 프로세스 A가 이 페이지에 쓰기를 시도하면
- CPU는 write-protect violation에 의해 page fault(Page Fault)를 발생시킨다.
- 커널은 해당 예외를 처리하기 위해 page fault handler로 들어간다.
- 커널은 해당 페이지가 CoW의 대상임을 인식한다.
인식한 후, 커널은 아래의 과정을 수행한다:
- 새로운 물리 페이지를 할당한다.
- 원래 Page Cache 페이지의 내용을 복사한다.
- 프로세스 A의 PTE를 새로 할당한 물리 페이지로 교체한다.
- 쓰기 권한을 부여한다.
이렇게 되면,
- 프로세스 A에서는 Page Cache의 Copy가 읽기/쓰기 권한으로 매핑되어 있는 상태
- 프로세스 B에서는 Page Cache가 읽기 전용으로 매핑되어 있는 상태
가 된다.
즉, A는 Page Cache의 Copy 페이지에 쓰고, B는 원본 파일을 그대로 보는 상태가 된다.
이렇게 Copy-on-Write를 통해 커널은
- 이 Page Cache는 파일의 내용을 대표해야 한다.
- 한 프로세스의 쓰기가 다른 프로세스나 파일을 오염시켜선 안된다.
의 규칙을 지킬 수 있게 된다.