리눅스 IPC — Shared Memory (POSIX / SYSV)
1. 개요 (What / Why)
- Shared Memory(공유 메모리): 둘 이상의 프로세스가 동일한 메모리 영역을 직접 읽고/쓰는 방식으로 데이터를 주고받기 위한 IPC 메커니즘. 파일을 거치지 않고 메모리 공유가 가능하다.
- 주의점: 동일 메모리를 여러 프로세스가 사용하므로 **동기화(동시성 제어)**가 필수적(예: 세마포어, 뮤텍스 등). (Velog)
2. 종류 및 차이점
-
SYSV (System V) Shared Memory
- 오래된 레거시 인터페이스. 기존 시스템에서 널리 쓰여진 코드가 많음. (Velog)
-
POSIX Shared Memory
- POSIX 규격 기반. 이름 기반(
/name)으로 식별하고/dev/shm에 매핑되는 경우가 많음. 생성/삭제(shm_open/shm_unlink)와 메모리 매핑(mmap)이 분리되어 있는 점이 특징. (Velog)
- POSIX 규격 기반. 이름 기반(
3. 주요 API 요약 (SysV / POSIX)
3.1 SysV API (요약)
-
int shmget(key_t key, size_t size, int shmflg);- 역할: shared memory segment 의 id(shmid) 얻기(필요시 생성).
- 주요 플래그:
IPC_CREAT,IPC_EXCL,SHM_HUGETLB,SHM_HUGE_2MB등. - 반환: 성공 → shmid, 실패 → -1. (Velog)
-
void *shmat(int shmid, const void *shmaddr, int shmflg);- 역할: shmid에 해당하는 shared memory를 현재 프로세스 주소 공간에 매핑(attach).
shmaddrNULL이면 커널이 주소 결정. 비-NULL이면 PAGE_SIZE 정렬 필요하거나SHM_RND사용.- 플래그:
SHM_RND,SHM_EXEC,SHM_RDONLY,SHM_REMAP등. - 반환: 성공 → 매핑된 주소 포인터, 실패 →
(void *)-1. (Velog)
-
int shmdt(const void *shmaddr);- 역할: detach(매핑 해제). 성공 0, 실패 -1. (Velog)
-
int shmctl(int shmid, int cmd, struct shmid_ds *buf);- 역할: control operations (
IPC_STAT,IPC_SET,IPC_RMID,IPC_INFO,SHM_INFO,SHM_STAT등). IPC_RMID로 제거(마지막 detach 후 메모리 제거). (Velog)
- 역할: control operations (
-
관련 구조체:
struct shmid_ds(권한, seg size, atime/dtime/ctime, creator pid, nattch 등). (Velog)
3.2 POSIX API (요약)
-
int shm_open(const char *name, int oflag, mode_t mode);- 역할: 이름 기반 POSIX shared memory object 생성 또는 열기.
name은 반드시 슬래시로 시작 (/testshm등). oflag:O_RDONLY,O_RDWR,O_CREAT,O_EXCL,O_TRUNC등을 조합.- 반환: 성공 → non-negative file descriptor, 실패 → -1. (Velog)
- 역할: 이름 기반 POSIX shared memory object 생성 또는 열기.
-
int shm_unlink(const char *name);- 역할: 이름으로 등록된 shared memory 객체 제거(언링크). 실제 메모리는 마지막 close/munmap 이후에 해제될 수 있음. 반환 0 성공, -1 실패. (Velog)
-
POSIX 방식은
shm_open()→ftruncate()(크기 설정) →mmap()(매핑) 순서가 일반적. (pubs.opengroup.org)
4. POSIX 프로그래밍 시퀀스 (일반적인 절차)
shm_open(name, O_CREAT | O_RDWR, mode)— 객체 생성/오픈.ftruncate(fd, size)— 크기 설정 (shm_open 자체는 크기 설정을 하지 않음).mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)— 매핑.- 프로세스 간 읽기/쓰기(동기화 필요).
munmap(...),close(fd).- 필요 시
shm_unlink(name)로 이름 제거. (Velog)
5. 동기화(중요)
- 공유 메모리는 동기화 없이 읽기/쓰기를 수행하면 데이터 레이스가 발생. 프로세스 간 동기화 수단으로 POSIX named semaphores(
sem_open,sem_wait,sem_post,sem_unlink)나 파일 잠금, futex, 혹은 별도의 IPC(메시지 큐 등)를 사용한다. - POSIX 세마포어 및 동작은 매뉴얼을 참고할 것(예:
sem_open(3)문서). (man7.org)
6. 예제 코드 (원문 그대로 — POSIX & SYSV 혼합 예제)
아래 예제는 원문에 있는 예제를 거의 그대로 포함합니다. (컴파일/실행 방법은 아래 참조)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/mman.h>
#include<sys/stat.h>
#define KEY_PATH "/tmp"
#define PROJ_ID 123
#define PAGE_SIZE 4096
#define MONITORING_COUNT 5
#define ROUNDUP(x) (((x) + (PAGE_SIZE -1)) & ~(PAGE_SIZE - 1))
#define ERROR_CHECK(x, y, z) if((x) == (-1)) { perror(y); return z; }
#define SHM_FILE_PATH "/posix_test"
typedef struct data_info {
int size;
char message[PAGE_SIZE];
} DATA_INFO;
static void print_usage(const char *progname)
{
printf("Usage: %s (posix|sysv) (recv|send Message)\n", progname);
}
static int posix_init(void)
{
int fd;
fd = shm_open(SHM_FILE_PATH, O_CREAT | O_RDWR, 0644);
ERROR_CHECK(fd, "shm_open()", -1);
ERROR_CHECK(ftruncate(fd, sizeof(DATA_INFO)), "ftruncate()", -1);
return fd;
}
DATA_INFO *posix_gen_info(int fd)
{
DATA_INFO *info;
info = (DATA_INFO *)mmap(NULL, sizeof(DATA_INFO), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
ERROR_CHECK(info, "mmap()", MAP_FAILED);
return info;
}
static int posix_finish(int fd, DATA_INFO *info)
{
munmap(info, sizeof(DATA_INFO));
close(fd);
return 0;
}
static int posix_recv_message(void)
{
int fd;
int n;
DATA_INFO *info;
DATA_INFO local;
fd = posix_init();
info = posix_gen_info(fd);
memset(&local, 0, sizeof(local));
memset(info, 0, sizeof(DATA_INFO));
n = 0;
while(1){
if(memcmp((const void *)&local, (const void *)info, sizeof(local))){
/* diff */
printf("[Monitor - POSIX] size: %d, send Message: %s\n", info->size, info->message);
memcpy(&local, info, sizeof(DATA_INFO));
n ++;
if( n == MONITORING_COUNT) break;
}
sleep(1);
}
return posix_finish(fd, info);
}
static int posix_send_message(const char * message)
{
int fd;
DATA_INFO *info;
fd = shm_open(SHM_FILE_PATH, O_CREAT | O_RDWR, 0644);
ERROR_CHECK(fd, "shm_open()", -1);
info = (DATA_INFO *)mmap(NULL, sizeof(DATA_INFO), PROT_WRITE, MAP_SHARED, fd, 0);
info->size = sizeof(message);
strcpy(info->message, message);
munmap(info, sizeof(DATA_INFO));
return 0;
}
static int sysv_init(void)
{
key_t key;
int shm_id;
key = ftok(KEY_PATH, PROJ_ID);
ERROR_CHECK(key, "ftok()", -1);
shm_id = shmget(key, ROUNDUP(sizeof(DATA_INFO)), IPC_CREAT | 0644);
ERROR_CHECK(shm_id, "shmget()", -1);
return shm_id;
}
static DATA_INFO *sysv_gen_info(int shm_id)
{
DATA_INFO *info;
info = (DATA_INFO *)shmat(shm_id, NULL, 0);
ERROR_CHECK((void *)info, "shmat()", NULL);
memset((void *)info, 0, sizeof(DATA_INFO));
return info;
}
static int sysv_finish(const void *info)
{
ERROR_CHECK((const void *)info, "shmdt()", -1);
return 0;
}
static int sysv_recv_message(void)
{
int n;
int shm_id;
DATA_INFO *info;
DATA_INFO local;
shm_id = sysv_init();
ERROR_CHECK(shm_id, "sysv_init()", -1);
info = sysv_gen_info(shm_id);
if(info == NULL){
perror("sysv_gen_info()");
return -1;
}
memset(&local, 0, sizeof(local));
n = 0;
while(1){
if(memcmp((const void *)&local, (const void *)info, sizeof(local))){
/* diff */
printf("[Monitor - SYSV] size: %d, send Message: %s\n", info->size, info->message);
memcpy(&local, info, sizeof(DATA_INFO));
n ++;
if( n == MONITORING_COUNT) break;
}
sleep(1);
}
return sysv_finish((const void *)info);
}
static int sys_send_message(const char *message)
{
int shm_id;
DATA_INFO *info;
shm_id = sysv_init();
ERROR_CHECK(shm_id, "sysv_init()", -1);
info = sysv_gen_info(shm_id);
if(info == NULL){
perror("sysv_gen_info()");
return -1;
}
info->size = sizeof(message);
strcpy(info->message, message);
return sysv_finish(info);
}
int main(int argc, char **argv)
{
if(argc < 3){
print_usage(argv[0]);
return -1;
}
if(!strcmp((const char *)argv[1], "posix")) {
/* POSIX */
if(!strcmp((const char *)argv[2], "recv")){
/* recv Message */
posix_recv_message();
} else if(!strcmp((const char *)argv[2], "send")){
/* send Message */
if(argc < 4) { goto main_err; }
posix_send_message((const char *)argv[3]);
} else { goto main_err;}
} else if(!strcmp((const char *)argv[1], "sysv")){
/* SYSV */
if(!strcmp((const char *)argv[2], "recv")){
/* recv Message */
sysv_recv_message();
} else if(!strcmp((const char *)argv[2], "send")){
/* send Message */
if(argc < 4) { goto main_err; }
sys_send_message((const char *)argv[3]);
} else { goto main_err;}
} else { goto main_err; }
return 0;
main_err:
print_usage(argv[0]);
return -1;
}
코드 출처: 원문 Velog 글의 예제 코드(위 내용은 원문을 바탕으로 그대로 정리). (Velog)
7. 코드 설명 (핵심 포인트)
-
PAGE_SIZE와ROUNDUP매크로: SysV의 shmget()에는 페이지 정렬(size는 페이지 크기의 배수) 요구를 맞추기 위해 사용. (Velog) -
POSIX 흐름(
posix_*)posix_init():shm_open()로 fd를 얻고ftruncate()로 크기를 설정.posix_gen_info():mmap()으로 매핑.posix_finish()에서munmap()및close()호출.- 송신:
posix_send_message()는mmap()해 포인터에 쓰고munmap()호출(동기화 없음 — 실제 환경에선 세마포어 필요). (Velog)
-
SYSV 흐름(
sysv_*)sysv_init():ftok()로 key 생성 →shmget()호출(생성 포함).sysv_gen_info():shmat()로 attach 및memset초기화.sysv_finish()는shmdt()호출. (Velog)
-
주의: 예제는 단순 메시지 교환/모니터링 예제. 실제 서비스 코드에서는 동기화, 에러 처리 강화, 경계 검사(버퍼 오버플로우 방지) 등이 필요.
8. 컴파일 및 실행 예
-
컴파일:
- 일반:
gcc -o shm_example shm_example.c - (과거 glibc/링커에서는 POSIX RT 라이브러리 필요할 수 있음) 필요 시:
gcc -o shm_example shm_example.c -lrt - (대부분 현대 리눅스 배포판에서는
-lrt불필요함) (man.archlinux.org)
- 일반:
-
실행 예:
- POSIX 리시버(모니터):
./shm_example posix recv - POSIX 송신:
./shm_example posix send "Hello POSIX" - SYSV 리시버:
./shm_example sysv recv - SYSV 송신:
./shm_example sysv send "Hello SYSV"(Velog)
- POSIX 리시버(모니터):
9. 정리된 체크리스트 / 팁
- POSIX 이름은 항상
/로 시작: 예:/posix_test. (shm_open규칙) (Velog) - POSIX:
shm_open()→ftruncate()→mmap()순서. (shm_open로는 크기 설정 불가) (pubs.opengroup.org) shm_unlink()는 이름 제거만 수행. 실제 메모리는 모든 매핑/파일 디스크립터가 닫힌 뒤 해제됨. (man7.org)- SysV는
shmget()+shmat()+shmdt()+shmctl(..., IPC_RMID, ...)조합으로 관리. (Velog) - 동기화 필수 — 최소한 POSIX named semaphores(
sem_open,sem_wait,sem_post)같은 방법으로 임계영역 보호. 관련 매뉴얼 참조. (man7.org)
10. 참고(더 읽을거리)
- 원문 Velog: “[리눅스 프로그래밍] (IPC) Shared Memory (POSIX, SYSV)”. (예제 코드 포함). (Velog)
- POSIX
shm_open매뉴얼 (man pages). (man7.org) - POSIX 세마포어
sem_open매뉴얼 (동기화 사례). (man7.org)
0 댓글