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

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

메시지 큐

IPC기법 중 하나인 메시지큐는 Data Structure 중 큐를 사용합니다. 기본적으로는 먼저온 메시지가 먼저 꺼내어집니다. 메시지큐의 msgtype에 따라 특정 메시지 중 가장 먼저들어온 메시지도 받아올 수 있습니다. 이 메시지는 커널에서 보관하고 있으니 프로세스가 종료되어도 사라지지 않습니다.

메시지 큐의 용량이 허용하는 한 메시지는 계속 큐에 쌓일 수 있습니다. 메시지를 얻어오는 쪽은 가장 메시지를 읽고 메시지큐에서 그 메시지를 삭제합니다.

 

메시지 큐에는 두가지의 종류가 있습니다.

- System V의 Message Queue

- POSIX의 Message Queue

mq_open, mq_send, mq_receive와 같은 함수를 사용하는 POSIX에서 메시지큐 활용방법을 알아보시려면 아래의 포스팅을 참고하시면 됩니다.

https://reakwon.tistory.com/209

 

[리눅스] POSIX 메시지 큐 사용 방법 - 예제 코드

Message Queue 프로세스 간 통신 방식인 IPC기법 중 하나인 메시지 큐는 두 가지 사용 방법이 있습니다. msgget, msgsend, msgrecv와 같은 함수를 사용하는 방식인 System V 방식의 메시지큐, 그리고 지금 알아

reakwon.tistory.com

이번 포스팅에서는 System V의 Message Queue를 리눅스에서 어떻게 이용할 수 있는지 알아보도록 합시다.

 

메시지 큐 관련 시스템 콜

메시지큐를 사용하기 위해서는 3개의 헤더파일이 필요합니다.

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

 

1) msgget : System V의 메시지 큐 id를 얻어옵니다.

 int msgget(key_t key, int msgflg);

 

key : 메시지큐를 얻어올 때 사용하는 고유 key값입니다.

msgflg : flag에는 2가지가 있는데요. IPC_CREAT과 IPC_EXCL입니다.

 - IPC_CREAT : 메시지큐가 없으면 새로 생성합니다.

 - IPC_EXCL : IPC_CREAT과 같이 사용하는 flag인데, 만약 해당 메시지큐가 존재하면 msgget은 오류를 반환합니다.

 

2) msgsnd : 메시지를 보냅니다. 

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

 

msqid : 메시지 큐의 id입니다.

msgp : msgp는 void*이나 우리는 구조체 형식으로 아래와 같이 정의해주어야합니다. 

struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[1];    /* message data */
};

 

- mtype : 메시지의 타입을 나타냅니다. 이 값은 0보다 커야한다고 주석이 말해주고 있군요. 메시지큐의 msgbuf를 정의할때 long형의 mtype을 반드시 명시해주어야합니다. 

- mtext : mtext은 실제 메시지 큐에 보낼 데이터를 말합니다. mtext와 같이 배열일 수도 있고, 구조체일 수도 있습니다. 

msgsz : msgsz는 메시지 큐에 전송할 데이터의 사이즈를 의미하는데, 위의 msgbuf의 mtype멤버를 제외한 실데이터의 크기를 전달해야합니다. 

msgflg : 큐의 공간이 없을때 msgsnd의 동작은 blocking입니다. 즉, msgsnd에서 큐의 공간이 생겨날때까지 기다리는 것이지요. 여기서 IPC_NOWAIT을 사용한다면 msgsnd는 blocking되지 않고 실패합니다.

msgsnd가 성공적으로 동작하면 msqid_ds라는 구조체의 필드의 값이 변경됩니다. sys/msg.h에 이 구조체가 정의되어있습니다.

 

struct msqid_ds {
       struct ipc_perm msg_perm;     /* Ownership and permissions */
       time_t          msg_stime;    /* Time of last msgsnd(2) */
       time_t          msg_rtime;    /* Time of last msgrcv(2) */
       time_t          msg_ctime;    /* Time of last change */
       unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
       msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
       msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
       pid_t           msg_lspid;    /* PID of last msgsnd(2) */
       pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};

이 구조체는 메시지큐의 메타데이터같은 것인데 msgsnd를 성공적으로 호출하고 나면 아래와 같이 변경됩니다.

- msg_lspid는 호출된 process id로 변경

- msg_qnum의 값 1 증가

- msg_stime을 현재 시간으로 설정

 

3) msgrcv : msgsnd를 했다면 받는 시스템 콜이 있어야겠지요. msgrcv가 그 역할을 합니다. 메시지큐 id의 메시지를 하나 읽고 그 메시지를 큐에서 제거합니다.

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

 

msqid : 다 아시겠지만 메시지큐 id입니다.

msgp : 읽어들인 메시지를 msgp가 가리키는 주소에 위치시킵니다. 쉽게말해 읽어온 메시지입니다.

msgsz : 메시지를 읽을 크기(더 정확히는 msgbuf의 text 크기입니다.)인데 만약 읽어들인 메시지가 지정된 크기보다 크다면 msgflg에 따라 동작이 결정됩니다. 만약 msgflg가 MSG_NOERROR라면 메시지를 읽어들이나 잘려서 읽히게 됩니다. MSG_NOERROR가 명시되어있지 않다면 메시지를 읽어오지 않고 msgrcv 시스템콜은 -1을 반환합니다.

msgtyp : 0, >0, <0으로 동작이 나뉩니다.

msgtyp == 0 큐의 첫번째 메시지를 읽어옵니다.
msgtyp > 0 그 값과 동일한 메시지 타입을 갖는 메시지를 반환합니다. 
msgtyp < 0 msgtyp의 절대값 이하의 가장 작은 메시지를 읽어옵니다.

msgflg : 4개 정도의 flag가 있습니다. 

- IPC_NOWAIT

- MSG_COPY : 리눅스 3.8이상부터 지원합니다.

- MSG_EXCEPT

- MSG_NOERROR

 

4) msgctl : 메시지큐를 제어하기 위한 시스템 콜입니다. 

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msqid : 다들 아시죠? 메시지 큐 id입니다.

cmd : 제어할 command입니다. 몇개 있는데 3개만 보도록 하겠습니다.

- IPC_STAT : buf로 메시지큐 정보를 읽어옵니다. 

- IPC_SET : buf로 메시지큐 정보를 설정합니다.

- IPC_RMID : 메시지큐를 지웁니다.

buf : 아까 위에서 보았던 구조체네요. cmd로 IPC_STAT과 IPC_SET을 사용할때 전달해주면 되고 

딱히 필요없으면 NULL을 전달합니다.

 

이제 메시지큐를 통한 예제를 볼까요?

아래의 예제는 sender에서 어떤 사람의 나이와 이름을 receiver에게 전달하는 메시지 큐 예제입니다. sender에서는 메시지큐의 정보 또한 보여주고 있습니다. 메시지 큐에 있는 message라는 구조체는 sender와 receiver가 사용하고 있으므로 헤더파일 msg_data.h를 두어 같이 사용합니다.

 

예제는 너무 간단하기 때문에 따로 설명할 필요는 없고 한번 따라 해보면 다 이해하실거에요.

 

msg_data.h

struct real_data{
        short age;
        char name[16];
};
struct message{
        long msg_type;
        struct real_data data;
};

 

sender.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include "msg_data.h"


void printMsgInfo(int msqid){

        struct msqid_ds m_stat;
        printf("========== messege queue info =============\n");
        if(msgctl(msqid,IPC_STAT,&m_stat)==-1){
                printf("msgctl failed");
                exit(0);
        }
        printf(" message queue info \n");
        printf(" msg_lspid : %d\n",m_stat.msg_lspid);
        printf(" msg_qnum : %d\n",m_stat.msg_qnum);
        printf(" msg_stime : %d\n",m_stat.msg_stime);

        printf("========== messege queue info end =============\n");
}
int main(){
        key_t key=12345;
        int msqid;

        struct message msg;
        msg.msg_type=1;
        msg.data.age=80;
        strcpy(msg.data.name,"REAKWON");

        //msqid를 얻어옴.
        if((msqid=msgget(key,IPC_CREAT|0666))==-1){
                printf("msgget failed\n");
                exit(0);
        }

        //메시지 보내기 전 msqid_ds를 한번 보자.
        printMsgInfo(msqid);

        //메시지를 보낸다.
        if(msgsnd(msqid,&msg,sizeof(struct real_data),0)==-1){
                printf("msgsnd failed\n");
                exit(0);
        }

        printf("message sent\n");
        //메시지 보낸 후  msqid_ds를 한번 보자.
        printMsgInfo(msqid);
}

 

receiver.c 

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include "msg_data.h"

int main(){
        key_t key=12345;
        int msqid;
        struct message msg;

        //받아오는 쪽의 msqid얻어오고
        if((msqid=msgget(key,IPC_CREAT|0666))==-1){
                printf("msgget failed\n");
                exit(0);
        }
        //메시지를 받는다.
        if(msgrcv(msqid,&msg,sizeof(struct real_data),0,0)==-1){
                printf("msgrcv failed\n");
                exit(0);
        }

        printf("name : %s, age :%d\n",msg.data.name,msg.data.age);

        //이후 메시지 큐를 지운다.
        if(msgctl(msqid,IPC_RMID,NULL)==-1){
                printf("msgctl failed\n");
                exit(0);
        }
}

 

두개의 c파일을 컴파일 해줍시다.

# gcc sender.c -o sender
# gcc receiver.c -o receiver

 

이제 sender부터 실행하도록 합시다.

# ./sender
========== messege queue info =============
 message queue info
 msg_lspid : 0
 msg_qnum : 0
 msg_stime : 0
========== messege queue info end =============
message sent
========== messege queue info =============
 message queue info
 msg_lspid : 5129
 msg_qnum : 1
 msg_stime : 1586521322
========== messege queue info end =============

 

메시지 큐의 정보가 변경된 것을 알 수있네요. 메시지가 성공적으로 보내졌나봅니다.

ipcs를 구경해볼까요?

# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0x00003039 5          root       666        18

 

0x3039는 우리가 정한 key 12345입니다. sender 프로그램이 끝나고 메시지 큐는 아직 남아있습니다. 

이제 receiver를 실행해보도록 하지요.

# ./receiver
name : REAKWON, age :80

 

음, 잘 읽히네요. 한번더 receiver를 실행하면 아무런 동작을 하지 않습니다. 메시지큐에 아무것도 없기 때문입니다. ipcs를 들여다보면 메시지큐가 삭제되었네요. 그 이유는 receiver 코드를 보면 msgctl에서 cmd를 IPC_RMID를 주어 제거했기 때문입니다. 

# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

 

이상으로 메시지큐와 관련한 포스팅을 마치도록 하겠습니다. 긴글 읽어주셔서 감사합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,