epoll のカーネルソースを読むための準備

はじめに

epoll は Linux カーネル 2.6 で導入された I/O イベント通知機構で、select(2) や poll(2) の代替として利用されます。本記事では、epoll のソースコードを読む前に理解しておくべき基礎知識を整理します。

従来の I/O マルチプレクサの問題点

  • select(2): 最大 1024 個のファイル記述子しか扱えず、毎回ユーザ空間とカーネル空間の間でデータをコピーする必要があり、パフォーマンスが劣る。
  • poll(2): select の制限を緩和するが、依然として全イベントを毎回コピーし、全ファイル記述子をスキャンする必要がある。
  • epoll(2): ファイル記述子とイベントをカーネル空間に常駐させ、必要に応じてイベントを通知する仕組みを採用。コピーとスキャンのコストを削減。

epoll の主なシステムコール

#include <sys/epoll.h>

typedef union epoll_data {
    void*        ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;  /* イベントの種類 */
    epoll_data_t data;   /* ユーザ定義データ */
};

int epoll_create(int size);
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epoll_create(): epoll インスタンスを作成し、新しいファイル記述子を返す。
  • epoll_create1(): epoll_create() と同様だが、フラグで設定を細かく制御可能。
  • epoll_ctl(): epoll インスタンスに登録・変更・削除を行う。
  • epoll_wait(): epoll で監視しているイベントが発生するまで待つ。

イベントトリガの種類

epoll では、イベント通知のトリガ方法を レベルトリガ (LT)エッジトリガ (ET) から選択できます。

  • レベルトリガ (デフォルト): ファイル記述子の状態が有効な間、イベントが通知され続ける。
  • エッジトリガ: 状態変化が起きたときのみ通知される。データが残っている場合でも、一度イベントを処理すると再度変化がない限り通知されない。

エッジトリガの注意点

エッジトリガを利用する場合は、非同期・ノンブロッキングなファイル記述子を使用することが推奨されます。また、以下のルールに従う必要があります。

  1. ファイル記述子は非ブロッキングモードで開く。
  2. read(2) / write(2) が EAGAIN を返した後にのみ、次のイベントを待機する。

サンプルコード

eventfd を使用して、2つのスレッド間で通信を行うサンプルコードを以下に示します。

#include <unistd.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <stdio.h>

int efd;

void* write_thread(void* arg) {
    uint64_t count = 1;
    while (1) {
        printf("write count: %zu\n", count);
        write(efd, &count, sizeof(count));
        count++;
        sleep(2);
    }
    return NULL;
}

int main() {
    efd = eventfd(1000, EFD_SEMAPHORE);

    int epfd = epoll_create(32);
    struct epoll_event event, events[1];
    event.data.fd = efd;
    event.events = EPOLLIN | EPOLLET;  // EPOLLET を有効にする
    epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &event);

    pthread_t tid;
    pthread_create(&tid, NULL, write_thread, NULL);

    while (1) {
        int ret = epoll_wait(epfd, events, 1, 1000);
        if (ret > 0) {
            uint64_t count;
            read(efd, &count, sizeof(count));
            printf("read count: %zu\n", count);
        } else if (ret == 0) {
            printf("no event\n");
        }
    }

    return 0;
}

その他のフラグ

  • EPOLLONESHOT: イベントが一度だけ通知される。
  • EPOLLWAKEUP: システムのスリープを防ぐ。
  • EPOLLEXCLUSIVE: サーバーの「スパコン問題 (thundering herd)」を回避する。

まとめ

epoll のカーネル実装を深く理解するには、以下のポイントに注目する必要があります。

  1. epoll のイベント取得が O(1) な理由。
  2. 通常のファイル(例: eventfd)との違い。
  3. EPOLLET トリガの実装方法。
  4. epoll 同士が監視しあう場合の相互通知問題。
  5. 相互通知問題に対するカーネルの対処。

タグ: epoll Linux Kernel I/O multiplexing event-driven programming system call

6月7日 16:24 投稿