I/O多重化機構:epoll

9314 ワード

従来のlinuxネットワークプログラミングでは、selectを使用してイベントトリガを行い、selectを呼び出すたびにfdセットをカーネルにコピーする必要があり、カーネルは伝達されたすべてのfdを遍歴しなければならないという欠点があった.これは、fdが多くなるとオーバーヘッドが大きくなり、selectに比べてepollはfdの増加によって性能を低下させることはない.
Epollインタフェース:
#include <sys/epoll.h>

int epoll_create(int size);

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_create epollの作成
epollのハンドルを作成し、sizeはカーネルというリスニングの数が合計でどれだけ大きいかを示すために使用されます.このパラメータはselect()の最初のパラメータとは異なり、最大リスニングfd+1の値を与える.注意すべきは、epollハンドルを作成するとfd値が占有され、linuxで/proc/プロセスid/fd/を表示すると、このfdが表示されるので、epollを使用した後、close()を呼び出して閉じる必要があります.そうしないと、fdが消費される可能性があります.
イベントの編集
epoll_ctl関数は、イベントの追加、イベントの変更、イベントの削除に使用します.
EPOLL_CTL_ADD:新しいfdをepfdに登録する.
EPOLL_CTL_MOD:登録されたfdの傍受イベントを変更する.
EPOLL_CTL_DEL:epfdからfdを削除します.
3番目のパラメータは傍受が必要なfdで、4番目のパラメータはカーネルに何を傍受する必要があるかを教えて、struct epoll_イベント構造は次のとおりです.
struct epoll_event {



__uint32_t events; /* Epoll events */



epoll_data_t data; /* User data variable */



};



typedef union epoll_data {

void *ptr;

int fd;

__uint32_t u32;

__uint64_t u64;

} epoll_data_t;



struct epoll_event {

__uint32_t events; /* Epoll events */

epoll_data_t data; /* User data variable */

};

eventsは、以下のマクロのセットであってもよい.
EPOLLIN:対応するファイル記述子が読めることを示す(対端SOCKETが正常に閉じることを含む);
EPOLLOUT:対応するファイル記述子が書けることを示す;
EPOLLPRI:対応するファイル記述子が緊急のデータ読み取り可能であることを示す(ここでは帯域外データが到来していることを示すべきである).
EPOLLERR:対応するファイル記述子にエラーが発生したことを示す;
EPOLLHOP:対応するファイル記述子が切断されたことを示す;
EPOLLET:EPOLLをエッジトリガ(Edge Triggered)モードに設定します.これは水平トリガ(Level Triggered)に対してです.
EPOLLONESHOT:一度だけイベントをリスニングします.今回のイベントをリスニングした後、このソケットをリスニングし続ける必要がある場合は、このソケットを再びEPOLLキューに追加する必要があります.
トリガ待ち
epoll_waitはselectに似ています.パラメータeventsはカーネルからイベントの集合を得るために使用されます.maxeventsはカーネルというeventsがどれだけ大きいか(配列メンバーの個数)を示します.このmaxeventsの値はepoll_の作成より大きくありません.create()時のsize、パラメータtimeoutはタイムアウト時間(ミリ秒、0はすぐに戻り、-1は不確定、永久ブロックという説もあります).この関数は、処理するイベントの数を返します.たとえば、0を返すとタイムアウトします.
動作モード
epollのファイル記述子の操作には、LT(level trigger)とET(edge trigger)の2つのモードがあります.LTモードはデフォルトモードであり、LTモードとETモードの違いは以下の通りである.
LT(leveltriggered)はデフォルトの動作であり、blockとno-blocksocketを同時にサポートします.このイベントをすぐに処理しなくても、カーネルは絶えず通知します.この方法では、カーネルはファイル記述子が準備されているかどうかを教えてくれ、この準備されたfdをIO操作することができます.何もしないとカーネルが通知し続けるので、このモードのプログラミングでエラーが発生する可能性は小さくなります.従来のselect/pollはこのモデルの代表である.
ET(edge-triggered)は高速動作方式であり、no-blocksocketのみをサポートする.イベントはすぐに処理する必要があります.カーネルは通知しません.このモードでは、記述子が準備ができていないときに、カーネルはepollを通じて教えてくれます.その後、ファイル記述子が準備完了していることを知っていて、そのファイル記述子に対してより多くの準備完了通知を送信しないと仮定します(たとえば、要求を送信、受信、または受信したデータが一定量未満の場合、EWOULDBLOCKエラーが発生するなど).ただし、このfdをIO操作しないと(再び準備ができていない)、カーネルはより多くの通知(only once)を送信しません.
ETモードはepollイベントが繰り返しトリガされる回数を大幅に減少させるため,LTモードよりも効率が高い.epollがETモードで動作する場合、1つのファイルハンドルのブロック読み取り/ブロック書き込み操作によって複数のファイル記述子を処理するタスクが餓死することを避けるために、非ブロックソケットインタフェースを使用する必要があります.
select,poll実装では,デバイスが準備できるまで,すべてのfdセットを自分で絶えずポーリングする必要があり,その間に睡眠と目覚ましが何度も交互に行われる可能性がある.epollもepollを呼び出す必要がありますwaitは常に準備チェーンテーブルをポーリングし、その間に何度も睡眠と目覚ましが交互に行われることもありますが、デバイスが準備されている場合は、コールバック関数を呼び出し、準備fdを準備チェーンテーブルに入れ、epoll_で目覚まします.waitでは睡眠のプロセスに入ります.睡眠と交代が必要だが、selectとpollは「目が覚める」ときにfd集合全体を遍歴し、epollは「目が覚める」ときに準備チェーンテーブルが空いているかどうかを判断すればよいので、CPU時間を大幅に節約できる.これがコールバックメカニズムによるパフォーマンスの向上です.
select,pollは呼び出すたびにfdセットをユーザ状態からカーネル状態に1回コピーし,currentをデバイス待機キューに1回保留し,epollは1回コピーするだけで,また、currentを待機キューに掛けても一度だけ掛けます(epoll_waitの開始では、ここでの待機キューはデバイス待機キューではなく、epoll内部で定義された待機キューであることに注意してください).これも多くの費用を節約することができます.
      #define MAX_EVENTS 10

           struct epoll_event ev, events[MAX_EVENTS];

           int listen_sock, conn_sock, nfds, epollfd;



           /* Set up listening socket, 'listen_sock' (socket(),

              bind(), listen()) */



           epollfd = epoll_create(10);

           if (epollfd == -1) {

               perror("epoll_create");

               exit(EXIT_FAILURE);

           }



           ev.events = EPOLLIN;

           ev.data.fd = listen_sock;

           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {

               perror("epoll_ctl: listen_sock");

               exit(EXIT_FAILURE);

           }



           for (;;) {

               nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

               if (nfds == -1) {

                   perror("epoll_pwait");

                   exit(EXIT_FAILURE);

               }



               for (n = 0; n < nfds; ++n) {

                   if (events[n].data.fd == listen_sock) {

                       conn_sock = accept(listen_sock,

                                       (struct sockaddr *) &local, &addrlen);

                       if (conn_sock == -1) {

                           perror("accept");

                           exit(EXIT_FAILURE);

                       }

                       setnonblocking(conn_sock);

                       ev.events = EPOLLIN | EPOLLET;

                       ev.data.fd = conn_sock;

                       if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,

                                   &ev) == -1) {

                           perror("epoll_ctl: conn_sock");

                           exit(EXIT_FAILURE);

                       }

                   } else {

                       do_use_fd(events[n].data.fd);

                   }

               }

           }