공유메모리 한눈에 보기 — 가상 주소 공간·mmap·세그먼트·락 허브

NOTE

“프로세스는 heap을 쓰는데, 프로세스끼리 통신하는 공유메모리는 어디에 있고 크기·접근은 누가 정하나?”라는 질문에서 출발해, 가상 주소 공간 → mmap → 세그먼트(크기·권한·수명) → 동시성 락까지 이어진 학습 내용을 그림 중심으로 한 장에 정리한 허브다. 세부는 주제별 노트로 나눴다.


1. 전체 그림 (이것만 보면 된다)

   프로세스 A                         프로세스 B
┌───────────────────┐             ┌───────────────────┐
│ 가상 주소 공간     │             │ 가상 주소 공간     │
│                   │             │                   │
│  stack            │             │  stack            │
│  ▒ mmap 영역 ▒    │             │  ▒ mmap 영역 ▒    │
│  ├ 공유라이브러리  │             │  ├ 공유라이브러리  │
│  └ [공유메모리]─┐  │             │  ┌─[공유메모리]    │
│  heap(malloc)   │  │             │  │  heap(malloc)   │
│  data / text    │  │             │  │  data / text    │
└─────────────────┼──┘             └──┼─────────────────┘
        (가상 주소 0x7f3a…)   (가상 주소 0x7e90… ← A와 달라도 됨)
                  │                   │
                  ▼ 페이지 테이블이     ▼ 같은 물리 페이지를 가리킴
            ┌───────────────────────────────┐
            │     물리 메모리 (RAM)          │
            │  ┌─────────────────────────┐  │
            │  │ 공유메모리 세그먼트 1개   │  │  ← 커널이 관리 (shmid / 이름)
            │  └─────────────────────────┘  │
            └───────────────────────────────┘
                          ▲
              ┌───────────┴───────────┐
              │ 커널의 세그먼트 표      │  key, shmid, 크기, 소유자(uid),
              │ (ipcs -m 로 확인)       │  권한(0660), 붙은 수(nattch)
              └─────────────────────────┘

읽는 법: 각 프로세스는 자기 가상 주소로 접근하지만, 실제로는 같은 물리 메모리 하나를 본다. 공유메모리는 heap이 아니라 mmap 영역에 붙고, 세그먼트 자체는 커널이 별도 객체로 관리한다.


2. 공유메모리의 일생

flowchart LR
    A["① 생성<br/>shmget / shm_open<br/>크기·권한 지정"] --> B["② 붙임<br/>shmat / mmap<br/>내 주소 공간에 매핑"]
    B --> C["③ 사용<br/>포인터로 읽기/쓰기<br/>+ 락으로 보호"]
    C --> D["④ 떼어냄<br/>shmdt / munmap<br/>내 연결만 해제"]
    D --> E["⑤ 삭제<br/>IPC_RMID / shm_unlink<br/>세그먼트 자체 삭제"]
    style A fill:#e8f1ff,stroke:#4a7bd0
    style B fill:#e8f1ff,stroke:#4a7bd0
    style C fill:#fff4d6,stroke:#d0a030
    style D fill:#e8f8e8,stroke:#4aa04a
    style E fill:#ffe8e8,stroke:#d04a4a
단계누가주의
① 생성보통 기동 데몬 1곳크기는 이때 한 번 정해지고 못 바꾼다
② 붙임사용하는 모든 프로세스프로세스마다 받는 주소가 다르다 → 포인터 대신 오프셋
③ 사용사용하는 모든 프로세스락 없이 동시에 쓰면 race (락 노트)
④ 떼어냄사용하는 모든 프로세스free() 아님. 안 해도 프로세스 종료 시 자동 정리
⑤ 삭제한 곳(보통 생성자)안 하면 프로세스가 죽어도 세그먼트가 남는다(고아)

3. 두 프로세스가 통신하는 순서 (시퀀스)

sequenceDiagram
    participant D as 기동 데몬(생성자)
    participant K as 커널
    participant A as 프로세스 A
    participant B as 프로세스 B
    D->>K: shmget(key, 크기, IPC_CREAT|IPC_EXCL|0660)
    K-->>D: shmid=101 (세그먼트 생성)
    D->>K: shmat(101) → 포인터 pD
    A->>K: shmget(key, 0, 0) → shmid=101
    A->>K: shmat(101) → 포인터 pA
    B->>K: shmget(key, 0, 0) → shmid=101
    B->>K: shmat(101) → 포인터 pB
    Note over A,B: pA ≠ pB (가상 주소 다름)<br/>가리키는 물리 메모리는 같음
    A->>A: lock → pA->count++ → unlock
    B->>B: lock → pB->count++ → unlock
    A->>K: shmdt(pA)
    B->>K: shmdt(pB)
    D->>K: shmdt(pD)
    D->>K: shmctl(101, IPC_RMID) ← 삭제는 생성자가
    K-->>K: nattch=0 → 세그먼트 소멸

4. 용어 한 줄 사전

용어한 줄 정의
IPCInter-Process Communication. 분리된 프로세스끼리 데이터를 주고받거나 동작을 맞추는 방법 전체를 가리키는 범주
System V IPC / POSIX IPCIPC의 구현 규격. 메시지 큐·세마포어·공유메모리를 각자 방식으로 제공 (개념)
가상 주소 공간프로세스마다 따로 가진 주소 번호 체계. 64비트 리눅스 유저 영역 약 128TB (노트)
세그먼트커널이 한 단위로 관리하는 공유메모리 한 덩어리 (ipcs -m의 한 줄)
key세그먼트를 찾기 위한 검색용 이름표 (System V). POSIX는 이름 문자열
shmid커널이 붙이는 세그먼트의 실제 식별자
mmap대상(파일·공유메모리·빈 메모리)을 내 가상 주소 공간에 붙여 포인터로 쓰게 하는 시스템 콜 (노트)
attach / detach붙임(shmat/mmap) / 떼어냄(shmdt/munmap) — 프로세스별 연결
IPC_RMID / shm_unlink세그먼트 삭제 예약 — 마지막 프로세스가 떼면 소멸
/dev/shmPOSIX 공유메모리가 파일처럼 보이는 곳. tmpfs(RAM 기반)라 데이터는 디스크가 아님

IPC 수단은 공유메모리만이 아니다

수단데이터 전달 방식
파이프 / FIFO바이트 스트림을 커널 버퍼로 전달 (한 방향)
메시지 큐메시지 단위로 커널 큐에 넣고 꺼냄
공유메모리같은 물리 메모리를 각자 주소 공간에 매핑 (복사 없음, 가장 빠름)
소켓양방향 (같은 머신: 유닉스 도메인 소켓 / 다른 머신: TCP·UDP)
시그널아주 작은 알림만 (데이터 거의 없음)
세마포어데이터가 아니라 동기화 신호 (System V에서 같은 방식으로 관리해 IPC로 분류)

5. read/write는 언제 쓰고, 공유메모리에는 왜 안 쓰나

먼저 바로잡기

shmget이 돌려주는 것은 포인터가 아니라 shmid(번호) 다. 포인터는 shmat(또는 mmap)이 준다. 그리고 read/write는 공유메모리 전용 함수가 아니라 “데이터가 커널 안에 있는 대상”을 다루는 범용 함수다.

[ read / write 방식 ]  (파일·소켓·파이프)
  프로세스 버퍼 ──write──→ 커널 버퍼 ──read──→ 상대 프로세스 버퍼
                 (복사 1)             (복사 2)      + 시스템 콜 2번

[ 공유메모리 방식 ]
  프로세스 A ──p->x = 1──→ [ 같은 물리 메모리 ] ←──y = p->x── 프로세스 B
                          복사 없음, 시스템 콜 없음 (그냥 메모리 접근)
read/write공유메모리 포인터
데이터 이동커널을 거쳐 복사복사 없음, 같은 메모리를 직접 봄
시스템 콜매번 필요shmat/mmap 때 한 번, 이후는 일반 메모리 접근
속도상대적으로 느림IPC 중 가장 빠름
동기화커널이 보장 (파이프는 쓰기 전엔 읽기가 대기)직접 락을 걸어야 함 (락 노트)
쓰는 대상파일·파이프·소켓·터미널공유메모리 (메시지 큐는 전용 msgsnd/msgrcv)

공유메모리를 쓰는 이유가 곧 “복사와 시스템 콜을 없애려고” 이므로 포인터로 바로 쓰는 것이 정석이고, 여기에 read/write를 쓰면 이점이 사라진다. (POSIX 공유메모리 fd에는 문법상 read/write도 가능하지만 실무에서는 ftruncate로 크기만 정하고 mmap으로 붙여 포인터로 쓴다.)

C/Java 비교

Java의 InputStream.read()/OutputStream.write()는 read/write 쪽(복사해서 전달), MappedByteBuffer(내부적으로 mmap)는 공유메모리 쪽에 해당한다.

파일은 두 방식 모두 가능하다. 순차로 한 번 읽을 때는 read가 단순하고, 여기저기 자주 접근하거나 여러 프로세스가 공유할 때는 mmap이 유리하다 (mmap 노트).


6. 질문 → 답 지도

처음 가졌던 질문한 줄 답자세히
공유메모리의 크기·접근은 어떻게 설정되나?생성하는 프로세스가 크기·권한 지정, 상한은 커널 한도세그먼트 §2·§4
세그먼트가 뭐고 접근 권한은 뭐지?세그먼트=커널이 관리하는 공유메모리 한 덩어리, 권한=소유자/그룹/기타 rwx세그먼트 §1·§4
메모리인데 파일을 만드나?System V 아니요, POSIX는 /dev/shm에 파일처럼 보이나 데이터는 RAM세그먼트 §5
mmap이 뭐지? 포인터로 바로 쓰나?대상을 내 주소 공간에 붙이는 시스템 콜, 붙이면 malloc 포인터처럼 사용mmap
동시성 이슈가 나오나? 락은 세마포어뿐?나온다. 세마포어 외에 공유 뮤텍스·원자 연산 등이 있다락
공유메모리가 heap에 생기나?아니요, mmap 영역에 생긴다가상 주소 공간
read/write는 언제 쓰나? 공유메모리엔 안 쓰나?파일·소켓·파이프처럼 커널을 거쳐 복사하는 대상에 씀. 공유메모리는 포인터로 직접 접근허브 §5
free도 해야 하나?free 아님. munmap/shmdt + 삭제세그먼트 §6
접근만 하는 프로세스가 삭제할 수 있나?소유자·생성자 uid가 같으면 가능세그먼트 §7
재기동 시 같은 key면 중복 아닌가?삭제 예약 시 옛 key는 반납됨. 대신 데이터가 갈라지는 게 문제세그먼트 §7
기동 때 받은 메모리에서 잡히나? heap이 줄지 않나?가상 주소 빈 공간에 표시될 뿐, 64비트에선 사실상 안 줄어듦가상 주소 공간

7. 흔한 오해 정정

오해실제
공유메모리는 heap에 생긴다mmap 영역에 생긴다 (heap은 malloc 전용 영역)
3개 프로세스가 100MB를 붙이면 RAM 300MB물리 RAM은 100MB 한 번. 가상 구간만 각자 생김 (top의 SHR/RSS에는 각자 포함되어 보임)
포인터 값을 다른 프로세스에 넘기면 된다프로세스마다 주소가 달라 무효. 오프셋을 저장한다
메모리인데 파일이 디스크에 생긴다System V는 파일 없음, POSIX /dev/shm도 RAM(tmpfs)
삭제는 만든 프로세스만 할 수 있다uid가 맞으면 누구나 (설계상의 약속으로 한 곳에 정할 뿐)
삭제하면 바로 사라진다삭제 예약. 붙은 프로세스가 모두 떼면 소멸
shmget이 포인터를 돌려준다shmget은 shmid(번호), 포인터는 shmat/mmap이 준다
공유메모리도 read/write로 읽고 쓴다포인터로 직접 접근한다 (read/write는 커널을 거쳐 복사하는 대상용)
락은 세마포어로만 한다공유 뮤텍스·파일 락·원자 연산도 가능 (프로세스 공유 속성 필수)
공유메모리에 락이 자동으로 걸린다OS는 공유까지만. 동기화는 직접
구조체를 그대로 올리면 어디서나 같다컴파일 옵션·아키텍처가 다르면 패딩이 달라 필드가 어긋남 (구조체 패딩)

8. 명령어 치트시트

# System V 공유메모리
ipcs -m                  # 세그먼트 목록 (key, shmid, owner, perms, bytes, nattch, status)
ipcs -m -l               # 시스템 한도 (shmmax / shmall / shmmni)
ipcs -m -i <shmid>       # 한 세그먼트 상세
ipcrm -m <shmid>         # 세그먼트 삭제 (nattch 0인지 확인 후!)
sysctl kernel.shmmax     # 한도 개별 확인
 
# POSIX 공유메모리
ls -l /dev/shm           # 파일처럼 보이는 세그먼트 목록
df -h /dev/shm           # tmpfs 용량
 
# 프로세스 메모리 구조
cat /proc/<pid>/maps               # 이 프로세스의 매핑 구간 (heap, stack, 공유메모리, 라이브러리)
ps -o pid,vsz,rss,cmd -p <pid>     # VSZ=가상, RSS=실제 물리 (KB)

9. 주제별 노트

노트다루는 내용
프로세스 가상 주소 공간heap·stack·mmap 영역 배치, 기동 시 실제 동작, demand paging, ASLR, VSZ/RSS
mmap시그니처, 매핑 종류, 포인터 사용, munmap, POSIX 공유메모리 전체 예제
공유메모리 세그먼트크기·한도, key/shmid, 권한, 파일 여부, 수명·삭제·삭제 권한·key 반납
공유메모리 동시성race 원리, 락 수단 비교, robust 뮤텍스, SEM_UNDO, 체크리스트

관련 문서 (기존)