Pintos Project 3 (Virutal Memory)를 구현하다가 이상한 점을 발견했다.
ELF 파일을 로딩하는 load_segment는 항상 VM_ANON 타입으로 페이지를 만든다.
// process.c - load_segment()
if (!vm_alloc_page_with_initializer(VM_ANON, upage, // ← 왜 ANON?
writable, lazy_load_segment, aux))
return false;
어차피 파일에서 읽어오는 건데, 굳이 ANON 타입으로 페이지를 만들어서 파일 정보를 끊어버리는 구조가 이해가 되지 않았다.
evict될 때 원본 파일에서 다시 읽으면 되는데, 왜 swap에 저장할까?
Pintos의 ELF 로딩과 mmap 구조
Pintos에서 ELF 로딩과 mmap 구조를 살펴보면 다음과 같다.
ELF 로딩 (load_segment)
→ VM_ANON 페이지 생성 ← 파일 연결 끊김
→ lazy_load_segment()에서 파일 1회 읽기
→ 이후 evict 시 → swap 영역으로
mmap syscall
→ VM_FILE 페이지 생성 ← 파일 연결 유지
→ evict 시 → 원본 파일로 flush(반영) 또는 discard
load_segment는 lazy loading을 위해 파일 정보를 aux 인자로 넘기긴 하지만, 페이지 타입 자체는 VM_ANON이다.
-> 즉, 최초 1회 파일에서 읽은 뒤 파일과의 연결은 완전히 끊어진다.
반면, mmap은 파일과의 연결을 끊지 않는다.
페이지 구조체 안에 원본 파일 정보(file, offset)가 계속 유지된다.
실제 OS는 어떻게 할까?
Linux 같은 실제 OS는 ELF 로딩에도 file-backed 페이지를 사용한다.
그리고, 세그먼트 종류에 따라 다르게 처리한다.
코드 세그먼트(.text, .rodata(read-only data))
- 읽기 전용이므로 절대 수정되지 않는다.
- evict 시 그냥 버린다.
- 다시 필요하면 원본 ELF 파일에서 재로드한다.
- swap 공간을 전혀 사용하지 않는다.
데이터 세그먼트(.data, .bss)
데이터 세그먼트는 수정이 가능하지만, 원본 파일에는 반영하면 안된다!
예를 들어, 아래와 같은 코드가 있다고 하자.
int a = 10;
a = 5;
만약 이 페이지가 file-backed로 연결되어 있다면, a = 5 라는 수정이 원본 ELF 파일에 그대로 반영된다. 즉, 다음에 이 프로그램을 실행하면 a의 초기값이 이미 5가 되어 있다는 뜻이다. 당연히 이래선 안 된다.
그래서 COW(Copy-on-Write) 방식으로 처리한다.
1. 처음에는 file-backed 페이지로 매핑.
2. 프로세스가 해당 페이지에 쓰기를 시도하는 순간 page fault 발생.
3. 커널이 해당 페이지만 복사해서 private ANON 페이지로 전환.
4. 이후 수정은 복사본에만 반영, 원본 파일은 그대로 유지.
✅ Private ANON 페이지
특정 프로세스만 소유하는 익명 메모리 페이지를 뜻한다.
- Anonymous : 백업 파일이 없다. evict 시 swap 영역으로 저장된다.
- Private : 다른 프로세스와 공유되지 않는다. 이 프로세스만의 독립적인 복사본이다.
Pintos가 ELF 로딩시 ANON 페이지로 할당하는 이유
구현 단순화 때문이다.
COW를 제대로 구현하려면 꽤 복잡하기 때문이다.
- 물리 프레임 참조 카운팅 : fork() 시 부모와 자식이 같은 물리 프레임을 공유한다. write fault 발생 시 새 프레임에 복사해야 하는데, 만약 이미 아무도 공유하지 않는 ANON 프레임이라면 굳이 복사할 필요 없이 writable로만 바꾸면 된다. 이 판단을 위해 모든 프레임마다 몇 개의 프로세스가 공유 중인지 refcount를 관리해야 한다.
- COW 플래그 구분 : fork() 시 writable 페이지를 read-only로 표시하는데, "진짜 read-only"인지 "COW라서 임시로 read-only인지"를 구분하는 플래그가 따로 필요하다. 없으면 write fault 시 segfault를 낼지, 복사를 할지 알 수 없다.
- dirty 추적 : file-backed 페이지는 evict 시 수정 여부에 따라 파일에서 재로드할지, swap으로 보낼지 판단해야 한다.
이 과정을 전부 구현하는 대신, Pintos는 처음부터 전부 ANON 페이지로 만든다.
하지만, 이로 인해 생기는 단점도 존재한다.
코드 세그먼트처럼 절대 수정되지 않는 페이지도 evict되면 swap에 저장한다.
이렇게 되면, 원본 파일에 이미 내용이 있는데도 swap 공간을 낭비하게 된다.