timerfd_settime(2) ファイルディスクリプタ経由で通知するタイマー

Other Alias

timerfd_create, timerfd_gettime

書式

#include <sys/timerfd.h>


int timerfd_create(int clockid, int flags);

int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);

int timerfd_gettime(int fd, struct itimerspec *curr_value);

説明

これらのシステムコールは、満了通知をファイルディスクリプタ経由で配送する タイマーの生成と操作を行う。 これらは、 setitimer(2) や timer_create(2) を用いる方法の代わりとなるものであり、このファイルディスクリプタを select(2), poll(2), epoll(7) で監視できるという利点がある。

これらのシステムコールを使うのは、それぞれ timer_create(2), timer_settime(2), timer_gettime(2) を使うのと同様である (timer_getoverrun(2) に対応するものはなく、以下で説明するように この機能は read(2) により提供される)。

timerfd_create()

timerfd_create() は新規のタイマーオブジェクトを生成し、そのタイマーを参照するファイル ディスクリプタを返す。 clockid 引き数は、タイマーの進捗を管理するためのクロックを指定するもので、 CLOCK_REALTIMECLOCK_MONOTONIC のいずれかでなければならない。 CLOCK_REALTIME はシステム全体で使用されるクロックで、このクロックは変更可能である。 CLOCK_MONOTONIC は変更されることのないクロックで、(システム時刻の手動での変更などの) システムクロックの不連続な変化の影響を受けない。 これらのクロックの現在の値は clock_gettime(2) を使って取得できる。

Linux 2.6.27 以降では、 以下の値のいくつかをビット単位の論理和 (OR) で指定することで、 timerfd_create() の振舞いを変更することができる。

TFD_NONBLOCK
新しく生成されるオープンファイル記述 (open file description) の O_NONBLOCK ファイルステータスフラグをセットする。 このフラグを使うことで、 O_NONBLOCK をセットするために fcntl(2) を追加で呼び出す必要がなくなる。
TFD_CLOEXEC
新しいファイルディスクリプタに対して close-on-exec (FD_CLOEXEC) フラグをセットする。 このフラグが役に立つ理由については、 open(2) の O_CLOEXEC フラグの説明を参照のこと。

バージョン 2.6.26 以前の Linux では、 flags 引き数は未使用であり、0 を指定しなければならない。

timerfd_settime()

timerfd_settime() は、ファイルディスクリプタ fd により参照されるタイマーを開始したり停止したりする。

new_value 引き数は、タイマーの満了時間 (expiration) の初期値と間隔 (interval) を 指定する。この引き数で使用されている itimerspec 構造体には 2 つのフィールドがあり、各フィールドは timespec 型の構造体である。

struct timespec {
    time_t tv_sec;                /* Seconds */
    long   tv_nsec;               /* Nanoseconds */
};
struct itimerspec {
    struct timespec it_interval;  /* Interval for periodic timer */
    struct timespec it_value;     /* Initial expiration */
};

new_value.it_value はタイマーの満了時間の初期値を、秒とナノ秒で指定する。 new_value.it_value のフィールドのうち少なくとも一方に 0 以外の値を設定すると、 タイマーが開始される。 両方のフィールドに 0 を設定すると、タイマーが停止する。

new_value.it_interval はタイマーの一回目の満了後に繰り返しタイマーの満了間隔を、秒とナノ秒で指定する。 new_value.it_interval のフィールドのうち少なくとも一方に 0 以外の値を設定すると、 繰り返しタイマーが有効になる。 両方のフィールドに 0 を設定した場合、タイマーは new_value.it_value で指定された時間後に、一回だけ満了して停止する。

flags 引き数には 0 か TFD_TIMER_ABSTIME を指定する。 0 は相対時刻タイマーを意味し、 new_value.it_value では clockid で指定されたクロックの現在の値からの相対的な時刻を指定する。 TFD_TIMER_ABSTIME は絶対時刻タイマーを意味し、 new_value.it_interval では clockid で指定されたクロックの絶対時刻を指定する。 つまり、クロックの値が new_value.it_interval で指定された時刻に達したら、タイマーが満了する。

old_value 引き数が NULL でない場合、 old_value 引き数が指す itimerspec 構造体は、 timerfd_settime() を呼び出した時点でのタイマーの設定を返すのに使用される。 下記の timerfd_gettime() の説明を参照。

timerfd_gettime()

timerfd_gettime() は、ファイルディスクリプタ fd で参照されるタイマーの現在の設定が入った itimerspec 構造体を、 curr_value に格納して返す。

it_value フィールドは、タイマーが次に満了するまでの残り時間を返す。 この構造体の両方のフィールドが 0 であれば、タイマーは現在停止している。 タイマー設定時に TFD_TIMER_ABSTIME フラグが指定されたかに関わらず、このフィールドは常に相対値が格納される。

it_interval フィールドは、タイマーの間隔を返す。 この構造体の両方のフィールドが 0 であれば、タイマーは new_value.it_value で指定された時間後に一回だけ満了して停止するように設定されている。

タイマー・ファイルディスクリプタに対する操作

timerfd_create() が返すファイルディスクリプタは以下の操作をサポートしている。
read(2)
timerfd_settime() を使ってタイマーの設定が最後変更されて以降、または read(2) の呼び出しに最後に成功して以降に、タイマーの満了が一回以上発生していれば、 read(2) に渡されたバッファに、タイマー満了回数を示す 8 バイトの unsigned 型の整数 (uint64_t) が返される (返される値はホストバイトオーダ、つまりそのホストマシンにおける 整数の通常のバイトオーダである)。
read(2) を行った時点でタイマーの満了が発生していなければ、 read(2) は停止 (block) する、もしくはファイルディスクリプタが 非停止 (nonblocking) に設定されている場合はエラー EAGAIN で失敗する (非停止モードにするには、 fcntl(2) の F_SETFL 命令で O_NONBLOCK フラグをセットする)。
渡されたバッファの大きさが 8 バイト未満の場合、 read(2) はエラー EINVAL で失敗する。
poll(2), select(2) (と同様の操作)
一つ以上のタイマー満了が発生していれば、 ファイルディスクリプタは読み出し可能となる (select(2) の readfds 引き数や poll(2) の POLLIN フラグ)。
このファイルディスクリプタは、他のファイルディスクリプタ多重 API である pselect(2), ppoll(2), epoll(7) もサポートしている。
close(2)
ファイルディスクリプタがそれ以降は必要なくなった際には、クローズすべきである。 同じ timer オブジェクトに関連付けられたファイルディスクリプタが全て クローズされると、そのタイマーは解除され、 そのオブジェクト用の資源がカーネルにより解放される。

fork(2) での扱い

fork(2) が行われると、子プロセスは timerfd_create() により生成されたファイルディスクリプタのコピーを 継承する。そのファイルディスクリプタは、親プロセスの対応する ファイルディスクリプタと同じタイマーオブジェクトを参照しており、 子プロセスの read(2) でも同じタイマーの満了に関する情報が返される。

execve(2) での扱い

execve(2) の前後で timerfd_create() により生成されたファイルディスクリプタは保持され、 タイマーが開始されていた場合にはタイマーの満了が発生し続ける。

返り値

成功すると、 timerfd_create() は新しいファイルディスクリプタを返す。 エラーの場合、-1 を返し、 errno にエラーを示す値を設定する。

timerfd_settime() と timerfd_gettime() は成功すると 0 を返す。 エラーの場合、-1 を返し、 errno にエラーを示す値を設定する。

エラー

timerfd_create() は以下のエラーで失敗する可能性がある。
EINVAL
clockid 引き数が CLOCK_MONOTONIC でも CLOCK_REALTIME でもない。
EINVAL
flags が無効である。もしくは、Linux 2.6.26 以前の場合には flags が 0 以外である。
EMFILE
オープン済みのファイルディスクリプタの数がプロセスあたりの上限に 達していた。
ENFILE
オープン済みのファイル総数がシステム全体の上限に達していた。
ENODEV
(カーネル内の) 無名 inode デバイスをマウントできなかった。
ENOMEM
タイマーを作成するのに十分なカーネルメモリがなかった。

timerfd_settime() と timerfd_gettime() は以下のエラーで失敗する可能性がある。

EBADF
fd が有効なファイルディスクリプタでない。
EFAULT
new_value, old_value, curr_value が有効なポインタではない。
EINVAL
fd が有効な timerfd ファイルディスクリプタでない。

timerfd_settime() は以下のエラーで失敗することもある。

EINVAL
new_value が適切に初期化されていない (tv_nsec の一つが 0 から 999,999,999 までの範囲に入っていない)。
EINVAL
flags が無効である。

バージョン

これらのシステムコールはカーネル 2.6.25 以降の Linux で利用可能である。 ライブラリ側のサポートはバージョン 2.8 以降の glibc で提供されている。

準拠

これらのシステムコールは Linux 固有である。

バグ

現在のところ、 timerfd_create() が対応している clockid の種類は timer_create(2) よりも少ない。

以下のプログラムは、タイマーを作成し、その進捗をモニターするものである。 このプログラムは最大で 3 個のコマンドライン引き数を取り、 第一引き数ではタイマーの満了時間の初期値 (秒数単位) を、 第二引き数ではタイマーの間隔 (秒数単位) を、 第三引き数ではタイマーが何回満了したらプログラムが終了するかを指定する。 第二引き数と第三引き数は省略可能である。

以下のシェルのセッションはこのプログラムの使用例を示したものである。

$ a.out 3 1 100
0.000: timer started
3.000: read: 1; total=1
4.000: read: 1; total=2
^Z                  # type control-Z to suspend the program
[1]+  Stopped                 ./timerfd3_demo 3 1 100
$ fg                # Resume execution after a few seconds
a.out 3 1 100
9.660: read: 5; total=7
10.000: read: 1; total=8
11.000: read: 1; total=9
^C                  # type control-C to suspend the program

プログラムのソース

#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>        /* Definition of uint64_t */
#define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)
static void
print_elapsed_time(void)
{
    static struct timespec start;
    struct timespec curr;
    static int first_call = 1;
    int secs, nsecs;
    if (first_call) {
        first_call = 0;
        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
            handle_error("clock_gettime");
    }
    if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
        handle_error("clock_gettime");
    secs = curr.tv_sec - start.tv_sec;
    nsecs = curr.tv_nsec - start.tv_nsec;
    if (nsecs < 0) {
        secs--;
        nsecs += 1000000000;
    }
    printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
int
main(int argc, char *argv[])
{
    struct itimerspec new_value;
    int max_exp, fd;
    struct timespec now;
    uint64_t exp, tot_exp;
    ssize_t s;
    if ((argc != 2) && (argc != 4)) {
        fprintf(stderr, "%s init-secs [interval-secs max-exp]\n",
                argv[0]);
        exit(EXIT_FAILURE);
    }
    if (clock_gettime(CLOCK_REALTIME, &now) == -1)
        handle_error("clock_gettime");
    /* Create a CLOCK_REALTIME absolute timer with initial
       expiration and interval as specified in command line */
    new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
    new_value.it_value.tv_nsec = now.tv_nsec;
    if (argc == 2) {
        new_value.it_interval.tv_sec = 0;
        max_exp = 1;
    } else {
        new_value.it_interval.tv_sec = atoi(argv[2]);
        max_exp = atoi(argv[3]);
    }
    new_value.it_interval.tv_nsec = 0;
    fd = timerfd_create(CLOCK_REALTIME, 0);
    if (fd == -1)
        handle_error("timerfd_create");
    if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
        handle_error("timerfd_settime");
    print_elapsed_time();
    printf("timer started\n");
    for (tot_exp = 0; tot_exp < max_exp;) {
        s = read(fd, &exp, sizeof(uint64_t));
        if (s != sizeof(uint64_t))
            handle_error("read");
        tot_exp += exp;
        print_elapsed_time();
        printf("read: %llu; total=%llu\n",
                (unsigned long long) exp,
                (unsigned long long) tot_exp);
    }
    exit(EXIT_SUCCESS);
}

この文書について

この man ページは Linux man-pages プロジェクトのリリース 3.65 の一部である。 プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。