QUESTION
MAP_PRIVATE로 바꾼 내용은 왜 원본 파일에 기록되지 않는가?
MAP_SHARED mapping의 write fault는 page cache page를 writable하게 연결하고 dirty로 표시해 다른 shared mapper와 file I/O에 보이게 한다. MAP_PRIVATE mapping은 처음에는 file page를 읽어도 write 시 anonymous private copy를 만든다.
msync는 mapping 종류와 filesystem 정책 안에서 dirty page writeback을 요청한다. CPU cache coherence, 다른 process에 대한 가시성, storage durability는 서로 다른 층의 보장이다.
STRUCTURE
구조 그림
Process A · MAP_SHARED
- VA 0x7000
- write 'S'
- PTE → page cache
File page cache
- inode index 0
- dirty folio
- msync/writeback
Process B · MAP_PRIVATE
- read → page cache
- write fault
- PTE → anonymous page
Private page B
- content 'P'
- Private_Dirty
- file에는 미반영
MAP_SHARED write는 page cache page를 dirty하게 하고, MAP_PRIVATE write는 process 전용 anonymous page를 만든다.
CALL PATH
호출 흐름
mapping flag는 page fault 뒤 쓰기가 어느 backing object에 귀속되는지를 결정한다. 주소가 같은지보다 page cache와 anonymous page 중 어느 쪽이 dirty해지는지 본다.
함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.
SOURCE COORDINATES
Linux 6.18.37 LTS 소스 위치
glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.
| 파일 | 함수·구조체 | 여기서 볼 것 |
|---|---|---|
| mm/mmap.c | mmap_region() | VMA에 file과 sharing flag 연결 |
| mm/filemap.c | filemap_fault(), filemap_map_pages() | file page cache를 PTE에 연결 |
| mm/memory.c | do_wp_page(), wp_page_copy() | private mapping write에서 COW page 생성 |
COMPLETE PROGRAM
실행 예제 원본
아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.
cc -std=c17 -Wall -Wextra -O2 mmap_file.c -o mmap_file01#define _POSIX_C_SOURCE 200809L
02#include <fcntl.h>
03#include <stdio.h>
04#include <string.h>
05#include <sys/mman.h>
06#include <unistd.h>
07
08int main(int argc, char **argv)
09{
10 if (argc != 2)
11 return 2;
12 long page = sysconf(_SC_PAGESIZE);
13 int fd = open(argv[1], O_RDWR | O_CREAT | O_CLOEXEC, 0644);
14 if (fd < 0 || ftruncate(fd, page) < 0)
15 return 1;
16
17 char *shared = mmap(NULL, (size_t)page, PROT_READ | PROT_WRITE,
18 MAP_SHARED, fd, 0);
19 char *private = mmap(NULL, (size_t)page, PROT_READ | PROT_WRITE,
20 MAP_PRIVATE, fd, 0);
21 if (shared == MAP_FAILED || private == MAP_FAILED)
22 return 1;
23
24 memcpy(shared, "shared", 7);
25 if (msync(shared, (size_t)page, MS_SYNC) < 0)
26 return 1;
27 memcpy(private, "private", 8);
28 printf("shared='%s' private='%s'\n", shared, private);
29 munmap(private, (size_t)page);
30 munmap(shared, (size_t)page);
31 close(fd);
32 return 0;
33}
CODE NOTES
코드 조각별 설명
ftruncate(fd, page)mapping할 file 길이를 먼저 확보한다. EOF를 넘는 page에 접근하면 SIGBUS가 발생할 수 있다.
MAP_SHARED, fd, 0첫 page의 page cache backing을 shared mapping으로 연결한다. 쓰기는 같은 inode page를 보는 다른 mapping에 보인다.
MAP_PRIVATE, fd, 0read는 file page를 사용할 수 있지만 write fault 뒤 private anonymous copy로 분기한다.
msync(sharedshared dirty range의 writeback 완료를 요청한다. directory entry durability나 storage device 전원 손실 보호까지 자동 보장하지 않는다.
memcpy(privateprivate mapping 내용만 바뀌고 file과 shared mapping에는 'private' 문자열이 기록되지 않는다.
DETAILS
세부 동작
file size와 mapping size를 맞춘다
mmap은 page 단위 range를 만들 수 있어도 file EOF 뒤 접근 가능한 data를 만들어 주지 않는다. 마지막 partial page의 EOF 뒤 byte는 zero로 보일 수 있지만 다음 page 접근은 SIGBUS 대상이다.
다른 process가 file을 truncate하면 이미 mapping된 process도 이후 접근에서 SIGBUS를 받을 수 있다.
visibility와 synchronization을 분리한다
MAP_SHARED page의 CPU store는 cache-coherent system에서 다른 process가 볼 수 있지만 data structure consistency는 보장하지 않는다. atomic, mutex, sequence counter 같은 protocol이 필요하다.
msync는 thread memory ordering primitive가 아니다.
mmap I/O도 page fault와 writeback 비용을 낸다
read/write syscall 횟수가 줄어도 최초 접근 fault, dirty throttling, reclaim, filesystem writeback이 사라지지 않는다. latency는 syscall count가 아니라 fault와 storage interaction까지 측정한다.
random access와 zero-copy parsing에 유리할 수 있지만 truncate, SIGBUS, address space pressure 처리 비용이 생긴다.
OBJECTS
객체와 수명
| 대상 | 언제 생기고 없어지는가 | 확인할 값 |
|---|---|---|
file-backed VMA | mmap에서 inode address_space와 연결되고 munmap까지 유지된다 | vm_pgoff, shared/private flag |
page cache folio | file read/fault에서 생기고 dirty/writeback/reclaim을 거친다 | index, dirty, writeback |
private COW page | MAP_PRIVATE write fault에서 생기고 process mm에만 속한다 | anonymous, dirty, RSS |
FAILURE PATH
실패 조건과 오해하기 쉬운 부분
| 겉으로 보이는 현상 | 실제 원인 후보 | 확인 방법 |
|---|---|---|
| SIGBUS | mapping 중 file truncate 또는 EOF 밖 접근 | si_addr와 current file size |
| private 변경이 파일에 없음 | MAP_PRIVATE의 정상 COW 동작 | smaps Anonymous/Private_Dirty |
| msync가 EINVAL | page-aligned address/range 또는 flag 오류 | mapping boundary와 page size |
LAB
직접 확인
- 실행 후 shared.dat를 hexdump해 shared만 기록됐는지 확인한다.
- 별도 process가 file을 MAP_SHARED로 mapping해 msync 전후 가시성과 storage 반영을 구분한다.
- mapping 중 다른 process가 ftruncate(0)를 호출하게 해 SIGBUS handler와 복구 가능 범위를 확인한다.
./mmap_file shared.datstrace -e trace=openat,ftruncate,mmap,msync,munmap,close ./mmap_file shared.datPRIMARY REFERENCES