시스템 V IPC (System V) 1 -Infinite
CS/시스템 프로그래밍 2022. 6. 21. 01:21

시스템 V IPC (System V) 1 -Infinite

@Beemo9
목차

이번 포스팅은 System V IPC 기법들에 대하여 알아보도록 하겠습니다.

System V에서는 메시지 큐, 공유 메모리 기법을 통해 프로세스 간 통신을 진행 할 수 있습니다.

독립적인 프로세스들이 서로 통신을 함에 있어 Critical section(중요한 데이터 영역)에 동시에 진입할 경우 Race Condition(충돌)이 발생 할 수 있습니다.  이를 막기위해 독립적인 프로세스들은 동기화 과정이 필요한데 세마포어가 해결을 도와줄 수 있습니다. -> 세마포어는 단순히 메모리 영역에 접근할 수 있는 프로세스들을 알려주는 역할이며 이는 통신기법이 아닌 동기화기법에 해당합니다.

또한 이번 챕터에서 다룰 통신기법들은 각각 공통된 키 생성 -> 키를 통한 식별자 생성 과정을 거친 후 제공되는 함수들을 사용하여 통신을 할 수 있습니다.

● System V

-> 시스템 V에서 제공되는 프로세스 간 통신 방법으로는 메시지 큐, 공유 메모리, 세마포어 3가지가 있습니다.시스템 V IPC를 이용하려면 IPC 객체를 생성해야 합니다. 이를 위해 공통으로 사용되는 요소로는 키와 식별자가 있습니다.키를 생성하는 방법에는 두가지 방식이 있습니다.

1. ftok(*pathname, int proj_id) // 임의의 파일 경로명, 임의의 번호(1~255)

2. IPC_PRIVATE // 임의의 키 값

같은 키로 생성된 식별자만 통신에 사용이 가능함으로 미리 정해진 키를 서버와 클라이언트 프로세스가 공유할 수 있게 해줘야 합니다.

● 메시지 큐

-> 메시지 큐는 파이프와 유사합니다. 단, 파이프는 스트림 기반으로 동작하고, 메시지 큐는 메시지 단위로 동작 합니다.

-> 각 메시지에는 메시지 유형이 있으므로 수신 프로세스는 어떤 유형의 메시지를 받을 것인지 선택할 수 있습니다.

메시지 큐에서 제공되는 함수

- 식별자 생성 : msgget(key, msgflg); // key는 사전에 생성한 key값, msgfla는 속성 플래그이며, 보통 IPC_CREAT를 사용합니다.

- 메시지 전송 : msgsnd(식별자, 메시지 버퍼주소, 메시지 크기, msgflg);

- 메시지 수신 : msgrcv(식별자, 메시지 버퍼주소, 버퍼 크기, 메시지 유형, msgflg);

- 메시지 제어 : msgctl(식별자, 수행할 기능, 메시지 큐 구조체 주소) // 보통 IPC 객체를 해제할 때 주로 사용합니다.

msgfla에는 블록 모드(0) / 비블록 모드(IPC_NOWAIT)가 있으며 둘의 차이는 메시지 큐가 가득 차있을 경우 기다리느냐 / 오류를 바로 리턴 하느냐의 차이입니다.

또한 메시지를 담고 있는 메시지 버퍼는 msgbuf 구조체를 사용하여 정의를 합니다. 이 구조체에는 int mtype(메시지 유형), char mtext[n](메시지 내용이 저장될 메모리) 두 개의 변수가 존재합니다.

● 메시지 큐 예제

이렇게 제공되는 각각의 함수를 눈으로만 보면 이해가 잘 되지 않을테니 예제를 통해 확인을 해보도록 합시다.

아래 예제는 send측에서 메시지 큐를 생성하여 메시지를 담고, receive 측에서는 각각의 메시지 유형에 맞는 메시지만을 수신하는 프로그램입니다.

/* msgq_send.c */
include <sys/msg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// 메시지 버퍼 구조체
struct mymsgbuf {
        long mtype;
        char mtext[80];
};

int main(int argc, char * argv[]) {
        key_t key;
        int msgid, i;
        long mtype;
        struct mymsgbuf mesg;

        // 키 생성과 동시에 메시지 큐 식별자 생성
        key = ftok("keyfile", 1);
        msgid = msgget(key, IPC_CREAT|0644);

        if (msgid ==-1) {
                perror("msgget");
                exit(1);
        }
        i = atoi(argv[2]); // 커맨드라인으로 메시지 유형값을 받아옴
        if(i%2 == 0) { // 받아온 값이 짝수라면
                mesg.mtype = 4;
        } else { //받아온 값이 홀수라면
                mesg.mtype = 3;
        }
        strcpy(mesg.mtext, argv[1]); //mesg 구조체의 mtext멤버에 argv값을 copy
        //메시지 송신
        if (msgsnd(msgid, (void *)&mesg, 80, IPC_NOWAIT) ==-1) {
                perror("msgsnd");
                exit(1);
        }
        return 0;
}

위의 소스코드는 메시지를 송신하는 프로세스입니다. 메시지 유형을 실행할 때 받아오도록 구현하였습니다.

/* msgq_recv_even.c */
// 메시지 유형이 짝수인 경우에만 수신하는 프로세스입니다.

#include <sys/msg.h>
#include <stdlib.h>
#include <stdio.h>

// 메시지 버퍼 구조체
struct mymsgbuf {
        long mtype;
        char mtext[80];
};

int main(int argc, char* argv[]) {
        struct mymsgbuf inmsg;
        key_t key;
        int msgid, len;
        int x;
        // 수신측 또한 동일한 키를 생성하고 식별자를 얻음
        key = ftok("keyfile", 1);
        if ((msgid = msgget(key, 0)) < 0) {
                perror("msgget");
                exit(1);
        }
        // 메시지 유형이 4인 메시지만을 수신. IPC_NOWAIT로 메모리가 비어있을 경우 기다리지 않고 -1을 return
        if(len = msgrcv(msgid, &inmsg, 80,4 , IPC_NOWAIT) == -1) {
                msgctl(msgid, IPC_RMID, (struct msgid_ds*)NULL); //msgctl을 이용하여 IPC 객체를 삭제
                exit(0);
        } else { // 메시지 수신이 된 경우
                printf("Received Msg = %s, Len=%d\n", inmsg.mtext, len);
        }
        msgctl(msgid, IPC_RMID, (struct msgid_ds*)NULL);

        return 0;
}

 

/* msgq_recv_odd.c */
// 메시지 유형이 홀수인 경우에만 수신하는 프로세스입니다.

#include <sys/msg.h>
#include <stdlib.h>
#include <stdio.h>

struct mymsgbuf {
        long mtype;
        char mtext[80];
};

int main(int argc, char* argv[]) {
        struct mymsgbuf inmsg;
        key_t key;
        int msgid, len;
        int x;
        key = ftok("keyfile", 1);
        if ((msgid = msgget(key, 0)) < 0) {
                perror("msgget");
                exit(1);
        }
        // 메시지 유형이 3인 경우에만 수신.
        len = msgrcv(msgid, &inmsg, 80, 3, 0);
        printf("Received Msg = %s, Len=%d\n", inmsg.mtext, len);
        
        // 수신이 끝난 뒤 메모리 해제
        msgctl(msgid, IPC_RMID, (struct msgid_ds*)NULL);

        return 0;
}

이제 위의 소스코드에 대한 결과를 확인해 보도록 합시다.

짝수 예제 결과값

위 결과값은 짝수 메시지 유형으로 ByeBye라는 메시지를 송신한 뒤 수신하는 결과값입니다.

홀수 예제 결과값

위 결과값은 홀수 메시지 유형으로 Hello라는 메시지를 송신한 뒤 수신하는 결과값입니다.

이렇게 메시지 큐를 이용한 예제를 확인하면서 볼 수 있듯 메시지 큐는 단방향이며, 그때 그때 메시지 큐를 생성한 뒤 메시지를 주고 받습니다.  이는 Message Passing 방법의 일종이며 특징으로는 동기화 과정이 필요없지만 메시지 큐 생성 및 메시지 copy로 인한 추가적인 오버헤드가 발생하여 Shared Memory에 비해 효율적이진 못하다는 점입니다.

다음 포스팅에서는 Shared Memory와 Message Passing에 관한 차이를 간단하게 알아보고 Shared Memory에 대한 소개를 드리려 합니다.

'CS > 시스템 프로그래밍' 카테고리의 다른 글

Semaphore(System V vs POSIX) -Infinite  (0) 2022.06.21
시스템 V IPC (System V) 2 -Infinite  (0) 2022.06.21
파이프(PIPE) -Infinite  (0) 2022.06.20
시그널(signal) -Infinite  (0) 2022.06.20
쓰레드(Thread) -Infinite  (0) 2022.06.20
Beemo9
@Beemo9
개발 기술 블로그, Dev 포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!
image