Linux时间子系统之八:定时器的应用--createtimer()

高精度时钟下对应的timer定时处理函数的调用和基本处理流程

程序2:通知方式为信号的处理方式

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#define CLOCKID CLOCK_REALTIME

void sig_handler(int signo)
{
printf("timer_signal function! %d\n", signo);
}

int main()
{
//XXX int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// signum--指定的信号编号,可以指定SIGKILL和SIGSTOP以外的所有信号编号
// act结构体--设置信号编号为signum的处理方式
// oldact结构体--保存上次的处理方式
//
// struct sigaction
// {
// void (*sa_handler)(int); //信号响应函数地址
// void (*sa_sigaction)(int, siginfo_t *, void *); //但sa_flags为SA——SIGINFO时才使用
// sigset_t sa_mask; //说明一个信号集在调用捕捉函数之前,会加入进程的屏蔽中,当捕捉函数返回时,还原
// int sa_flags;
// void (*sa_restorer)(void); //未用
// };
//
timer_t timerid;
struct sigevent evp;

struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
act.sa_flags = 0;

// XXX int sigaddset(sigset_t *set, int signum); //将signum指定的信号加入set信号集
// XXX int sigemptyset(sigset_t *set); //初始化信号集

sigemptyset(&act.sa_mask);

if (sigaction(SIGUSR1, &act, NULL) == -1)
{
perror("fail to sigaction");
exit(-1);
}

memset(&evp, 0, sizeof(struct sigevent));
evp.sigev_signo = SIGUSR1;
evp.sigev_notify = SIGEV_SIGNAL;
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}

struct itimerspec it;
it.it_interval.tv_sec = 2;
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 1;
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, 0) == -1)
{
perror("fail to timer_settime");
exit(-1);
}

pause();

return 0;
}

本文为作者原创,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。

作者:misteryoung

博客:http://blog.chinaunix.net/uid/20706239

=======================================================================

1 前言

在很多用户态程序使用select实现定时器的功能。因为该函数既可以实现fd的多路复用,又可以实现超时的功能,可谓功能强大。那么Linux有没有给用户态提供标准的定时器接口呢?答案当然是:有。

下面介绍几个标准的用户态定时器。讲解内容包括:

1)原理及用法;

2)实现细节(包括用户态及内核态),因为涉及系统调用,因此内核部分比较多;

3)本文只介绍3个定时器:alarm、setitimer、timer_create

2 alarm定时器

alarm函数大家应该并不陌生,在busybox版的ping源码中就使用了该函数,用以实现超时功能。

其函数原型及man中的帮助信息如下所示:

点击(此处)折叠或打开

unsigned int alarm(unsigned int seconds);

DESCRIPTION

alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.

If seconds is zero, no new alarm() is scheduled.

In any event any previously set alarm() is canceled.

RETURN VALUE

alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously scheduled alarm.

2.1 原理

简单来说,其功能实现包含两部分:

1)注册SIGALRM信号处理函数;

2)通过alarm设置超时时间(单位为秒),超时后信号处理函数被执行;

alarm(seconds)代表设置新的定时器,并覆盖老的定时器(如果有的话)。若取消已有定时器可执行alarm(0)。

2.2 用法

1)signal(SIGALRM, alarm_func)

2)alarm(expire)

expire秒后alarm_func函数被执行。

2.3 alarm跟踪

我们都知道,alarm通过系统调用陷入到内核态,内核对应的函数为sys_alarm。

点击(此处)折叠或打开

SYSCALL_DEFINE1(alarm, unsigned int, seconds)

{

return alarm_setitimer(seconds);

}

上面代码展开后如下所示:

sys_alarm(unsigned intseconds)

{

return alarm_setitimer(seconds);

}

继续跟踪alarm_setitimer:

点击(此处)折叠或打开

unsigned int alarm_setitimer(unsigned int seconds)

{

struct itimerval it_new, it_old;

#if BITS_PER_LONG < 64

if (seconds > INT_MAX)

seconds = INT_MAX;

#endif

it_new.it_value.tv_sec = seconds;    时间初始化

it_new.it_value.tv_usec = 0;

it_new.it_interval.tv_sec = it_new.it_interval.tv_usec = 0;

do_setitimer(ITIMER_REAL, &it_new, &it_old); 设置新的超时时间,并获取老的剩余的超时时间

/*

* We can't return 0 if we have an alarm pending ... And we'd

* better return too much than too little anyway

*/

if ((!it_old.it_value.tv_sec && it_old.it_value.tv_usec) ||

it_old.it_value.tv_usec >= 500000)

it_old.it_value.tv_sec++;

return it_old.it_value.tv_sec;    返回老的剩余的超时时间

}

继续跟踪do_setitimer

点击(此处)折叠或打开

int do_setitimer(int which, struct itimerval *value, struct itimerval *ovalue)

{

struct task_struct *tsk = current;

struct hrtimer *timer;

ktime_t expires;

/*

* Validate the timevals in value.

*/

if (!timeval_valid(&value->it_value) ||

!timeval_valid(&value->it_interval))

return -EINVAL;

switch (which) {

case ITIMER_REAL:

again:

spin_lock_irq(&tsk->sighand->siglock);

timer= &tsk->signal->real_timer; 获取当前进程对应的timer

if (ovalue) {

ovalue->it_value = itimer_get_remtime(timer);

ovalue->it_interval

= ktime_to_timeval(tsk->signal->it_real_incr);

}

/* We are sharing ->siglock with it_real_fn() */

if (hrtimer_try_to_cancel(timer) < 0) {    取消老的timer

spin_unlock_irq(&tsk->sighand->siglock);

goto again;

}

expires = timeval_to_ktime(value->it_value);

if (expires.tv64 != 0) {

tsk->signal->it_real_incr =

timeval_to_ktime(value->it_interval);

hrtimer_start(timer, expires, HRTIMER_MODE_REL);    启动新的timer

} else

tsk->signal->it_real_incr.tv64 = 0;

trace_itimer_state(ITIMER_REAL, value, 0);

spin_unlock_irq(&tsk->sighand->siglock);

break;

case ITIMER_VIRTUAL:

set_cpu_itimer(tsk, CPUCLOCK_VIRT, value, ovalue);

break;

case ITIMER_PROF:

set_cpu_itimer(tsk, CPUCLOCK_PROF, value, ovalue);

break;

default:

return -EINVAL;

}

return 0;

}

函数hrtimer_start会调用enqueue_hrtimer将timer定时器加入相应的定时器队列,并等待超时。

下面再跟踪timer到底是什么?

从上面的代码“tsk->signal->real_timer”可知,timer是进程相关的。那我们再从进程的创建开始跟踪该timer。

sys_fork

-> do_fork

-> copy_process

-> copy_signal

点击(此处)折叠或打开

static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)

{

struct signal_struct *sig;

if (clone_flags & CLONE_THREAD)

return 0;

sig = kmem_cache_alloc(signal_cachep, GFP_KERNEL);

tsk->signal = sig;

if (!sig)

return -ENOMEM;

。。。。。。

init_sigpending(&sig->shared_pending);

INIT_LIST_HEAD(&sig->posix_timers);

hrtimer_init(&sig->real_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);

sig->it_real_incr.tv64 = 0;

sig->real_timer.function = it_real_fn;    timer响应函数为it_real_fn

。。。。。。。

return 0;

}

最终,timer响应函数被设置为it_real_fn。

it_real_fn的执行依赖于内核的hrtimer框架,并且在超时后被执行。下面跟踪一下该函数。

点击(此处)折叠或打开

enum hrtimer_restart it_real_fn(struct hrtimer *timer)

{

struct signal_struct *sig =

container_of(timer, struct signal_struct, real_timer);

trace_itimer_expire(ITIMER_REAL, sig->leader_pid, 0);

kill_pid_info(SIGALRM, SEND_SIG_PRIV, sig->leader_pid);发送SIGALRM信号

return HRTIMER_NORESTART;

}

很显然,it_real_fn的作用就是给当前进程发送SIGALRM信号,这样的alarm就与SIGALRM信号关联起来了。

2.4 总结

alarm定时器的原理是:内核hrtimer在定时器超时后,通过函数it_real_fn给当前进程发送SIGALRM信号,这样通过signal等函数注册的信号响应函数就会被调用。

3 setitimer定时器

setitimer函数原型及man中的帮助信息如下所示:

点击(此处)折叠或打开

int setitimer(int which, const struct itimerval *new_value,

struct itimerval *old_value);

DESCRIPTION

The system provides each process with three interval timers, each decrementing in a distinct time domain. When any timer expires, a signal is sent to the process, and

the timer (potentially) restarts.

ITIMER_REAL decrements in real time, and delivers SIGALRM upon expiration.

ITIMER_VIRTUAL decrements only when the process is executing, and delivers SIGVTALRM upon expiration.

ITIMER_PROF decrements both when the process executes and when the system is executing on behalf of the process. Coupled with ITIMER_VIRTUAL, this timer is usually

used to profile the time spent by the application in user and kernel space. SIGPROF is delivered upon expiration.

3.1 原理

与alarm类似,setitimer也是注册信号处理函数,并且超时后处理函数被执行。同样,也可以通过将超时时间设置为全0来取消老的定时器。

但与alarm不同的是:

1)setitimer可以指定具体的信号。通常我们第一个参数设置为ITIMER_REAL,此时需要注册SIGALRM信号处理函数。

2) 时间精度可以精确到微秒;

3)老的定时器剩余时间通过参数返回,而返回值代表函数是否执行成功;

3.2 用法

1)signal(SIGALRM, alarm_func)

2)setitimer(ITIMER_REAL, &timerval, NULL)

timerval指定的时间过后,alarm_func函数被执行。

3.3 setitimer跟踪

setitimer是系统调用,直接跟进内核:

点击(此处)折叠或打开

SYSCALL_DEFINE3(setitimer, int, which, struct itimerval __user *, value,

struct itimerval __user *, ovalue)

{

struct itimerval set_buffer, get_buffer;

int error;

if (value) {

if(copy_from_user(&set_buffer, value, sizeof(set_buffer)))

return -EFAULT;

} else

memset((char *) &set_buffer, 0, sizeof(set_buffer));

error = do_setitimer(which, &set_buffer, ovalue ? &get_buffer : NULL);

if (error || !ovalue)

return error;

if (copy_to_user(ovalue, &get_buffer, sizeof(get_buffer)))

return -EFAULT;

return 0;

}

setitimer陷入内核态与alarm一样,均调用do_setitimer。后面不再分析。

3.4 总结

setitimer与alarm功能、用法相同,只是参数不太一样。

下面再从函数调用的角度分析二者的区别:

点击(此处)折叠或打开

alarm                     用户态

-> sys_alarm          内核态

-> alarm_setitimer

-> do_setitimer

setitimer                 用户态

-> sys_setitimer      内核态

-> do_setitimer

4 timer_create定时器

与alarm和setitimer的单一函数就能实现定时器功能不同,timer_create需要结合函数timer_settime使用。下面看一下这两个函数的函数原型及man中的帮助信息。如下所示:

点击(此处)折叠或打开

int timer_create(clockid_t clockid, struct sigevent *sevp,

timer_t *timerid);

DESCRIPTION

timer_create() creates a new per-process interval timer. The ID of the new timer is returned in the buffer pointed to by timerid, which must be a non-NULL pointer.

This ID is unique within the process, until the timer is deleted. The new timer is initially disarmed.

The clockid argument specifies the clock that the new timer uses to measure time. It can be specified as one of the following values:

CLOCK_REALTIME

A settable system-wide real-time clock.

CLOCK_MONOTONIC

A nonsettable monotonically increasing clock that measures time from some unspecified point in the past that does not change after system startup.

CLOCK_PROCESS_CPUTIME_ID (since Linux 2.6.12)

A clock that measures (user and system) CPU time consumed by (all of the threads in) the calling process.

CLOCK_THREAD_CPUTIME_ID (since Linux 2.6.12)

A clock that measures (user and system) CPU time consumed by the calling thread.

timer_settime函数:

点击(此处)折叠或打开

int timer_settime(timer_t timerid, int flags,

const struct itimerspec *new_value,

struct itimerspec * old_value);

DESCRIPTION

timer_settime()  arms  or  disarms the timer identified by timerid.  The new_value argument is an itimerspec structure that specifies the new initial value and the new

interval for the timer.  The itimerspec structure is defined as follows:

4.1 原理

与alarm和setitimer不同的是,timer_create需要分两步操作:创建定时器(timer_create)和设置超时时间(timer_settime)。

timer_create主要功能:注册超时处理函数及返回timerid供timer_settime使用;

timer_settime主要功能:根据timerid找到定时器,并设置超时时间;

4.2 用法

1)注册信号处理函数

signal(SIGRTMIN, sig_handler)

sv.sigev_notify = SIGEV_SIGNAL

sv.sigev_signo = SIGRTMIN

sv.sigev_value.sival_ptr = &timerid

timer_create(CLOCK_REALTIME, &sv, &timerid)

2)设置超时时间

timer_settime(&timerfd, 0, &time, NULL)

注:考虑到本文重点不在用法的讲解,因此只是举个简单例子来说明timer_create是如何使用的。事实上,timer_create功能比较强大,用法也比较复杂。

4.3 timer_create跟踪

timer_create是系统调用,直接跟进内核:

点击(此处)折叠或打开

SYSCALL_DEFINE3(timer_create, const clockid_t, which_clock,

struct sigevent __user *, timer_event_spec,

timer_t __user *, created_timer_id)

{

struct k_itimer *new_timer;

new_timer = alloc_posix_timer();

new_timer->it_clock = which_clock;

common_timer_create(new_timer)

if (timer_event_spec) {

if (copy_from_user(&event, timer_event_spec, sizeof (event))) {

error = -EFAULT;

goto out;

}

} else {

event.sigev_notify = SIGEV_SIGNAL;  默认为超时后触发信号

event.sigev_signo = SIGALRM;        默认为触发SIGALRM信号

event.sigev_value.sival_int = new_timer->it_id;

new_timer->it_pid = get_pid(task_tgid(current));

}

new_timer->it_sigev_notify = event.sigev_notify;

new_timer->sigq->info.si_signo = event.sigev_signo;

new_timer->sigq->info.si_value = event.sigev_value;

new_timer->sigq->info.si_tid = new_timer->it_id;

new_timer->sigq->info.si_code = SI_TIMER;

new_timer->it_signal = current->signal;

list_add(&new_timer->list, ¤t->signal->posix_timers);

return 0;

}

再跟踪timer_settime:

点击(此处)折叠或打开

SYSCALL_DEFINE4(timer_settime, timer_t, timer_id, int, flags,

const struct itimerspec __user *, new_setting,

struct itimerspec __user *, old_setting)

{

。。。。。。

error = CLOCK_DISPATCH(timr->it_clock, timer_set,

(timr, flags, &new_spec, rtn));

。。。。。。

return error;

}

再跟踪CLOCK_DISPATCH:

点击(此处)折叠或打开

/*

* Call the k_clock hook function if non-null, or the default function.

*/

#define CLOCK_DISPATCH(clock, call, arglist) \

((clock) < 0 ? posix_cpu_##call arglist : \

(posix_clocks[clock].call != NULL \

? (*posix_clocks[clock].call) arglist : common_##call arglist))

CLOCK_DISPATCH被展开成common_timer_set,再跟踪该函数

点击(此处)折叠或打开

static int

common_timer_set(struct k_itimer *timr, int flags,

struct itimerspec *new_setting, struct itimerspec *old_setting)

{

struct hrtimer *timer = &timr->it.real.timer;

enum hrtimer_mode mode;

if (hrtimer_try_to_cancel(timer) < 0)   取消老的定时器

return TIMER_RETRY;

mode = flags & TIMER_ABSTIME ? HRTIMER_MODE_ABS : HRTIMER_MODE_REL;

hrtimer_init(&timr->it.real.timer, timr->it_clock, mode);

timr->it.real.timer.function = posix_timer_fn;    设置定时器响应函数

hrtimer_set_expires(timer, timespec_to_ktime(new_setting->it_value));

/* Convert interval */    设置超时时间

timr->it.real.interval = timespec_to_ktime(new_setting->it_interval);

hrtimer_start_expires(timer, mode);    启动新的定时器

return 0;

}

定时器超时后,posix_timer_fn被执行,再跟踪该函数:

点击(此处)折叠或打开

static enum hrtimer_restart posix_timer_fn(struct hrtimer *timer)

{

struct k_itimer *timr;

timr = container_of(timer, struct k_itimer, it.real.timer);

if (posix_timer_event(timr, si_private)) {

if (timr->it.real.interval.tv64 != 0) {

ktime_t now = hrtimer_cb_get_time(timer);

timr->it_overrun += (unsigned int)

hrtimer_forward(timer, now,

timr->it.real.interval);

ret = HRTIMER_RESTART;

++timr->it_requeue_pending;

}

}

return ret;

}

再跟踪posix_timer_event:

点击(此处)折叠或打开

int posix_timer_event(struct k_itimer *timr, int si_private)

{

struct task_struct *task;

task = pid_task(timr->it_pid, PIDTYPE_PID);

if (task) {

shared = !(timr->it_sigev_notify & SIGEV_THREAD_ID);

ret = send_sigqueue(timr->sigq, task, shared);    处理信号

}

return ret > 0;

}

再跟踪send_sigqueue:

点击(此处)折叠或打开

int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)

{

int sig = q->info.si_signo;

ret = 1; /* the signal is ignored */

if (!prepare_signal(sig, t, 0))  如果进程t忽略sig信号,则直接返回

goto out;

ret = 0;

signalfd_notify(t, sig);

pending = group ? &t->signal->shared_pending : &t->pending;

list_add_tail(&q->list, &pending->list);

sigaddset(&pending->signal, sig);

complete_signal(sig, t, group);    给进程t发送信号sig,这样信号响应函数就会被执行

return ret;

}

4.4 总结

本文只分析了timer_create常用用法:注册信号处理函数,并设置定时器。其原理也与alarm与setitmier类似:利用hrtimer定时器,当超时后信号处理函数会被调用。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值