공유 메모리, 메시지큐 등 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

공유메모리(Shared Memory)

프로세스에서 메모리는 해당 프로세스만이 사용하는게 일반적입니다. 메모리에는 명령어, 지역 변수, 동적 변수, 전역 변수와 같이 데이터가 존재하는데 그 프로세스만 접근할 수 있고 변경가능합니다. 일반적으로 이렇다는 건데, 아주 가끔 이 데이터가 다른 프로세스에서 쓰일 수 있도록 만들 수도 있습니다. 그게 바로 공유메모리라는 IPC기법입니다.

서로 다른 프로세스가 특정 메모리를 공유하면 데이터를 더 빠르게 접근할 수 있기 때문에 프로그램을 더 효율적으로 만들 수 있습니다. 이해도 직관적으로 할 수 있기 때문에 코드도 그렇게 어렵지가 않습니다.

 

관련 함수

공유메모리를 사용하기 위해서 우리가 필요한 헤더파일은 아래와 같이 2개입니다.

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

 

1) shmget : 인자로 전달된 key의 값으로 공유메모리를 얻고 공유메모리 조각(shared memory segment)의 id를 돌려줍니다. 

int shmget(key_t key, size_t size, int shmflg);

key : 공유메모리를 할당할때 사용하는 고유 key값입니다.

size : 메모리의 최소 size를 의미합니다. 새로운 공유메모리를 할당받는다면 size를 명시하고 이미 존재하는 메모리면 0을 주면 됩니다.

shmflg : IPC_CREATIPC_EXCL 두 가지가 존재합니다.

- IPC_CREAT : 새로운 메모리 세그먼트는 만듭니다. 이 flag를 사용하지 않는다면 shmget은 명시된 key와 연관된 찾고 접근할 수 있는 권한이 있는지 확인합니다.

 

- IPC_EXCL : IPC_CREAT과 함께쓰는 플래그로 만약 메모리 세그먼트가 존재하면 shmget은 실패하게 됩니다.

IPC_EXCL을 사용하는 경우는 우선 공유메모리가 있는지 확인 후 없으면 IPC_CREAT을 통해 할당받으라는 뜻입니다. 이렇게 공유메모리가 오염되는 것을 방지할 수 있습니다.

 

2) shmat : 공유메모리를 얻었으면 메모리의 위치에 이 프로세스를 묶는(attach) 시스템 콜입니다.

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid : 공유메모리의 id를 의미합니다. shmget을 통해 얻어올 수 있습니다.

shmaddr

-NULL(0)일 경우 : 커널에서 적절한 주소를 반환하여 줍니다.

-NULL이 아닐 경우 : shmflg로 SHM_RND일때, 그 주소와 attach할 가장 가까운 주소를 반환합니다.

 

정상적으로 동작한다면 적절한 포인터를 넘기고 실패하면 (void*) -1을 반환하게 됩니다. 여기서 왜 void*를 반환하는지 아시겠죠? 공유 메모리에 들어가있는 데이터가 정수형인지, 부동 소수 형태인지, 또는 구조체인지 모르니까 무엇이든 받을 수 있는 void*으로 넘겨줄테니 알아서 반환해서 써라 이 이야기입니다.

 

3) shmdt : 공유메모리를 이 프로세스와 떼어냅니다. 이는 공유메모리를 제거하는 것이 아님에 주의하세요.

int shmdt(const void *shmaddr);

shmaddr : shmat에서 전달받은 그 포인터를 전달하면 됩니다.

성공 시 0, 실패시 -1을 반환합니다.

 

4) shmctl : 공유메모리를 제어하기 위해 사용합니다. 예를 들면 공유메모리의 정보를 얻거나 어떤 값을 쓰거나 공유메모리를 삭제하는 등의 조작이 있습니다.

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid : 말 안해도 아시겠지만 공유메모리 id입니다.

cmd : 제어할 일종의 command입니다. 정수형을 갖으며 여러 command가 존재하는데 우리는 IPC_RMID라는 cmd를 예제를 통해 사용하도록 하겠습니다.

buf : 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 */
       ...
};

 

복잡하게 보이지만 사실 옆의 주석만 좀 읽어보면 추측은 할 수 있겠네요. 

shm_perm : 공유메모리의 접근제어가 저장되어있습니다.

shm_segsz : 공유메모리의 size를 의미합니다.

shm_atime : attach된 시간 정보를 기록하네요.

shm_dtime : 반대로 detach된 시간을 기록합니다.

shm_ctime : 공유메모리가 변경될때의 시간을 기록합니다.

shm_cpid : 이 공유메모리를 최초로 만들어낸 process id입니다.

shm_lpid : 최근 shmat, 또는 shmdt를 수행한 process id입니다. 

 

첫번째 필드인 ipc_perm이라는 구조체도 한번 볼까요?

struct ipc_perm {
       key_t          __key;    /* Key supplied to shmget(2) */
       uid_t          uid;      /* Effective UID of owner */
       gid_t          gid;      /* Effective GID of owner */
       uid_t          cuid;     /* Effective UID of creator */
       gid_t          cgid;     /* Effective GID of creator */
       unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
       unsigned short __seq;    /* Sequence number */
};

 

아실테지만 uid, gid 등의 permission 정보가 있습니다. 

 

 

 

 

예제 

이제 공유메모리를 사용한 아주 간단한 예를 보도록 하겠습니다. 너무나 간단합니다.

#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
        int shmid;
        int *num;
        key_t key=987654;
        void *memory_segment=NULL;

        if((shmid=shmget(key,sizeof(int),IPC_CREAT|0666))==-1){
                printf("shmget failed\n");
                exit(0);
        }


        if((memory_segment=shmat(shmid,NULL,0))==(void*)-1){
                printf("shmat failed\n");
                exit(0);
        }

        num=(int*)memory_segment;
        (*num)++;
        printf("shared memory value :%d\n",(*num));
        return 0;
}

 

공유메모리 id를 얻어와서 공유메모리의 값을 하나씩 증가시키고 있는 예제입니다. 보시다시피 공유메모리의 포인터는 void*를 반환하지만 우리는 int*로 연산할 것이기에 int*형변환을 하고 값을 증가시켰지요.

사용되는 key값은 아무 정수나 넣어주시면 됩니다. 

 

자, 컴파일하고 a.out을 연타해봅시다.

# gcc shm.c
# ./a.out
shared memory value :1
# ./a.out
shared memory value :2
# ./a.out
shared memory value :3
# ./a.out
shared memory value :4
# ./a.out

 

본래 프로세스가 끝나면 메모리도 정리가 되서 계속 1이라는 값이 출력이 되어야하는데 메모리가 아직 살아있고 그 메모리의 값을 참조하여 증가시키니 프로그램을 계속 실행시키면 이렇게 증가된 값을 보여주게 됩니다.

공유메모리가 아직 살아있다는 것을 어떻게 알까요?

 

ipcs -m 명령으로 볼 수 있습니다.

# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 4          root       600        524288     2          dest
0x000181cd 5          root       660        4          0
0x000181cb 6          root       666        4          0
0x000f1206 14         root       666        4          0

 

key값을 잘 보시기 바랍니다. 0x000f1206은 10진수로 987654, 즉 우리가 위 코드에 설정한 key값입니다.

 

저희는 프로그램이 종료되면 공유메모리도 종료시키고 싶습니다. 가령 아주 위험하게 이 공유메모리에 민감한 정보라도 있다면 큰일이잖아요? www.poxxhub.com/post_id=xxxxx

 

 

 

다음과 같이 코드를 고쳐봅시다. 

#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
        int shmid;
        int *num;
        key_t key=987654;
        void *memory_segment=NULL;

        if((shmid=shmget(key,sizeof(int),IPC_CREAT|0666))==-1){
                printf("shmget failed\n");
                exit(0);
        }


        if((memory_segment=shmat(shmid,NULL,0))==(void*)-1){
                printf("shmat failed\n");
                exit(0);
        }

        num=(int*)memory_segment;
        (*num)++;
        printf("shared memory value :%d\n",(*num));
        if(shmctl(shmid,IPC_RMID,NULL)==-1){
                printf("shmctl failed\n");
        }

        return 0;
}

 

위 코드에 추가한것은 shmctl을 추가한것 밖에 없습니다. cmd로 IPC_RMID를 전달했네요. 그렇다면 이제 이 shmid는 사용할 수 없게 됩니다.

이제 컴파일하고 실행해봅시다.

# gcc shm.c
# ./a.out
shared memory value :5
# ./a.out
shared memory value :1
# ./a.out
shared memory value :1
# ./a.out
shared memory value :1
# ./a.out
shared memory value :1
# ./a.out
shared memory value :1

 

다시 ipcs -m 명령으로 공유메모리가 남아있는지 확인해보면 사라진것을 볼 수 있습니다.

# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 4          root       600        524288     2          dest
0x000181cd 5          root       660        4          0
0x000181cb 6          root       666        4          0

 

여기까지 공유메모리에 대해서 살펴보았는데요. IPC기법중에서도 그렇게 어렵지 않는 공유메모리를 사용할때 주의해야할 한 가지는 데이터가 오염되지 않게 처리하는 방법입니다. semaphore이든 mutex이든 메모리를 동시에 접근할때 처리를 해주어야하며 이 포스팅에서는 그러한 처리는 하지 않았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,