Edited at

timerfdの使い方

More than 1 year has passed since last update.

epollと合わせて使うと嬉しい系FDの一つtimerfdの使い方をちょっと纏めてみる

基本的な考え方は、これまではインターバルタイマなどで発生するシグナルを待ち構えていないとダメだったのが、FD経由でのIO多重化をしやすくなるという点だと思います


従来の処理

インターバルタイマを使用した待ち合わせを行う様な場合を想定します


インターバルタイマでの周期処理

/**

* initInterval - インターバルタイマ初期化処理
*/

timer_t initInterval (void)
{
timer_t timer;
struct itimerspec interval = {{1,0},{1,0}};
timer_create (CLOCK_REALTIME, NULL, &timer);
timer_settime (timer, 0, &interval, NULL);
return timer;
}

/**
* doIntervalTimer - インターバルタイマ処理
*/

void doIntervalTimer (void)
{
while (!done) {
pause (); // wait for intervaltimer
doTimer ();
}
return ;
}


SIGALRMなどのハンドリングは割愛しますが、インターバルタイマによるシグナル発生→pauseで受信待ち→シグナルによる割り込みでdoTimer起動というような流れになります。


timerfd + epoll

ということで、メインのループ処理をこんな感じにします。


timerfd+epollにしてみる

/**

* initTimerFD - タイマFD初期化処理
*/

int initTimerFD (void)
{
struct itimerspec interval = {{1,0},{1,0}};
int timerFD = timerfd_create (CLOCK_REALTIME, 0);
timerfd_settime (timerFD, 0, &interval, NULL);
return timerFD;
}

/**
* EpollLoop - イベント待ちおよびイベント発生時の処理起動
*/

void EpollLoop (void)
{
while (!done) {
struct epoll_event event = {0};
int evnum = epoll_wait (epollFD, &event, 1, -1);
for (int i=0; i<evnum; ++i) {
if (event.data.fd == timerFD) {
doTimer ();
}
/* else if (event->data.fd == any_other_fd) { ... } */
}
}
return ;
}


初期化処理として必要な手順(create→settime)に違いはないため、全体的にはループ処理の作りのところに差分が出る事になります。

epoll_waitにより他のイベントも一つのスレッドで受信することができる、というメリットがあるのと、シグナルとの絡みでのアレコレを無視できるのも嬉しいところ。

interval.it_interval.tv_sec = 1 で1秒周期のインターバルタイマとしていますが、ここで doTimer() が1秒以上の重い処理を行った場合など、従来の処理では次のSIGALRMまではpauseし続けます。

※シグナルが来ないと動けない/doTimer内の処理でシグナルが発生した場合を考慮する必要がある

signalfdとすることで、発生済みのイベントはepoll_waitで回収することが出来ます。

※2周期(2秒)以上の遅延があっても、遅延回数がreadで取得できるのも安心


その他

readすることで取得できるのはカウンタなので…


timerfd_read

typedef uint64_t timerfd_t;

int timerfd_read (int tfd, timerfd_t *value) {
return read (tfd, value, sizeof(* value));
}

こういうのがあってもいいかもなのだけど、eventfd_readはあるのになんでこっちは無いんだろう…


関連

signalfdの使い方

eventfdの使い方