[리눅스 프로그래밍] (IPC) Shared Memory (POSIX, SYSV)

Yoon Yeoung-jin·2022년 7월 10일
0

Linux

목록 보기
11/13

01. Shared Memory

그림 1. 하나의 공유 메모리를 사용하는 Process 들

mmap을 사용한 메모리 공유 방법은 파일을 사용하여 메모리를 메핑한다. 하지만 이번에 소개할 Shared Memory는 파일을 사용하지 않고 직접적으로 메모리를 공유하는 방법에 대해서 기술한다.

Shared Memory 는 둘 이상의 프로세스 간에 공유하는 메모리 공간을 의미하며, unrelated process 간에 도 공유가 가능하다. 하지만 여러 프로세스가 하나의 메모리 공간을 사용함으로 향후에 작성할 동기화 메커니즘이 필수적으로 들어가야 한다.

02. 사용 API

Shared Memory 를 사용하는데 다음 두가지가 있다.

  • Sysv
  • POSIX

여기서 POSIX가 기능적으로 좋지만 옛날에 작성된 코드들은 대부분 Sysv로 되어있기 때문에 둘다 알아두어야 한다. SYSV와는 달리 POSIX는 다음과 같은 특징을 가지고 있다.

  • Shm ID는 “/~~~” 와 같이 슬래시가 필수적이다.
  • shared memory의 생성/삭제와 memory mapping은 별도이다.
  • 시스템 상의 shared memory 정보는 일반적으로 /dev/shm에 마운트 되어있다.

SYSV와 POSIX에서 사용하고 있는 API는 다음과 같다.

02-01. Sysv

  • int shmget(key_t key, size_t size, int shmflg); : shared memory id 값을 가져오는 함수. 때에 따라 생성도 가능하다.
    파라미터
    key: shared memory key값 --> ftok() API를 통해서 생성 가능.
    size: shared memory size in bytes ‐ 단, PAGE_SIZE의 배수이어야 함
    shmflg: permission + mask
    ‐ IPC_CREAT: 생성 요청
    ‐ IPC_EXCL: 같은 key값을 갖는 shared memory 존재시 에러
    ‐ SHM_HUGETLB: hugepage로부터 생성하도록 설정
    ‐ SHM_HUGE_2MB, SHM_HUGE_1GB: hugepage size 지정
    반환값
    • 성공: shared memory id
    • 실패: -1
  • void *shmat(int shmid, const void *shmaddr, int shmflg); : shared memory에 접근한다.
    파라미터
    shmid: shared memory id(i.e. from shmget())
    shmaddr: mapping 할 address hint
    	‐ NULL: kernel이 알아서 지정하도록
    	‐ non-NULL: 반드시 이 주소로 mapping 하도록 함
    		▫ 단, PAGE_SIZE로 align되어 있거나
    		▫ shmflg에 SHM_RND를 명시 
    shmflg
    ‐ SHM_RND: shmaddr을 align하도록 지정
    ‐ SHM_EXEC: mapping 영역에 execution 가능하도록 설정
    ‐ SHM_RDONLY: read-only access
    ‐ SHM_REMAP: 기존에 mapping 된 영역에서 remap 하도록 설정
    		▫ 단, shmaddr은 반드시 non-NULL이어야 함
    반환값
    • 성공: attached shared memory address pointer
    • 실패: (void *) -1 
  • int shmdt(const void *shmaddr); : shared memory를 detach 한다.
    파라미터
     - shmaddr: detach 할 shared memory address
    반환값
     - 성공: 0
     - 실패: -1
  • int shmctl(int shmid, int cmd, struct shmid_ds *buf);: shared memory에 대한 제어 기능 수행
    파라미터
    shmid: shared memory id • cmd
    	‐ IPC_STAT: 커널의 정보를 buf에 복사
    	‐ IPC_SET: buf의 내용을 커널에 설정
    	‐ IPC_RMID: shared memory id 제거. --> 해당 설정을 많이 사용.
    	‐ IPC_INFO: system-wide shared memory 관련 정보 요청(buf에 struct shminfo * )
    	‐ SHM_INFO: shared memory에 의해 사용된 메모리 정보 요청(buf에 struct shm_info *)
    	‐ SHM_STAT: shmid를 index로 하여 IPC_STAT 요청
    buf
    
    반환값
    • IPC_INFO, SHM_INFO: 성공시 가장 많이 사용된 shared memory entry의 index를 리턴
    • SHM_STAT: 성공시 해당 shared memory id 리턴
    • 나머지 operation: 성공시 0 리턴
    • 실패시 -1 리턴
    • struct shmid_ds 구조
      struct shmid_ds {
                     struct ipc_perm shm_perm;    /* Ownership and permissions */
                     size_t          shm_segsz;   /* Size of segment (bytes) */
                     time_t          shm_atime;   /* Last attach time */
                     time_t          shm_dtime;   /* Last detach time */
                     time_t          shm_ctime;   /* Last change time */
                     pid_t           shm_cpid;    /* PID of creator */
                     pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
                     shmatt_t        shm_nattch;  /* No. of current attaches */
                     ...
                 };:q

02-02 POSIX

  • int shm_open(const char *name, int oflag, mode_t mode); : shared memory 생성/열기
    파라미터
    • name: SHM 이름(반드시 /로 시작해야 함.:/testshm”) 
    • oflag
    	‐ mask of O_RDONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC 
    • mode: permission
    	‐oflag에 O_CREAT가 없다면 mode는 반드시 0이어야 함
    반환값
    • 성공: shared memory descriptor 리턴(음수가 아닌 정수)
    • 실패: -1 
  • int shm_unlink(const char *name); : shared memory 삭제
    파라미터
    • name: shared memory 이름(반드시 /로 시작해야 함.:/testshm”)
    반환값
    • 성공: 0 
    • 실패: -1 

03. POSIX Shared memory를 사용하는 프로그래밍 시퀀스

POSIX 를 사용하는 shared memory 프로그래밍 시퀀스는 어떠한 형식을 가지고 있다.

  • 생성 (Create)
  • 접근 (Attach)

각 과정에 대한 시퀀스는 다음과 같다.

04. 예제 코드

#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;
}
profile
신기한건 다 해보는 사람

0개의 댓글