← System Programming DUJINLABS.COM

Virtual Memory · Linux userspace / kernel ABI

mmap MAP_SHARED와 MAP_PRIVATE

같은 file page를 공유하는 mapping과 copy-on-write private mapping의 dirty/writeback 경로를 실제 두 process 예제로 비교합니다.

Series
18 / 37
Build
cc -std=c17 -Wall -Wextra -O2 mmap_file.c -o mmap_file
Run
./mmap_file shared.dat
Kernel
Linux 6.18.37 LTS

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는 서로 다른 층의 보장이다.

구조 그림

그림 1. 같은 file page에서 갈라지는 shared write와 private COW

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를 만든다.

호출 흐름

그림 2. 사용자 코드에서 관찰 가능한 결과까지
open file inode/page cache
mmap shared 또는 private VMA
write fault page cache/COW 분기
msync writeback 요청
reader 가시성 확인

mapping flag는 page fault 뒤 쓰기가 어느 backing object에 귀속되는지를 결정한다. 주소가 같은지보다 page cache와 anonymous page 중 어느 쪽이 dirty해지는지 본다.

그림 3. 커널 내부에서 지나가는 주요 지점
mmap_region vm_file 연결
filemap_fault page cache lookup
do_wp_page shared/private 분기
set_page_dirty shared write
writeback filesystem I/O

함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.

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 생성

실행 예제 원본

아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.

빌드cc -std=c17 -Wall -Wextra -O2 mmap_file.c -o mmap_file
01#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}

코드 조각별 설명

실제 코드 14행ftruncate(fd, page)

mapping할 file 길이를 먼저 확보한다. EOF를 넘는 page에 접근하면 SIGBUS가 발생할 수 있다.

실제 코드 18행MAP_SHARED, fd, 0

첫 page의 page cache backing을 shared mapping으로 연결한다. 쓰기는 같은 inode page를 보는 다른 mapping에 보인다.

실제 코드 20행MAP_PRIVATE, fd, 0

read는 file page를 사용할 수 있지만 write fault 뒤 private anonymous copy로 분기한다.

실제 코드 25행msync(shared

shared dirty range의 writeback 완료를 요청한다. directory entry durability나 storage device 전원 손실 보호까지 자동 보장하지 않는다.

실제 코드 27행memcpy(private

private mapping 내용만 바뀌고 file과 shared mapping에는 'private' 문자열이 기록되지 않는다.

세부 동작

01

file size와 mapping size를 맞춘다

mmap은 page 단위 range를 만들 수 있어도 file EOF 뒤 접근 가능한 data를 만들어 주지 않는다. 마지막 partial page의 EOF 뒤 byte는 zero로 보일 수 있지만 다음 page 접근은 SIGBUS 대상이다.

다른 process가 file을 truncate하면 이미 mapping된 process도 이후 접근에서 SIGBUS를 받을 수 있다.

02

visibility와 synchronization을 분리한다

MAP_SHARED page의 CPU store는 cache-coherent system에서 다른 process가 볼 수 있지만 data structure consistency는 보장하지 않는다. atomic, mutex, sequence counter 같은 protocol이 필요하다.

msync는 thread memory ordering primitive가 아니다.

03

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 처리 비용이 생긴다.

객체와 수명

대상언제 생기고 없어지는가확인할 값
file-backed VMAmmap에서 inode address_space와 연결되고 munmap까지 유지된다vm_pgoff, shared/private flag
page cache foliofile read/fault에서 생기고 dirty/writeback/reclaim을 거친다index, dirty, writeback
private COW pageMAP_PRIVATE write fault에서 생기고 process mm에만 속한다anonymous, dirty, RSS

실패 조건과 오해하기 쉬운 부분

겉으로 보이는 현상실제 원인 후보확인 방법
SIGBUSmapping 중 file truncate 또는 EOF 밖 접근si_addr와 current file size
private 변경이 파일에 없음MAP_PRIVATE의 정상 COW 동작smaps Anonymous/Private_Dirty
msync가 EINVALpage-aligned address/range 또는 flag 오류mapping boundary와 page size

직접 확인

  1. 실행 후 shared.dat를 hexdump해 shared만 기록됐는지 확인한다.
  2. 별도 process가 file을 MAP_SHARED로 mapping해 msync 전후 가시성과 storage 반영을 구분한다.
  3. mapping 중 다른 process가 ftruncate(0)를 호출하게 해 SIGBUS handler와 복구 가능 범위를 확인한다.
실행./mmap_file shared.dat
추적strace -e trace=openat,ftruncate,mmap,msync,munmap,close ./mmap_file shared.dat

원문