다중 입출력 - poll()

poll() 시스템콜은 유닉스 운영체제의 최초 상용화 버전 중 하나인 'Unix System V'에서 제공하는 다중 입출력 방식이었습니다. 리눅스에서 제공하고 있던 select() 시스템콜보다 더 좋았던 까닭에 리눅스에서도 poll() 시스템콜을 도입하였습니다. select() 시스템콜의 단점을 보완한 poll() 시스템콜이지만, 기존에 select()로 다중 입출력을 구현했던 개발자의 습관과 타 시스템으로의 이식성을 이유로 덜 사용된다고 합니다.

#include <poll.h>

int poll(struct pollfd* fds, nfds_t nfds, int timeout);

struct pollfd {
    int fd;         // 파일 디스크립터
    short events;   // 감시할 이벤트
    short revents;  // 발생한 이벤트
}

struct pollfd* fds: 감시하고자 하는 파일 디스크립터와 이벤트를 설정한 후 넘겨줍니다.

nfds_t nfds: 그럼 첫 번째 매개변수로 설정한 구조체 1개만 넘겨줄 수 있느냐? 그렇지 않습니다. poll() 시스템콜은 단일 pollfd 구조체 배열을 nfds 개수만큼 사용합니다. 즉 몇 개의 구조체 배열을 넘겼는지 알려줍니다.

int timeout: 해당 시간만큼 poll() 시스템콜은 작동합니다.

 

 

select() 시스템콜은 '읽기', '쓰기', '예외' 3가지를 독립적으로 설정한 후, 매개변수로 넘겨줘야했습니다. 그에 반면, poll() 시스템콜은 구조체 배열을 사용함으로써 설계상으로도 탐색 시간상으로도 훨씬 더 좋아졌다는걸 알 수 있습니다. pollfd 구조체 멤버를 보면 감시할 이벤트를 설정할 수 있는 events 필드가 있습니다. 이 필드를 설정한 후 poll() 시스템콜을 작동시키면 등록한 이벤트 중 발생한 이벤트가 revents 필드에 설정됩니다. revents 필드는 등록한 이벤트 중 발생된 이벤트 정보를 커널이 설정해주므로 사용자는 해당 필드를 통해 확인할 수 있습니다. 그럼 events 필드에 등록할 수 있는 이벤트는 어떤 것들이 있을지 살펴보겠습니다.

  • POLLIN - 읽을 데이터가 존재한다. 즉, 읽기가 블록(blokc)되지 않는다.
  • POLLRDNORM - 일반 데이터를 읽을 수 있다.
  • POLLRDBAND - 우선권이 있는 데이터를 읽을 수 있다.
  • POLLPRI - 시급히 읽을 데이터가 존재한다.
  • POLLOUT - 쓰기가 블록(block)되지 않는다.
  • POLLWRNORM - 일반 데이터 쓰기가 블록(block)되지 않는다.
  • POLLWRBAND - 우선권이 있는 데이터 쓰기가 블록(block)되지 않는다.
  • POLLMSG - SIGPOLL 메시지가 사용 가능하다.

 

그리고 revents 필드에는 다음 이벤트가 설정될 수 있습니다.

  • POLLER - 주어진 파일 디스크립터에 에러가 있다.
  • POLLHUP - 주어진 파일 디스크립터에서 이벤트가 지체되고 있다.
  • POLLNVAL - 주어진 파일 디스크립터가 유효하지 않다.
#include <stdio.h>
#include <unistd.h>
#include <poll.h>

#define TIMEOUT 5

int main() {
    struct pollfd fds[2];
    int ret;
    
    // 표준 입력에 대한 이벤트를 감시하기 위한 준비를 합니다.
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    
    // 표준 출력에 쓰기가 가능한지 감시하기 위한 준비를 합니다.
    fds[1].fd = STDOUT_FILENO;
    fds[1].events = POLLOUT;
    
    // 위에서 pollfd 구조체 설정을 모두 마쳤으니 poll() 시스템콜을 작동시킵니다.
    ret = poll(fds, 2, TIMEOUT * 1000);
    
    if (ret == -1) {
        perror("poll");
        return 1;
    }
    
    if (!ret) {
        printf("%d seconds elapsed.\n", TIMEOUT);
        return 0;
    }
    
    if (fds[0].revents & POLLIN) printf("stdin is readable\n");
    if (fds[1].revents & POLLOUT) printf("stdout is writeable\n");
    
    return 0;
}