本文简要介绍了Linux实现POSIX定时器的内核代码。内核中对posix定时器的实现代码在kernel/posix-timers.c/h中,本文使用的代码是2.6.29;关于用户空间如何使用POSIX定时器请查阅相关man文档。
Linux提供的POSIX定时器功能主要由以下几个函数组成:
int timer_create(clockid_t which_clock, struct sigevent* timer_event_spec, timer_t* created_timer_id);
int timer_gettime(timer_t timer_id, struct itimerspec* setting);
int timer_settime(timer_t timer_id, int flags, struct itimerspec* new_setting, struct itimerspec* old_setting);
int timer_delete(timer_t timer_id);
除此之外,还有一些附加函数,比如:
clock_nanosleep
clock_getres
clock_settime
clock_gettime
timer_getoverrun
新建定时器的函数是timer_create。
int timer_create(clockid_t which_clock, struct sigevent* timer_event_spec, timer_t* created_timer_id);
l 参数which_clock可以是系统默认的CLOCK_REALTIME,CLOCK_MONOTONIC,或者其它被登记的clock。
l 参数timer_event_spec是定时器到期时的通知方式。在kernel中,timer_event_spec的it_sigev_notify域可以是SIGEV_SIGNAL、SIGEV_NONE、SIGEV_THREAD_ID;但是在用户空间,除上述三个值之外,还有一个SIGEV_THREAD。SIGEV_THREAD比较特殊,是POSIX库在用户空间实现的,与内核无关。这可以在代码的include/asm-generic/siginfo.h中找到说明:
/*
* sigevent definitions
*
* It seems likely that SIGEV_THREAD will have to be handled from
* userspace, libpthread transmuting it to SIGEV_SIGNAL, which the
* thread manager then catches and does the appropriate nonsense.
* However, everything is written out here so as to not get lost.
*/
#define SIGEV_SIGNAL 0 /* notify via signal */
#define SIGEV_NONE 1 /* other notification: meaningless */
#define SIGEV_THREAD 2 /* deliver via thread creation */
#define SIGEV_THREAD_ID 4 /* deliver to thread */
另外,SIGEV_THREAD_ID被用于实现POSIX线程库,应用程序也不能随意使用。(见附注)
l 参数created_timer_id是创建定时器成功后,返回的定时器编号。
下面看一下新建定时器的具体实现:
int timer_create(clockid_t which_clock, struct sigevent* timer_event_spec, timer_t* created_timer_id);
struct k_itimer * new_timer = alloc_posix_timer(); // 分配定时器占用的内存
。。。 // 调用idr模块将new_timer与一个id关联,并设置相关域
// 下面的COCK_DISPATCH宏默认调用到common_timer_create函数
CLOCK_DISPATCH(which_clock, timer_create, (new_timer));
。。。 // 将struct sigevent* timer_event_spec保存到new_timer的相应结构中,这里有 // 用户空间和内核空间的转换
。。。 // 将new_timer挂入struct task_struct::signal->posix_timers中。这里如果指定 // 了it_sigev_notify域为SIGEV_THREAD_ID,那么task_struct为定时器到期 // 时需要通知的线程;否则,task_struct为当前线程所属进程
// posix_timers队列维护一个task中所有已使用的定时器链表,该链表用于进 // 程退出时删除所有已使用的posix定时器
common_timer_create函数初始化k_itimer中的hrtimer域,hrtimer是Linux的高精度时钟
static int common_timer_create(struct k_itimer *new_timer)
{
hrtimer_init(&new_timer->it.real.timer, new_timer->it_clock, 0);
return 0;
}
定时器的删除操作与新建定时器对应,释放定时器建立时分配的资源。
int timer_settime(timer_t timer_id, int flags, struct itimerspec* new_setting, struct itimerspec* old_setting);
l 参数timer_id是timer_create返回的定时器id号。
l 参数flags为TIMER_ABSTIME或者0,代表设置的超时值是绝对还是相对时间
l 参数new_setting是需要被设置的超时时间
l 参数old_setting用于返回设置新超时值之前定时器的超时时间
timer_setting的功能主要由宏
CLOCK_DISPATCH(timr->it_clock, timer_set, (timr, flags, &new_spec, rtn));
完成,该宏默认情况下会调用common_timer_set。
int common_timer_set(struct k_itimer *timr, int flags,
struct itimerspec *new_setting, struct itimerspec *old_setting)
common_timer_set中,会设置timr参数中包含的hrtimer(struct k_itimer:: it.real.timer),通过hrtimer机制完成定时器的超时设置,hrtimer的超时函数被设置为posix_timer_fn。
static enum hrtimer_restart posix_timer_fn(struct hrtimer *timer)
。。。
// 如果定时器处于循环模式,每一次到期时加一
if (timr->it.real.interval.tv64 != 0)
si_private = ++timr->it_requeue_pending;
// 调用posix_timer_event发送定时器超时消息
// 如果定时器设置的是SIGEV_NONE且处于循环模式,则posix_timer_event返回值为 // 非0。这时定时器的循环设置将在这个超时函数中完成(返回值设为 // HRTIMER_RESTART可使hrtimer模块重启定时器)。
if (posix_timer_event(timr, si_private))
if (timr->it.real.interval.tv64 != 0)
hrtimer_forward (timer, now, timr->it.real.interval);
ret = HRTIMER_RESTART;
在定时器没有设置SIGEV_NONE时,超时函数posix_timer_event将不会重启处于循环模式的定时器,重启定时器的工作会在signal处理的函数中。
posix_timer_event的主要功能是填充sigqueue结构,并发送到相应的sigpending(Linux的signal机制)队列。
int posix_timer_event(struct k_itimer *timr, int si_private)
。。
imr->sigq->info.si_code = SI_TIMER; // SI_TIMER比较重要,下文会提到
。。。
if (timr->it_sigev_notify & SIGEV_THREAD_ID) {
struct task_struct *leader;
// 注意这里最后一个参数是0,代表发给线程
int ret = send_sigqueue(timr->sigq, timr->it_process, 0);
if (likely(ret >= 0)) // 如果成功发送,则返回;否则发给该线程所属的进程
return ret;
timr->it_sigev_notify = SIGEV_SIGNAL;
leader = timr->it_process->group_leader;
put_task_struct(timr->it_process);
timr->it_process = leader;
}
// 将信号发给进程(最后一个参数为1)
return send_sigqueue(timr->sigq, timr->it_process, 1);
send_sigqueue是Linux的signal机制实现的函数,但该函数中有一个小细节与timer相关。
// 下面条件判断定时器超时消息是否已经在sigpending队列中,如果已经在,说明上一 // 次超时还没有处理,下一次定时器超时就已经再次到达。这时只增加sigqueue的 // si_overrun标记位以标记定时器超时溢出,而不会再一次排队sigqueue
if (unlikely(!list_empty(&q->list))) {
/*
* If an SI_TIMER entry is already queue just increment
* the overrun count.
*/
BUG_ON(q->info.si_code != SI_TIMER);
q->info.si_overrun++;
goto out;
}
q->info.si_overrun = 0;
// 将sigqueue排入sigpending队列
。。。
out:
unlock_task_sighand(t, &flags);
前面提到定时器超时时,会调用posix_timer_event,这个函数会发信号给相关task;而在task的signal处理函数中,会对定时器超时消息作一些特殊处理,主要是在函数dequeue_signal中。
int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
。。。
// 前面posix_timer_event函数中设置__SI_TIMER就是为了这里
if ((info->si_code & __SI_MASK) == __SI_TIMER && info->si_sys_private) {
/*
* Release the siglock to ensure proper locking order
* of timer locks outside of siglocks. Note, we leave
* irqs disabled here, since the posix-timers code is
* about to disable them again anyway.
*/
// 按照上面注释所说,下面虽然给spinlock解锁,但没有开中断
// 这里代码逻辑比较繁琐,不知道以后版本会不会有所改进
spin_unlock(&tsk->sighand->siglock);
do_schedule_next_timer(info);
spin_lock(&tsk->sighand->siglock);
}
void do_schedule_next_timer(struct siginfo *info)
struct k_itimer *timr;
unsigned long flags;
timr = lock_timer(info->si_tid, &flags);
if (timr && timr->it_requeue_pending == info->si_sys_private)
if (timr->it_clock < 0)
posix_cpu_timer_schedule(timr); // 暂不研究
else
schedule_next_timer(timr); // 调用hrtimer的重设机制
上面do_schedule_next_timer函数中,有一行条件判断
timr->it_requeue_pending == info->si_sys_private // 很奇怪的条件判断
这里的info->si_sys_private在posix_timer的超时函数posix_timer_fn中被设置:
if (timr->it.real.interval.tv64 != 0)
si_private = ++timr->it_requeue_pending;
这里不太清楚为什么在do_schedule_next_timer函数中要加上这么一个判断,一开始我猜想是为了防止循环模式时定时器多次超时但信号来不及处理的情况。但是,定时器即使处于循环模式,超时后在执行do_schedule_next_timer之前也不会被重新启动,所以猜想不成立。那么在何种情况下这个判断会起作用?盼高人解答。
l 如何标识一个定时器?
在应用程序中,经常需要支持多个定时器,这时,可以用下面方法实现:
struct sigevent sigev;
sigev.sigev_value.sival_int = id; // id 可用于标识一个定时器
l SIGEV_THREAD_ID在什么情况下能被使用?
通常情况下,SIGEV_THREAD_ID被posix线程库用来实现SIGEV_THREAD选项。但是,如果使用sigtimedwait函数等待超时信号,那就可以绕过线程库,这是因为sigtimedwait有优先权(详情见《Linux Signal实现代码分析》中的complete_signal函数)。所以,在应用程序中,可以起一个定时器服务线程,该线程调用sigtimedwait等待超时信号,在收到信号后再使用timer ID区分是哪一个定时器触发了超时。
创建定时器时,指定SIGEV_THREAD_ID,并指定超时信号发送到服务线程的ID号即可。