---
title: epoll とイベント駆動
url: https://doc.liz6.com/ja/systems-programming/05-io-and-file-systems/03-epoll-and-event-driven-programming
locale: ja
area: systems-programming
tags:
- systems-programming
- io-and-file-systems
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: epoll_create1/epoll_ctl/epoll_wait → LT vs ET → timerfd/signalfd/eventfd → select/pollとの比較 → epoll内部実装のポイント 対象: Linux 2.6+'
---

# epoll とイベント駆動

> カバー範囲: epoll_create1/epoll_ctl/epoll_wait → LT vs ET → timerfd/signalfd/eventfd → select/pollとの比較 → epoll内部実装のポイント
> 対象: Linux 2.6+

## 概要

epoll は、Linux における高性能なイベント駆動プログラミングの標準インターフェースです。nginx、HAProxy、Redis はすべて epoll を基盤として構築されています。select/poll との根本的な違いは、epoll が O(1) の待機（アクティブな fd のみを返す）であるのに対し、select/poll は毎回 fd 集合全体を O(n) でスキャンする必要がある点です。

## epoll API

```c
int epfd = epoll_create1(EPOLL_CLOEXEC);  // epoll インスタンスの作成

struct epoll_event ev = {
    .events   = EPOLLIN | EPOLLET,       // 読み込み可能を監視、エッジトリガー
    .data.fd  = listen_fd,               // ユーザーデータ (ptr でも可)
};
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);  // fd の登録

struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout_ms);

for (int i = 0; i < nfds; i++) {
    if (events[i].events & EPOLLIN)
        handle_read(events[i].data.fd);
}
```

### epoll_event.events

```
EPOLLIN:       読み込み可能 (データ到着または接続確立)
EPOLLOUT:      書き込み可能 (送信バッファに空きがある)
EPOLLERR:      エラー (自動検出、設定不要)
EPOLLHUP:      ハングアップ (相手が切断)
EPOLLRDHUP:    相手の書き込み側接続が閉じた (4.17+)
EPOLLET:       エッジトリガー (以下参照)
EPOLLONESHOT:  一度限り (トリガー発生後に自動で削除され、EPOLL_CTL_MOD で再登録するまで有効にならない)
EPOLLEXCLUSIVE: 排他的ウェイクアップ (スラッシングを軽減、4.5+)
```

## LT vs ET: 重要な違い

```
LT (Level-Triggered、デフォルト):
  fd が読み込み可能/書き込み可能な状態である限り → 毎回の epoll_wait で返される
  → 単純: データの一部だけを読み取り、残りは次回に読むことができる
  → リスク: 読み込みが完了していない場合、次の epoll_wait でも返される → ループが必要

ET (Edge-Triggered):
  状態遷移時のみ通知される (読み込み不可→読み込み可、書き込み不可→書き込み可)
  → 効率的: 重複通知がない
  → 要件: EAGAIN (すべて処理し終わった) になるまで読み書きを行う必要がある
  → O_NONBLOCK を必須とする (しないと read がブロックする可能性がある)
```

### ET モードの正しい実装

```c
// ET モードでは必ずループして読み込む必要がある:
while (1) {
    ssize_t n = read(fd, buf, sizeof(buf));
    if (n > 0) {
        process(buf, n);
    } else if (n == 0) {
        close(fd);  // EOF
        break;
    } else {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            break;  // すべて読み終わった
        // エラー処理
        break;
    }
}
```

## timerfd / signalfd / eventfd: epoll の相棒

```c
// 統一されたイベントループ: すべての非同期イベントを fd に変換し、epoll で一元管理する

int epfd = epoll_create1(0);

// 1. ネットワークソケット
epoll_add(epfd, listen_fd, EPOLLIN);

// 2. timerfd: タイマー
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
epoll_add(epfd, tfd, EPOLLIN);

// 3. signalfd: シグナル
sigset_t mask; sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);
int sfd = signalfd(-1, &mask, SFD_NONBLOCK);
epoll_add(epfd, sfd, EPOLLIN);

// 4. eventfd: スレッド間通知
int efd = eventfd(0, EFD_NONBLOCK);
epoll_add(epfd, efd, EPOLLIN);

// 単一スレッド: epoll_wait → すべてのイベントを統一して処理
while (1) {
    int nfds = epoll_wait(epfd, events, MAX, -1);
    for (int i = 0; i < nfds; i++) {
        if      (events[i].data.fd == listen_fd) accept();
        else if (events[i].data.fd == tfd)       handle_timer();
        else if (events[i].data.fd == sfd)       handle_signal();
        else if (events[i].data.fd == efd)       handle_notification();
        else                                      handle_client(events[i].data.fd);
    }
}
```

## select/poll との比較

| | select | poll | epoll |
|---|---|---|---|
| fd の数 | FD_SETSIZE (1024) | 上限なし | 上限なし |
| 待機 | O(n) スキャン | O(n) スキャン | O(1) アクティブ数 |
| 状態の保存 | 毎回渡す (スタック) | 毎回渡す (スタック) | カーネルが管理 (RB木) |
| エッジトリガー | なし | なし | あり (ET) |
| 1万コネクション (アイドル) | ~100μs | ~100μs | ~1μs |

## epoll の内部実装

```c
// fs/eventpoll.c
// epoll インスタンスは以下の要素を含む:
//   1. 赤黒木 (rbr): 登録された fd のリスト (epoll_ctl ADD → 挿入)
//   2. 準備済みリスト (rdllist): アクティブな fd (epoll_wait で返される)
//
// イベント到着 → ドライバ/プロトコルスタックが ep_poll_callback を呼び出す:
//   → イベントが一致するか確認? → rdllist に追加
//   → 待機者がいる場合 (epoll_wait) → ウェイクアップ
```

## 参考

- **man**: epoll(7), epoll_create1(2), epoll_ctl(2), epoll_wait(2)
- **ソースコード**: `fs/eventpoll.c`
- **LWN**: "The epoll API", "Edge triggered epoll"

*キーワード: epoll, EPOLLET, EPOLLONESHOT, edge-triggered, timerfd, signalfd, eventfd, event loop*
