QUESTION
같은 physical page를 mapping하면 synchronization 없이 구조체도 안전하게 공유되는가?
shared memory는 byte를 같은 backing page에서 보이게 할 뿐, 여러 CPU의 read/write 순서와 object consistency를 자동으로 정하지 않는다. writer가 payload를 완성하기 전에 ready flag가 보이면 reader는 절반만 갱신된 구조를 읽을 수 있다.
process-shared sem_t, pthread mutex/condvar, futex, atomic protocol 중 하나로 publish와 consume 순서를 만든다. synchronization object 자체도 MAP_SHARED range 안에 있고 pshared 속성으로 초기화돼야 한다.
STRUCTURE
구조 그림
Process A
- VA 0x7f10…
- message write
- sem_post release
Shared pages
- offset 0: sem_t
- offset 64: message
- tmpfs/memfd backing
Process B
- VA 0x6a20…
- sem_wait acquire
- message read
Naming / lifetime
- shm_open name
- fd references
- shm_unlink 후 mapping 유지
process마다 mapping 주소는 달라도 backing page는 같다. raw pointer 대신 offset을 저장하고 semaphore로 publish 순서를 맞춘다.
CALL PATH
호출 흐름
shared data 수명, 이름 수명, fd 수명, mapping 수명, synchronization object 수명을 각각 누가 끝내는지 정한다.
함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.
SOURCE COORDINATES
Linux 6.18.37 LTS 소스 위치
glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.
| 파일 | 함수·구조체 | 여기서 볼 것 |
|---|---|---|
| mm/shmem.c | shmem_file_setup(), shmem_mmap() | tmpfs/shared memory backing과 page fault |
| kernel/futex/waitwake.c | futex_wait(), futex_wake() | semaphore contention 시 sleep/wakeup 기반 |
| mm/mmap.c | mmap_region() | 같은 file을 MAP_SHARED VMA로 연결 |
COMPLETE PROGRAM
실행 예제 원본
아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.
cc -std=c17 -Wall -Wextra -O2 -pthread shared_sem.c -o shared_sem01#define _DEFAULT_SOURCE
02#include <errno.h>
03#include <fcntl.h>
04#include <semaphore.h>
05#include <stdio.h>
06#include <string.h>
07#include <sys/mman.h>
08#include <sys/wait.h>
09#include <unistd.h>
10
11struct shared_data {
12 sem_t ready;
13 char message[128];
14};
15
16int main(void)
17{
18 struct shared_data *data = mmap(NULL, sizeof(*data), PROT_READ | PROT_WRITE,
19 MAP_SHARED | MAP_ANONYMOUS, -1, 0);
20 if (data == MAP_FAILED || sem_init(&data->ready, 1, 0) < 0)
21 return 1;
22 pid_t pid = fork();
23 if (pid == 0) {
24 while (sem_wait(&data->ready) < 0)
25 if (errno != EINTR)
26 _exit(2);
27 dprintf(STDOUT_FILENO, "child read: %s\n", data->message);
28 _exit(0);
29 }
30
31 strcpy(data->message, "published through shared memory");
32 sem_post(&data->ready);
33 waitpid(pid, NULL, 0);
34 sem_destroy(&data->ready);
35 return munmap(data, sizeof(*data)) != 0;
36}
CODE NOTES
코드 조각별 설명
struct shared_datasynchronization object와 보호할 payload를 같은 shared mapping에 둔다. 서로 다른 process의 일반 heap pointer를 구조체에 저장하면 의미가 없다.
MAP_SHARED | MAP_ANONYMOUSfork 전 만든 anonymous shared mapping이라 parent와 child가 같은 backing page를 본다. unrelated process라면 shm_open/memfd를 사용한다.
sem_init(&data->ready, 1, 0)pshared=1로 process 사이에서 쓰는 semaphore를 초기값 0으로 만든다.
strcpy(data->messagepayload를 먼저 완성한 뒤 sem_post로 publish한다. semaphore operation이 필요한 memory ordering을 제공한다.
sem_destroy다른 process/thread가 더 이상 wait/post하지 않는다는 합의 뒤에 파괴한다. wait 중인 semaphore를 destroy하면 정의되지 않은 동작이다.
DETAILS
세부 동작
이름 제거와 object 제거는 다르다
shm_unlink는 새 shm_open이 찾는 이름을 제거하지만 이미 열린 fd와 mapping은 계속 유효하다. 일반 file unlink와 비슷하다.
creator가 초기화 완료 전에 이름을 공개하면 opener가 미완성 header를 볼 수 있으므로 version/magic/ready protocol을 둔다.
shared pointer는 address가 다를 수 있다
unrelated process는 같은 shared object를 서로 다른 virtual address에 mapping할 수 있다. mapping 안에 raw pointer를 저장하면 다른 process에서 유효하지 않다.
base-relative offset, index, fixed-width handle을 사용하고 range validation을 한다.
owner crash를 protocol에 포함한다
semaphore를 감소한 process가 작업 중 죽으면 자동으로 credit이 복구되지 않는다. mutex라면 robust process-shared mutex와 EOWNERDEAD 복구를 검토한다.
shared header에 generation, state, checksum을 두고 recovery owner를 한 명 정한다.
OBJECTS
객체와 수명
| 대상 | 언제 생기고 없어지는가 | 확인할 값 |
|---|---|---|
shared backing object | shm_open/memfd 또는 anonymous fork mapping에서 생기고 마지막 참조 후 해제된다 | size, seals, name/link |
shared mapping | 각 process mmap에서 별도로 생기고 munmap/exit에서 사라진다 | base address, protection |
sem_t | shared page에서 sem_init되고 합의된 마지막 사용 뒤 sem_destroy된다 | pshared, count, waiter |
FAILURE PATH
실패 조건과 오해하기 쉬운 부분
| 겉으로 보이는 현상 | 실제 원인 후보 | 확인 방법 |
|---|---|---|
| reader가 깨진 구조체를 봄 | publish synchronization 누락 | writer order와 acquire primitive |
| 다른 process pointer crash | raw virtual address 공유 | offset/index encoding 확인 |
| 영원히 sem_wait | producer crash 또는 credit protocol 오류 | timeout, owner heartbeat, recovery |
LAB
직접 확인
- sem_post를 message write 앞으로 옮겨 잘못된 publish 순서를 stress loop에서 관찰한 뒤 원복한다.
- shm_open 버전으로 바꿔 unrelated process 두 개가 서로 다른 mapping base에서도 offset으로 data를 찾게 만든다.
- PTHREAD_PROCESS_SHARED와 PTHREAD_MUTEX_ROBUST mutex를 사용해 owner crash 뒤 EOWNERDEAD 복구를 구현한다.
./shared_semstrace -f -e trace=openat,ftruncate,mmap,futex,wait4,munmap,unlink ./shared_semPRIMARY REFERENCES