Copy-on-Write(CoW)는 여러 프로세스가 같은 물리 페이지를 공유할 때,
쓰기 시점에만 페이지를 복사하여 분리하는 메커니즘이다.

Copy-on-Write가 생긴 이유는 아주 단순하다.
”공유는 유지하고, 쓰기는 격리하기” 위해서이다.

Memory-Mapped FilePage Cache 구조에서는,
여러 프로세스 (Process)의 가상 주소 (Virtual Memory)가
같은 Page Cache 페이지를 가리킬 수 있다.

이로 인해, 두 프로세스가 같은 Page Cache에 접근하는 일도 발생할 것이다.

이때 Page Cache를 통해 읽는 페이지는 실제 파일의 내용이며,
동시에 여러 프로세스가 읽고 있는 상태이다.

만약 이 상황에서 한 프로세스가 해당 페이지에 쓰기를 시도하면 어떻게 될까?

이때, 커널은 다음 2가지 규칙을 지켜야 한다:

  1. Page Cache의 내용은 파일의 실제 내용과 일치해야 한다.
  2. 한 프로세스의 쓰기가 다른 프로세스나 파일을 오염시켜선 안된다.

만약 프로세스의 해당 페이지에 대한 쓰기를 허용하면 2번 규칙이 깨지고,
그렇다고 쓰기를 제한하면 프로세스는 파일에 쓰기를 할 수 없는 상황이 발생한다.

이 문제를 해결하기 위해, Copy-on-Write라는 개념이 생겨났다.
즉, “읽는 것은 공유하고, 쓰는 것은 따로 하기”가 Copy-on-Write의 의미인 것이다.

이제 Copy-on-Write (CoW)가 어떻게 동작하는지 살펴보자.

Memory-Mapped File (MMF)을 생성할 때, 2가지의 flag가 CoW의 사용 여부를 결정한다.

  1. MAP_SHARED: “데이터의 변경 내용을 공유하겠다” 라는 뜻으로,
    이 flag를 가지고 MMF를 생성할 경우, 직접 쓰기가 가능하다.

  2. MAP_PRIVATE: “데이터의 변경 내용을 공유하지 않겠다” 라는 뜻으로,
    이 flag를 가지고 MMF를 생성할 경우, 쓰기 시도 시 CoW가 발생한다.

CoW의 동작을 알아보기 위해, 프로세스 A와 B에서
MMF를 MAP_PRIVATE로 매핑했다고 가정해보자.

이때 커널은 파일의 Page Cache 페이지를 읽기 전용으로
각 프로세스의 PTE (Page Table Entry (PTE))에 매핑하게 된다.

이 상황에서 프로세스 A가 이 페이지에 쓰기를 시도하면

  1. CPU는 write-protect violation에 의해 page fault(Page Fault)를 발생시킨다.
  2. 커널은 해당 예외를 처리하기 위해 page fault handler로 들어간다.
  3. 커널은 해당 페이지가 CoW의 대상임을 인식한다.

인식한 후, 커널은 아래의 과정을 수행한다:

  1. 새로운 물리 페이지를 할당한다.
  2. 원래 Page Cache 페이지의 내용을 복사한다.
  3. 프로세스 A의 PTE를 새로 할당한 물리 페이지로 교체한다.
  4. 쓰기 권한을 부여한다.

이렇게 되면,

  • 프로세스 A에서는 Page Cache의 Copy가 읽기/쓰기 권한으로 매핑되어 있는 상태
  • 프로세스 B에서는 Page Cache가 읽기 전용으로 매핑되어 있는 상태

가 된다.

즉, A는 Page Cache의 Copy 페이지에 쓰고, B는 원본 파일을 그대로 보는 상태가 된다.

이렇게 Copy-on-Write를 통해 커널은

  1. 이 Page Cache는 파일의 내용을 대표해야 한다.
  2. 한 프로세스의 쓰기가 다른 프로세스나 파일을 오염시켜선 안된다.

의 규칙을 지킬 수 있게 된다.