timerfd
Rev.1を表示中。最新版はこちら。
ファイル読み込みでタイマ満了通知を取得します。timerfdファイルIDでreadすることで、タイマ満了しなければwaitします。サンプルは、まず4秒そして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; uint64_t read_cnt; int cnt = 0; double rtn; clock_gettime( CLOCK_MONOTONIC, &cur ); struct itimerspec val; val.it_value.tv_sec = cur.tv_sec + 4; val.it_value.tv_nsec = cur.tv_nsec; val.it_interval.tv_sec = 2; val.it_interval.tv_nsec = 0; int fd = timerfd_create( CLOCK_MONOTONIC, 0 ); timerfd_settime( fd, TFD_TIMER_ABSTIME, &val, 0 ); gettimeofday( &tv1, 0 ); while (1) { read( fd, &read_cnt, sizeof( uint64_t )); gettimeofday( &tv2, 0 ); rtn = TV2SEC( tv2 ) - TV2SEC( tv1 ); printf( "timerfd %d time:[%f] %d\n", cnt + 1, rtn , read_cnt); tv1 = tv2; cnt++; } close( fd ); } [root@localhost kitamura]# ./a.out timerfd 1 time:[4.000367] 1 timerfd 2 time:[2.000236] 1 timerfd 3 time:[2.000139] 1 timerfd 4 time:[2.000367] 1 timerfd 5 time:[1.999956] 1 timerfd 6 time:[1.999231] 1 timerfd 7 time:[2.000228] 1 timerfd 8 time:[2.000384] 1ファイル読み込みと言うので、負荷がどうなのかと思ってしまいますが、実態はファイルシステムを介しておらず、ただFILEオペレーションコールのインターフェースを使っているにすぎません。
このファイルIDを複数のプロセスで共有することで、同一タイマー処理をマルチプロセスとして実装できます。1秒単位に処理しなければならなく、その1処理が2秒かかる処理みたいに、マルチプロセスによる実装が必要となるケースが想定されます。
anon_inode_getfd()でファイルIDを取得します。[timerfd]はdentry名で、timerfd_fopsはFILEコールバックオペレーションです。ファイルシステムを介してなくても、FILE構造体の設定情報としてdentryが必要となります。ctxがタイマをコントロールする情報で、file->private_dataに設定されます。
static const struct file_operations timerfd_fops = { .release = timerfd_release, .poll = timerfd_poll, .read = timerfd_read, .llseek = noop_llseek, }; SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags) { int ufd; struct timerfd_ctx *ctx; /* Check the TFD_* constants for consistency. */ BUILD_BUG_ON(TFD_CLOEXEC != O_CLOEXEC); BUILD_BUG_ON(TFD_NONBLOCK != O_NONBLOCK); if ((flags & ~TFD_CREATE_FLAGS) || (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME)) return -EINVAL; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; init_waitqueue_head(&ctx->wqh); ctx->clockid = clockid; hrtimer_init(&ctx->tmr, clockid, HRTIMER_MODE_ABS); ctx->moffs = ktime_get_monotonic_offset(); ufd = anon_inode_getfd("[timerfd]", &timerfd_fops, ctx, O_RDWR | (flags & TFD_SHARED_FCNTL_FLAGS)); if (ufd < 0) kfree(ctx); return ufd; } int anon_inode_getfd(const char *name, const struct file_operations *fops, void *priv, int flags) { int error, fd; struct file *file; error = get_unused_fd_flags(flags); if (error < 0) return error; fd = error; file = anon_inode_getfile(name, fops, priv, flags); if (IS_ERR(file)) { error = PTR_ERR(file); goto err_put_unused_fd; } fd_install(fd, file); return fd; err_put_unused_fd: put_unused_fd(fd); return error; }timerfd_read()は読み込みのコールバック関数です。タイマ満了毎にctx->ticksがインクリメントさ、wait_event_interruptible_locked_irq()は、ctx->ticks!=0までループウエイトし、ctx->ticksをput_user()でchar __user *bufして復帰します。ctx->ticksはタイマ満了毎にインクリメントされます。
if (ctx->expired && ctx->tintv.tv64)は、インターバルタイマでの満了通知であると言うことで、コメントによると、短いインターバルタイム設定によるDoSアタックの回避という事で、タイマーコールバック関数内で、再度タイマスタートしないよう、次の満了通知までタイマを進めるといった処理のようですが・・・。
static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct timerfd_ctx *ctx = file->private_data; ssize_t res; u64 ticks = 0; if (count < sizeof(ticks)) return -EINVAL; spin_lock_irq(&ctx->wqh.lock); if (file->f_flags & O_NONBLOCK) res = -EAGAIN; else res = wait_event_interruptible_locked_irq(ctx->wqh, ctx->ticks); if (timerfd_canceled(ctx)) { ctx->ticks = 0; ctx->expired = 0; res = -ECANCELED; } if (ctx->ticks) { ticks = ctx->ticks; if (ctx->expired && ctx->tintv.tv64) { ticks += hrtimer_forward_now(&ctx->tmr, ctx->tintv) - 1; hrtimer_restart(&ctx->tmr); } ctx->expired = 0; ctx->ticks = 0; } spin_unlock_irq(&ctx->wqh.lock); if (ticks) res = put_user(ticks, (u64 __user *) buf) ? -EFAULT: sizeof(ticks); return res; }