はじめに
前回は、イベント用ファイルディスクリプタ「eventfd」の使用法や特徴を説明しました。今回は、時間を通知するファイルディスクリプタ「timefd」について解説していきたいと思います。
連載概要
この連載は、次のような内容について述べていく予定です。
連載目次
- 第1回:ディスクリプタの概要
- 第2回:イベント用ディスクリプタ「eventfd」の特徴
- 第3回:タイマー用ディスクリプタ「timerfd」の特徴
- 第4回:シグナル用ディスクリプタ「signalfd」の特徴
- 第5回:多重I/O「Multiplex I/O」の種類の特徴、使い方
- 第6回:多重I/Oの性能とC10K問題
- 第7回:シグナル駆動I/Oの特徴、使い方
- 第8回:非同期I/O「Asynchronous I/O」の使い方と性能差
- 第9回:ファイルディスクリプタパッシングの特徴、使い方
サンプルプログラムは100行前後程度までは画面に記載します。全プログラムは圧縮してページ上部よりダウンロード可能にしています。make
コマンドでコンパイルできます。i386/x86_64環境で動作確認済みです。
プログラムのボリューム上、エラー処理や引数チェックなどを省いているので、あらかじめご了解ください。また使用法を誤るとシステムに重大な影響を与える可能性があります。利用する場合は責任のとれる環境において実行するよう、お願いします。
当トピックでは、実際にプログラムを通して動作確認や性能測定を行うことで、個人的な見解を述べさせていただきます。あくまで個人的な感想に基づいているので、反論や指摘などあるかと思います。指摘や質問などは大歓迎なので、その際はぜひご連絡ください。可能な限りの対応に努めます。
timerfdについて
timerfd(2)
は、あらかじめ設定された時間が経過すると、ファイルディスクリプタ経由で満了したことを通知できるディスクリプタを生成します。同じような機能は提供されていましたが、timerfd(2)
はファイルディスクリプタを用意してくれるので、多重I/Oとして監視することが可能となりました。関数は下記のとおりです。詳細はリファレンスを参照ください。
int timerfd_create ( int clockid, int flags );
int timerfd_settime( int fd, int flags, const struct itimerspec * new_value, struct itimerspec * curr_value );
int timerfd_gettime( int fd, struct itimerspec * curr_value );
timerfd_create(2)
の戻り値がファイルディスクリプタとなります。
サンプルプログラム
#include <sys/timerfd.h> #include <sys/time.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <stdint.h> #define TV2SEC(tv) ((double)((tv).tv_sec) + (double)((tv).tv_usec / 1000000.0)) int main(int argc, char ** argv ) { struct timespec cur; struct timeval tv1,tv2; if( argc < 4 ) { fprintf( stderr, "Usage: %s <num> <num> <num>...\n", argv[0] ); exit( EXIT_FAILURE ); } int fd = timerfd_create( CLOCK_MONOTONIC, 0 ); clock_gettime( CLOCK_MONOTONIC, &cur ); struct itimerspec val; val.it_value.tv_sec = cur.tv_sec + atoi( argv[1] ); val.it_value.tv_nsec = 0; val.it_interval.tv_sec = atoi( argv[2] ); val.it_interval.tv_nsec = 0; timerfd_settime( fd, TFD_TIMER_ABSTIME, &val, 0 ); gettimeofday( &tv1, 0 ); uint64_t read_cnt; int cnt; for( cnt = 0; cnt < atoi( argv[3] ); cnt ++ ) { read( fd, &read_cnt, sizeof( uint64_t )); gettimeofday( &tv2, 0 ); double rtn = TV2SEC( tv2 ) - TV2SEC( tv1 ); printf( "timerfd %d Path... [%f]\n", cnt + 1, rtn ); tv1 = tv2; } close( fd ); exit( EXIT_SUCCESS ); }
上記プログラムは、タイマー完了時間と、その後の繰り返し完了間隔を設定しています。read(2)
で完了時間を待っています。
関数リファレンスはMANページを参照して欲しいと述べましたが、一点だけ、timerfd_create(2)
に渡しているCLOCK_MONOTONICについて言及したいと思います。
timerfd_create(2)
の第1引数は、CLOCK_REALTIMEかCLOCK_MONOTONICのどちらかを設定します。
- CLOCK_REALTIME
そのサーバで設定されている時刻を表しています。
言いかえれば目覚まし時計のようなものです。その時刻になると呼び出しますが、意識して時刻を遅らせれば、目覚める時間もその分遅延します。 - CLOCK_MONOTONIC
そのサーバが起動してからの時刻を表します。具体的な時刻はclock_gettime(2)
で取得します。
サーバの時刻に依存せず、定期的に呼び出してくれます。
個人的な見解としては、ほとんどの局面でCLOCK_MONOTONICを使って問題は無いと考えています。しかし、時間がさかのぼってしまう状況でもサーバの時刻ぴったりに動作させたいのであれば、CLOCK_REALTIMEを使用します。特徴を理解して使いましょう。