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
ということで、メインのループ処理をこんな感じにします。
/**
* 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
することで取得できるのはカウンタなので…
typedef uint64_t timerfd_t;
int timerfd_read (int tfd, timerfd_t *value) {
return read (tfd, value, sizeof(* value));
}
こういうのがあってもいいかもなのだけど、eventfd_read
はあるのになんでこっちは無いんだろう…