timerfdによる周期処理のサンプル


Linuxでtimerfdを使用したウェイト処理の手順について説明します。

一定時間ウェイトする関数には以下のものがあります。

  1. sleep
  2. usleep
  3. nanosleep
  4. selectのタイムアウト指定で待つ
  5. settimer
  6. timerfd

それぞれメリット、デメリットがありますが、 ここに挙げたほとんどの関数はSIGNALが発生すると中断してしまい、正しく指定時間待つには残り時間を再度待つ為にウェイトのリトライを行う必要があります。
リトライでウェイトを継続できるとはいえ、指定時間の分解能や中断中の処理のオーバーヘッドにより当初指定した時間を正しく待てる保障はありません。

上の一覧のうち、timerfdを使用すればリトライ処理やオーバーヘッドを気にする事無く正確に指定時間を待つ事が出来ます。

ではtimerfdとはどの様なものでしょう?
timerfd_createのmanページには「満了通知をファイルディスクリプター経由で配送する タイマーの生成と操作を行う。……このファイルディスクリプターを select(2), poll(2), epoll(7) で監視できるという利点がある。」とあります。
つまり、タイマーが満了したらそのファイルディスクリプターにデータが入るというものです。

timerfdのその他のメリットについて

  • selectを使用する事で、ファイルI/Oやソケット通信などのディスクリプタを使用する他のI/Oとの同時待ちができる。
  • 複数のタイマーを同時に使える。
  • 周期的なタイマーを設定できる。

以下にtimerfdを使用してreadで待つ例、selectで待つ例、ポーリングする例を示します。
timerfdに関しては本ブログ等よりオンラインマニュアルの方が詳しく書かれており、使用例も有ります。
Linuxマシンの端末でman timerfd_create と打つか、man timerfd_create でググって見ることをお勧めします。

========== readで待つ場合のサンプル ==========

#include <sys/timerfd.h>
#include <signal.h>
#include <signal.h>
#include <stdint.h>

void timerfd_read()
{
    int tfd;
    struct itimerspec stime;
    ssize_t     rtn;
    uint64_t   exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, 0 );

    stime.it_value.tv_sec  = 1;        // ①
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    do {
        rtn = read( tfd, &exp, sizeof(uint64_t));        // ②
    } while ( rtn < 0 && errno == EINTR);

    printf("timerfd timeout.  exp=%lu\n", exp);
    close( tfd );
}

① itimerspec構造体で初回のタイマー及び2回目以後の周期タイマーを設定する。
  it_valueが初回タイマーで、it_intervalが周期タイマーである。
② readでtimerfdのタイマー満了を待つ。
  この例ではタイマー待ち中にシグナルの発生でreadがエラー終了する場合、リトライする様に対処している。

========== selectで待つ場合のサンプル ==========

#include <sys/select.h>
#include <sys/timerfd.h>

void timerfd_select()
{
    int tfd;
    struct itimerspec stime;
    fd_set fds;
    ssize_t    rtn;
    uint64_t exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, 0 );

    stime.it_value.tv_sec  = 1;
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    while( 1 ){
        FD_ZERO( &fds );
        FD_SET( tfd, &fds );

        rtn = select( tfd+1, &fds, NULL, NULL, NULL );     // ①
        if ( rtn > 0 ){
            if ( FD_ISSET( tfd, &fds )){
                read( tfd, &exp, sizeof(uint64_t));        // ②
                printf("timerfd timeout.  exp=%lu\n", exp);
                break;
            }
        }
    }
    close( tfd );
}

① selectでタイマー満了を待つ
② selectで待った時でもreadでtimerfdのデータを読み込む必要がある。

========== readでポーリングする場合のサンプル ==========

#include <sys/timerfd.h>
#include <signal.h>
#include <signal.h>
#include <stdint.h>
void timerfd_read()
{
    int tfd;
    struct itimerspec stime;
    ssize_t    rtn;
    uint64_t   exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK );        // ①
    stime.it_value.tv_sec  = 1;
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    while (1) {
        rtn = read( tfd, &exp, sizeof(uint64_t));        // ②
        if ( rtn < 0 && errno == EINTR  ) continue;
        if ( rtn < 0 && errno == EAGAIN ) continue;        // ③
        break;
    }
    printf("timerfd timeout.  exp=%lu, rtn = %ld, errno=%d\n", exp, rtn, errno);

    close( tfd );
}

① TFD_NONBLOCKを指定する事でtimerfdはノンブロッキングになる。
② ノンブロッキンッグなので、このreadはタイマー満了していなくても終了する。
③ タイマー満了していない場合、readの戻り値は-1でerrnoにEAGAINがセットされる。
  readが成功完了するまで繰り返してタイマー満了を待つ。

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。


関連記事

TOP
CONTACT ACCESS LINE CALL