名前
epoll - I/O イベント通知機能
書式
#include <sys/epoll.h>
説明
epoll は poll(2) の一種であり、エッジトリガインタフェースと レベルトリガインタフェースのどちらとして使用することができ、 監視するファイルディスクリプタの数が多い場合にも使用できる。 epoll 集合を設定したり制御したりするために、 次の 3 つのシステムコールが提供されている: epoll_create(2), epoll_ctl(2), epoll_wait(2).
epoll 集合は epoll_create(2) で作成されるファイルディスクリプタに接続される。 ファイルディスクリプタに対する監視内容を epoll_ctl(2) で登録する。 最後に epoll_wait(2) で実際のイベント待ちを開始する。
レベルトリガとエッジトリガ
epoll イベント配送 (distribution) インタフェースは、 エッジトリガ (ET) としてもレベルトリガ (LT) としても動作させることができる。 ET イベント配送機構と LT イベント配送機構の違いは、次のように説明できる。 このようなシナリオが起こったとしよう:
1 | パイプの読み込み側を表すファイルディスクリプタ (RFD) が epoll デバイスの内部に追加される。 | ||||
2 | パイプへ書き込むプログラムが 2Kb のデータをパイプの書き込み側へ書き込む。 | ||||
3 | epoll_wait(2) を呼び出すと、読み込み可能 (ready) なファイルディスクリプタとして RFD が返る。 | ||||
4 | パイプから読み出すプログラムが、1Kb のデータを RFD から読み出す。 | ||||
5 | epoll_wait(2) の呼び出しが行われる。 | ||||
RFD ファイルディスクリプタが EPOLLET フラグを使って epoll に追加されていると、 利用可能なデータがファイル入力バッファにまだ存在し、 リモートの接続先 (peer) が既に送られたデータに基づいて 応答を期待しているために、ステップ 5 の epoll_wait(2) の呼び出しでハングする可能性がある。 これは、エッジトリガイベント配送では、モニタしているファイルで イベントが起ったときにのみイベントが配送されるためである。 したがって、ステップ 5 では、呼び出し側は結果的に 入力バッファ内にすで存在するデータを待つことになるかもしれない。 上記の例では、 2 で行われた書き込みによって RFD に関するイベントが生成され、 3 でイベントが消費 (consume) される。 4 で行われる読み込み操作では、全部のバッファデータを消費しないので、 ステップ 5 で行われる epoll_wait(2) の呼び出しが 無期限にロックするかもしれない。 EPOLLET フラグ (エッジトリガ) と共に使用する場合、 epoll インタフェースはブロックしないファイルディスクリプタを使うべきである。 これは、ブロックされる読み込みや書き込みによって、 複数のファイルディスクリプタを扱うタスクを 飢え (starve) させないようにするためである。 epoll をエッジトリガ (EPOLLET) インタフェースとして使うために提案される方法は以下の通りであり、 ありがちな落とし穴を避ける方法も続けて述べる。 | |||||
| |||||
おすすめな使用例
レベルトリガインタフェースとして使用するときの epoll の使い方は poll(2) と同じである。 しかしエッジトリガとして使う場合は、 アプリケーションのイベントループでストール (stall) しないように、 使い方をより明確にしておく必要がある。 この例では、リスナはブロックしないソケットであり、 listen(2) が呼ばれている。 関数 do_use_fd() は、 read(2) または write(2) によって EAGAIN が返されるまでは、 新しい準備済みのファイルディスクリプタを使う。 イベント駆動ステートマシンアプリケーションは、 EAGAIN を受信した後、カレントの状態を記録しておくべきである。 これにより、次の do_use_fd() 呼び出しのときに、以前に停止したところから read(2) または write(2) を継続することができる。
struct epoll_event ev, *events;
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1);
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listener) {
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
fprintf(stderr, "epoll set insertion error: fd=%d\n",
client);
return -1;
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
エッジトリガインタフェースとして使う場合、性能上の理由により、 一度 (EPOLLIN|EPOLLOUT) を指定してから (EPOLL_CTL_ADD) でファイルディスクリプタを epoll インタフェースに追加することができる。 これにより、 epoll_ctl(2) に EPOLL_CTL_MOD を指定して呼び出すことで EPOLLIN と EPOLLOUT の連続的な切り替えが避けられる。
質問と解答
Q1 | 同じファイルディスクリプタを 1 つの epoll_set に 2 回追加するとどうなるか? |
A1 | たぶん EEXIST を受け取るだろう。 しかし 2 つのスレッドが同じファイルディスクリプタを 2 回追加することは可能である。 これは無害な状態である。 |
Q2 | 2 つの epoll セットが同じファイルディスクリプタを待ち受けることは可能か? もし可能であれば、イベントは両方の epoll セットのファイルディスクリプタに報告されるか? |
A2 | 可能であるが、推奨されない。 またイベントは両方に報告される。 |
Q3 | epoll ファイルディスクリプタ自身は poll/epoll/select が可能か? |
A3 | 可能である。 |
Q4 | epoll ファイルディスクリプタを自身のファイルディスクリプタセットに入れると どうなるか? |
A4 | 失敗するだろう。 ただし epoll ファイルディスクリプタを他の epoll ファイルディスクリプタセットの内部に追加することは可能である。 |
Q5 | epoll ファイルディスクリプタを unix ソケットで他のプロセスに送ることは可能か? |
A5 | 不可能である。 |
Q6 | ファイルディスクリプタをクローズすると、そのファイルディスクリプタは全ての epoll セットから自動的に削除されるか? |
A6 | 削除される。 |
Q7 | 2 つ以上のイベントが epoll_wait(2) コールの間に来た場合、それらはまとめて報告されるか、 それとも別々に報告されるか? |
A7 | まとめて報告されるだろう。 |
Q8 | ファイルディスクリプタに対する操作は、 既に集められているがまだ報告されていないイベントに影響するか? |
A8 | 既存のファイルディスクリプタに対して 2 つの操作を行うことができる。 この場合、削除には意味がない。 変更すると、使用可能な I/O が再び読み込まれる。 |
Q9 | EPOLLET フラグ (エッジトリガ動作) を使っている場合、EAGAIN を受け取るまで、 継続してファイルディスクリプタを読み書きする必要があるか。 |
A9 | その必要はない。 epoll_wait(2) からイベントを受け取ることは、 「そのファイルディスクリプタが要求された I/O 操作に対して準備済みである」 ということをユーザに示すものである。 「次の EAGAIN を受け取るまではファイルディスクリプタは準備済みである」 と単純に考えるべきである。 そのようなファイルディスクリプタをいつどのように使うかは、 全くユーザに任されてる。 また読み込み用 / 書き込み用 I/O 空間が使い尽くされた状態は、 対象となるファイルディスクリプタから読み込んだデータ量または 書き込んだデータ量をチェックすることで検知できる。 例えば、ある特定の量のデータを読み込むために read(2) を呼んだときに、 read(2) が返したバイト数がそれより少なかった場合、 そのファイルディスクリプタの読み込み用 I/O 空間が 使い尽くされたことが分かる。 write(2) 関数を使って書き込みをするときも、同じことが言える。 |
ありがちな落とし穴と回避方法
o 飢餓 (starvation) (エッジトリガ) | |
大きな I/O 空間がある場合、 その I/O 空間のデータを全て処理 (drain) しようとすると、 他のファイルが処理されず、飢えを発生させることがある。 これは epoll に固有のものではない。 | |
この問題の解決法は、準備済み状態のリストを管理して、 関連する data 構造体の中でファイルディスクリプタが 利用可能であるとマークすることである。 それによって、利用可能なすべてのファイルの中で どのファイルを処理する必要があるかを憶えることができ、 しかも順番に処理 (round robin) することができる。 既に利用可能であるファイルディスクリプタに対して それ以後に受け取るイベントを無視することもできる。 | |
o イベントキャッシュを使っている場合 | |
この問題を解決する 1 つの方法は、イベント 47 の処理をしている間に、 ファイルディスクリプタ 13 を削除して close(2) するために epoll_ctl(EPOLL_CTL_DEL) を呼び出し、関連付けられた data 構造体を削除済みとマークして、 クリーンアップリストにリンクすることである。 バッチ処理の中でファイルディスクリプタ 13 についての 他のイベントを見つけた場合、 そのファイルディスクリプタが以前に削除されたものであると分かるので、 混乱は起きない。
バージョン
epoll(7) は Linux カーネル 2.5.44 に導入された新しい API である。 インタフェースは Linux カーネル 2.5.66 で確定されるべきである。
準拠
epoll API は Linux 固有である。 他のシステムでも同様の機構が提供されている場合がある。 例えば、FreeBSD の kqueue や Solaris の /dev/poll などである。