1. selectによるI/O監視
selectは、複数のファイルディスクリプタ(FD)を監視し、いずれかが読み書き可能な状態になるまでプロセスを待機させる古典的な手法です。一般的に、監視可能なFDの最大数は1024(FD_SETSIZE)に制限されています。
select関数の定義
int select(int max_fd_plus_1, fd_set *read_fds, fd_set *write_fds, fd_set *error_fds, struct timeval *timeout);
- max_fd_plus_1: 監視するFDの最大値に1を加えたもの。
- read_fds / write_fds / error_fds: それぞれ読み込み、書き込み、例外状態を監視するためのビットマスク集合。
- timeout: タイムアウト時間。
selectの問題点と限界
selectには、大規模なネットワークアプリケーションにおいて性能上のボトルネックとなる要因がいくつか存在します。
- ユーザ・カーネル間のコピー: 呼び出しのたびに、監視対象のFD集合をユーザ空間からカーネル空間へコピーする必要があります。
- 線形走査によるオーバーヘッド: カーネルは、イベントが発生したFDを特定するために、すべてのFDを順番に走査(O(N))しなければなりません。
- FD集合の再初期化: カーネルが引数として渡されたFD集合を直接書き換えるため、再利用時には毎回集合を初期化し直す必要があります。
2. pollによる改善
pollはselectの基本概念を継承しつつ、いくつかの制約を解消したモデルです。
poll関数の定義と構造体
struct poll_entry {
int target_fd; /* 監視対象のファイルディスクリプタ */
short request_events; /* 期待するイベント(POLLIN, POLLOUTなど) */
short returned_events; /* 実際に発生したイベント */
};
int poll(struct poll_entry *fds, nfds_t nfds, int timeout_ms);
pollがselectより優れている点は以下の通りです。
- 最大FD数の撤廃: 配列としてFDを管理するため、メモリが許す限り1024個以上の監視が可能です。
- 入力と出力の分離:
request_events(入力)とreturned_events(出力)が分かれているため、呼び出しのたびに構造体を再設定する手間が軽減されます。
ただし、依然として「呼び出しごとのFD配列のコピー」および「全FDの線形走査(O(N))」という根本的な性能課題は解決されていません。
3. epollによる高度な最適化
epollはLinux独自のI/O多重化機構であり、数万件規模の高並列接続を効率的に処理するために設計されています。selectやpollが抱えていた性能問題を「イベント通知」の仕組みで解決しています。
epollの主要なAPI
epollは単一の関数ではなく、以下の3つのシステムコールで構成されます。
/* epollインスタンスの生成 */
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の内部メカニズム
- 赤黒木による管理: 監視対象のFDはカーネル内の赤黒木(Red-Black Tree)で管理されます。これにより、特定のFDの追加・削除・検索が高速(O(log N))に行えます。
- 準備完了リスト(Ready List): デバイスドライバからのコールバックを介して、準備が整ったFDだけをダブルリンク形式のリストに追加します。
- O(1)の計算量:
epoll_waitは、準備が完了したFDだけをユーザ空間に返すため、監視数に関わらず発生したイベント数に比例した処理時間で済みます。
トリガーモードの違い
epollには、通知のタイミングに関する2つのモードが存在します。
- Level Trigger (LT): 状態が継続している間、繰り返し通知されます。デフォルトの挙動であり、実装が比較的容易です。
- Edge Trigger (ET): 状態が変化した瞬間(データが到着した瞬間など)に一度だけ通知されます。効率は極めて高いですが、非ブロッキングI/Oと組み合わせ、データが空になるまで読み取る処理をループさせる必要があります。
4. まとめ:モデルの比較
| 特性 | select | poll | epoll |
|---|---|---|---|
| 最大FD数 | 1024(固定) | 無制限(メモリ依存) | 無制限(メモリ依存) |
| 計算量(効率) | O(N) | O(N) | O(1) / O(発生イベント数) |
| データ構造 | ビットマップ集合 | 構造体配列 | 赤黒木 + 準備完了リスト |
| メモリコピー | 毎回全コピー | 毎回全コピー | 登録時のみ(ctl) |