본문 바로가기
컴퓨터공학기초 개념/시스템 프로그래밍

26. 프로세스 - IPC 기법(pipe, message queue)

by devraphy 2021. 9. 17.

0. 시작하기전에 

- 운영체제에서 배운 IPC 기법을 간단하게 복습해보자.


1. IPC 기법이란

- IPC 기법은 프로세스 간의 통신을 위한 기술이다. 

- 프로세스 교체 등의 이유로 한 프로세스에서 다른 프로세스의 상태를 확인해야 하는데, 

- 보안상의 이유로 프로세스는 다른 프로세스의 데이터에 접근할 수 없고, 프로세스 간의 통신은 불가능하다. 

- 그러므로 일종의 우회 방식으로 프로세스간의 통신을 가능하게 하는 기술이 IPC 기법이다. 


2. IPC 기법의 종류

a) File 기법 

- 프로세스는 저장매체에 접근이 가능하므로, 각 프로세스의 상태를 기록하는 공유 파일을 통해 통신하는 방식이다.

- 저장매체에 접근하는데 시간이 오래걸리며, 공유 파일이 업데이트 되더라도 다른 파일은 이를 알 수 있는 방법이 없다.

- 그러므로 잘 사용되지 않는 IPC 기법이다. 

 

 

b) 커널 공간을 사용하는 IPC 기법 

- 프로세스의 커널 공간은 모두 공유된 영역이다. 즉, 커널영역은 동일한 물리메모리의 주소를 가리킨다. 

- 이 커널공간을 활용하는 방식에 따라 IPC 기법이 다양하게 분류된다. 

- 여기에는 message queue, pipe, shared memory, semaphore, signal, socket 등의 기법이 있다. 

 


3. Pipe 기법

- 단방향 통신을 이용한 기법

- fork()로 자식 프로세스를 생성했을 때, 부모 프로세스의 데이터가 자식 프로세스에게 그대로 복사된다. 

- 이 복사된 데이터를 이용한 단방향 통신방법을 pipe 기법이라 한다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MSGSIZE 255

char* msg = "Hello Child Process!";
int main()
{
        char buf[255];
        int fd[2], pid, nbytes;
        if(pipe(fd) < 0) // pipe(fd)로 파이프 생성
                exit(1);
        pid = fork(); // 이 함수 실행 다음 코드부터 부모/자식 프로세스로 나뉨
        if (pid > 0) { // 부모 프로세스에는 자식프로세스의 pid값이 들어감
                printf("parent PID: %d, child PID: %d\n", getpid(), pid);
                write(fd[1], msg, MSGSIZE); // fd[1]에 write한다.
                exit(0);
        }
        else { // 자식프로세스에는 pid값이 0이 됨
                printf("child PID: %d\n", getpid());
                nbytes = read(fd[0], buf, MSGSIZE); // fd[0]을 읽음
                printf("%d %s\n", nbytes, buf);
                exit(0);
        }
        return 0;
}

 

▶ 실행결과


4. Message Queue

- Queue 자료구조를 사용하므로 FIFO 방식으로 프로세스의 우선순위를 지정하는 방식이다. 

- 다음 예제 코드를 통해서 message queue가 어떻게 구현되는지 알아보자. 

 

a) msgget()

- msgget() 함수를 통해서 message queue를 생성한다.

- key 값은 해당 메세지 큐를 구분할 수 있는 고유값을 입력한다.

 

- msgflg()에는 메세지 큐를 생성할 때 부여할 여러가지 옵션을 입력한다. 

- 옵션에는 대표적으로 IPC_CREAT가 있으며, 이는 입력된 key값이 새로운 고유값인 경우, 메세지 큐를 새로 생성하는 기능이다.

- IPC_CREAT 옵션은 |(or) 기호를 사용하여 메세지큐의 권한까지 설정할 수 있다.

 

 

b) msgsnd()

- msgsnd() 함수는 메세지 큐를 전송할 때 사용하는 함수다.

- msqid는 위에서 생성된 메세지 큐의 id를 사용한다. 

 - 나머지 인자는 메세지큐에 담을 데이터를 의미한다. 

 

- IPC_NOWAIT 옵션은 수신자가 메세지를 읽지 않더라도 기다리지 않고 다음 코드를 실행하는 기능이다. 

- IPC_NOWAIT 옵션을 사용하지 않고 0을 입력하면 메세지를 읽을 때까지 대기상태가 된다.

 

 

▶ 메세지큐 전송코드 예시

 

 

c) msgrcv()

- 메세지 큐를 수신할 때 사용하는 함수

- msgtyp는 메세지의 타입을 설정하는 부분이다. 

- 이 부분에 대해서는 아래의 실습예제에서 자세히 설명할 예정이다. 

 

 

▶ 메세지큐 수신코드 예시

- 각 메세지 큐는 고유번호를 갖고 있다. 

- 그러므로 어떤 메세지 큐의 메세지를 수신한다면, 해당 메세지 큐의 msqid를 입력해서 직접 수신 받아야 한다. 

 

 

d) message queue 실습예제

- 아래의 코드로 message queue를 작성하여 실습해보자. 

 

▶ 메세지 큐 전송 프로그램 

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

// 메세지 타입을 설정하는 부분
typedef struct msgbuf { 
        long type;
        char text[50];
} MsgBuf;

int main(void) {
        int msgid, len;
        MsgBuf msg;
        key_t key = 1234;
        msgid = msgget(key, IPC_CREAT|0644); // 메세지 큐 생성 

        if(msgid == -1) { // 메세지 큐가 생성이 안된 경우
                perror("msgget");
                exit(1);
        }

        msg.type = 1;
        strcpy(msg.text, "Hello Message Queue!\n");

        if(msgsnd(msgid, (void *)&msg, 50, IPC_NOWAIT) == -1) { // 메세지 큐 전송 실패
                perror("msgsnd");
                exit(1);
        }
        return 0;
}

 

 

▶ 메세지 큐 수신 프로그램 

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

typedef struct msgbuf { // 메세지 큐 전송 프로그램과 동일한 메세지 타입을 생성 
        long type;
        char text[50];
} MsgBuf;

int main(void) {
        MsgBuf msg;
        int msgid, len;
        key_t key = 1234; // 메세지 큐 전송 프로그램과 동일한 메세지 id 사용 
        if((msgid = msgget(key, IPC_CREAT|0644)) < 0) { // 메세지 키도 동일하게 생성
                perror("msgget");
                exit(1);
        }
        len = msgrcv(msgid, &msg, 50, 0, 0); // 위에서 입력한 키값을 가진 메세지 큐를 수신 
        printf("Received Message is [%d] %s\n", len, msg.text);
        return 0;
}

 

- msgrcv() 함수는 msgtyp을 인자로 받는다. 

- 위의 예시를 보면, msgrcv()에서 msgtyp의 값으로 0을 입력받았다. 

- 다음 그림을 보면서 이해해보자. 

- 위의 그림과 같이 메세지 큐에는 메세지들이 들어있다. 

- msgtyp를 0으로 입력하면 가장 나중에 입력된 메시지 A가 수신되는 것이다. 

- msgtyp를 0이 아닌 다른 수를 입력하면, 해당 타입에 매칭되는 메세지가 수신된다. 

- 예를 들어서, msgtyp를 1로 한다면 D가 가장 먼저 수신된다. 

 

 

▶ 메세지 큐 전송/수신 결과

- message queue 전송 프로그램을 먼저 실행한 후에 수신 프로그램을 실행해야 올바르게 작동한다. 

- message queue를 전송하는 프로그램을 실행하면 커널영역에 메세지큐가 생성되어 메모리 상에 존재하는 것이다. 

- message queue를 수신하는 프로그램을 실행하면 커널영역에 존재하는 메세지큐를 읽어오는 것이다. 

 

 

e) 결론

- 이와 같이 메세지 큐 함수를 이용하여 메세지를 보낼 수 있다. 

- 이를 응용하면 하나의 프로세스 내부에서 스스로에게 메세지를 전송할 수도 있다. 

- 메세지 큐는 전송부와 수신부로 나뉘기에, 양방향 통신이 가능한 것이 장점이다. 

 


5. 참고자료 

- 키를 생성하는 방법 중 한가지를 소개한다.  

- ftok()는 어떤 파일 또는 디렉토리의 inode값을 이용하여 고유한(unique) 키를 생성하는 함수다. 

 

댓글