linux IO多重(epoll)メモ


一、epollの概要
epollはLinuxカーネルが大量のファイル記述子を処理するために改良されたpollであり、Linux下でIOインタフェースselect/pollを多重化する拡張バージョンであり、大量の同時接続でプログラムが少量しかアクティブでない場合のシステムCPU利用率を著しく向上させることができる.もう1つの理由は、イベントを取得する際に、カーネルIOイベントによって非同期に起動されてReadyキューに追加された記述子のセット全体を巡回する必要がないためである.
二、epollのAPI関数
1.ハンドル作成関数
int epoll_create(int size);

epollのハンドルを作成し、sizeはカーネルというリスニングの数が合計でどれだけ大きいかを示すために使用されます.
int epoll_create1(int flag);

この関数はlinux 2.6.27に組み込まれていますが、実はepoll_とcreateの差は多くありませんが、違いはepollです.create 1関数のパラメータはflagです.
flagが0の場合、表現とepoll_create関数はまったく同じで、sizeのヒントは必要ありません.
flag=EPOLL_の場合CLOEXEC、作成したepfdはFD_を設定しますCLOEXECは、fdの識別説明であり、ファイルclose-on-exec状態を設定するために使用されます.
flag=EPOLL_の場合NONBLOCKでは、作成したepfdが非ブロックに設定されます.
2.イベント操作関数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

最初のパラメータepfdはepoll_createが返すepollファイル記述子.
2番目のパラメータopは、操作値を表す.3つの操作タイプがあります.
EPOLL_CTL_ADD  //    fd epfd ,       event fd 
 
EPOLL_CTL_MOD  //       fd     
 
EPOLL_CTL_DEL  // epfd   /      fd, event     ,     NULL

3番目のパラメータfdは、リスニングが必要なfdを表す.
4番目のパラメータeventは、リスニングが必要なイベントを表します.
eventパラメータは、イベントタイプを「|」で追加できる列挙の集合です.列挙は次のとおりです.
// EPOLLIN:      fd        。
// EPOLLOUT:      fd        。
// EPOLLRDHUP(since Linux 2.6.17):           ,             。
// EPOLLPRI:      fd               。
// EPOLLERR:      fd     , epoll_wait         ,              。
// EPOLLHUP:      fd   , epoll_wait         ,              。
// EPOLLET:      fd ET     , epoll        LT。
// EPOLLONESHOT(since Linux 2.6.2):      fd one-shot     。         ,        ,    socket   epoll   。

3.イベント待ち関数
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout,  const sigset_t *sigmask);

上の2つの関数のパラメータの意味:
最初のパラメータ:epoll_を表しますwaitはepfd上のイベントを待つ.
2番目のパラメータ:eventsポインタはepollを持っています.data_tデータ.
3番目のパラメータ:maxeventsは、カーネルeventsの大きさを0より大きくする必要があることを示します.
4番目のパラメータ:timeoutはタイムアウト時間(単位:ミリ秒)を表し、0の場合はすぐに戻り、-1の場合はイベントが戻るまで待っていたことを示し、任意の正の整数の場合はこんなに長い時間待っていたことを示し、イベントがなければ戻ってくる.一般に,ネットワークの主ループが単独のスレッドであれば−1などを用いることができ,いくつかの効率を保証することができ,主論理と同じスレッドであれば0で主ループの効率を保証することができる.
epoll_pwait(since linux 2.6.19)は、fdデバイスが準備が整うか、信号量がキャプチャされるまで、アプリケーションが安全に待機することを可能にする.ここでsigmaskは、取得する信号量を表す.
関数が成功を待つと、fdの数値が返されます.0はfd待ちタイムアウトを示し、他のエラー番号はerrnoを参照してください.
4.ハンドルクローズ関数
int close(int fd);

戻り値:ファイルが正常に閉じると0を返し、エラーが発生した場合は-1を返します.
三、epollの2種類のトリガモード
1.Level Triggered(LT)水平トリガ
LTはepollのデフォルトのトリガ方式で、以下の通りです.
socket受信バッファは空ではなく、データが読み取り可能であれば、読み取りイベントは常にトリガーされる.
socket送信バッファが不満で、データの書き込みを継続できる場合、書き込みイベントは常にトリガーされます.
LTの処理手順:
accept接続で、epollに追加してEPOLLINイベントをリスニングします.
EPOLLINイベントが到着すると、read fdのデータが処理される.
データを書き込む必要がある場合は、まず直接データwriteをfdに書き込む.データが大きい場合、一度に書き込むことができない場合は、epollでEPOLLOUTイベントを傍受します.
EPOLLOUTイベントが到着すると、データwriteをfdに続けます.データの書き込みが完了した場合、epollでEPOLLOUTイベントを閉じます.
2.Edge Triggered(ET)エッジトリガ
socketの受信バッファ状態が変化すると、読み出しイベントがトリガーされ、すなわち、空の受信バッファがデータを受信したばかりのときに読み出しイベントがトリガーされる.
socketの送信バッファ状態が変化すると書き込みイベントがトリガーされ、すなわち、満タンのバッファが空間を空けたばかりのときに読み取りイベントがトリガーされる.
ステータスが変化したときにのみイベントがトリガーされます
ETの処理手順:
accept接続で、epollに追加してEPOLLIN|EPOLLOUTイベントをリスニングします.
EPOLLINイベントが到着すると、read fdのデータが処理され、readはEAGAINに戻るまでずっと読む必要がある.
データを書く必要がある場合は、データがすべて書き終わるまでwriteをfdに、またはwriteがEAGAINに戻るまで.
EPOLLOUTイベントが到着すると、データがすべて書き終わるまでwriteをfdに、またはwriteがEAGAINに戻るまで、データwriteをfdに続けます.
ETモードでは、正しいacceptは2つの問題を考慮します.
(1)ブロックモードでのacceptの問題点
このことを考慮すると、TCP接続はクライアントによって夭折され、すなわち、サーバがacceptを呼び出す前に、クライアントがRSTをアクティブに送信して接続を終了し、確立したばかりの接続が準備キューから移動し、ソケットインタフェースがブロックモードに設定されている場合、サーバは他のクライアントが新しい接続を確立するまでaccept呼び出しにブロックされる.しかし、その間、サーバはaccept呼び出しに単純にブロックされ、準備キュー内の他の記述子は処理されない.
解決策は、リスニング・スリーブ・インタフェースを非ブロックに設定することであり、クライアントがサーバがacceptを呼び出す前に接続を中止すると、accept呼び出しはすぐに-1に戻ることができ、Berkeleyからの実装はカーネルでイベントを処理し、epollに通知することはなく、他の実装はerrnoをECONNABORTEDまたはEPROTOエラーに設定し、この2つのエラーを無視する必要があります.
(2)ETモードにおけるacceptの問題点
このような状況を考慮すると、複数の接続が同時に到着し、サーバのTCPレディキューは瞬時に複数のレディ接続を蓄積し、エッジトリガモードであるため、epollは1回しか通知されず、acceptは1つの接続しか処理されず、TCPレディキューの残りの接続は処理されない.
解決策は,accept呼び出しをwhileループで抱きしめ,TCPレディキュー内のすべての接続を処理した後,ループを終了することである.準備キュー内のすべての接続を処理したかどうかを知るにはどうすればいいですか?acceptは-1を返し、errnoがEAGAINに設定されると、すべての接続が処理されたことを示します.
以上の2つの状況を総合すると、サーバは非ブロックacceptを使用すべきであり、acceptのETモードでの正しい使用方法は以下の通りである.
while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) {
    handle_client(conn_sock);
}
if (conn_sock == -1) {
    if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
    perror("accept");
}

3.まとめ
ETの処理過程から,ETの要求はEAGAINに戻るまで読み書きを続ける必要があり,そうしないとイベントが漏れてしまうことがわかる.一方、LTの処理中は、EAGAINが戻るまでハードな要求ではないが、通常の処理中はEAGAINが戻るまで読み書きされるが、LTはETよりもEPOLLOUTイベントをスイッチするステップが1つ増えている.LTのプログラミングはpoll/selectに近く、これまでの習慣に合っていて、間違いにくい.ETのプログラミングはより簡潔にでき,一部のシーンではより効率的であるが,一方ではイベントが漏れやすく,バグが発生しやすい.