|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
对于开发环境的选择尽量要轻量级和高度可定制,航空母舰级别的工具往往会让你迷惑不解;
Linux中的准时器 在Linux内核中次要有两品种型的准时器。一类称为timeout范例,另外一类称为timer范例。timeout范例的准时器一般用于检测各类毛病前提,比方用于检测网卡收发数据包是不是会超时的准时器,IO设备的读写是不是会超时的准时器等等。一般情形下这些毛病很少产生,因而,利用timeout范例的准时器一样平常在超时之前就会被移除,从而很少发生真实的函数挪用和体系开支。总的来讲,利用timeout范例的准时器发生的体系开支很小,它是下文说起的timerwheel一般利用的情况。别的,在利用timeout范例准时器的中央常常其实不体贴超时处置,因而超时准确与否,早0.01秒大概晚0.01秒其实不非常主要,这鄙人文叙述deferrabletimers时会进一步先容。timer范例的准时器与timeout范例的准时器正相反,利用timer范例的准时器常常请求在准确的时钟前提下完成特定的事务,一般是周期性的而且依附超机会制举行处置。比方设备驱动一般会准时读写设备来举行数据交互。怎样高效的办理timer范例的准时器对进步体系的处置效力非常主要,下文在先容hrtimer时会有加倍具体的叙述。
内核必要举行时钟办理,离不开底层的硬件撑持。在初期是经由过程8253芯片供应的PIT(ProgrammableIntervalTimer)来供应时钟,可是PIT的频次很低,只能供应最高1ms的时钟精度,因为PIT触发的中止速率太慢,会招致很年夜的时延,关于像音视频这类对工夫精度请求更高的使用其实不充足,会极年夜的影响用户体验。跟着硬件平台的不休开展变更,连续呈现了TSC(TimeStampCounter),HPET(HighPrecisionEventTimer),ACPIPMTimer(ACPIPowerManagementTimer),CPULocalAPICTimer等精度更高的时钟。这些时钟连续被Linux的时钟子体系所采取,从而不休的进步Linux时钟子体系的功能和天真性。这些分歧的时钟会鄙人文分歧的章节平分别举行先容。
Timerwheel
在Linux2.6.16之前,内核一向利用一种称为timerwheel的机制来办理时钟。这就是熟知的kernel一向接纳的基于HZ的timer机制。Timerwheel的中心数据布局如清单1所示:
清单1.Timerwheel的中心数据布局
#define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6)
#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
struct tvec {
struct list_head vec[TVN_SIZE];
};
struct tvec_root {
struct list_head vec[TVR_SIZE];
};
struct tvec_base {
spinlock_t lock;
struct timer_list *running_timer;
unsigned long timer_jiffies;
unsigned long next_timer;
struct tvec_root tv1;
struct tvec tv2;
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
} ____cacheline_aligned;
以CONFIG_BASE_SMALL界说为0为例,TVR_SIZE=256,TVN_SIZE=64,如许
能够失掉如所示的timerwheel的布局。
.Timerwheel的逻辑布局
list_head的感化
list_head是Linux内核利用的一个双向轮回链表表头。任何一个必要利用链表的数据布局能够经由过程内嵌list_head的体例,将其链接在一同从而构成一个双向链表。拜见list_head在include/Linux/list.h中的界说和完成。
在timerwheel的框架下,一切体系正在利用的timer并非按次寄存在一个平展的链表中,由于如许做会使得查找,拔出,删除等操纵效力低下。Timerwheel供应了5个timer数组,数组之间存在着相似时分秒的进位干系。TV1为第一个timer数组,个中寄存着从timer_jiffies(以后到期的jiffies)到timer_jiffies+255共256个tick对应的timerlist。由于在一个tick上大概同时有多个timer守候超时处置,timerwheel利用list_head将一切timer串成一个链表,以便在超不时按次处置。TV2有64个单位,每一个单位都对应着256个tick,因而TV2所暗示的超不时间局限从timer_jiffies+256到timer_jiffies+256*64C1。顺次类推TV3,TV4,TV5。以HZ=1000为例,每1ms发生一次中止,TV1就会被会见一次,可是TV2要每256ms才会被会见一次,TV3要16s,TV4要17分钟,TV5乃至要19小时才无机会反省一次。终极,timerwheel能够办理的最年夜超时价为2^32。一共利用了512个list_head(256+64+64+64+64)。假如CONFIG_BASE_SMALL界说为1,则终极利用的list_head个数为128个(64+16+16+16+16),占用的内存更少,更合适嵌进式体系利用。Timerwheel的处置逻辑如清单2所示:
清单2.timerwheel的中心处置函数
static inline void __run_timers(struct tvec_base *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock);
while (time_after_eq(jiffies, base->timer_jiffies)) {
struct list_head work_list;
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK;
/*
* Cascade timers:
*/
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
++base->timer_jiffies;
list_replace_init(base->tv1.vec + index, &work_list);
while (!list_empty(head)) {
void (*fn)(unsigned long);
unsigned long data;
timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
. . . .
fn(data);
. . . .
}
base->timer_jiffies用来纪录在TV1中最靠近超时的tick的地位。index是用来遍历TV1的索引。每次轮回index会定位一个以后待处置的tick,并处置这个tick下一切超时的timer。base->timer_jiffies会在每次轮回后增添一个jiffy,index也会随之向前挪动。当index变成0时暗示TV1完成了一次完全的遍历,此时一切在TV1中的timer都被处置了,因而必要经由过程cascade将前面TV2,TV3等timerlist中的timer向前挪动,相似于进位。这类层叠的timerlist完成机制能够年夜年夜下降每次反省超时timer的工夫,每次中止只必要针对TV1举行反省,只要需要时才举行cascade。即使云云,timerwheel的完成机制仍旧存在很年夜坏处。一个坏处就是cascade开支过年夜。在极度的前提下,同时会有多个TV必要举行cascade处置,会发生很年夜的时延。这也是为何说timeout范例的准时器是timerwheel的次要使用情况,大概说timerwheel是为timeout范例的准时器优化的。由于timeout范例的准时器的使用场景多是毛病前提的检测,这类毛病产生的机率很小,一般不到超时就被删除,因而不会发生cascade的开支。另外一方面,因为timerwheel是创建在HZ的基本上的,因而其计时精度没法进一步进步。究竟一味的经由过程进步HZ值来进步计时精度并没有意义,了局只能是发生大批的准时中止,增添分外的体系开支。因而,有需要将高精度的timer与低精度的timer分隔,如许既能够确保低精度的timeout范例的准时器使用,也便于高精度的timer范例准时器的使用。另有一个主要的要素是timerwheel的完成与jiffies的耦合性太强,十分方便于扩大。因而,自从2.6.16入手下手,一个新的timer子体系hrtimer被到场到内核中。
hrtimer(High-resolutionTimer)
hrtimer起首要完成的功效就是要克制timerwheel的弱点:低精度和与内核其他模块的高耦合性。在正式先容hrtimer之前,有需要先先容几个经常使用的基础观点:
时钟源设备(clock-sourcedevice)
体系中能够供应必定精度的计时设备都能够作为时钟源设备。如TSC,HPET,ACPIPM-Timer,PIT等。可是分歧的时钟源供应的时钟精度是纷歧样的。像TSC,HPET等时钟源既撑持高精度形式(high-resolutionmode)也撑持低精度形式(low-resolutionmode),而PIT只能撑持低精度形式。别的,时钟源的计时都是单调递增的(monotonically),假如时钟源的计时呈现翻转(即前往到0值),很简单形成计时毛病,内核的一个patch(commitid:ff69f2)就是处置这类成绩的一个很好示例。时钟源作为体系时钟的供应者,在牢靠而且可用的条件下精度越高越好。在Linux中分歧的时钟源有分歧的rating,具有更高rating的时钟源会优先被体系利用。如所示:
表1.时钟源中rating的界说
1~99100~199200~299300~399400~499十分差的时钟源,只能作为最初的选择。如jiffies基础可使用但并不是幻想的时钟源。如PIT准确可用的时钟源。如ACPIPMTimer,HPET疾速而且准确的时钟源。如TSC幻想时钟源。如kvm_clock,xen_clock
时钟事务设备(clock-eventdevice)
体系中能够触发one-shot(单次)大概周期性中止的设备都能够作为时钟事务设备。如HPET,CPULocalAPICTimer等。HPET对照出格,它既能够做时钟源设备也能够做时钟事务设备。时钟事务设备的范例分为全局和per-CPU两品种型。全局的时钟事务设备固然从属于某一个特定的CPU上,可是完成的是体系相干的事情,比方完成体系的tick更新;per-CPU的时钟事务设备次要完成LocalCPU上的一些功效,比方对在以后CPU上运转历程的工夫统计,profile,设置LocalCPU上的下一次事务中止等。和时钟源设备的完成相似,时钟事务设备也经由过程rating来辨别优先级干系。
tickdevice
Tickdevice用来处置周期性的tickevent。Tickdevice实际上是时钟事务设备的一个wrapper,因而tickdevice也有one-shot和周期性这两种中止触发形式。每注册一个时钟事务设备,这个设备会主动被注册为一个tickdevice。全局的tickdevice用来更新诸如jiffies如许的全局信息,per-CPU的tickdevice则用来更新每一个CPU相干的特定信息。
broadcast
CPU的C-STATE
CPU在余暇时会依据余暇工夫的是非选择进进分歧的就寝级别,称为C-STATE。C0为一般运转形态,C1到C7为就寝形态,数值越年夜,就寝水平越深,也就越省电。CPU余暇越久,进入眠眠的级别越高,可是叫醒所需的工夫也越长。叫醒也是必要损耗动力的,因而,只要选择符合的就寝级别才干确保节能的最年夜化。
Broadcast的呈现是为了应对如许一种情形:假定CPU利用LocalAPICTimer作为per-CPU的tickdevice,可是某些特定的CPU(如Intel的Westmere之前的CPU)在进进C3+的形态时LocalAPICTimer也会同时中断事情,进入眠眠形态。在这类情况下broadcast能够替换LocalAPICTimer持续完成统计历程的实行工夫等有关操纵。实质上broadcast是发送一个IPI(Inter-processor interrupt)中止给其他一切的CPU,当方针CPU收到这个IPI中止后就会挪用本来LocalAPICTimer一般事情时的中止处置函数,从而完成了一样的功效。今朝次要在x86和MIPS下会用到broadcast功效。
Timekeeping>OD(GenericTime-of-Day)
Timekeeping(能够了解为工夫丈量大概计时)是内核工夫办理的一个中心构成部分。没有Timekeeping,就没法更新体系工夫,保持体系“心跳”。GTOD是一个通用的框架,用来完成诸如设置体系工夫gettimeofday大概修正体系工夫settimeofday等事情。为了完成以上功效,Linux完成了多种与工夫相干但用于分歧目标的数据布局。
struct timespec {
__kernel_time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
timespec精度是纳秒。它用来保留从00:00:00GMT,1January1970入手下手经由的工夫。内核利用全局变量xtime来纪录这一信息,这就是一般所说的“WallTime”大概“RealTime”。与此对应的是“SystemTime”。SystemTime是一个单调递增的工夫,每次体系启动时从0入手下手计时。
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
timeval精度是微秒。timeval次要用来指定一段工夫距离。
union ktime {
s64 tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
struct {
# ifdef __BIG_ENDIAN
s32 sec, nsec;
# else
s32 nsec, sec;
# endif
} tv;
#endif
};
ktime_t是hrtimer次要利用的工夫布局。不管利用哪一种系统布局,ktime_t一直坚持64bit的精度,而且思索了巨细真个影响。
typedef u64 cycle_t;
cycle_t是从时钟源设备中读取的时钟范例。
为了办理这些分歧的工夫布局,Linux完成了一系列帮助函数来完成互相间的转换。
ktime_to_timespec,ktime_to_timeval,ktime_to_ns/ktime_to_us,反过去有诸如ns_to_ktime等相似的函数。
timeval_to_ns,timespec_to_ns,反过去有诸如ns_to_timeval等相似的函数。
timeval_to_jiffies,timespec_to_jiffies,msecs_to_jiffies,usecs_to_jiffies,clock_t_to_jiffies反过去有诸如ns_to_timeval等相似的函数。
clocksource_cyc2ns/cyclecounter_cyc2ns
有了以上的先容,经由过程能够加倍明晰的看到这几者之间的联系关系。
.内核时钟子体系的布局干系
时钟源设备和时钟事务设备的引进,将底本放在各个别系布局中反复完成的冗余代码封装到各自的笼统层中,如许做不仅打消了本来timerwheel与内核其他模块的紧耦合性,更主要的是体系能够在运转形态静态改换时钟源设备和时钟事务设备而不影响体系一般利用,比如当CPU因为就寝要封闭以后利用的时钟源设备大概时钟事务设备时体系能够光滑的切换到其他仍处于事情形态的设备上。Timekeeping/GTOD在利用时钟源设备的基本上也接纳相似的封装完成了系统布局的有关性和通用性。hrtimer则能够经由过程timekeeping供应的接口完成准时器的更新,经由过程时钟事务设备供应的事务机制,完成对timer的办理。在中另有一个主要的模块就是tickdevice的笼统,特别是dynamictick。Dynamictick的呈现是为了能在体系余暇时经由过程中断tick的运转以到达下降CPU功耗的目标。利用dynamictick的体系,只要在有实践事情时才会发生tick,不然tick是处于中断形态。下文会有专门的章节举行叙述。
hrtimer的完成机制
hrtimer是创建在per-CPU时钟事务设备上的,关于一个SMP体系,假如只要全局的时钟事务设备,hrtimer没法事情。由于假如没有per-CPU时钟事务设备,时钟中止产生时体系必需发生需要的IPI中止来关照其他CPU完成响应的事情,而过量的IPI中止会带来很年夜的体系开支,如许会令利用hrtimer的价值太年夜,不如不必。为了撑持hrtimer,内核必要设置CONFIG_HIGH_RES_TIMERS=y。hrtimer有两种事情形式:低精度形式(low-resolutionmode)与高精度形式(high-resolutionmode)。固然hrtimer子体系是为高精度的timer筹办的,可是体系大概在运转过程当中静态切换到分歧精度的时钟源设备,因而,hrtimer必需可以在低精度形式与高精度形式下自在切换。因为低精度形式是创建在高精度形式之上的,因而即使体系只撑持低精度形式,部分撑持高精度形式的代码仍旧会编译到内核傍边。
在低精度形式下,hrtimer的中心处置函数是hrtimer_run_queues,每次tick中止都要实行一次。如清单3所示。这个函数的挪用流程为:
update_process_times
run_local_timers
hrtimer_run_queues
raise_softirq(TIMER_SOFTIRQ)
清单3.低精度形式下hrtimer的中心处置函数
void hrtimer_run_queues(void)
{
struct rb_node *node;
struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);
struct hrtimer_clock_base *base;
int index, gettime = 1;
if (hrtimer_hres_active())
return;
for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; index++) {
base = &cpu_base->clock_base[index];
if (!base->first)
continue;
if (gettime) {
hrtimer_get_softirq_time(cpu_base);
gettime = 0;
}
raw_spin_lock(&cpu_base->lock);
while ((node = base->first)) {
struct hrtimer *timer;
timer = rb_entry(node, struct hrtimer, node);
if (base->softirq_time.tv64 <=
hrtimer_get_expires_tv64(timer))
break;
__run_hrtimer(timer, &base->softirq_time);
}
raw_spin_unlock(&cpu_base->lock);
}
}
hrtimer_bases是完成hrtimer的中心数据布局,经由过程hrtimer_bases,hrtimer能够办理挂在每个CPU上的一切timer。每一个CPU上的timerlist不再利用timerwheel中多级链表的完成体例,而是接纳了红黑树(Red-BlackTree)来举行办理。hrtimer_bases的界说如清单4所示:
清单4.hrtimer_bases的界说
DEFINE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases) =
{
.clock_base =
{
{
.index = CLOCK_REALTIME,
.get_time = &ktime_get_real,
.resolution = KTIME_LOW_RES,
},
{
.index = CLOCK_MONOTONIC,
.get_time = &ktime_get,
.resolution = KTIME_LOW_RES,
},
}
};
展现了hrtimer怎样经由过程hrtimer_bases来办理timer。
.hrtimer的时钟办理
每一个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;
/*
* Emulate tick processing via per-CPU hrtimers:
*/
hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
ts->sched_timer.function = tick_sched_timer;
. . . .
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;
bit = find_last_bit(&mask, BITS_PER_LONG);
mask = (1 << bit) - 1;
expires_limit = expires_limit & ~(mask);
return expires_limit;
}
跟着古代盘算机体系的开展,对节能的需求愈来愈高,特别是在利用条记本,手持设备等挪动情况是对节能请求更高。Linux固然也会加倍存眷这方面的需求。hrtimer子体系的优化只管确保在利用高精度的时钟的同时勤俭动力,假如体系在余暇时也能够只管的节能,则Linux体系的节能上风能够进一步缩小。这也是引进dynamictick的基本缘故原由。
Dynamictick&tickless
在dynamictick引进之前,内核一向利用周期性的基于HZ的tick。传统的tick机制在体系进进余暇形态时仍旧会发生周期性的中止,这类频仍的中止迫使CPU没法进进更深的就寝。假如摊开这个限定,在体系进进余暇时中断tick,有事情时恢复tick,完成完整自在的,依据必要发生tick的机制,可使CPU取得更多的就寝时机和更深的就寝,从而进一步节能。dynamictick的呈现,就是为完全交换失落周期性的tick机制而发生的。周期性运转的tick机制必要完成诸如历程工夫片的盘算,更新profile,帮忙CPU举行负载平衡等诸多事情,这些事情dynamictick都供应了响应的摹拟机制来完成。因为dynamictick的完成必要内核的良多模块的共同,包含了良多完成细节,这里只先容dynamictick的中心事情机制,和怎样启动和中断dynamictick。
Dynamictick的中心处置流程
从上文中可知内核时钟子体系撑持低精度和高精度两种形式,因而dynamictick也必需有两套对应的处置机制。从清单5中能够得知,假如体系撑持hrtimer的高精度形式,hrtimer能够在此从低精度形式切换到高精度形式。实在清单5另有别的一个主要功效:它也是低精度形式下从周期性tick到dynamictick的切换点。假如以后体系不撑持高精度形式,体系会实验切换到NOHZ形式,也就是利用dynamictick的形式,固然条件是内核使能了NOHZ形式。其中心处置函数如清单10所示。这个函数的挪用流程以下:
tick_check_oneshot_change
tick_nohz_switch_to_nohz
tick_switch_to_oneshot(tick_nohz_handler)
清单10.低精度形式下dynamictick的中心处置函数
static void tick_nohz_handler(struct clock_event_device *dev)
{
struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
struct pt_regs *regs = get_irq_regs();
int cpu = smp_processor_id();
ktime_t now = ktime_get();
dev->next_event.tv64 = KTIME_MAX;
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++;
}
update_process_times(user_mode(regs));
profile_tick(CPU_PROFILING);
while (tick_nohz_reprogram(ts, now)) {
now = ktime_get();
tick_do_update_jiffies64(now);
}
}
在这个函数中,起首摹拟周期性tickdevice完成相似的事情:假如以后CPU卖力全局tickdevice的事情,则更新jiffies,同时完成对当地CPU的历程工夫统计等事情。假如以后tickdevice在此之前已处于中断形态,为了避免tick中断工夫太长形成watchdog超时,从而激发soft-lockdep的毛病,必要经由过程挪用touch_softlockup_watchdog复位软件看门狗避免其溢出。正如代码中正文所形貌的,这类情形有大概呈现在启动终了,完整余暇守候登录的SMP体系上。最初必要设置下一次tick的超不时间。假如tick_nohz_reprogram实行工夫凌驾了一个jiffy,会招致设置的下一次超不时间已过时,因而必要从头设置,响应的也必要再次更新jiffies。这里固然设置了下一次的超时势件,可是因为体系余暇时会中断tick,因而下一次的超时势件大概产生,也大概不产生。这也恰是dynamictick基本特征。
从清单7中能够看到,在高精度形式下tick_sched_timer用来摹拟周期性tickdevice的功效。dynamictick的完成也利用了这个函数。这是由于hrtimer在高精度形式时必需利用one-shot形式的tickdevice,这也同时切合dynamictick的请求。固然利用一样的函数,外表上城市触发周期性的tick中止,可是利用dynamictick的体系在余暇时会中断tick事情,因而tick中止不会是周期发生的。
Dynamictick的入手下手和中断
当CPU进进余暇时是最好的机会。此时能够启动dynamictick机制,中断tick;反之在CPU从余暇中恢复到事情形态时,则能够中断dynamictick。见清单11所示:
清单11.CPU在idle时dynamictick的启动/中断设置
void cpu_idle(void)
{
. . . .
while (1) {
tick_nohz_stop_sched_tick(1);
while (!need_resched()) {
. . . .
}
tick_nohz_restart_sched_tick();
}
. . . .
}
timer子体系的初始化历程
在分离懂得了内核时钟子体系各个模块后,如今能够体系的先容内核时钟子体系的初始化历程。体系刚上电时,必要注册IRQ0时钟中止,完成时钟源设备,时钟事务设备,tickdevice等初始化操纵并选择符合的事情形式。因为刚启动时没有出格主要的义务要做,因而默许是进进低精度+周期tick的事情形式,以后会依据硬件的设置(如硬件上是不是撑持HPET等高精度timer)和软件的设置(如是不是经由过程命令行参数大概内核设置使能了高精度timer等特征)举行切换。在一个撑持hrtimer高精度形式并使能了dynamictick的体系中,第一次产生IRQ0的软中止时hrtimer就会举行从低精度到高精度的切换,然后再进一步切换到NOHZ形式。IRQ0为体系的时钟中止,利用全局的时钟事务设备(global_clock_event)来处置的,其界说以下:
static struct irqaction irq0 = {
.handler = timer_interrupt,
.flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_IRQPOLL | IRQF_TIMER,
.name = "timer"
};
它的中止处置函数timer_interrupt的简化完成如清单12所示:
清单12.IRQ0中止处置函数的简化完成
static irqreturn_t timer_interrupt(int irq, void *dev_id)
{
. . . .
global_clock_event->event_handler(global_clock_event);
. . . .
return IRQ_HANDLED;
}
在global_clock_event->event_handler的处置中,除更新localCPU上运转历程工夫的统计,profile等事情,更主要的是要完成更新jiffies等全局操纵。这个全局的时钟事务设备的event_handler依据利用情况的分歧,在低精度形式下多是tick_handle_periodic/tick_handle_periodic_broadcast,在高精度形式下是hrtimer_interrupt。今朝只要HPET大概PIT能够作为global_clock_event利用。其初始化流程清单13所示:
清单13.timer子体系的初始化流程
void __init time_init(void)
{
late_time_init = x86_late_time_init;
}
static __init void x86_late_time_init(void)
{
x86_init.timers.timer_init();
tsc_init();
}
/* x86_init.timers.timer_init 是指向 hpet_time_init 的回调指针 */
void __init hpet_time_init(void)
{
if (!hpet_enable())
setup_pit_timer();
setup_default_timer_irq();
}
由清单13能够看到,体系优先利用HPET作为global_clock_event,只要在HPET没有使能时,PIT才无机会成为global_clock_event。在使能HPET的过程当中,HPET会同时被注册为时钟源设备和时钟事务设备。
hpet_enable
hpet_clocksource_register
hpet_legacy_clockevent_register
clockevents_register_device(&hpet_clockevent);
clockevent_register_device会触发CLOCK_EVT_NOTIFY_ADD事务,即创立对应的tickdevice。然后在tick_notify这个事务处置函数中会增加新的tickdevice。
clockevent_register_device trigger event CLOCK_EVT_NOTIFY_ADD
tick_notify receives event CLOCK_EVT_NOTIFY_ADD
tick_check_new_device
tick_setup_device
在tickdevice的设置过程当中,会依据新到场的时钟事务设备是不是利用broadcast来分离设置event_handler。关于tickdevice的处置函数,可见所示:
表2.tickdevice在分歧形式下的处置函数
lowresolutionmodeHighresolutionmodeperiodicticktick_handle_periodichrtimer_interruptdynamicticktick_nohz_handlerhrtimer_interrupt
别的,在体系运转的过程当中,能够经由过程检察/proc/timer_list来显现体系以后设置的一切时钟的具体情形,比如以后体系举动的时钟源设备,时钟事务设备,tickdevice等。也能够经由过程检察/proc/timer_stats来检察以后体系中一切正在利用的timer的统计信息。包含一切正在利用timer的历程,启动/中断timer的函数,timer利用的频次等信息。内核必要设置CONFIG_TIMER_STATS=y,并且在体系启动时这个功效是封闭的,必要经由过程以下命令激活"echo1>/proc/timer_stats"。/proc/timer_stats的显现格局以下所示:
<count>,<pid><command><start_func>(<expire_func>)
总结
跟着使用情况的改动,利用需求的多样化,Linux的时钟子体系也在不休的衍变。为了更好的撑持音视频等对工夫精度高的使用,Linux提出了hrtimer这一高精度的时钟子体系,为了勤俭动力,Linux改动了久长以来一向利用的基于HZ的tick机制,接纳了tickless体系。即便是在对硬件平台的撑持上,也是在精益求精。举例来讲,因为TSC精度高,是首选的时钟源设备。可是古代CPU会在体系余暇时下降频次以勤俭动力,从而招致TSC的频次也会跟从产生改动。如许会招致TSC没法作为不乱的时钟源设备利用。跟着新的CPU的呈现,即便CPU的频次产生变更,TSC也能够一向保持在流动频次上,从而确保其不乱性。在Intel的Westmere之前的CPU中,TSC和LocalAPICTimer相似,城市在C3+形态时进入眠眠,从而招致体系必要切换到其他较低精度的时钟源设备上,可是在IntelWestmere以后的CPU中,TSC能够一向坚持运转形态,即便CPU进进了C3+的就寝形态,从而制止了时钟源设备的切换。在SMP的情况下,特别是16-COREs,32-COREs如许的多CPU体系中,每一个CPU之间的TSC很难坚持同步,很简单呈现“Out-of-Sync”。假如在这类情况下利用TSC,会形成CPU之间的计时偏差,但是在Intel最新的Nehalem-EXCPU中,已能够确保TSC在多个CPU之间坚持同步,从而可使用TSC作为首选的时钟源设备。因而可知,不管是如今仍是未来,只需有必要,内核的时钟子体系就会一向向前开展。
</p>
使用gcc或g++进行编译,使用gdb进行调试; |
|