[Linux] 메시지 큐(message queue)
1. 시작하기 전에
프로세스간 데이터를 주고 받는 두가지 방법이 있다.
1. 메시지 기반
2. 공유 메모리 기반
메시지(message) 기반은 앞에서 보았던 FIFO 와 굉장히 비슷하다.
한 프로세스가 send로 메시지를 보내면 다른 프로세스는 receive로 메시지를 받을 수 있다.
순차적으로 보냈던 메시지를 받으므로 메시지 기반의 방법을 일반적으로 메시지 큐(Message Queue)라 한다.
공유 메모리 (Shared Memory) 기반에 대해서도 간단하게 알아보자.
원래는 서로 다른 프로세스는 서로의 메모리를 공유할 수 없다.
한 프로세스에서 쓰는 메모리를 virtuall memory라고 하는데 이는 한 기계에서 고유한 physical memory 와 다르기 때문이다.
한 프로그램에서 x = 0인 정수 변수를 선언한 후 자식 프로세스와 부모 프로세스와 나뉘어져 &x를 확인하면 두 프로세스에서 &x는 같은데 x++를 하면 두 프로세스 모두에서 x = 1인 이유가 두개의 프로세스에서 &x는 virtuall memory이고 virtual memory 주소가 같을 뿐 physical meomory 주소가 같은 것이 아니기 때문이다.
하지만 시스템에서 x라는 변수를 사용하여 메모리를 공유할 수 있게 해주면 데이터의 교환이 쉬워진다.
x에 어떤 프로세스는 write를 하고 어떤 프로세는 read를 하게 하면 쉽게 데이터의 교환이 가능해진다.
이것이 공유메모리 기반의 데이터 교환이다.
이 두개가 대표적인 데이터 교환 방법인데 이 중 메시지 기반에 대해서 먼저 알아보자.
2. 메시지 기반 (Message Queue)
메시지를 기반으로 데이터를 교환하는 툴을 리눅스에서 제공하고 있다.
명령어 중에서 ipcs라는 명령어가 있는데 이를 타이핑하면
이와 같은 화면을 볼 수 있는데 지금은 비어있다.
이들 각각은 사용자가 임의로 여러개 만들 수 있는데
이 중 메시지 큐를 먼저 만들어보자.
(1) msgsnd
우선 msgsend.c라는 이름으로 다음의 코드를 타이핑해보자.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
int main()
{
int msgqid;
key_t key;
char buf[100];
key = 1234;
msgqid = msgget(key, IPC_CREAT | 0666);
printf("msgqid = %d\n", msgqid);
printf("Enter a string to send: ");
gets(buf);
msgsnd(msgqid, buf, strlen(buf) + 1, 0);
return 0;
}
이 때 메시지 큐의 키가 필요한데, 이는 메시지큐를 구분하는데 사용하고
프로그램을 작성하기 전 어떤 key를 사용할지는 미리 정해놓아야 한다.
그리고 msgget으로 해당 key를 가진 메시지큐를 없으면 만들고, 있다면 호출할 수 있다.
(O_CREAT, 0666 과 달리 OR연산자를 사용한다. ex. IPC_CREAT | 0666)
msgget으로 큐를 만들거나 호출하면 메시지큐의 식별자를 반환해주고
이를 이용하여 메시지를 보내거나 (msgsnd)
받을 수 있다. (msgrcv)
위의 코드를 실행하고 메시지를 보낸 후 다시 ipcs 명령어를 타이핑하면
이와 같은 화면을 확인할 수 있다.
key는 1234 였으므로 16진수로 4d2(256 x 4 + 16 x 13 + 2)이고 message가 1개 저장된 것까지 볼 수 있다.
큐만 보고 싶다면 ipcs -q 명령어를 타이핑하면 된다.
그리고 한번 더 실행하면 key가 1234인 큐가 이미 있으므로 큐가 하나 더 생기거나 하진 않는다.
메시지를 보내는데 사용하는 함수는 msgsnd이고
매개변수로 큐의 id, 포인터, 보낼 데이터의 크기, flag 를 사용한다.
보낼 데이터의 크기로 sizeof(buf)를 대신 쓰게 되면
Hello World! 라는 (널문자 포함)13바이트의 데이터를 보내어도 100바이트의 데이터가 써지게 된다.
그래서 코드에는 strlen(buf) + 1이 쓰여져 있다.
flag로는 일반적으로 0을 쓰는데 flag로 0을 쓰면 큐가 꽉 차면 (이 메시지 큐는 크기가 무한정하지 않다!) 더이상 프로그램이 실행되지 않는다.
Tip. ! 명령어
! 문자열 - 느낌표 다음에 문자열이 오면 최근 실행했던 순서대로 해당 문자열에 매칭되는 명령을 찾아서 실행한다.
!! - 가장 최근에 실행한 명령을 다시 실행시킨다.
pipe 같은 경우에는 두 프로세스 간에서만 사용가능하고 두 프로세스 모두 실행 중이어야만 사용 가능하지만
메시지 큐는 프로세스 하나가 일방적으로 send를 해도 잘 보관가능하다는 차이가 있다.
그리고 권한이 있는한 누구나 읽어갈 수 있다.
여기까지 send 하는 예제였고 이제 receive 하는 예제를 살펴보자.
(2) msgrcv
아래의 코드와 같이 msgrecv.c를 작성해보자.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
int main ()
{
int msgqid, nread;
key_t key;
char buf[100];
key = 1234;
msgqid = msgget (key, IPC_CREAT | 0666);
printf ("msgqpid = %d \n", msgqid);
nread = msgrcv (msgqid, buf, 100, 0, 0);
printf ("nread = %d, buf =[%s] \n", nread, buf);
}
key = 1234 의 경우 이 key를 사용하겠다고 약속을 해야하는 것이다.
msqrecv 의 매개변수를 보자.
id, 읽어오는 데이터가 저장될 포인터, 최대 읽어올 byte수 까지는 일반적으로 읽어오는 함수에서 볼 수 있는 매개변수들이다.
그럼 뒤의 0 두개는 무엇일까?
첫 0 은 메시지 타입이다. 0이면 다 받고 0보다 크면 큐에 가장 처음 메시지에서 타입이 같은 것만 받는다.
(이 메시지 타입은 msgsnd에서도 설정할 수 있다.)
두번째 0 은 데이터가 없으면 기다리라는 의미이다.
별일이 없으면 0 두개를 일반적으로 쓴다고 생각하면 된다.
위의 코드를 실행하면 큐에 들어간 순서대로 문장이 출력되는 것을 확인할 수 있다.
메시지 큐와 pipe의 결정적 차이 중 하나는
pipe는 여러가지 메시지를 보내놓아도 한번에 읽을 수 있는 데이터의 사이즈(위 코드에서는 100byte)를 넘지 않으면
한꺼번에 데이터를 읽어오나
메시지 큐는 send 단위로 읽어올 수 있다는 것이다.
메시지큐는 메시지큐가 비었을 때 msgrcv를 호출하면 메시지 큐에 데이터가 들어올 때까지 기다린다.
이를 blocking이라고 한다.
이런 식으로 두 개의 프로세스가 메시지큐를 이용해 메시지를 주고 받을 수 있다.
지금까지는 String을 주고 받는 것만을 보았는데
문자열뿐 아니라 정수데이터 역시 주고 받을 수 있다.
아래와 같이 msgsend.c와 msgrevc.c 를 수정해보자.
msgsend2.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
int main ()
{
int msgqid;
key_t key;
char buf[100];
int data;
key = 1234;
msgqid = msgget (key, IPC_CREAT | 0666);
printf ("msgqpid = %d \n", msgqid);
printf ("Enter a string to send : ");
gets (buf);
msgsnd (msgqid, buf, strlen(buf) + 1, 0);
printf ("Enter an integer to send : ");
scanf ("%d", &data);
msgsnd (msgqid, &data, sizeof(int), 0);
}
msgrecv2.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
int main ()
{
int msgqid;
key_t key;
char buf[100];
int data;
key = 1234;
msgqid = msgget (key, IPC_CREAT | 0666);
printf ("msgqpid = %d \n", msgqid);
msgrcv (msgqid, buf, 100, 0, 0);
printf ("buf = %s \n", buf);
msgrcv (msgqid, &data, sizeof(int), 0, 0);
printf ("data = %d \n", data);
}
이런 식으로 임의의 데이터를 send할 수 있고
당연히 정수형으로 데이터를 보냈다면 받는 사람 역시 정수형으로 데이터를 받아야 한다.
메시지큐를 여러개 만들 수도 있다.
이 때 key는 당연히 다르게 해야한다.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
int main()
{
int msgqid;
key_t key1, key2;
key1 = 1234;
key2 = 4567;
msgqid = msgget(key1, IPC_CREAT | 0666);
msgqid = msgget(key2, IPC_CREAT | 0666);
return 0;
}
이렇게 코드를 작성한 후 컴파일한 뒤 실행해보면
서로 다른 키를 가진 메시지 큐 두개가 생성되는 것을 확인할 수 있다.
큐를 삭제하고 싶다면?
ipcrm 을 사용하면 된다.
ipcrm -q 1 이라고 하면 1을 id로 가진 메시지큐를 삭제할 수 있다.
즉, ipcrm -q 옵션을 이용하여 해당 id를 가진 메시지 큐를 삭제할 수 있다.