SHOEISHA iD

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

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

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

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

第5回

  • X ポスト
  • このエントリーをはてなブックマークに追加

一般的な多重I/Oプログラム

 通常(というか今までは)、多重I/Oはselect(2)もしくはpoll(2)関数が使用されてきました。

 以下サンプルはあえて多少冗長にしているため、読みづらいと思います。多重I/O対応のプログラムで、eventfd, timerfd を監視しています。

 timerfd(2)によって2秒間隔で通知を受け付け、同時に子プロセス群からeventfd(2)を使って通知を待ちます(子プロセス数はプログラム実行時の引数を使いますので、大きな数値にすると、サーバ自身がサスペンドに陥ります、使用には注意してください)。子プロセスグループがすべていなくなった時点でプロセスを停止させています。

 おそらく子プロセスからeventfd(2)を使って処理を行う場合、サンプルのように定期的に子プロセスがいるか確認するしか手段はないのかな、と考えます。

select_sample.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <sys/param.h>

void sigchld_ign( );
int  timerfd( );
void child_process( int efd, int child_cnt );

int main(int argc, char ** argv ) {
    uint64_t       read_cnt;
    fd_set         rset, rset_org;
    struct timeval tm, tm_org;

    if( argc < 2 ) {
    fprintf( stderr, "Usage: %s <num>\n", argv[0] );
        exit( EXIT_FAILURE );
    }
    sigchld_ign( );
    int efd = eventfd( 0, 0 );
    int tfd = timerfd( );
    pid_t pid = fork();
    if( pid == 0 ) {
        child_process( efd, atoi( argv[1] ));
    }
    printf( "Parent read to efd. pid:[%d] ppid:[%d]\n", getpid(), getppid());

    FD_ZERO( &rset_org );
    FD_SET( efd, &rset_org );
    FD_SET( tfd, &rset_org );
    int max_fd = MAX( efd, tfd ) + 1;
    tm_org.tv_sec  = 1;
    tm_org.tv_usec = 0;
    while( 1 ) {
        rset = rset_org;
        tm   = tm_org;
        if( select( max_fd, &rset, 0, 0, &tm ) == 0 ) {
            kill( -pid, 0 );
            if( errno == ESRCH ) {
                break;
            }
            continue;
        }
        if( FD_ISSET( efd, &rset )) {
            read( efd, &read_cnt, sizeof( uint64_t ));
            printf( "eventfd Parent read:[%ld]\n", read_cnt );
        }
        if( FD_ISSET( tfd, &rset )) {
            read( tfd, &read_cnt, sizeof( uint64_t ));
            printf( "timerfd Parent read:[%ld]\n", read_cnt );
        }
    }
    printf( "Parent completed read loop\n" );
    return( 0 );
}

void sigchld_ign( ) {
    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sa.sa_flags   = SA_NOCLDWAIT;
    sigemptyset( &sa.sa_mask );
    sigaction( SIGCHLD, &sa, 0 );
}

int timerfd( ) {
    struct timespec cur;
    int fd = timerfd_create( CLOCK_MONOTONIC, 0 );
    clock_gettime( CLOCK_MONOTONIC, &cur );
    struct itimerspec val;
    val.it_value.tv_sec     = cur.tv_sec + 2;
    val.it_value.tv_nsec    = 0;
    val.it_interval.tv_sec  = 2;
    val.it_interval.tv_nsec = 0;
    timerfd_settime( fd, TFD_TIMER_ABSTIME, &val, 0 );
    return fd;
}

void child_process( int efd, int child_cnt ) {
    int i;
    setsid( );
    sigchld_ign( );
    for( i = 0; i < child_cnt; i ++ ) {
        if( 0 == fork( )) {
            printf( "Child write to efd. pid:[%d] ppid:[%d]\n", getpid(), getppid());
            sleep( 10 );
            uint64_t write_cnt = ( uint64_t )i + 1;
            write( efd, &write_cnt, sizeof( uint64_t ));
            exit( EXIT_SUCCESS );
        }
    }
    wait( 0 );
    printf( "Child completed write loop\n" );
    exit( EXIT_SUCCESS );
}
poll_sample.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <poll.h>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <sys/param.h>

void sigchld_ign( );
int  timerfd( );
void child_process( int efd, int child_cnt );

int main(int argc, char ** argv ) {
    uint64_t       read_cnt;
    struct pollfd  pollfd[2];

    if( argc < 2 ) {
    fprintf( stderr, "Usage: %s <num>\n", argv[0] );
        exit( EXIT_FAILURE );
    }
    sigchld_ign( );
    int efd = eventfd( 0, 0 );
    int tfd = timerfd( );
    pid_t pid = fork();
    if( pid == 0 ) {
        child_process( efd, atoi( argv[1] ));
    }
    printf( "Parent read to efd. pid:[%d] ppid:[%d]\n", getpid(), getppid());

    pollfd[0].fd     = efd;
    pollfd[0].events = POLLIN;
    pollfd[1].fd     = tfd;
    pollfd[1].events = POLLIN;
    while( 1 ) {
        if( poll( pollfd, 2, 1000 ) == 0 ) {
            kill( -pid, 0 );
            if( errno == ESRCH ) {
                break;
            }
            continue;
        }
        if( pollfd[0].revents & POLLIN ) {
            read( efd, &read_cnt, sizeof( uint64_t ));
            printf( "eventfd Parent read:[%ld]\n", read_cnt );
        }
        if( pollfd[1].revents & POLLIN ) {
            read( tfd, &read_cnt, sizeof( uint64_t ));
            printf( "timerfd Parent read:[%ld]\n", read_cnt );
        }
    }
    printf( "Parent completed read loop\n" );
    return( 0 );
}
・・・(以降は、select プログラムと同じ)

 select(2)poll(2)の違いは、管理できるディスクリプタ数です。select(2)で管理できるディスクリプタ数はFD_SETSIZE(一般的には1,024:※1)ですが、pollは制限がありません(※2)。よってディスクリプタの同時管理数見込みが1,000以上あるのであれば、少なくともselect(2)は使えないということになります。

注2

 1024とは、1024個分のディスクリプタを管理できるという意味ではなく、1024までの数値のディスクリプタを管理できるという意味です。

注3

 poll自身は制限を設けていないという意味であり、実際にはプログラム中で配列を用意した際のサイズやファイルオープン上限数、ポート使用制限数など、そのサーバのリソースの制限などによって制限が発生します。

 なお、ファイルオープン数はulimitコマンドなどを使って変更でき、ポート使用制限数はカーネルパラメータで変更可能で、その数値は少なくとも数万くらいまでの設定が可能なようです。

次のページ
epoll

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

  • X ポスト
  • このエントリーをはてなブックマークに追加
ファイルディスクリプタについて連載記事一覧

もっと読む

この記事の著者

赤松 エイト(エイト)

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

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

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

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4816 2010/02/26 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング