赞
踩
3.3 定时器的添加
hrtimer添加的流程图如下:
在添加定时器到红黑树时,如果已经存在与红黑树上,必须得先删除定时器,之后使用enqueue_hrtimer函数将hrtimer插入到红黑树上。如果当前添加的定时器是最早到期的,则需要重新设定定时器硬件的到期时间,需要将当前定时器的到期时间设置到定时器硬件,使其可以最早得到处理。
3.4 定时器的处理
高精度定时器系统有3个入口可以对到期的定时器进行处理,分别是
1 没有切换到高精度模式时,在每个jiffies的tick事件中断中进行查询和处理。
2 在HRTIMER_SOFTIRQ软中断中进行查询和处理。
3 切换到高精度模式以后,在每个clock_event_device的到期事件的中断中进行查询和处理。
3.4.1 低精度模式
系统并不是一开始就会支持高精度模式,而是在系统启动后的某个阶段,等待所有的条件都满足后,才会切换到高精度模式。当系统没有切换到高精度模式时,所有的高精度定时器都运行在低精度模式下,在每个jiffies的tick事件中断中进行到期定时器的查询和处理,显然此时的精度和低分辨率定时器是一样的(HZ级别)。低精度模式下,每个tick事件中断中,hrtimer_run_queues函数会被调用,由它完成定时器的到期处理。hrtimer_run_queue首先判断目前高精度模式是否已经启用,如果已经切换到高精度模式下,直接返回。
void hrtimer_run_queues(void)
{
struct timerqueue_node *node;
struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);
struct hrtimer_clock_base *base;
int index, gettime = 1, raise = 0;
if (hrtimer_hres_active()) /* 判定是否启用高精度模式。如果启用,则直接退出。 */
return;
for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; index++) { /* 遍历各个时间基准系统。 */
base = &cpu_base->clock_base[index];
if (!timerqueue_getnext(&base->active))
continue;
if (gettime) {
hrtimer_get_softirq_time(cpu_base);
gettime = 0;
}
raw_spin_lock(&cpu_base->lock);
while ((node = timerqueue_getnext(&base->active))) { /* 获取base->active红黑树中的最早到期节点。*/
struct hrtimer *timer;
timer = container_of(node, struct hrtimer, node);
if (base->softirq_time.tv64 <=
hrtimer_get_expires_tv64(timer)) /* 当前时间小于定时器timer的到期时间,说明此时钟基准的定时器
未到期,直接退出循环。*/
break;
if (!hrtimer_rt_defer(timer))
__run_hrtimer(timer, &base->softirq_time); /* 到期则使用__run_hrtimer函数进行处理。 */
else
raise = 1;
}
raw_spin_unlock(&cpu_base->lock);
}
if (raise)
raise_softirq_irqoff(HRTIMER_SOFTIRQ);
}
如果hrtimer_hres_active返回false,说明目前处于低精度模式下,则继续处理。它用一个for循环便利各个时间基准系统,查询每个hrtimer_clock_base对应红黑树的左下节点,判断其是否到期。如果到期,则使用__run_hrtimer函数,对到期定时器进行处理。包括,调用定时器回调函数,从红黑树中移除定时器,根据回调函数返回值决定是否重启该定时器等。
函数中,while循环可以不断的使用timerqueue_getnext获取红黑树中的左下节点next,是因为__run_hrtimer会在处理过程中,移除到期的定时器,从而新的最早到期的节点会被更新到next字段中,使得循环可以一直执行,知道没有到期的定时器为止。
3.4.2 高精度模式
在切换到高精度模式后,原来给cpu提供tick时间的tick_device会被高精度定时器系统接管,它的中断时间回调函数被设置为hrtimer_interrupt,红黑树中最左下节点的定时器的到期时间被编程到该clock_event_device中。这样,每次clock_event_device的中断意味着有意个高精度定时器到期。另外,当timerkeeper系统中的时间需要修正,后者clock_event_device的到期事件时间被重新编程时,系统会发出HRTIMER_SOFTIRQ软中断,软中断的处理函数run_hrtimer_softirq最终会调用hrtimer_interrupt函数对定时器进行处理,所在在这里,我们只需要讨论hrtimer_interrupt函数即可。
hrtimer_interrupt函数的前半部分和低精度模式下的hrtimer_run_queues函数完成相同的事情。它用一个for循环遍历各个时间基准系统,查询每个hrtimer_clock_base对应的红黑树的左下节点,判断它是否到期,如果到期,通过__run_hrtimer函数,对到期定时器进行处理。高精度定时器在处理完所有到期定时器之后,下一个定到期定时器的到期时间保存在变量expires_next中,接下来的工作就是把这个到期时间编程到tick_device中。
void hrtimer_interrupt(struct clock_event_device *dev)
{
struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);
ktime_t expires_next, now, entry_time, delta;
int i, retries = 0, raise = 0;
BUG_ON(!cpu_base->hres_active);
cpu_base->nr_events++;
dev->next_event.tv64 = KTIME_MAX;
entry_time = now = ktime_get();
retry:
expires_next.tv64 = KTIME_MAX;
raw_spin_lock(&cpu_base->lock);
/*
* We set expires_next to KTIME_MAX here with cpu_base->lock
* held to prevent that a timer is enqueued in our queue via
* the migration code. This does not affect enqueueing of
* timers which run their callback and need to be requeued on
* this CPU.
*/
cpu_base->expires_next.tv64 = KTIME_MAX;
for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) { /* 与hrtimer_run_queues一致,遍历各时间基准,查询到期的定时器并使用
__run_hrtimer进行处理。 */
struct hrtimer_clock_base *base;
struct timerqueue_node *node;
ktime_t basenow;
if (!(cpu_base->active_bases & (1 << i)))
continue;
base = cpu_base->clock_base + i;
basenow = ktime_add(now, base->offset);
while ((node = timerqueue_getnext(&base->active))) {
struct hrtimer *timer;
timer = container_of(node, struct hrtimer, node);
trace_hrtimer_interrupt(raw_smp_processor_id(),
ktime_to_ns(ktime_sub(
hrtimer_get_expires(timer), basenow)),
current,
timer->function == hrtimer_wakeup ?
container_of(timer, struct hrtimer_sleeper,
timer)->task : NULL);
/*
* The immediate goal for using the softexpires is
* minimizing wakeups, not running timers at the
* earliest interrupt after their soft expiration.
* This allows us to avoid using a Priority Search
* Tree, which can answer a stabbing querry for
* overlapping intervals and instead use the simple
* BST we already have.
* We don't add extra wakeups by delaying timers that
* are right-of a not yet expired timer, because that
* timer will have to trigger a wakeup anyway.
*/
if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {
ktime_t expires;
expires = ktime_sub(hrtimer_get_expires(timer),
base->offset);
if (expires.tv64 < expires_next.tv64)
expires_next = expires;
break;
}
if (!hrtimer_rt_defer(timer)) /* 判断timer->irqsafe是否等于1,如果相等,然会0。*/
__run_hrtimer(timer, &basenow);
else
raise = 1;
}
}
/*
* Store the new expiry value so the migration code can verify
* against it.
*/
cpu_base->expires_next = expires_next; /* 记录下一个即将到期的定时器的时间。*/
raw_spin_unlock(&cpu_base->lock);
/* Reprogramming necessary ? */
if (expires_next.tv64 == KTIME_MAX ||
!tick_program_event(expires_next, 0)) { /* 如果此时tick_program_event返回非0值,表示过期时间已经在当前时间的前面,
通常可能由以下原因造成:1 系统正在被调试跟踪,导致时间在走,程序不走。2
定时器的回调函数花了太长的时间。3 系统运行在虚拟机中,而虚拟机被调度导致
停止运行。默认设置成功,tick_program_event返回0。hrtimer_interrupt
函数执行if中的代码,程序根据raise的值,判断是否唤醒HRTIMER_SOFTIRQ后
退出。*/
cpu_base->hang_detected = 0;
if (raise)
raise_softirq_irqoff(HRTIMER_SOFTIRQ);
return;
}
/*
* The next timer was already expired due to:
* - tracing
* - long lasting callbacks
* - being scheduled away when running in a VM
*
* We need to prevent that we loop forever in the hrtimer
* interrupt routine. We give it 3 attempts to avoid
* overreacting on some spurious event.
*/
now = ktime_get();
cpu_base->nr_retries++;
if (++retries < 3) /* 为了避免当前时间已经过了下一个定时器到期时间的发生,系统提供三次机会,重新执行之前的循环
处理到期的定时器。 */
goto retry;
/*
* Give the system a chance to do something else than looping
* here. We stored the entry time, so we know exactly how long
* we spent here. We schedule the next event this amount of
* time away.
*/
cpu_base->nr_hangs++;
cpu_base->hang_detected = 1;
delta = ktime_sub(now, entry_time); /* 计算本次总循环的时间。now为当前时间,entry_time为进入hrtimer_interrupt的时间。*/
if (delta.tv64 > cpu_base->max_hang_time.tv64)
cpu_base->max_hang_time = delta;
/*
* Limit it to a sensible value as we enforce a longer
* delay. Give the CPU at least 100ms to catch up.
*/
if (delta.tv64 > 100 * NSEC_PER_MSEC) /* tick_device的到期时间被强制设定在100ms以内。*/
expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);
else
expires_next = ktime_add(now, delta);
tick_program_event(expires_next, 1); /* 设置下一次到期时间。 */
printk_once(KERN_WARNING "hrtimer: interrupt took %llu ns\n",
ktime_to_ns(delta));
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。