리눅스 IPC — Shared Memory (POSIX / SYSV)

리눅스 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)

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).
    • shmaddr NULL이면 커널이 주소 결정. 비-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)
  • 관련 구조체: 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)
  • int shm_unlink(const char *name);

    • 역할: 이름으로 등록된 shared memory 객체 제거(언링크). 실제 메모리는 마지막 close/munmap 이후에 해제될 수 있음. 반환 0 성공, -1 실패. (Velog)
  • POSIX 방식은 shm_open()ftruncate()(크기 설정) → mmap()(매핑) 순서가 일반적. (pubs.opengroup.org)


4. POSIX 프로그래밍 시퀀스 (일반적인 절차)

  1. shm_open(name, O_CREAT | O_RDWR, mode) — 객체 생성/오픈.
  2. ftruncate(fd, size) — 크기 설정 (shm_open 자체는 크기 설정을 하지 않음).
  3. mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) — 매핑.
  4. 프로세스 간 읽기/쓰기(동기화 필요).
  5. munmap(...), close(fd).
  6. 필요 시 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_SIZEROUNDUP 매크로: 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)

9. 정리된 체크리스트 / 팁

  1. POSIX 이름은 항상 /로 시작: 예: /posix_test. (shm_open 규칙) (Velog)
  2. POSIX: shm_open()ftruncate()mmap() 순서. (shm_open로는 크기 설정 불가) (pubs.opengroup.org)
  3. shm_unlink()는 이름 제거만 수행. 실제 메모리는 모든 매핑/파일 디스크립터가 닫힌 뒤 해제됨. (man7.org)
  4. SysV는 shmget() + shmat() + shmdt() + shmctl(..., IPC_RMID, ...) 조합으로 관리. (Velog)
  5. 동기화 필수 — 최소한 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 댓글