문제 상황

기존 fork 시스템 호출은 부모 프로세스의 메모리 공간을 자식 프로세스에 즉시 복사합니다. 이는 복사해야 할 공간의 크기가 클 경우, 많은 시간이 소요됩니다.

해결 방법

복사를 지연시켜, 복사가 되는 횟수를 줄여야 합니다. 대개 fork 호출 뒤에는 exec이 호출되며, 자식 프로세스는 프로그램을 적재하기 위해 새로운 페이지가 할당되므로 부모의 페이지가 필요없습니다. 따라서, copy-on-write 전략을 사용해 fork 호출 시에는 복사가 일어나지 않고, 자식 프로세스에서 최초로 write를 하는 경우에 복사를 하는 지연 복사를 하면 됩니다.

코드

implement copy-on-write fork · plming/xv6-labs-2022@7fad4ac

고민한 부분

언제 복사를 할 것인가?

fork 호출 시, 부모와 자식의 PTE(page table entry)에서 write bit를 unset합니다. 따라서 write 시도 시 trap handler가 실행되며, 이때 부모/자식 누가 쓰기를 하든 복사를 하면 됩니다.

이때, 기존 구현의 read-only page 기능을 고려하여, PTE의 RSW 비트를 활용하여 PTE_C 비트를 새로 정의합니다. 이 비트는 fork 호출 시, 원래 writeable한 페이지인지를 나타냅니다. 따라서 PTE_WPTE_C가 모두 꺼진 비트는 read-only 페이지입니다.

PTE_R PTE_W PTE_C 설명
1 0 0 읽기 전용 페이지. 쓰기 시 fault 발생, 프로세스 종료
1 0 1 COW 공유 상태. 아직 복사되지 않은 페이지
1 1 0 일반 페이지. COW가 아닌 정상 쓰기 가능 상태

언제 메모리를 회수할 것인가?

참조 카운팅(reference counting) 전략을 사용합니다.

모든 물리 페이지는 커널의 kalloc, kfree 함수를 통해 할당 및 해제되며, 커널 공간 내 페이지 단위의 참조 횟수를 저장하는 배열을 두어 관리합니다.

kalloc 호출 시에는 해당 페이지의 참조 카운트를 1로 초기화하고, fork 호출 시 공유된 페이지의 참조 카운트를 1 증가시킵니다.

kfree 호출 시에는 참조 카운트를 1 감소시키고, 0이 될 경우에만 메모리를 해제합니다. 이때 배열의 인덱스는 물리 주소를 페이지 크기로 나눈 값을 사용해, 인덱스 변환을 효과적으로 처리했습니다.