epoll - 約束事その他の説明 - Linux コマンド集 一覧表
- 名前
- 書式
- 説明
- 注意
- おすすめな使用例
- 質問と解答 (linux-kernel より)
- ありがちな落とし穴と回避方法
- 準拠
- バージョン
- 関連項目
名前
書式
説明
注意
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) の呼び出しでハングする可能性がある。
これは、エッジトリガイベント配送では、モニタしているファイルで
イベントが起ったときにのみイベントが配送されるためである。
上記の例では、
2
で行われた書き込みによって
RFD
に関するイベントが生成され、
3
でイベントが消費 (consume) される。
4
で行われる読み込み操作では、全部のバッファデータを消費しないので、
ステップ
5
で行われる
epoll_wait
(2) の呼び出しが
無期限にロックするかもしれない。
EPOLLET
フラグ (エッジトリガ) と共に使用する場合、
epoll
インタフェースはブロックしないファイルディスクリプタを使うべきである。
これは、ブロックされる読み込みや書き込みによって、
複数のファイルディスクリプタを扱うタスクを
飢え (starve) させないようにするためである。
epoll
をエッジトリガ
(
EPOLLET
)インタフェースとして使うために提案される方法は以下の通りであり、
ありがちな落とし穴を避ける方法も続けて述べる。
-
i
-
ブロックしないファイルディスクリプタと共に使う。
-
ii
-
read
(2) または
write
(2) が EAGAIN を返した後でのみ、イベントを待つ。
反対にレベルトリガインタフェースとして使う場合は、
epoll
は正により高速な
poll
(2) であり、使い方が同じなので、
poll
(2) が使われているところではどこでも使用することができる。
エッジトリガを使った場合でも、複数のデータを受信すると複数の
epoll
イベントが生成されるので、
呼び出し側には
EPOLLONESHOT
フラグを指定するオプションがある。
このフラグは
epoll
に対して、
epoll_wait
(2) によるイベントを受信した後で、関連するファイルディスクリプタを無効にさせる。
EPOLLONESHOT
フラグが指定された場合、
epoll_ctl
(2) に
EPOLL_CTL_MOD
を指定してファイルディスクリプタを再度使用できるようにするのは、
呼び出し側の責任である。
おすすめな使用例
レベルトリガインタフェースとして使用するときの
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
の連続的な切り替えが避けられる。
質問と解答 (linux-kernel より)
-
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 イベントキャッシュを使っている場合
-
イベントキャッシュを使っている場合、
または
epoll_wait
(2) から返された全てのファイルディスクリプタを格納している場合、
クローズされたことを動的にマークする
(つまり前のイベントの処理によってマークされる) 方法を提供すべきである。
epoll_wait
(2) から 100 個のイベントを受け取り、
イベント #47 ではある条件でイベント #13 が閉じられると仮定する。
イベント #13 の構造体を削除しファイルディスクリプタを
close
()すると、イベントキャッシュはそのファイルディスクリプタを待つイベントが
存在するといって、混乱が起きる。
この問題を解決する 1 つの方法は、イベント 47 の処理をしている間に、
ファイルディスクリプタ 13 を削除して
close
()するために
epoll_ctl
(
EPOLL_CTL_DEL
)を呼び出し、関連付けられた data 構造体を削除済みとマークして、
クリーンアップリストにリンクすることである。
バッチ処理の中でファイルディスクリプタ 13 についての
他のイベントを見つけた場合、
そのファイルディスクリプタが以前に削除されたものであると分かるので、
混乱は起きない。
準拠
epoll API は Linux 固有である。
他のシステムでも同様の機構が提供されている場合がある。
例えば、FreeBSD の
kqueue
や Solaris の
/dev/poll
などである。
バージョン
epoll
(7) は Linux カーネル 2.5.44 に導入された新しい API である。
インタフェースは Linux カーネル 2.5.66 で確定されるべきである。
関連項目
- Linux Tips 関連記事
- Linux Tips(目次)
- Linux ディストリビューション一覧
- rpm のファイル名にあるi386とかi686とは
- 自分のマシンの情報を調べる
- cron の設定
- ssh の root ログインを禁止する
- ssh を、ユーザ、IPでアクセス制限
- 鍵交換方式によるssh接続
- 鍵交換方式によるssh接続( windowsから )
- 複数ファイル内の文字列を置換して上書き保存する
- あるグループをイニシャルグループとするユーザー一覧出力
- 複数ファイルのファイル名を一括変換する
- 連番ファイルをコマンド一発で作成する
- 中身がランダムなファイルを任意のサイズで作成する
- Linux ユーザーアカウントをロック・アンロックする