timerfd
ファイル読み込みでタイマ満了通知を取得します。timerfdファイルIDでreadすることで、タイマ満了しなければwaitします。
サンプルを参考にしました。まず4秒そして2秒周期でタイマ満了しています。
このファイルIDを複数のプロセスで共有することで、同一タイマー処理をマルチプロセスとして実装できます。1秒単位に処理しなければならなく、その1処理が2秒かかる処理みたいに、マルチプロセスによる実装が必要となるケースが想定されます。
anon_inode_getfd()でファイルIDを取得します。[timerfd]はdentry名で、timerfd_fopsはFILEコールバックオペレーションです。ファイルシステムを介してなくても、FILE構造体の設定情報としてdentryが必要となります。ctxがタイマをコントロールする情報で、file->private_dataに設定されます。
if (ctx->expired && ctx->tintv.tv64)は、インターバルタイマでの満了通知であると言うことで、コメントによると、短いインターバルタイム設定によるDoSアタックの回避という事で、タイマーコールバック関数内で、再度タイマスタートしないよう、次の満了通知までタイマを進めるといった処理のようですが・・・。
サンプルを参考にしました。まず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;
}






