NAME

poll, ppoll - 파일 디스크립터에서 어떤 이벤트 기다리기

SYNOPSIS

#include <poll.h>

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

#define _GNU_SOURCE         /* feature_test_macros(7) 참고 */
#include <signal.h>
#include <poll.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
        const struct timespec *tmo_p, const sigset_t *sigmask);

DESCRIPTION

poll()select(2)와 비슷한 작업을 한다. 즉 한 파일 디스크립터라도 I/O를 수행할 수 있는 상태가 되기를 기다린다. 리눅스 전용인 epoll(7) API도 비슷한 작업을 하며 poll()에서 볼 수 있는 것 이상의 기능을 제공한다.

감시할 파일 디스크립터들의 집합을 fds 인자에서 지정하는데, 다음과 같은 구조체의 배열이다.

struct pollfd {
    int   fd;         /* 파일 디스크립터 */
    short events;     /* 요청한 이벤트 */
    short revents;    /* 반환된 이벤트 */
};

fds의 항목 개수를 호출자가 nfds에 명시해야 한다.

fd 필드는 열린 파일에 대한 파일 디스크립터를 담는다. 이 필드가 음수이면 대응하는 events 필드를 무시하며 revents 필드에 0을 반환한다. (이 점을 이용하면 어떤 파일 디스크립터를 poll() 호출 한 번 동안만 무시하는 걸 간단히 할 수 있다. fd 필드 부호만 바꾸면 된다. 다만 파일 디스크립터 0은 이 기법으로 무시할 수 없다.)

events 필드는 입력 매개변수이며 파일 디스크립터 fd에 대해 응용에서 관심 있는 이벤트들을 나타내는 비트 마스크이다. 이 필드를 0으로 지정할 수도 있으며, 그 경우 revents로 반환될 수 이벤트는 POLLHUP, POLLERR, POLLNVAL뿐이다. (아래 참고.)

revents 필드는 출력 매개변수이며 실제 발생한 이벤트들을 커널에서 채운다. revents로 반환되는 비트들에는 events에 지정한 비트들이, 그리고 값 POLLERR, POLLHUP, POLLNVAL 중 하나가 포함될 수 있다. (이 세 비트는 events 필드에서는 무의미하며 해당 조건이 참이면 revents 필드에 설정된다.)

파일 디스크립터 모두에서 요청 이벤트 어느 것도 (그리고 어떤 오류도) 발생하지 않았으면 그 이벤트들 중 하나가 일어날 때까지 poll()이 블록 한다.

timeout 인자는 파일 디스크립터가 준비 상태가 되기를 기다리며 poll()에서 블록 할 밀리초 수를 나타낸다. 다음 어느 경우든 해당할 때까지 호출이 블록 하게 된다.

참고로 timeout 시간을 시스템 클럭 해상도에 따라 올림 하게 되며 커널 스케줄링 지연도 있기 때문에 그 블록 시간을 약간 넘길 수도 있다. timeout에 음수 값을 지정하는 건 무한 타임아웃을 뜻한다. timeout을 0으로 지정하면 준비 상태인 파일 디스크립터가 없더라도 poll()이 즉시 반환하게 된다.

events에 설정하고 revents로 반환될 수 있는 비트들이 <poll.h>에 정의돼 있다.

POLLIN
읽을 데이터가 있다.
POLLPRI

파일 디스크립터에 어떤 예외 상황이 있다. 다음 경우들 등이 가능하다.

  • TCP 소켓에 대역외 데이터가 있다. (tcp(7) 참고.)

  • 패킷 모드인 유사 터미널 마스터가 슬레이브에서의 상태 변화를 보았다. (ioctl_tty(2) 참고.)

  • cgroup.events 파일이 변경되었다. (cgroups(7) 참고.)

POLLOUT
현재 쓰기가 가능하다. 다만 소켓 내지 파이프의 가용 공간보다 큰 데이터를 써넣으면 (O_NONBLOCK이 설정돼 있지 않으면) 여전히 블록 된다.
POLLRDHUP (리눅스 2.6.17부터)
스트림 소켓의 상대가 연결을 닫았거나 연결의 쓰기 쪽을 닫았다. 이 비트 정의를 쓸 수 있으려면 (어떤 헤더 파일도 포함시키기 전에) 기능 확인 매크로 _GNU_SOURCE가 정의돼 있어야 한다.
POLLERR
오류 상황. (revents로 반환되기만 하고 events에서는 무시.) 파이프의 쓰기 쪽을 가리키는 파일 디스크립터에 대해서 읽기 쪽이 닫혔을 때도 이 비트가 설정된다.
POLLHUP
연결 끊겼음. (revents로 반환되기만 하고 events에서는 무시.) 참고로 파이프나 스트림 소켓 같은 채널에서 읽기를 할 때 이 이벤트는 상대가 채널의 그쪽 끝을 닫았다는 표시일 뿐이다. 그 채널의 미처리 데이터를 모두 소비한 후에야 채널 읽기가 0(파일 끝)을 반환하게 된다.
POLLNVAL
유효하지 않은 요청: fd가 열려 있지 않음. (revents로 반환되기만 하고 events에서는 무시.)

_XOPEN_SOURCE가 정의된 채로 컴파일 하면 다음 비트들도 쓸 수 있게 되는데, 위에 나열한 비트들 이상의 정보를 주지는 않는다.

POLLRDNORM
POLLIN과 동등하다.
POLLRDBAND
우선 대역 데이터를 읽을 수 있다. (리눅스에서는 보통 쓰지 않음.)
POLLWRNORM
POLLOUT과 동등하다.
POLLWRBAND
우선 대역 데이터를 쓸 수 있다.

리눅스에는 POLLMSG도 있지만 쓰지는 않는다.

ppoll()

poll()ppoll()의 관계는 select(2)pselect(2)의 관계와 비슷하다. pselect(2)처럼 응용에서 ppoll()을 사용해 파일 디스크립터가 준비 상태가 되거나 시그널을 잡을 때까지 안전하게 대기할 수 있다.

timeout 인자의 정밀도 차이를 제외하면 다음 ppoll() 호출은

ready = ppoll(&fds, nfds, tmo_p, &sigmask);

다음 호출들을 원자적으로 실행하는 것과 거의 동등하다.

sigset_t origmask;
int timeout;

timeout = (tmo_p == NULL) ? -1 :
          (tmo_p->tv_sec * 1000 + tmo_p->tv_nsec / 1000000);
pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ready = poll(&fds, nfds, timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);

위 코드가 거의 동등하다고 한 이유는 poll()에선 음수 timeout 값을 무한대 타임아웃으로 해석하지만 ppoll()에선 *tmo_p의 값이 음수이면 오류가 나기 때문이다.

ppoll()이 필요한 이유는 pselect(2)의 설명을 보라.

sigmask를 NULL로 지정한 경우에는 시그널 마스크 조작을 수행하지 않는다. (그러면 ppoll()poll()의 차이는 timeout 인자의 정밀도뿐이다.)

tmo_p 인자는 ppoll()에서 블록 할 시간의 상한을 나타낸다. 이 인자는 다음 구조체의 포인터이다.

struct timespec {
    long    tv_sec;         /* 초 */
    long    tv_nsec;        /* 나노초 */
};

tmo_p를 NULL로 지정한 경우에는 ppoll()에서 무한정 블록 할 수 있다.

RETURN VALUE

성공 시 poll()은 음수 아닌 값을 반환하는데, 이는 revents 필드가 (이벤트나 오류를 나타내는) 0 아닌 값으로 설정된 pollfds 내 항목 개수다. 반환 값 0은 파일 디스크립터가 준비 상태가 되기 전에 호출이 타임아웃 되었음을 나타낸다.

오류 시 -1을 반환하며 오류를 나타내도록 errno를 설정한다.

ERRORS

EFAULT
인자로 준 배열이 호출 프로그램의 주소 공간에 들어 있지 않다.
EINTR
요청한 이벤트들에 앞서 시그널이 발생했다. signal(7) 참고.
EINVAL
nfds 값이 RLIMIT_NOFILE 값을 초과한다.
EINVAL
(ppoll()) *ip에 있는 타임아웃 값이 유효하지 않다 (음수다).
ENOMEM
커널 자료 구조를 위한 메모리를 할당할 수 없다.

VERSIONS

리눅스 2.1.23에서 poll() 시스템 호출이 도입되었다. 이 시스템 호출이 없는 더 전 커널에서는 glibc의 래퍼 함수에서 select(2)를 써서 에뮬레이션을 제공한다.

리눅스 커널 2.6.16에서 ppoll() 시스템 호출이 추가되었다. glibc 2.4에서 ppoll() 라이브러리 호출이 추가되었다.

CONFORMING TO

poll()은 POSIX.1-2001 및 POSIX.1-2008을 준수한다. ppoll()은 리눅스 전용이다.

NOTES

poll()ppoll()의 동작은 O_NONBLOCK 플래그에 영향을 받지 않는다.

일부 다른 유닉스 시스템에서는 커널 내부 자원을 할당하지 못했을 때 리눅스의 ENOMEM이 아니라 EAGAIN 오류로 실패할 수 있다. POSIX에서 그런 동작을 허용한다. 이식 가능한 프로그램에서는 EAGAIN을 확인해서 EINTR 경우처럼 루프를 계속 도는 게 좋을 수 있다.

어떤 구현들에서는 poll()에서 timeout에 쓸 수 있는 비표준 상수 INFTIM을 -1 값으로 정의하고 있다. glibc에서는 이 상수를 제공하지 않는다.

poll()로 감시 중인 파일 디스크립터를 다른 스레드에서 닫으면 어떻게 될 수 있는지에 대한 설명은 select(2)를 보라.

C 라이브러리/커널 차이

리눅스의 ppoll() 시스템 호출에서는 tmo_p 인자를 변경한다. 하지만 glibc 래퍼 함수에서 타임아웃 인자에 대한 지역 변수를 쓰고 그 변수를 시스템 호출로 전달하여 그 동작 방식을 감춘다. 그리하여 glibc의 ppoll() 함수는 tmo_p 인자를 변경하지 않는다.

진짜 ppoll() 시스템 호출에는 다섯 번째 인자 size_t sigsetsize가 있는데, 이는 sigmask 인자의 바이트 단위 크기를 나타낸다. glibc의 ppoll() 래퍼 함수에서 이 인자를 고정된 값(sizeof(kernel_sigset_t))으로 지정한다. 커널과 libc에서의 시그널 집합 개념 차이에 대한 설명은 sigprocmask(2)를 보라.

BUGS

select(2)의 BUGS 절에 있는 준비 상태 거짓 알림 설명을 보라.

EXAMPLES

아래 프로그램은 명령행 인자로 받은 파일 각각을 열어서 파일 디스크립터가 읽기 준비(POLLIN)가 되는지 감시한다. 루프를 돌며 poll()을 반복 사용해서 파일 디스크립터들을 감시하고, 준비 상태인 파일 디스크립터 수를 찍는다. 그리고 준비 상태인 파일 디스크립터 각각에 대해 다음을 수행한다.

한 터미널에서 프로그램을 실행해서 FIFO를 열게 한다고 하자.

$ mkfifo myfifo
$ ./poll_input myfifo

그리고 두 번째 터미널 창에서 그 FIFO를 쓰기용으로 열어서 데이터를 좀 써넣은 다음 닫자.

$ echo aaaaabbbbbccccc > myfifo

그러면 프로그램이 도는 터미널에서 다음을 보게 된다.

Opened "myfifo" on fd 3
About to poll()
Ready: 1
  fd=3; events: POLLIN POLLHUP
    read 10 bytes: aaaaabbbbb
About to poll()
Ready: 1
  fd=3; events: POLLIN POLLHUP
    read 6 bytes: ccccc

About to poll()
Ready: 1
  fd=3; events: POLLHUP
    closing fd 3
All file descriptors closed; bye

위 출력을 보면 poll()이 세 번 반환한 것을 알 수 있다.

프로그램 소스

/* poll_input.c

   Licensed under GNU General Public License v2 or later.
*/
#include <poll.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

int
main(int argc, char *argv[])
{
    int nfds, num_open_fds;
    struct pollfd *pfds;

    if (argc < 2) {
       fprintf(stderr, "Usage: %s file...\n", argv[0]);
       exit(EXIT_FAILURE);
    }

    num_open_fds = nfds = argc - 1;
    pfds = calloc(nfds, sizeof(struct pollfd));
    if (pfds == NULL)
        errExit("malloc");

    /* 명령행의 각 파일을 열어서 `pfds` 배열에 추가하기. */

    for (int j = 0; j < nfds; j++) {
        pfds[j].fd = open(argv[j + 1], O_RDONLY);
        if (pfds[j].fd == -1)
            errExit("open");

        printf("Opened \"%s\" on fd %d\n", argv[j + 1], pfds[j].fd);

        pfds[j].events = POLLIN;
    }

    /* 파일 디스크립터가 하나라도 열려 있는 동안 계속 poll()
       호출하기. */

    while (num_open_fds > 0) {
        int ready;

        printf("About to poll()\n");
        ready = poll(pfds, nfds, -1);
        if (ready == -1)
            errExit("poll");

        printf("Ready: %d\n", ready);

        /* poll()이 반환한 배열 다루기. */

        for (int j = 0; j < nfds; j++) {
            char buf[10];

            if (pfds[j].revents != 0) {
                printf("  fd=%d; events: %s%s%s\n", pfds[j].fd,
                        (pfds[j].revents & POLLIN)  ? "POLLIN "  : "",
                        (pfds[j].revents & POLLHUP) ? "POLLHUP " : "",
                        (pfds[j].revents & POLLERR) ? "POLLERR " : "");

                if (pfds[j].revents & POLLIN) {
                    ssize_t s = read(pfds[j].fd, buf, sizeof(buf));
                    if (s == -1)
                        errExit("read");
                    printf("    read %zd bytes: %.*s\n",
                            s, (int) s, buf);
                } else {                /* POLLERR | POLLHUP */
                    printf("    closing fd %d\n", pfds[j].fd);
                    if (close(pfds[j].fd) == -1)
                        errExit("close");
                    num_open_fds--;
                }
            }
        }
    }

    printf("All file descriptors closed; bye\n");
    exit(EXIT_SUCCESS);
}

SEE ALSO

restart_syscall(2), select(2), select_tut(2), epoll(7), time(7)


2021-03-22