복사를 실제로 쓰기 전까지 미루는 기법.
fork에서 어떻게 동작하나
-
① fork 시 자식에게 페이지를 복사해 주지 않는다
- 대신 부모와 같은 물리 프레임을 가리키게 하고, 양쪽 모두 '읽기 전용' 으로 표시한다
-
② 둘 중 하나가 쓰기를 시도한다
- 읽기 전용 페이지에 쓰기 → 페이지 폴트 발생
-
③ 커널이 그때 그 페이지만 복사해 준다
- 쓰려던 쪽이 자기 사본을 갖는다
-
결과: 읽기만 하는 페이지는 영영 복사되지 않는다
왜 큰 이득인가
fork 직후 대부분은 exec 로 다른 프로그램이 된다
- 복사해 봐야 곧바로 버려진다
- CoW 덕분에 그 낭비가 사라진다
메모리 4GB 를 쓰는 프로세스가 fork 해도
- 즉시 필요한 건 페이지 테이블 복사뿐 (수 MB)
- fork 가 거의 즉시 끝난다
어디에 더 쓰이나
- 공유 라이브러리 — 여러 프로세스가 같은 libc 프레임을 공유한다
- 실행 파일의 코드 영역 — 읽기 전용이라 애초에 복사할 일이 없다
- 컨테이너 이미지 레이어 — 같은 베이스 이미지를 여러 컨테이너가 공유한다
- 스냅샷 · 파일 시스템(Btrfs · ZFS)
- 언어 런타임의 문자열·컬렉션 최적화
함정 — 나중에 터진다
fork 시점에는 메모리가 충분해 보인다
- 이후 자식이 페이지를 하나씩 건드릴 때마다 실제 복사가 일어난다
- 어느 순간 메모리가 부족해진다
"fork 는 성공했는데 한참 뒤에 OOM" 이 이 구조에서 나온다
sysctl vm.overcommit_memory
# 0 휴리스틱(기본) · 1 항상 허용 · 2 엄격하게 제한
# Redis 처럼 fork 로 스냅샷을 뜨는 프로그램이
# "overcommit 을 켜라" 고 경고하는 이유가 이것이다
관측
cat /proc/<pid>/smaps_rollup
# Rss: 실제 물리 메모리 사용량
# Pss: 공유분을 나눠 계산한 값 (프로세스별 실사용에 가깝다)
# Private_Dirty: 이 프로세스만의 수정된 페이지 = CoW 로 실제 복사된 부분
여러 프로세스의 RSS를 단순히 더하면 실제보다 훨씬 크게 나온다 — 공유분이 중복 계산되기 때문이다. PSS를 봐야 한다.
면접 함정
- ❌ "fork는 메모리를 두 배 쓴다" → CoW 덕분에 쓰기가 일어난 만큼만 는다.
- ❌ "CoW는 항상 이득" → 쓰기가 많으면 폴트 비용만 더한다. 읽기 위주일 때 이득이다.
언어 런타임에서도 쓴다
-
문자열 — 복사하지 않고 참조를 공유하다 수정 시점에 복사
-
컬렉션 — 불변 컬렉션의 구조 공유 (persistent data structure)
-
스냅샷 — 배열을 복사하는 대신 참조만 넘기고 쓰기 때 분기
-
같은 발상이 파일 시스템 · 컨테이너 이미지 · DB 의 MVCC 까지 관통한다
fork 후 exec 를 안 하면
멀티스레드 프로세스에서 fork 하면 자식은 '호출한 스레드 하나' 만 갖는다
- 다른 스레드가 쥐고 있던 락이 영원히 잠긴 채로 복제된다
- 자식이 그 락을 요구하면 데드락
그래서 fork 직후에는 async-signal-safe 한 일만 하고 곧바로 exec 하는 것이 원칙이다
측정
# 두 프로세스가 실제로 얼마나 공유하는지
grep -E 'Rss|Pss|Shared|Private' /proc/<pid>/smaps_rollup
# Shared_Clean 공유 중이고 수정 안 됨 (CoW 가 아직 안 일어남)
# Private_Dirty 이 프로세스만의 수정분 (CoW 로 실제 복사된 양)