高精度定时器hrtimer属于某个cpu的hrtimer_cpu_base,意思即他是与cpu绑定的。如果某个进程在另外的cpu上使能这个hrtimer,就需要改变这个hrtimer的cpu base了。这个很类似进程在cfs_rq之间的迁移,不过一个定时器占用的资源太少了,迁移起来相对容易。
这种设计思路,我认为是有些问题的。时间轴是唯一的,所有的定时器,只要是realtime/monotonic time,都可以挂在这个标尺上去维护。多核上定时器在cpu之间迁移,应该是为了其他mode的定时器考虑。最新的hrtimer增加了些clock base和timer mode,目前还不清楚这些背后的机理。
简单了解下使能一个高精度定时器的流程。
hrtimer_start()在当前cpu上使能一个hrtimer,它调用__hrtimer_start_range_ns()完成这个任务。
int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
unsigned long delta_ns, const enum hrtimer_mode mode,
int wakeup)
{
struct hrtimer_clock_base *base, *new_base;
unsigned long flags;
int ret, leftmost;
base = lock_hrtimer_base(timer, &flags);
/* Remove an active timer from the queue: */
ret = remove_hrtimer(timer, base);//先从当前红黑树摘下
/* Switch the timer base, if necessary: */
new_base = switch_hrtimer_base(timer, base, mode & HRTIMER_MODE_PINNED);//迁移定时器的cpu base
if (mode & HRTIMER_MODE_REL) {
tim = ktime_add_safe(tim, new_base->get_time());
/*
* CONFIG_TIME_LOW_RES is a temporary way for architectures
* to signal that they simply return xtime in
* do_gettimeoffset(). In this case we want to round up by
* resolution when starting a relative timer, to avoid short
* timeouts. This will go away with the GTOD framework.
*/
#ifdef CONFIG_TIME_LOW_RES
tim = ktime_add_safe(tim, base->resolution);
#endif
}
hrtimer_set_expires_range_ns(timer, tim, delta_ns);
timer_stats_hrtimer_set_start_info(timer);
leftmost = enqueue_hrtimer(timer, new_base); //把定时器重新放入当前cpu的队列
/*
* Only allow reprogramming if the new base is on this CPU.
* (it might still be on another CPU if the timer was pending)
*
* XXX send_remote_softirq() ?
*/
if (leftmost && new_base->cpu_base == &__get_cpu_var(hrtimer_bases))
hrtimer_enqueue_reprogram(timer, new_base, wakeup); //如果是最左侧的定时器,就要重新对底层clock event device设置中断了。
unlock_hrtimer_base(timer, &flags);
return ret;
}
决定hrtimer是否迁移的函数是:
/*
* Switch the timer base to the current CPU when possible.
*/
static inline struct hrtimer_clock_base *
switch_hrtimer_base(struct hrtimer *timer, struct hrtimer_clock_base *base,
int pinned)
{
struct hrtimer_clock_base *new_base;
struct hrtimer_cpu_base *new_cpu_base;
int this_cpu = smp_processor_id();
int cpu = hrtimer_get_target(this_cpu, pinned);
again:
new_cpu_base = &per_cpu(hrtimer_bases, cpu);
new_base = &new_cpu_base->clock_base[base->index];
if (base != new_base) { //当前进程使能hrtimer的cpu base与hrtimer默认挂载的cpu base不同,则要pull hrtimer from old cpu to current cpu.
/*
* We are trying to move timer to new_base.
* However we can't change timer's base while it is running,
* so we keep it on the same CPU. No hassle vs. reprogramming
* the event source in the high resolution case. The softirq
* code will take care of this when the timer function has
* completed. There is no conflict as we hold the lock until
* the timer is enqueued.
*/
if (unlikely(hrtimer_callback_running(timer)))
return base;
/* See the comment in lock_timer_base() */
timer->base = NULL;
raw_spin_unlock(&base->cpu_base->lock);
raw_spin_lock(&new_base->cpu_base->lock);
if (cpu != this_cpu && hrtimer_check_target(timer, new_base)) {
cpu = this_cpu;
raw_spin_unlock(&new_base->cpu_base->lock);
raw_spin_lock(&base->cpu_base->lock);
timer->base = base; //完成修改base
goto again;
}
timer->base = new_base;
}
return new_base;
}
不同cpu的clock source配置可能不一致,这会引起hrtiemr实际运行中的精度问题。
阅读(3872) | 评论(0) | 转发(0) |