一般的な多重I/Oプログラム
通常(というか今までは)、多重I/Oはselect(2)
もしくはpoll(2)
関数が使用されてきました。
以下サンプルはあえて多少冗長にしているため、読みづらいと思います。多重I/O対応のプログラムで、eventfd, timerfd を監視しています。
timerfd(2)
によって2秒間隔で通知を受け付け、同時に子プロセス群からeventfd(2)
を使って通知を待ちます(子プロセス数はプログラム実行時の引数を使いますので、大きな数値にすると、サーバ自身がサスペンドに陥ります、使用には注意してください)。子プロセスグループがすべていなくなった時点でプロセスを停止させています。
おそらく子プロセスからeventfd(2)
を使って処理を行う場合、サンプルのように定期的に子プロセスがいるか確認するしか手段はないのかな、と考えます。
#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 ); }
#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)
は使えないということになります。
1024とは、1024個分のディスクリプタを管理できるという意味ではなく、1024までの数値のディスクリプタを管理できるという意味です。
poll自身は制限を設けていないという意味であり、実際にはプログラム中で配列を用意した際のサイズやファイルオープン上限数、ポート使用制限数など、そのサーバのリソースの制限などによって制限が発生します。
なお、ファイルオープン数はulimitコマンドなどを使って変更でき、ポート使用制限数はカーネルパラメータで変更可能で、その数値は少なくとも数万くらいまでの設定が可能なようです。