selectとepoll(memo あとで詳しく)

selectとepoll(kqueue)についてあまり理解していなかったので調べてみた。
(コードは全部manページの抜粋)
(個人用のメモ以上のものにはなっていない)

今のところの認識

selectは古い方法。epollはモダンな方法

これくらいの認識。

用途

select

  • select, pselectがあるらしい。*1
  • 以下のようなファイルディスクリプタの変更を通知してくれる。
    1. readfds
    2. writefds
    3. exceptfds
  • 後、上のファイルディスクリプタは固定長のバッファ。(FD_SIZEを越えた時の挙動は未定義)

実際の使い方

  1. FD_SETで監視したいファイルディスクリプタを渡す。
  2. selectを呼ぶ
  3. (FD_ISSETでデータが詰まったファイルディスクリプタを探す)
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int
main(void)
{
    fd_set rfds;
    struct timeval tv;
    int retval;

    /* Watch stdin (fd 0) to see when it has input. */
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);

    /* Wait up to five seconds. */
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    retval = select(1, &rfds, NULL, NULL, &tv);
    /* Don't rely on the value of tv now! */

    if (retval == -1)
        perror("select()");
    else if (retval)
        printf("Data is available now.\n");
        /* FD_ISSET(0, &rfds) will be true. */
    else
        printf("No data within five seconds.\n");

    exit(EXIT_SUCCESS);
}

http://www.kernel.org/doc/man-pages/online/pages/man2/select_tut.2.html にもう少し詳しい使い方が載っていた。

epoll

  • たくさんのファイルディスクリプタ(large numbers of watched file descripter)の監視に良い。
  • edge-triggered, level-triggered
    • defaultはlevel-triggered(EPOLLETフラグを使って登録するとevent-triggered)

edge-triggeredの例は以下のような動作が起きたときに、4でデータを全部消費してないので、5でずっとロックしたままになるかも.

       1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.
       2. A pipe writer writes 2 kB of data on the write side of the pipe.
       3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.
       4. The pipe reader reads 1 kB of data from rfd.
       5. A call to epoll_wait(2) is done.

level-triggredに関しては、高速なpollと思えば良いらしい。

使用例(event-triggered)

  1. epoll_ctlで監視対象を追加/削除
  2. epoll_waitで変更通知を待つ

というような使い方。

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Set up listening socket, 'listen_sock' (socket(),
   bind(), listen()) */

epollfd = epoll_create(10);
if (epollfd == -1) {
    perror("epoll_create");
    exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        perror("epoll_pwait");
        exit(EXIT_FAILURE);
    }

    for (n = 0; n < nfds; ++n) {
        if (events[n].data.fd == listen_sock) {
            conn_sock = accept(listen_sock,
                            (struct sockaddr *) &local, &addrlen);
            if (conn_sock == -1) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            setnonblocking(conn_sock);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = conn_sock;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
                perror("epoll_ctl: conn_sock");
                exit(EXIT_FAILURE);
            }
        } else {
            do_use_fd(events[n].data.fd);
        }
    }
}

ここ読んだ方がよく理解できる。 https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
ちなみに監視できる最大のファイルディスクリプタの数は以下のようにして調べられる.

 $ cat /proc/sys/fs/epoll/max_user_watches                                                                                               
785694

途中

  • selectは監視対象のどれかが更新されたら通知してくれる。
  • epollはイベント単位で更新を通知してくれる。

(あまり良いまとめじゃない)

*1:sigmaskの有無、timeval,timespecを使う。timeoutの引数を変更の有無とか細かな違いがあるらしい