每一个hrtimer_bases都包括两个clock_base,一个是CLOCK_REALTIME范例的,另外一个是CLOCK_MONOTONIC范例的。hrtimer能够选择个中之一来设置timer的expiretime,能够是实践的工夫,也能够是绝对体系运转的工夫。
在hrtimer_run_queues的处置中,起首要经由过程hrtimer_bases找到正在实行以后中止的CPU相干联的clock_base,然后逐一反省每一个clock_base上挂的timer是不是超时。因为timer在增加到clock_base上时利用了红黑树,最早超时的timer被放到树的最左边,因而寻觅超时timer的历程十分敏捷,找到的一切超时timer会被一一处置。超时的timer依据其范例分为softIRQ/per-CPU/unlocked几种。假如一个timer是softIRQ范例的,这个超时的timer必要被转移到hrtimer_bases的cb_pending的list上,待IRQ0的软中止被激活后,经由过程run_hrtimer_pending实行,别的两类则必需在hardIRQ中经由过程__run_hrtimer间接实行。不外在较新的kernel(>2.6.29)中,cb_pending被作废,如许一切的超时timers都必需在hardIRQ的context中实行。如许修正的目标,一是为了简化代码逻辑,二是为了削减2次context的切换:一次从hardIRQ到softIRQ,另外一次从softIRQ到被超时timer叫醒的历程。
在update_process_times中,除处置处于低精度形式的hrtimer外,还要叫醒IRQ0的softIRQ(TIMER_SOFTIRQ(run_timer_softirq))以便实行timerwheel的代码。因为hrtimer子体系的到场,在IRQ0的softIRQ中,还必要经由过程hrtimer_run_pending反省是不是能够将hrtimer切换到高精度形式,如清单5所示:
清单5.hrtimer举行精度切换的处置函数
void hrtimer_run_pending(void)
{
if (hrtimer_hres_active())
return;
/*
* This _is_ ugly: We have to check in the softirq context,
* whether we can switch to highres and / or nohz mode. The
* clocksource switch happens in the timer interrupt with
* xtime_lock held. Notification from there only sets the
* check bit in the tick_oneshot code, otherwise we might
* deadlock vs. xtime_lock.
*/
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
hrtimer_switch_to_hres();
}
正如这段代码的作者正文中所提到的,每次触发IRQ0的softIRQ都必要反省一次是不是能够将hrtimer切换到高精度,明显是非常低效的,但愿未来有更好的办法不必每次都举行反省。
假如能够将hrtimer切换到高精度形式,则挪用hrtimer_switch_to_hres函数举行切换。如清单6所示:
清单6.hrtimer切换到高精度形式的中心函数
/*
* Switch to high resolution mode
*/
static int hrtimer_switch_to_hres(void)
{
int cpu = smp_processor_id();
struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
unsigned long flags;
if (base->hres_active)
return 1;
local_irq_save(flags);
if (tick_init_highres()) {
local_irq_restore(flags);
printk(KERN_WARNING "Could not switch to high resolution "
"mode on CPU %d
", cpu);
return 0;
}
base->hres_active = 1;
base->clock_base[CLOCK_REALTIME].resolution = KTIME_HIGH_RES;
base->clock_base[CLOCK_MONOTONIC].resolution = KTIME_HIGH_RES;
tick_setup_sched_timer();
/* "Retrigger" the interrupt to get things going */
retrigger_next_event(NULL);
local_irq_restore(flags);
return 1;
}
hrtimer_interrupt的利用情况
hrtimer_interrupt有2种罕见的利用体例。一是作为tick的推进器在发生tick中止时被挪用;另外一种情形就是经由过程软中止HRTIMER_SOFTIRQ(run_hrtimer_softirq)被挪用,一般是被驱动程序大概直接的利用这些驱动程序的用户程序所挪用
在这个函数中,起首利用tick_init_highres更新与本来的tickdevice绑定的时钟事务设备的eventhandler,比方将在低精度形式下的事情函数tick_handle_periodic/tick_handle_periodic_broadcast换成hrtimer_interrupt(它是hrtimer在高精度形式下的timer中止处置函数),同时将tickdevice的触发形式变成one-shot,即单次触发形式,这是利用dynamictick大概hrtimer时tickdevice的事情形式。因为dynamictick能够随时中断和入手下手,以不纪律的速率发生tick,因而撑持one-shot形式的时钟事务设备是必需的;关于hrtimer,因为hrtimer接纳事务机制驱动timer行进,因而利用one-shot的触发形式也是水到渠成的。不外如许一来,底本tickdevice每次实行中止时必要完成的周期性义务如更新jiffies/walltime(do_timer)和更新process的利用工夫(update_process_times)等事情在切换到高精度形式以后就没有了,因而在实行完tick_init_highres以后紧接着会挪用tick_setup_sched_timer函数来完成这部分设置事情,如清单7所示:
清单7.hrtimer高精度形式下摹拟周期运转的tickdevice的简化完成
void tick_setup_sched_timer(void)
{
struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
ktime_t now = ktime_get();
u64 offset;
for (;;) {
hrtimer_forward(&ts->sched_timer, now, tick_period);
hrtimer_start_expires(&ts->sched_timer,
HRTIMER_MODE_ABS_PINNED);
/* Check, if the timer was already in the past */
if (hrtimer_active(&ts->sched_timer))
break;
now = ktime_get();
}
. . . .
}
这个函数利用tick_cpu_sched这个per-CPU变量来摹拟本来tickdevice的功效。tick_cpu_sched自己绑定了一个hrtimer,这个hrtimer的超时价为下一个tick,回调函数为tick_sched_timer。因而,每过一个tick,tick_sched_timer就会被挪用一次,在这个回调函数中起首完成本来tickdevice的事情,然后设置下一次的超时价为再下一个tick,从而到达了摹拟周期运转的tickdevice的功效。假如一切的CPU在统一工夫点被叫醒,并发实行tick时大概会呈现lock合作和cache-line抵触,为此Linux内核做了出格处置:假如假定CPU的个数为N,则一切的CPU都在tick_period前1/2的工夫内实行tick事情,而且每一个CPU的实行距离是tick_period/(2N),见清单8所示:
清单8.hrtimer在高精度形式下tick实行周期的设置
void tick_setup_sched_timer(void)
{
. . . .
/* Get the next period (per cpu) */
hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
offset = ktime_to_ns(tick_period) >> 1;
do_div(offset, num_possible_cpus());
offset *= smp_processor_id();
hrtimer_add_expires_ns(&ts->sched_timer, offset);
. . . .
}
回到hrtimer_switch_to_hres函数中,在统统筹办停当后,挪用retrigger_next_event激活下一次的timer就能够入手下手一般的运作了。
跟着hrtimer子体系的开展,一些成绩也渐渐表露了出来。一个对照典范的成绩就是CPU的功耗成绩。古代CPU都完成了节能的特征,在没有事情时CPU会自动下降频次,封闭CPU外部一些非关头模块以到达节能的目标。因为hrtimer的精度很高,触发中止的频次也会很高,频仍的中止会极年夜的影响CPU的节能。在这方面hrtimer一向在不休的举行调剂。以下几个例子都是针对这一成绩所做的改善。
schedule_hrtimeout函数
/**
* schedule_hrtimeout - sleep until timeout
* @expires: timeout value (ktime_t)
* @mode: timer mode, HRTIMER_MODE_ABS or HRTIMER_MODE_REL
*/
int __sched schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode)
schedule_hrtimeout用来发生一个高精度的调剂超时,以ns为单元。如许能够加倍细粒度的利用内核的调剂器。在ArjanvandeVen的最后完成中,这个函数有一个很年夜的成绩:因为其粒度很细,以是大概会加倍频仍的叫醒内核,招致损耗更多的动力。为了完成既能节俭动力,又能确保准确的调剂超时,ArjanvandeVen的举措是将一个超时点酿成一个超时局限。设置hrtimerA的超时价有一个下限,称为hardexpire,在hardexpire这个工夫点上设置hrtimerA的超时中止;同时设置hrtimerA的超时价有一个上限,称为softexpire。在softexpire到hardexpire之间假如有一个hrtimerB的中止被触发,在hrtimerB的中止处置函数中,内核会反省是不是有其他hrtimer的softexpire超时了,比如hrtimerA的softexpire超时了,即便hrtimerA的hardexpire没有到,也能够顺带被处置。换言之,将本来必需在hardexpire超时才干实行的一个点酿成一个局限后,能够只管把hrtimer中止放在一同处置,如许CPU被反复叫醒的概率会变小,从而到达节能的效果,同时这个hrtimer也能够包管其实行精度。
Deferrabletimers&roundjiffies
在内核中利用的某些legacytimer关于准确的超时价其实不敏感,早一点大概晚一点实行其实不会发生多年夜的影响,因而,假如能够把这些对工夫不敏感同时超不时间又对照靠近的timer搜集在一同实行,能够进一步削减CPU被叫醒的次数,从而到达节能的目地。这恰是引进Deferrabletimers的目地。假如一个timer能够被长久延时,那末能够经由过程挪用init_timer_deferrable设置defer标志,从而在实行时天真选择处置体例。不外,假如这些timers都被延时到统一个工夫点上也不是最优的选择,如许一样会发生lock合作和cache-line的成绩。因而,即使将defertimers搜集到一同,相互之间也必需稍稍错开一些以避免上述成绩。这恰是引进round_jiffies函数的缘故原由。固然如许做会使得CPU被叫醒的次数稍多一些,可是因为距离短,CPU其实不会进进很深的就寝,这个价值仍是能够承受的。因为round_jiffies必要在每次更新timer的超时价(mod_timer)时被挪用,显得有些烦琐,因而又呈现了更加便利的roundjiffies机制,称为timerslack。Timerslack修正了timer_list的布局界说,将必要偏移的jiffies值保留在timer_list外部,经由过程apply_slack在每次更新timer的过程当中主动更新超时价。apply_slack的完成如清单9所示:
清单9.apply_slack的完成
/*
* Decide where to put the timer while taking the slack into account
*
* Algorithm:
* 1) calculate the maximum (absolute) time
* 2) calculate the highest bit where the expires and new max are different
* 3) use this bit to make a mask
* 4) use the bitmask to round down the maximum time, so that all last
* bits are zeros
*/
static inline
unsigned long apply_slack(struct timer_list *timer, unsigned long expires)
{
unsigned long expires_limit, mask;
int bit;
expires_limit = expires;
if (timer->slack >= 0) {
expires_limit = expires + timer->slack;
} else {
unsigned long now = jiffies; /* avoid reading jiffies twice */
/* if already expired, no slack; otherwise slack 0.4% */
if (time_after(expires, now))
expires_limit = expires + (expires - now)/256;
}
mask = expires ^ expires_limit;
if (mask == 0)
return expires;
if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE))
tick_do_timer_cpu = cpu;
/* Check, if the jiffies need an update */
if (tick_do_timer_cpu == cpu)
tick_do_update_jiffies64(now);
/*
* When we are idle and the tick is stopped, we have to touch
* the watchdog as we might not schedule for a really long
* time. This happens on complete idle SMP systems while
* waiting on the login prompt. We also increment the "start
* of idle" jiffy stamp so the idle accounting adjustment we
* do when we go busy again does not account too much ticks.
*/
if (ts->tick_stopped) {
touch_softlockup_watchdog();
ts->idle_jiffies++;
}