공유메모리 세그먼트 — 크기·key·권한·수명과 삭제
NOTE
공유메모리는 프로세스의 heap이 아니라 OS 커널이 따로 만들어 관리하는 메모리 영역이다. 그 한 덩어리를 세그먼트(Segment) 라 부른다. 이 노트는 세그먼트의 크기는 누가 정하는지, 어떻게 찾고(key), 누가 접근할 수 있는지(권한), 언제 사라지는지(수명·삭제) 를 정리한다.
상위 허브: 공유메모리 한눈에 보기
1. 세그먼트란, 그리고 heap과 무엇이 다른가
세그먼트 = 커널이 “하나의 단위”로 관리하는 공유메모리 한 덩어리. 한 서버에 공유메모리가 여러 개 있을 수 있어서, 각각을 구분해 부르는 단위가 필요해 이렇게 부른다. ipcs -m 목록의 한 줄이 세그먼트 하나다.
커널의 공유메모리 관리 표
┌───────┬──────────┬──────┬───────┬──────┬────────┬─────────────┐
│ shmid │ key │ 크기 │ 소유자 │ 권한 │ 붙은 수 │ 물리 메모리 │
├───────┼──────────┼──────┼───────┼──────┼────────┼─────────────┤
│ 101 │ 0x1234 │ 40KB │ tap │ 0660 │ 3 │ → RAM 위치 │
│ 102 │ 0x5678 │ 8KB │ root │ 0600 │ 1 │ → RAM 위치 │
└───────┴──────────┴──────┴───────┴──────┴────────┴─────────────┘
↑ 각 줄이 세그먼트 하나 (프로세스가 아니라 커널이 가진 정보)
프로세스 A 가상 주소 공간 프로세스 B 가상 주소 공간
┌──────────────┐ ┌──────────────┐
│ stack │ │ stack │
│ 공유메모리 ───┼──┐ ┌──┼─── 공유메모리 │ ← mmap 영역에 매핑
│ heap(malloc) │ │ │ │ heap(malloc) │ ← 프로세스 전용
│ data/text │ │ │ │ data/text │
└──────────────┘ ▼ ▼ └──────────────┘
┌────────────────────┐
│ 물리 메모리 (RAM) │ ← 같은 물리 페이지 하나
│ 세그먼트 │
└────────────────────┘
heap (malloc) | 공유메모리 | |
|---|---|---|
| 소유 | 그 프로세스 하나 | 커널이 관리하는 독립 객체 (프로세스가 죽어도 남을 수 있음) |
| 다른 프로세스 접근 | 불가 | 가능 (각자 자기 주소 공간에 매핑) |
| 주소 | 프로세스마다 고유 | 같은 세그먼트를 프로세스마다 다른 가상 주소에 붙일 수 있음 |
| 위치 | heap 영역 | mmap 영역 (가상 주소 공간) |
2. 크기는 누가 정하나
만드는 프로세스가 요청하고, 커널이 한도 안에서 허용한다.
/* System V 방식 */
int shmid = shmget(key, 4096 * 10, IPC_CREAT | 0660);
/* ^^^^^^^^^^ ^^^^^^^^^^^^^^^^
크기(바이트) 생성 플래그 | 접근 권한 */
/* POSIX 방식 */
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0660);
ftruncate(fd, 4096 * 10); /* 크기는 ftruncate로 지정 */- 내부적으로 페이지(4KB) 단위로 할당된다.
- 한 번 정하면 못 바꾼다. 더 필요하면 새로 만들어야 하므로 최대치를 미리 계산해서 잡는다.
- 이미 있는 세그먼트에 붙는 쪽은 크기를 정하지 않는다. 같은 key로
shmget하면 기존 것을 얻고, 요청 크기가 실제보다 크면EINVAL이 난다. (붙기만 할 때는 크기 인자로 0을 넘기는 것이 안전)
OS 한도 (리눅스)
| 커널 파라미터 | 의미 |
|---|---|
kernel.shmmax | 세그먼트 하나의 최대 크기 |
kernel.shmall | 시스템 전체 공유메모리 총량 (페이지 단위) |
kernel.shmmni | 세그먼트 최대 개수 |
최신 커널은 기본값이 매우 크게 잡혀 있어 잘 걸리지 않지만, 구형 환경·컨테이너·보안 하드닝된 서버에서는 한도에 걸려 shmget이 실패하는 경우가 있다. POSIX 방식(shm_open)은 /dev/shm 파일시스템의 용량이 한도가 된다.
ipcs -m -l # 현재 한도 확인
sysctl kernel.shmmax # 개별 확인
ipcs -m # 지금 존재하는 세그먼트 목록 (key, shmid, 소유자, 권한, 크기, nattch)
df -h /dev/shm # POSIX 방식의 용량3. 세그먼트를 찾는 방법 — key 와 shmid
서로 모르는 두 프로세스가 같은 세그먼트를 쓰려면 약속된 식별자가 필요하다.
| 방식 | 식별자 | 비고 |
|---|---|---|
| System V | 정수 key (ftok(경로, 프로젝트ID) 또는 고정값) | 관련: ftok과 IPC 키 생성, 이름-키 매핑 테이블 |
| POSIX | 이름 문자열 "/myshm" | /dev/shm/myshm에 대응 |
| 번호 | 역할 | 유일성 |
|---|---|---|
| key | ”이 이름으로 찾아라”는 검색용 이름표 | 살아 있는(삭제 예약 안 된) 세그먼트 중에서만 유일 |
| shmid | 커널이 부여하는 실제 식별자 | 세그먼트마다 항상 다름 |
shmget 플래그에 따른 동작 (key가 이미 존재할 때)
| 호출 | 동작 |
|---|---|
shmget(key, size, IPC_CREAT | 0660) | 이미 있으면 기존 것을 그대로 반환(새로 안 만듦) |
shmget(key, size, IPC_CREAT | IPC_EXCL | 0660) | 이미 있으면 EEXIST 에러 (“내가 새로 만드는 게 맞는지” 확인) |
shmget(key, 0, 0) | 없으면 실패. 사용만 하는 쪽이 쓰는 형태 |
실무에서는 생성 담당(기동 데몬) 은 IPC_CREAT | IPC_EXCL(고아 세그먼트 감지), 사용 담당은 IPC_CREAT 없이 붙기만 하는 식으로 역할을 나눈다.
4. 접근 권한 — 누가 붙을 수 있나
같은 서버에는 여러 계정(root, 서비스 계정, 다른 개발자)의 프로세스가 돌고, 공유메모리에는 민감한 설정값이나 상태가 들어 있을 수 있다. 그래서 접근 통제가 필요하다. 방식은 파일 권한(chmod)과 같다.
0660 = 소유자(rw-) 그룹(rw-) 기타(---)
읽기·쓰기 읽기·쓰기 접근 불가
커널이 세그먼트마다 소유자 uid/gid와 mode를 같이 저장해 두고, shmget/shm_open/shmat 때 호출 프로세스의 uid/gid와 대조한다. 안 맞으면 EACCES.
실무에서 자주 겪는 일
프로세스를
root로 한 번 띄웠다가 일반 계정으로 다시 띄우면, 이전에 root가 만든 세그먼트에 권한이 없어 기동이 실패한다. (ipcs -m의 소유자 열 확인)
붙일 때도 프로세스별로 권한을 고를 수 있다
void *p = shmat(shmid, NULL, 0); /* 읽기/쓰기로 붙임 */
void *r = shmat(shmid, NULL, SHM_RDONLY); /* 읽기 전용으로 붙임 (쓰면 SIGSEGV) */POSIX는 mmap의 prot(PROT_READ/PROT_WRITE)로 같은 효과를 낸다. → mmap 노트
5. “메모리인데 파일을 만든다고?”
두 방식이 다르다.
System V (shmget) | POSIX (shm_open) | |
|---|---|---|
| 파일이 생기나 | 아니요. 커널 내부 표에 항목만 생김 | /dev/shm/이름에 파일처럼 보이는 것이 생김 |
ls로 보이나 | 안 보임 (ipcs -m으로만 확인) | 보임 (ls -l /dev/shm) |
| 데이터는 어디에 | RAM | RAM (/dev/shm은 tmpfs, 디스크 아님, 재부팅 시 사라짐) |
| 파일 권한 모델 | rwx 모양을 빌려 쓴 별도 체계 | 실제 파일 권한 그대로 |
POSIX가 파일처럼 만들어진 이유는 유닉스의 “모든 것을 파일로 다룬다” 철학이다. 파일 인터페이스를 재활용하면 이름으로 찾기, 권한, fd, mmap, ls/rm을 새로 만들 필요가 없다. 파일은 “이름 + 권한 + 핸들”을 제공하는 껍데기이고 실제 데이터는 RAM에 있다.
ftok의 파일도 저장소가 아니다
ftok("/some/file", 'A')에 넣는 경로는 그 파일의 inode 번호와 문자 하나를 섞어 key 숫자를 만드는 재료일 뿐, 파일 내용은 쓰지 않는다. 서로 다른 프로세스가 같은 key를 계산하도록 “공통으로 아는 경로”를 쓰는 것이다.
6. 수명과 해제 — free 가 아니다
생성(shmget/shm_open) → 붙임(shmat/mmap) → 사용 → 떼어냄(shmdt/munmap) → 삭제(IPC_RMID/shm_unlink)
└─ 프로세스별 ──────────────┘ └─ 세그먼트 단위 ──┘
| 붙인 방법 | 떼어내기 (내 연결만 해제) | 세그먼트 삭제 |
|---|---|---|
mmap | munmap(p, len) | POSIX면 shm_unlink("/name") |
shmat | shmdt(p) | shmctl(shmid, IPC_RMID, NULL) / 셸: ipcrm -m <shmid> |
free()를 부르면 안 된다(malloc이 만든 메모리가 아니므로 크래시).
두 단계는 역할이 다르다.
- 떼어내기: 내 프로세스의 연결만 끊는다. 공유메모리와 다른 프로세스의 연결은 그대로.
- 삭제: 세그먼트 자체를 없애겠다는 표시. 마지막으로 붙은 프로세스가 떼어낼 때 실제로 사라진다.
안 하면?
- 떼어내기를 깜빡: 프로세스 종료 시 커널이 자동 정리. 단 장시간 도는 데몬이 계속 붙이기만 하면 주소 공간이 누적된다.
- 삭제를 깜빡: 세그먼트가 커널에 남는다. 비정상 종료가 반복되면
ipcs -m에 고아 세그먼트가 쌓여, 다음 기동 때 “이미 존재(EEXIST)”·“크기 불일치(EINVAL)“로 실패하거나 RAM을 차지한다. 정리는ipcs -m(nattch 0 확인) →ipcrm -m <shmid>.
7. 삭제는 누가 할 수 있나
“만든 프로세스”가 아니라 “uid가 맞는 프로세스” 가 한다. 기준은 읽기/쓰기 권한 비트가 아니라 소유자/생성자 uid 일치다.
| 호출한 프로세스 | 삭제 가능 여부 |
|---|---|
| 세그먼트 소유자(owner) uid와 같은 계정 | 가능 |
| 생성자(creator) uid와 같은 계정 | 가능 |
| root(특권 보유) | 가능 |
| 그 외 계정 | EPERM |
같은 계정의 아무 프로세스(또는 ipcrm -m)가 지울 수 있다. 접근만 하던 프로세스도 같은 uid면 IPC_RMID를 호출할 수 있다는 뜻이다.
쓰는 중에 삭제하면 — “삭제 예약”
1. A가 세그먼트 생성, A·B·C가 붙어서 사용 중 key=0x1234 shmid=101 nattch=3
2. B가 IPC_RMID 호출
├─ 세그먼트에 "삭제 예정(dest)" 표시만 붙음
├─ 이미 붙은 A·B·C는 계속 정상 사용 가능
├─ 옛 세그먼트의 key는 IPC_PRIVATE(0)로 바뀜 key=0x00000000 shmid=101 status=dest
│ → 새로 shmget(0x1234)해도 옛 세그먼트를 못 찾는다
└─ 마지막 프로세스가 shmdt/종료하는 순간 실제로 소멸
파일의 unlink(지워도 열려 있는 동안은 유지)와 같은 동작이다. (리눅스 기준이며 다른 유닉스는 세부가 다를 수 있다)
“key가 중복되는 것 아닌가?” — 아니다
삭제 예약된 옛 세그먼트는 key를 반납(0x00000000)하므로 같은 key가 두 개 존재하는 일은 없다. 옛것(shmid 101)과 새것(shmid 102)은 shmid가 달라 별개의 메모리다.
진짜 문제: 데이터가 갈라진다
기존에 붙어 있던 프로세스 ──→ shmid 101 (옛 세그먼트, dest 상태)
재기동한 프로세스 ──→ shmid 102 (같은 key로 새로 만든 세그먼트)
코드로는 둘 다 "key 0x1234"를 쓰지만 실제로는 서로 다른 메모리를 본다.
에러가 안 나서 원인 찾기가 매우 어렵다.
설계 원칙: 삭제 책임은 한 곳에
“생성자가 삭제 책임자”는 커널이 강제하는 규칙이 아니라 설계상의 약속이다. 보통 기동/종료를 담당하는 데몬(관리 프로세스)만 생성과 삭제를 하고, 나머지 프로세스는 shmat/shmdt(또는 mmap/munmap)만 하도록 코드와 운영 절차로 정한다. 고아 정리 스크립트도 ipcs -m의 nattch가 0인지 확인한 뒤 지운다. (malloc/free의 소유권 개념과 비교: 동적 할당 소유권)
8. 한눈에 정리
| 질문 | 답 |
|---|---|
| 누가 제공하나 | OS 커널 (heap과 별개의 커널 관리 객체) |
| 크기는 | 생성하는 프로세스가 지정, 페이지 단위, 이후 변경 불가. 상한 shmmax/shmall/shmmni |
| 찾는 방법 | System V key(ftok 등) / POSIX 이름 문자열. 커널 내부 식별자는 shmid |
| 접근 제어 | 소유자·그룹·기타 퍼미션(0660). 붙일 때 읽기전용 선택 가능 |
| 파일을 만드나 | System V 아니요 / POSIX /dev/shm(tmpfs, 데이터는 RAM) |
| 해제 | free 아님 → shmdt/munmap (떼기) + IPC_RMID/shm_unlink (삭제) |
| 삭제 권한 | 소유자·생성자 uid 일치(또는 root). 접근만 하던 프로세스도 가능 |
| 쓰는 중 삭제 | 삭제 예약(dest), key 반납, 마지막 프로세스가 떼면 소멸 |
| 재기동 시 위험 | 옛 세그먼트와 새 세그먼트로 데이터가 갈라짐 → 삭제 책임을 한 곳에 |