본문 바로가기

CS/시스템 프로그래밍

[Linux] 공유 메모리를 이용한 데이터 교환

전 게시글에서 메시지 큐에 대해서 보았다.

이번에는 공유 메모리를 이용한 데이터 교환에 대해서 볼 것인데 메시지 큐와 구조는 매우 비슷하다.

 

다음의 코드를 shmwrite.c 라는 이름으로 작성해보자.

 

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

int main()
{
        key_t key = 1234;
        int shmid ;
        char* shmaddr;
        char buf[100];

        shmid = shmget(key, 100, IPC_CREAT | 0666);
        printf("shmid = %d\n", shmid);

        shmaddr = shmat(shmid, NULL, 0);
        printf("shmaddr = %p\n", shmaddr);
        printf("Enter a string to write: ");

        gets(buf);
        strcpy(shmaddr, buf);
        shmdt(shmaddr);

        return 0;
}

shmget은 해당하는 키를 가진 공유메모리가 없으면 메모리를 할당한다.

첫번째 매개변수는 key, 두번째 매개변수는 할당할 size, 세번째 매개변수는 메시지큐에서도 보았던 옵션이다.

 

ipcs -m 을 이용하여 shared memory만을 확인할 수 있다.

 

shmget으로 id를 얻은 후 write는 어떻게 할까?

메모리이기 때문에 프로그램에서 알아야 하는 것은 메모리 영역, 즉, 공유메모리의 주소를 알아야 한다.

그래서 이 프로그램 내에서 포인터로 access해야 하므로 그걸 얻어오는 함수가 shmat이다.

 

 

(나도 이 글을 쓰면서 궁금한 접이 생겼다.

공유메모리의 주소는 physical address일 것이고 프로세스에서 사용하는 주소는 virtual address 일 것인데,

그럼 이 공유메모리에 virtual address를 할당하는 함수가 shmat인가?

shmdt는 virtual memory 할당을 해제하는 것이고?

그럼 할당하는 virtual memory의 크기는? 그냥 8바이트짜리 virtual memory 주소로 접근하면 physical memory에 직접 접근가능한걸까? 아니면 virtual memory가 중간매개변수 역할을 하는걸까?)

 

 

shmat(shmid, NULL, 0) 함수를 호출하면 주소값이 void*로 리턴이 된다.

이 address애 write를 하면 공유메모리에 데이터가 써지게 된다.

 

이 shmaddr 변수는 프로그램 입장에서는 일반 변수와 다를게 없다.

그래서 void*로 리턴된 주소를 char* shmaddr로 받았으므로 strcpy를 이용하여 데이터를 쓸 수 있다.

 

이렇게 쓰기만 해서는 (message queue와는 달리) shared memory의 내용을 볼 수는 없다.

 

이제 데이터가 잘 써졌는지 확인해보기 위해 shmread.c를 다음과 같이 작성해 보자.

 

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

int main()
{
        int shmid;
        char buf[100];
        char* shmaddr;
        key_t key = 1234;

        shmid = shmget(key, 100, IPC_CREAT | 0666);
        printf("shmid = %d\n", shmid);

        shmaddr = shmat(shmid, NULL, 0);
        printf("shmaddr = %p\n", shmaddr);
        strcpy(buf, shmaddr);
        printf("buf = [%s]", buf);
        shmdt(shmaddr);

        return 0;
}

얻어온 shmaddr은 일반적인 변수처럼 사용하면 된다는 것이 포인트이다.

 

위의 shmwrite.c 를 컴파일하여 프로그램을 실행한 후 Hello를 입력한 후shmread.c를 컴파일하여 프로그램을 실행하면 Hello가 잘 출력되는 것을 볼 수 있다.

 

 

message queue와 다른 점은 메모리를 read하는 것이므로 메시지 큐와는 달리 값을 읽어온다고 해서 그 값이 사라지지 않는다.

 

그리고 메시지 큐는 동기화가 된다.

queue가 비어있다고 가정했을 때 어떤 프로세스 두개가 각각 receive와 send를 호출했을 때 receive가 먼저 호출이 되어도 큐가 비어있으면 send될 때까지 대기했다가 receive를 한다는 것이다.

 

그러나 shared memory의 경우 두 프로세스가 각각 read와 write를 했을 때 어떤 프로세스가 먼저 작업을 하느냐에 따라서 read되는 값이 달라진다.

(이 문제를 해결하기 위해 다음 게시글에서 볼 semaphore라는 것이 등장한다.)

 

그리고 공유 메모리 역시 문자열만 보낼 수 있는 것이 아니라 임의의 데이터를 쓸 수 있다.

 

 

다음의 shmwrite2.c, shmread2.c를 보자.

 

shmwrite2.c

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

int main ()
{
	int shmid;
	int data;
	char *shmaddr;
	key_t key = 1235;

	shmid = shmget (key, sizeof(int), IPC_CREAT | 0666);
	printf ("shmid = %d \n", shmid);

	shmaddr = shmat (shmid, NULL, 0);
	printf ("shmaddr = %p \n", shmaddr);
	printf ("Enter an integerto write: ");
	scanf ("%d", &data);

	*((int*)shmaddr) = data;

	shmdt (shmaddr);
}

integer 크기로 shared memory를 get하고, shmaddr변수 현재 char*이므로 int*로 형변환한후 데이터를 입력하여야 한다. 

 

 

shmread2.c

 

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

int main ()
{
	int shmid;
	int data;
	char *shmaddr;
	key_t key = 1235;

	shmid = shmget (key, sizeof(int), IPC_CREAT | 0666);
	printf ("shmid = %d \n", shmid);

	shmaddr = shmat (shmid, NULL, 0);
	printf ("shmaddr = %p \n", shmaddr);

	data = *((int*)shmaddr);
	printf ("data = %d \n", data);

	shmdt (shmaddr);
}

shmwrite2.c , shmread2.c를 컴파일하여 실행하면

 

1. key가 앞에서 먼저 작성한 shmwrite.c, shmread.c 와 다르므로 ipcs -m으로 생성된 공유 메모리를 확인하면 2개의 공유메모리가 생성된 것을 확인할 수 있다.

 

2.

이렇게 20을 입력한 후 shmread2.c를 컴파일하여 생성된 프로그램을 두번 실행해보면

.

이렇게 data = 20 이 두번 출력되는 것을 볼 수 있고, write로 값을 변경한 후에 출력하면 값이 바뀌는 것을 볼 수 있다.

(message queue와의 차이점)

 

임의의 구조체에 대해서도 당연히 사용가능하다.

 

shmwirte3.c

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

typedef struct node
{
        int x;
        char arr[100];
}Node;

int main()
{
        int shmid;
        int data;
        Node* shmaddr;
        Node n;
        key_t key = 1236;

        shmid = shmget(key, sizeof(Node), IPC_CREAT|0666);
        printf("shmid = %d\n", shmid);

        shmaddr = shmat(shmid, NULL, 0);
        printf("Enter an Integer to Write: ");
        scanf("%d", &(n.x));
        printf("Enter an String to Writer: ");
        getchar();
        gets(n.arr);

        *shmaddr = n;

        shmdt(shmaddr);

        return 0;
}

 

shmread3.c

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

typedef struct node
{
        int x;
        char arr[100];
}Node;

int main()
{
        int shmid;
        int data;
        Node* shmaddr;
        Node n;
        key_t key = 1236;

        shmid = shmget(key, sizeof(Node), IPC_CREAT|0666);
        printf("shmid = %d\n", shmid);

        shmaddr = shmat(shmid, NULL, 0);

        n = *shmaddr;

        printf("node: %d %s\n", n.x, n.arr);

        shmdt(shmaddr);

        return 0;
}

 

임의의 구조체에 대해서도 공유 메모리를 이용한 데이터의 교환이 가능하나 구조체는 미리 공유를 하여야 한다.

 

공유메모리는 메모리큐에 비해 서로 다른 n개의 프로세스에서도 비교적 쉽게 데이터의 공유가 가능하다는 장점이 있다.

그러나 ead시점에 따라서 제일 마지막에 업데이트된 데이터에 대해서만 read가 가능하다는 단점이 있다.