• Home
  • About
    • 검은색 잉크 블로그 photo

      검은색 잉크 블로그

      github GIST → https://gist.github.com/BlaCkinkGJ입니다.

    • Learn More
    • Email
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

[유닉스] shared memory

13 Apr 2019

Reading time ~4 minutes

Shared Memory

공유 메모리shared memory는 IPCinter-process communication를 시작하는 첫 걸음입니다. 일반적인 프로세스에서 사용되는 메모리 영역은 해당 프로세스만이 사용할 수 있는 특징이 있으나, 간혹 여러 개의 프로세스가 특정 메모리 영역을 사용을 하길 원하는 경우가 있을 수 있습니다. 그런 경우에 사용하는 것이 바로 공유 메모리입니다.

공유 메모리에 관련된 함수들로는 아래와 같습니다.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, int size, int shmflg)
void *shmat( int shmid, const void *shmaddr, int shmflg )
int shmdt( const void *shmaddr)
int shmctl(int shmid, int cmd, struct shmid_ds *buf)

각각에 관해서 알아보도록 하겠습니다.

함수 내용
shmget key를 접근 번호로 하는 공유 메모리 공간 할당을 커널에 요청하도록 합니다. 커널이 성공적으로 공유 메모리 공간을 할당을 하면 공유 메모리를 가리키는 식별자를 반환하도록 합니다.
크게 생성에 관련된 플래그로는 IPC_CREAT, IPC_EXCL, mode_flags가 존재합니다.
IPC_CREATE의 경우, 새로운 영역을 할당을 하는 것에 해당하며,
IPC_EXCL는 만약 해당 공유 메모리 영역이 존재하면 -1(에러)을 반환하도록 합니다.
마지막으로 mode_flags는 0666과 같이 접근 권한을 지정하는 데 사용합니다.
shmat shmid에 해당하는 공유 메모리 영역에 접근하여 프로세스에 해당 메모리를 사용할 수 있도록 가져오는 함수입니다.
그리고 적절한 공유 메모리 주소가 주어지지 않고 shmaddr로 (void *)0가 넘어가게 된다면 시스템이 알아서 판단하여 적절한 위치로 설정하도록 합니다.
그리고 shmflg는 가져오는 메모리 영역에 대한 접근 권한 설정입니다. 이를테면, SHM_RDONLY를 주게 되면 공유 메모리의 접근은 읽기 전용만 허가되게 됩니다. 만약 이 값이 0이라면 R/W가 다 허용됩니다.
shmdt 공유 메모리 영역에서 shmaddr에 해당하는 주소를 공유 메모리에서 빼내는 것에 해당합니다.
shmctl 공유 메모리의 정보를 읽거나, 정보를 변경, 할당된 공유 메모리 영역을 삭제합니다. 플래그로는 아래와 같습니다.
IPC_STAT은 공유 메모리의 정보를 조회하여 버퍼에 저장하는 플래그에 해당합니다.
IPC_SET은 파일의 권한 및 사용자 변경과 관련된 플래그에 해당합니다.
IPC_RMID는 생성된 공유 메모리의 키와 관련된 공유 메모리를 시스템에서 삭제합니다. 이 경우 버퍼는 사용되지 않으므로 NULL로 설정해주면 됩니다.

단적으로, 아래의 코드는 공유 메모리를 사용하는 예제에 해당합니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SEGSIZE 100
 
 void writeshm(int shmid, char *segptr, char *text)
{
    // 현재 공유 메모리에 쓰도록 합니다.
    strcpy(segptr, text);
    printf("Done...\n");
}
 
void readshm(int shmid, char *segptr)
{
    // 현재 공유 메모리를 읽도록 합니다.
    printf("segptr: %s\n", segptr);
}
 
void removeshm(int shmid)
{
    // 공유 메모리를 삭제하도록 합니다.
    shmctl(shmid, IPC_RMID, 0);
    printf("Shared memory segment marked for deletion\n");
}
 
void changemode(int shmid, char *mode) 
{
    struct shmid_ds myshmds;

    // 현재 공유 영역의 정보를 받아오도록 합니다.
    shmctl(shmid, IPC_STAT, &myshmds);

    // 과거 권한을 조회해보도록 합니다.
    printf("Old permissions were: %o\n", myshmds.shm_perm.mode);
 
    // 공유 권한을 변경을 하도록 합니다.
    sscanf(mode, "%ho", &myshmds.shm_perm.mode);
    shmctl(shmid, IPC_SET, &myshmds);
 
    // 갱신된 공유 권한을 확인합니다. 
    printf("New permissions are : %o\n", myshmds.shm_perm.mode);
}
 
void usage()
{
    fprintf(stderr, "shmtool - A utility for tinkering with shared memory\n");
    fprintf(stderr, "\nUSAGE:  shmtool (w)rite \n");
    fprintf(stderr, "        (r)ead\n");
    fprintf(stderr, "        (d)elete\n");
    fprintf(stderr, "        (m)ode change \n");
    exit(1);
}

int
main(int argc, char *argv[])
{
    key_t key;     // 공유 메모리 접근 번호입니다.
    int   shmid;   // 공유 메모리를 가리키는 식별자를 가집니다.
    char  *segptr; // 공유 메모리를 받을 포인터에 해당합니다.
 
    if(argc == 1)
        usage();
 
    key = ftok(".", 'S'); // 접근 번호를 생성하도록 합니다.
 
    // 공유 메모리를 생성을 해보도록 합니다.
    if((shmid = shmget(key, SEGSIZE, IPC_CREAT|IPC_EXCL|0666)) == -1) 
    {
        printf("Shared memory segment exists - opening as client\n");
 
        // 공유 메모리가 이미 존재한다면 공유 메모리를 생성하지 않고 읽도록 합니다.
        if((shmid = shmget(key, SEGSIZE, 0)) == -1) 
        {
            perror("shmget");
            exit(1);
        }
    }
    else
    {
        printf("Creating new shared memory segment\n");
    }

    // 공유 메모리를 로컬 포인터로 가져오도록 합니다.
    if ((segptr = shmat(shmid, 0, 0)) == (char *)(-1)) {
        perror("shmat"); // 공유 메모리를 받아오지 못하였습니다.
        exit(-1);
    }

    switch(tolower(argv[1][0])) { // 명령어를 읽습니다.
        case 'w': writeshm(shmid, segptr, argv[2]);
                  break;
        case 'r': readshm(shmid, segptr);
                  break;
        case 'd': removeshm(shmid);
                  break;
        case 'm': changemode(shmid, argv[2]);
                  break;
        default:
                  usage();
    }
}
 

위와 같은 방식을 통해서 공유 메모리의 접근과 사용이 가능합니다. 이런 공유 메모리 영역을 만들 때에 유의 사항으로는 되도록 구조체나 배열을 공유 메모리 영역에 할당을 할 때에는 정적으로 만들어야 합니다. 무슨 말인가 하면 아래와 같이 만들어야 함을 의미합니다.

struct shm_info {
    char str_ip[40]; // char *str_ip와 같이 하지 않도록 합니다.
    int  int_ip;
    int  int_id;
};

물론 자유자재로 공유 메모리를 사용하시는 분들은 동적 할당을 활용을 할 수도 있을 겁니다. 하지만 그런 사용은 필시 코드를 복잡하게 만들고 버그를 양산할 가능성이 높습니다. 따라서 최대한 정적 영역으로 사용하는 것을 추천 드립니다.

그리고 이러한 공유 메모리를 비롯한 IPC의 상태를 확인을 할 수 있는 명령으로 ipcs라는 명령이 있습니다. 이런 ipcs를 사용하면 아래와 같은 창이 나오게 됩니다.

IPC status from /dev/mem as of Mon Aug 14 15:03:46 1989
T    ID         KEY        MODE       OWNER     GROUP
Message Queues:
q       0    0x00010381 -Rrw-rw-rw-   root      system
q   65537    0x00010307 -Rrw-rw-rw-   root      system
q   65538    0x00010311 -Rrw-rw-rw-   root      system
q   65539    0x0001032f -Rrw-rw-rw-   root      system
q   65540    0x0001031b -Rrw-rw-rw-   root      system
q   65541    0x00010339--rw-rw-rw-    root      system
q       6    0x0002fe03 -Rrw-rw-rw-   root      system
Shared Memory:
m   65537    0x00000000 DCrw-------   root      system
m  720898    0x00010300 -Crw-rw-rw-   root      system
m   65539    0x00000000 DCrw-------   root      system
Semaphores:
s  131072    0x4d02086a --ra-ra----   root      system
s   65537    0x00000000 --ra-------   root      system
s 1310722    0x000133d0 --ra-------   7003      30720

출처: https://www.ibm.com/support/knowledgecenter/ko/ssw_aix_71/com.ibm.aix.cmds3/ipcs.htm

이를 통해, 현재 생성된 공유 메모리의 상태를 확인하고 관리할 수 있습니다. 만약에 프로세스의 비정상 종료로 인해서 적절하게 공유 메모리가 해제되지 않은 경우에는 ipcrm -m (shmid)를 치면 삭제를 할 수 있습니다.



UNIX Share Tweet +1