Researcher to Developer

IPC 기법 관련 시스템 콜 - pipe, 메세지 큐 본문

카테고리 없음

IPC 기법 관련 시스템 콜 - pipe, 메세지 큐

Probe29 2021. 1. 2. 17:39

#IPC

Inter Process Communication

 

Process는 기본적으로 서로 통신할 수 있는 방법이 없다.

둘 다 저장 매체에 접근할 수 있기 때문에 

파일을 통해 공유할 수 도 있지만 실시간 공유의 어려움이 있어 추천되지는 않는다.

그래서 등장하게 된 것이 IPC 기법이다.

 

기본 개념은 이러하다.

Process는 커널 공간을 공통적으로 가지고 있고 그렇기 때문에

물리 메모리의 특정한 주소를 공유할 수 있다.

이것이 가능하게 된 근거는

각 페이지 테이블의 커널 공간에 해당되는 주소가 둘 다 동일한 위치를 가리키도록 해놓으면

실제 메모리 상으로 공유가 가능하다.

 

즉, 커널 공간은 공유가 가능하다는 것을 기반으로 다양한 IPC 기법이 나오게 되었다.

 

IPC 기법 중 pipe와 message queue는 모두 커널 공간의 메모리를 사용(공유)합니다.

 

 

 

 

#1. pipe 파이프

파이프는 단방향 통신이다. (부모 → 자식)

fork () 로 자식 프로세스를 만들었을 때 부모로부터 자식에게로 통신하는 방법이다.

 

parent      →  fork ()  → child

fd[1] write →  pipe    →  fd[0] read

 

파이프에 대한 포인터값을 가지고 fork할 때 반드시 부모에서 자식으로 보낼 수만 있다.

 

code 예제는 다음과 같다.

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

char* msg= "Hello Child Process!";
int main()
{
    char buf[255];                                       //255개 배열
    int fd[2], pid, nbytes;                              // fd[2] 는 fd[0], fd[1] 을 가질 수 있다는 의미
    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);            // 커널 영역 - 부모에서 write () 을 사용해서 fd[1]에 msg를 씁니다. 
         exit(0);
    }
    else {                                                    // 자식 프로세스에는 pid 값이 0이됨
         printf("child PID:d\n", getpid());
         nbytes = read(fd[0], buf, MSGSIZE);        // 자식에서는 read를 하는데 fd[0]으로 읽음
         printf("%d %s\n", nbytes, buf);
         exit(0);
   }
   return 0;
}

 


 

 

#2. 메세지 큐(message queue)

큐니까, 기본은 FIFO 정책으로 데이터 전송
파이프 처럼 부모 자식 관계일 필요가 없다. → 양방향 통신이 가능하다.

 

msqid = msgget(key, msgflg)    // key는 1234, msgflg는  옵션
                                          // msgget 이라는 함수로 메세지 큐를 만들어야함. 
                                          // msgflg 에는 메세지 큐를 생성할 때 옵션을 줄 수 있는 항목이다.




#msgflg 설정
IPC_CREAT : 새로운 키라면 식별자를 새로 생성, IPC_CREATI 접근 권한 (대표적인 사용하는 옵션)

 

사용은 이렇게 한다.

 

IPC_CREAT|0655 → rw-r--r--  ( | : or 라는 의미, 생성하고 접근 권한을 줄 수 도 있다는 의미)

 


#메세지 큐를 전송할 때 사용하는 시스템 콜
msgsnd
msgsnd(msqud, &sbuf, buf_length, IPC_NOWAIT)
msgflg 설정 : 블록 모드 (0) / 비블록 모드(IPC_NOWAIT)

 

 

메세지 큐 전송 프로그램 일부 코드 예제는 다음과 같다.

msqid = msgget(1234, IPC_CREAT|0644)                       // key는 1234, msgflg는 옵션
msgsnd(msqid, &sbuf, buf_length, IPC_NOWAIT)

 


#메세지 큐를 수신할 때 사용하는 시스템 콜
ssize_t msgrcv(int msqid, void *msqp, size_t msgsz, ling msgtyp, int msgflg)
msgrcv(msqid, &rbuf, MSGSZ, 1, 0) //msgrcv 예

 


#메세지 타입 설정

msgtyp 설정 : 0이면 첫번째 메세지, 양수이면 타입이 일치하는 첫번째 메세지
msgflg 설정 : 블록 모드(0) / 비블록 모드(IPC_NOWAIT)

 

 


메시지 큐 수신 프로그램 일부 코드 예제는 다음과 같다.

msqid = msgget(1234, IPC_CREAT|0644)         // key는 동일하게 1234로 해야 해당 큐의 msgid를 얻을 수 있다.
msgrcv(msqid, &rbuf, MSGSZ, 1, 0)

 

 

메세지 큐 사용 예를 code로 확인해보자.

 

먼저 vi msgqueuesnd.c 입력 후 다음의 코드를 작성했다.

 

 

 

 

#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; 
        ket_t key = 1234; 
        msgid = msgget(key, IPC_CREAT|0644); 
        if(msgid == -1) { 
                perror("msgget"); 
                exit(1); 
        }

        msg.type = 1;                                                                // 타입을 1로 넣고

        strcpy(msg.text, "Hello Message Queue\n"); 
        if(msgsnd(msgid, (void*)&msg, 50, IPC_NOWAIT) == -1) {        // 50 size 만큼만 데이터를 전송 
                        perror("msgsnd"); 
                        exit(1); 
                } 
        return 0;

}

그 다음 msgqueue 를 받는 코드를 입력해야 한다.

 

vi msgqueuercv.c 입력 후 다음의 코드를 입력해준다.


#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;
        ket_t key = 1234;
        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;
}

이렇게 코드를 작성해준 다음

 

msgqueuesnd.c 
msgqueuercv.c
파일을 컴파일 해주고 

msgqueuesnd 실행을 하면 화면에 아무것도 출력되지 않는다.
그 이유는 일단 1234 키에 해당하는 메세지 큐는 커널 영역에 만들어 졌고 출력할 내용이 없기 때문이다.
그 상태에서 msgqueuercv 를 실행하면
Received Message is [50] Hello Message Queue 라고 출력된다.
해당 커널 영역은 프로그램이 꺼져도 공유하기 때문에 물리 메모리에 존재한다.
그 커널 영역의 특정 메세지 큐에는 우리가 작성한 데이터가 있는 것
프로세스를 실행하고 메세지 큐의 아이디를 가지고 해당 주소로 접근하면 
그곳에 있는 데이터를 읽을 수 있는 것
msgqueuesnd, msgqueuercv는 서로 다른 프로그램이지만 이렇게 통신이 가능하다.

 

 

msgctl
message를 control 하는 명령어

msgctl(msgid, IPC_RMID, 0);
해당 ID를 가진 메세지큐를 삭제하는 명령

 

 

 

 

#ftok()
키생성을 위한 함수
path 경로명의 inode 값과 숫자값(id)를 기반으로 키를 생성한다
경로 삭제 후 재생성시 inode 값이 달라지므로, 이전과는 다른 키값이 리턴된다.

#include <sys/ipc.h>
key_t ftok(const char *path, int id);

위 두 줄을 레퍼런스로 아래 코드를 작성할 수 있다.

key = ftok("keyfile", 1);     
id = msgget(key, IPC_CREAT|0640);
                          // 해당 디렉토리와 , 숫자값으로 조합을 해서 유니크한 키값을 생성한다. 그 값을 가지고 메                               세지 큐를 생성할 수 있다.