Linux Programmer

리눅스에서 XSI와 POSIX 메시지큐 비교 본문

컴퓨터 관련/프로그래밍 일반

리눅스에서 XSI와 POSIX 메시지큐 비교

sunyzero 2012. 10. 22. 19:35

리눅스나 유닉스에서 사용되는 메시지큐는 XSI와 POSIX 두가지 방식이 있다. 이 두 메시지큐의 모델과 프로그래밍 방식에 대해 간략하게 살펴보자.


1. 유닉스 표준과 IPC

현재 유닉스 계열(리눅스 포함)은 OpenGroup의 SUS(Single UNIX Specification) 표준을 따르고 있다. SUS는 기존에 나왔던 유닉스 계열의 다양한 표준인 POSIX, SVR4(SysV Release4), BSD 등의 표준을 통합했기 때문에 같은 기능이 중복된 경우가 꽤 있다.


그 중에서도 IPC(Inter-Process Communication) 기법이 대표적이다. IPC는 좁은 의미로는 세마포어, 공유메모리, 메시지큐를 다루고 넓은 의미로는 파일, 소켓, 메모리맵 등을 포함한다. 하지만 일반적으로 IPC라 하면 좁은 의미로 사용되는 경우가 많으니 여기서도 좁은 의미로만 다루겠다.


2. XSI와 POSIX 표준의 차이점

SUS표준에 속한 IPC 기법은 크게 XSI(X System Interface)라 불리는 과거 SVR4로부터 물려받은 구식의 방법과 POSIX 표준으로 새롭게 지정되었던 POSIX 방식이 있다. XSI는 과거 SysV방식이라고 불리었으며 SVR4가 릴리즈된 1998년도에 등장했으며, POSIX IPC는 2001년도 POSIX에 추가된 기법이다. 


연도만 보면 13년 뒤에 등장한 POSIX의 신형 IPC가 더 좋아야 하겠지만 표준안에는 성능에 관한 제약이 없었기 때문에 구식의 XSI IPC가 성능이 좋은 경우도 많았다. (하지만 지금은 새로운 POSIX IPC계열이 점차 개량되어 성능이 좋아지고 있다.)


두 방식의 가장 큰 차이점은 POSIX 방식에서는 파일기술자(file descriptor) 형식를 사용하는 통일된 시스템콜 방식을 채택하고 있다는 점이다. POSIX는 원래 가상화된 모든 자원을 기술자(descriptor)로 표기하는 방식을 띄기 때문에 모든 입출력의 형태가 마치 파일에 입출력 하는 방식과 흡사하다. 이는 개발자에게도 직관적으로 인식될 수 있으므로 많은 부분에서 유리하다.


실제로 일반 파일, 파이프, 소켓, 메시지큐, 메모리맵, 공유메모리 등 모든 입출력이 공통적인 형태를 가지고 있음은 예를 보면 알 수 있다. 아래 그림에 일반 메모리맵과 공유 메모리가 같은 입출력 형태를 가지고 있는 것을 볼 수 있다.


mmap vs posix shmmmap vs posix shm (출처 : 리눅스 시스템 네트워크 프로그래밍 2nd. ed)


이에 비해 XSI 방식은 키(key)를 이용해서 ID값을 얻어내어 접근하는 방식을 가지고 있다. 키값과 ID값을 매칭시키는 방식을 채택한 이유는 초기 설계자들이 고정된 주소값을 키값으로 하여 ID를 매칭시키는 것에서 영감을 얻었던 것이라고 생각된다. 더군다나 당시에는 가상화된 자원이나 장치에 대한 설계가 미흡했던 시기라서 그랬을 것이다.


아래 그림을 보면 XSI 방식의 어떻게 키값과 ID값으로 자원에 접근하는지 볼 수 있다. 실제로 보면 매우 복잡해 보이지만 사실 숙달되고나면 POSIX 방식보다 편리한 경우도 많다.


XSI IPCXSI IPC (출처 : 리눅스 시스템 네트워크 프로그래밍 2nd. ed)



3. 메시지큐의 비교

앞서 살펴본 것처럼 두가지 IPC 표준은 형태가 다르지만 사용목적이나 방식에서 큰 차이는 없다. 하지만 메시지큐의 경우는 모델 설계에서 약간의 차이가 있다는 것을 알아야 한다. 그 이유는 바로 POSIX 메시지큐에서 추가된 이벤트 감지 기능 때문이다.


XSI방식은 msgsnd, msgrcv 함수를 사용하여 송수신을 한다. XSI함수는 기본적으로 송수신시 메시지가 도착했는지 여부를 알 수 없기에 블록킹 모드에서 계속 대기하는 형태로 프로그래밍 한다. 물론 넌블록킹 모드로 시도할 수 있지만 프로그래밍이 복잡해지고 오버헤드가 심해질 수 있다. 그래서 XSI 메시지큐를 사용하는 대부분의 경우에 블록킹 모드에서 무한 대기하다가 메시지가 수신되면 깨어나서 작업하도록 프로그래밍 한다. 그 결과 단조로운 입출력 형식을 지니는 프로그래밍이 대부분이었다.


POSIX 방식도 블록킹 모드를 사용한다면 XSI방식과 비슷하게 작성한다. 하지만 POSIX 방식에서는 mq_notify라는 함수를 이용하면 메시지가 도착했을 때 이벤트를 감지할 수 있다. 그리고 이벤트가 발생하면 비동기적으로 실행할 코드를 등록할 수 있다. 이는 응답이 빠른 구조를 설계할 때 큰 이득이 된다.


참고로 mq_notify는 메시지가 수신되었을 때 리얼타임 시그널을 발생시키거나 메시지를 처리할 쓰레드를 생성시키는 것이 가능하다. 


이 외에 리눅스에서만 가능한 비표준이긴 하지만 POSIX 메시지큐의 파일기술자를 epoll이나 poll에 등록시켜두면 메시지가 도착했는지 감지할 수 있다. 이를 통해 소켓과 POSIX 큐를 한꺼번에 감시할 수 있다. 


아래는 epoll과 POSIX 메시지큐를 사용하는 간단한 예이다. 중요한 코드 부분만 보이기 위해 main 함수나 epoll, 메시지큐를 생성하는 부분은 제거했으므로 적당히 살을 붙이면 된다.


    struct  mq_attr mq_attrib;
    char    *p_buf;
    mq_getattr(mq_fd, &mq_attrib);
    if ( (p_buf = malloc(mq_attrib.mq_msgsize)) == NULL) { 
        /* error */
    }

    /* mq_fd = 메시지큐 fd, epoll_fd = epoll fd 라고 가정 */
    struct epoll_event ev, events[10];
    ev.events = EPOLLIN;
    ev.data.fd = mq_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mq_fd, &ev) == -1) {
        /* error */
    }
    int i, nfds;
    while(1) {
        if ( (nfds = epoll_wait(epoll_fd, events, 10, -1)) == -1) {
            /* error */
        }
        for (i=0; i<nfds; i++) {
            if ((n_recv = mq_receive(mq_fd, p_buf, mq_attrib.mq_msgsize, NULL)) == -1) {
                perror("FAIL: mq_receive()");
            }
            printf("+ Recv(%.*s)\n", n_recv, p_buf);
        }
    }



4. 결론

멀티코어가 난무하는 시대에 다수의 CPU 코어에 작업을 분배하는 것은 매우 중요한 일이다. 이를 위해서 쓰레드를 사용할 수도 있지만 일단은 메시지별로 분류하여 다양한 프로세스에 작업을 분배하는 것이 더 좋은 방법이다. 실제로 과거부터 금융권이나 대용량 데이터 처리 서버에서는 메시지큐를 써왔다. 10여년 전에는 XSI방식이 많이 쓰였으나 지금은 점차 POSIX 방식으로 기울어지고 있다.


하지만 레거시 시스템을 사용하는 경우도 많기 때문에 아직은 두가지 방식을 모두 알아둬야만 한다.

반응형
Comments