SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ファイルディスクリプタについて

ファイルディスクリプタについて(5)
~多重I/O「Multiplex I/O」の種類の特徴、使い方

第5回

レベルトリガ機能とエッジトリガ機能

 epoll(7)には、レベルトリガ機能とエッジトリガ機能を使い分けられる特徴があります。

 ディスクリプタを経由して、一方からデータが書かれたとします。そのディスクリプタを監視している側では、ディスクリプタがready状態になったことを検知し、ディスクリプタを介してデータを読み込みます。

 その際に一部のデータのみ読み出し、ディスクリプタが管理しているデータが残存している状況でselect(2),poll(2)などの多重I/O用関数に再度管理させたとします。

 すると、多重IO用関数はそのディスクリプタをすぐにready状態にします。つまりディスクリプタが管理しているデータをすべて読み込まない限りはready状態が解除されません。これをレベルトリガ機能と言います。

 エッジトリガ機能はこの逆で、いったんready状態になったものを再度管理させた時、ディスクリプタにデータが残っていても管理されます。残っているデータをどうするかは、完全にアプリケーション任せです。

 残ったまま再度データが書き込まれれば、ディスクリプタはready状態になり、残ったデータから読み込むことになります。全データをすべて読み書きしたい場合は、read(2)/write(2)がエラー(リターンコードが -1)かつerrnoにEAGAINが返されるまで処理を続行します。

 select(2), poll(2)はレベルトリガ機能だけしか使えませんが、epoll(7)は両機能を使い分けることができます。デフォルトはレベルトリガ機能です。エッジトリガ機能として使う場合、以下の点を意識して行う必要があります。

  • 対象ディスクリプタを epoll(7) 管理下にする際、EPOLLET フラグを付加する。
  • 対象ディスクリプタはノンブロッキング設定にする。
  • 対象ディスクリプタを通してすべてのデータを read(2) / write(2) する際 EAGAIN が返されたら処理を終える。

 なお(ややこしいことに)レベルトリガ機能を使用しつつエッジトリガ機能と同様の動作をさせることができます。その際は以下の処理が必要となります。

  • 対象ディスクリプタを epoll(7) 管理下にする際、EPOLLONESHOT フラグを付加する。
  • 対象ディスクリプタはノンブロッキング設定にする。
  • 対象ディスクリプタを通してすべてのデータをread(2)/write(2)する際EAGAINが返されたら処理を終える。
  • 処理を終えた際、epoll_ctl(2)を実行、その際のフラグとしてEPOLL_CTL_MODフラグを使用する。

 上記より、EPOLL_CTL_MODフラグを使用してepoll_ctl(2)を実行するのはユーザー(プログラマ)に任されています。

 サンプルプログラムとしてepoll_level_sample.c, epoll_edge_sample.c, epoll_level_sample_oneshot.cを用意しました。ループバックアドレスを通してコネクトし、スレッドから文字列"hello"を1秒間隔で3回書き込みます。メインプロセスではepoll(7)を使ってソケットを管理していますが、ready状態になったら1バイトだけ読み込んで再度epoll(7)管理にしています。その時の挙動を見ます。

レベルトリガのサンプル

 まずはレベルトリガ機能の動作を見ます。

epoll_level_sample.c
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/epoll.h>
#define  MAXCONNECTS 3
int  pipefd[2];
int  wrap_accept( int sock );
int  client_read( int sock );
void * client_threaed( void * arg );

int main( ) {

    struct sockaddr_in srv_addr;
    struct epoll_event ev, evs[MAXCONNECTS];
    int    epfd, nfds, fd, i;
    int    loop_flg = 1;

    int sock = socket( AF_INET, SOCK_STREAM, 0 );
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons( 0 );
    srv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
    bind( sock, ( struct sockaddr *)&srv_addr, sizeof( srv_addr ));
    listen( sock, SOMAXCONN );
    socklen_t socklen = sizeof( srv_addr );
    getsockname( sock, ( struct sockaddr *)&srv_addr, &socklen);

    pipe( pipefd );
    pthread_t pid;
    pthread_create( &pid, 0, &client_threaed, &srv_addr.sin_port );

    epfd = epoll_create( MAXCONNECTS );
    ev.events  = EPOLLIN;
    ev.data.fd = sock;
    epoll_ctl( epfd, EPOLL_CTL_ADD, sock, &ev );
    ev.events  = EPOLLIN;
    ev.data.fd = pipefd[0];
    epoll_ctl( epfd, EPOLL_CTL_ADD, pipefd[0], &ev );

    while( loop_flg ) {
        nfds = epoll_wait( epfd, evs, MAXCONNECTS, -1 );
        for( i = 0; i < nfds; i ++ ) {
            if( evs[i].data.fd == sock ) {
                fd = wrap_accept( sock );
                ev.events = EPOLLIN;
                ev.data.fd = fd;
                epoll_ctl( epfd, EPOLL_CTL_ADD, fd, &ev );
            }
            else if( evs[i].data.fd == pipefd[0] ) {
                loop_flg = 0;
                break;
            }
            else {
                if( client_read( evs[i].data.fd ) == 0 ) {
                    printf( "connection close ...\n" );
                    close( evs[i].data.fd );
                    ev.events = 0;
                    ev.data.fd = 0;
                    epoll_ctl( epfd, EPOLL_CTL_DEL, evs[i].data.fd, &ev );
                }
            }
        }
    }
    return 0;
}

int  wrap_accept( int sock ) {
    struct sockaddr_in addr;
    socklen_t len = sizeof( addr );
    return accept( sock, ( struct sockaddr *)&addr, &len );
}

int  client_read( int sock ) {
    static int cnt;
    char c = 0;
    int rtn = read( sock, &c, 1 );
    if( rtn == 0 ) {
        return 0;
    }
    cnt ++;
    printf( "cnt:%2d  [%c]\n", cnt, c );
    return rtn;
}

void * client_threaed( void * arg ) {
    unsigned short int port = *( unsigned short int * )arg;
    pthread_detach( pthread_self( ));

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons( ntohs( port ));
    addr.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
    int sock = socket( AF_INET, SOCK_STREAM, 0 );
    connect( sock, ( struct sockaddr *)&addr, sizeof( addr ));

    int i;
    for( i = 0; i < 3; i ++ ) {
        sleep( 1 );
        write( sock, "hello", 5 );
    }
    close( sock );
    sleep( 1 );
    close( pipefd[1] );
    return 0;
}

 makeして実行すると、次のとおりに出力されます。

epoll_level_sampleの動作結果
# ./epoll_level_sample
cnt: 1  [h]
cnt: 2  [e]
cnt: 3  [l]
cnt: 4  [l]
cnt: 5  [o]
cnt: 6  [h]
cnt: 7  [e]
cnt: 8  [l]
cnt: 9  [l]
cnt:10  [o]
cnt:11  [h]
cnt:12  [e]
cnt:13  [l]
cnt:14  [l]
cnt:15  [o]
connection close ...
# 

 データが残っている間はepoll(7)ですぐにready状態になるので、すぐに呼ばれます。

エッジトリガのサンプル

 次にエッジトリガ機能の動作を見ます。ファイルサイズが130行を超えるため、ダウンロードして参照してください(epoll_edge_sample.c)。

 makeして実行すると、次のように出力されます。

epoll_edge_sampleの動作結果
# ./epoll_edge_sample
cnt: 1  [h]
cnt: 2  [e]
cnt: 3  [l]
#

 擬似クライアント(スレッド)が1秒間隔で3回書き込むことでready状態になるのは3回だけなので、3文字しか出力されません。4バイト目が出力する場合、クライアントでソケットを閉じることでready状態となりepoll(7)が検知して残ったデータを読み込んでいるためです。

 次は下記の通りコンパイルし、実行してください。

epoll_edge_sampleのコンパイル&実行
# make ET
# ./epoll_edge_sample
cnt: 1  [h]
cnt: 1  [e]
cnt: 1  [l]
cnt: 1  [l]
cnt: 1  [o]
cnt: 2  [h]
cnt: 2  [e]
cnt: 2  [l]
cnt: 2  [l]
cnt: 2  [o]
cnt: 3  [h]
cnt: 3  [e]
cnt: 3  [l]
cnt: 3  [l]
cnt: 3  [o]
connection close ...
#

 今回はレベルトリガと同じように出力されました。read(2)にてEAGAINが返るまで読み込んだために、すべての情報を取得できました。

エッジトリガ風レベルトリガのサンプル

 さらにレベルトリガ機能の動作を見ます。こちらもファイルサイズが130行を超えるため、ダウンロードして参照してください(epoll_level_sample_oneshot.c)。

 1バイト読み出した後、あえてepoll_ctl(2)を実行する前にsleep(1)を実行しています。makeして実行すると、次のとおりエッジトリガと同じように出力されます。

epoll_level_sample_oneshotの実行結果
# ./epoll_level_sample_oneshot
cnt: 1  [h]
cnt: 2  [e]
cnt: 3  [l]
cnt: 4  [l]
#

 次は下記の通りコンパイルし、実行してください。

epoll_level_sample_oneshot のコンパイル&実行
# make ET
# ./epoll_level_sample_oneshot
cnt: 1  [h]
cnt: 1  [e]
cnt: 1  [l]
cnt: 1  [l]
cnt: 1  [o]
cnt: 2  [h]
cnt: 2  [e]
cnt: 2  [l]
cnt: 2  [l]
cnt: 2  [o]
cnt: 3  [h]
cnt: 3  [e]
cnt: 3  [l]
cnt: 3  [l]
cnt: 3  [o]
connection close ...
#

 レベルトリガと同じように出力されました。read(2)にてEAGAINが返るまで読み込んだために、すべての情報を取得できました。

まとめ

 多重I/Oの種類の紹介については以上となります。以降の回では、epoll(7)で実現できる機能の種類、および多重I/Oの性能測定について言及していきたいと思います。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
ファイルディスクリプタについて連載記事一覧

もっと読む

この記事の著者

赤松 エイト(エイト)

(株)DTSに勤てます。WebアプリやJavaやLL等の上位アプリ環境を密かに憧れつつも、ず~っとLinuxとかHP-UXばかり、ここ数年はカーネル以上アプリ未満のあたりを行ったり来たりしています。mixiもやってまして、こちらは子育てとか日々の日記メインです。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/4816 2010/02/26 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー