揭开xenomai双核系统下clock机制的面纱-双内核技术

4、高精度timer + 周期性Tick

这种配置不多见,多半是由于硬件无法支持one shot的clock event device,这种情况下,整个系统仍然是运行在周期tick的模式下。

总结一下:linux启动过程中初始化时钟系统,当xenomai内核未启动时,linux直接对底层硬件lapic-timer编程,底层硬件lapic-timer产生中断推动整个Linux中的各个时钟及调度运行。

我们可以将Linux抽出如下图,只需要为Linux提供设置下一个时钟事件set_next_event()和提供event触发eventHandler()执行两个接口就能推动整个linux时间子系统运转,下面解析Xenomai是怎样为linux提供这两个接口的,达到控制整个时钟系统的。

揭开xenomai双核系统下clock机制的面纱-双内核技术

二、xenomai时间子系统

2.1 xnclock

我们知道x86下每个cpu核有一个lapic,lapic中有定时硬件lapic-timer和hpet。tsc作为timeline,提供计时,lapic-timer用来产生clock event。对于现今X86 CPU 操作系统一般都是使用TSC和lapic-timer作为clock source和clock event,因为精度最高(Atom 系列处理器可能会有区别).

xenomai的默认时间管理对象是xnclock,xnclock管理着xenomai整个系统的时间、任务定时、调度等,xnclok的默认时钟源为TSC。当然我们可以自定义clocksource。比如在TSC不可靠的系统上,可以使用外部定时硬件来作为时钟源,当自定义时钟时需要实现结构体中的宏CONFIG_XENO_OPT_EXTCLOCK包含的几个必要函数,且编译配置使能CONFIG_XENO_OPT_EXTCLOCK。

注意:这里的自定义时钟源只是将TSC替换为其他时钟源,产生event的还是lapic-timer. structxnclock{ /**(ns)*/ xnticks_twallclock_offset;/*获取时钟偏移:timekeeping – tsc*/ /**(ns)*/ xnticks_tresolution; /**(rawclockticks).*/ structxnclock_gravitygravity; /**Clockname.*/ constchar*name; struct{ #ifdefCONFIG_XENO_OPT_EXTCLOCK xnticks_t(*read_raw)(structxnclock*clock); xnticks_t(*read_monotonic)(structxnclock*clock); int(*set_time)(structxnclock*clock, conststructtimespec*ts); xnsticks_t(*ns_to_ticks)(structxnclock*clock, xnsticks_tns); xnsticks_t(*ticks_to_ns)(structxnclock*clock, xnsticks_tticks); xnsticks_t(*ticks_to_ns_rounded)(structxnclock*clock, xnsticks_tticks); void(*program_local_shot)(structxnclock*clock, structxnsched*sched); void(*program_remote_shot)(structxnclock*clock, structxnsched*sched); #endif int(*set_gravity)(structxnclock*clock, conststructxnclock_gravity*p); void(*reset_gravity)(structxnclock*clock); #ifdefCONFIG_XENO_OPT_VFILE void(*print_status)(structxnclock*clock, structxnvfile_regular_iterator*it); #endif }ops; /*Privatesection.*/ structxntimerdata*timerdata; intid; #ifdefCONFIG_SMP /**PossibleCPUaffinityofclockbeat.*/ cpumask_taffinity; #endif #ifdefCONFIG_XENO_OPT_STATS structxnvfile_snapshottimer_vfile; structxnvfile_rev_tagtimer_revtag; structlist_headtimerq; intnrtimers;/*统计挂在xnclockxntimer的数量*/ #endif/*CONFIG_XENO_OPT_STATS*/ #ifdefCONFIG_XENO_OPT_VFILE structxnvfile_regularvfile;//vfile.ops=&clock_ops #endif };

wallclock_offset

:linux系统wall time(1970开始的时间值)与系统TSC cycle转换为时间的偏移resolution

:该xnclock的精度struct xnclock_gravity gravity:该xnclok下,中断、内核、用户空间程序定时器的调整量,对系统精确定时很重要,后面会说到。 structxnclock_gravity{ unsignedlongirq; unsignedlongkernel; unsignedlonguser; };

ops:该xnclok的各操作函数。

timerdata:xntimer 管理结构头节点,当系统中使用红黑树来管理xntimer时,他是红黑树head节点,当系统使用优先级链表来管理时它是链表头节点,系统会为每个cpu分配一个timerdata,管理着本CPU上已启动的xntimer,当为红黑树时head始终指向最近到期的xntimer,当某个cpu上一个clockevent到来时,xnclock会从该CPU timerdata取出head指向的那个timer看是否到期,然后进一步处理。 #ifdefined(CONFIG_XENO_OPT_TIMER_RBTREE) typedefstruct{ structrb_rootroot; xntimerh_t*head; }xntimerq_t; #else typedefstructlist_headxntimerq_t; #endif structxntimerdata{ xntimerq_tq; };

timerq:不论是属于哪个cpu的xntimer初始化后都会挂到这个链表上,nrtimers挂在timerq上xntimer的个数

vfile:proc文件系统操作接口,可通过proc查看xenomai clock信息。 cat/proc/xenomai/clock/coreclok gravity:irq=99kernel=1334user=1334 devices:timer=lapic-deadline,clock=tsc status:on setup:99 ticks:376931548560(0057c2defd90)

gravity即xnclock中的结构体gravity的值,devices表示xenomai用于产生clock event的硬件timer,clock为xnclock计时的时钟源。 xenomai 内核默认定义xnclock如下,名字和结构体名一样,至于xnclock怎么和硬件timer 、tsc联系起来后面分析: structxnclocknkclock={ .name=”coreclk”, .resolution=1,/*nanosecond.*/ .ops={ .set_gravity=set_core_clock_gravity, .reset_gravity=reset_core_clock_gravity, .print_status=print_core_clock_status, }, .id=-1, };

2.2 xntimer

实时任务的所有定时行为最后都会落到内核中的xntimer上,而xnclock管理着硬件clock event,xntimer要完成定时就需要xnclock来获取起始时间,xntimer结构如下: structxntimer{ #ifdefCONFIG_XENO_OPT_EXTCLOCK structxnclock*clock; #endif /**Linkintimerslist.*/ xntimerh_taplink; structlist_headadjlink; /**Timerstatus.*/ unsignedlongstatus; /**Periodicinterval(clockticks,0==oneshot).*/ xnticks_tinterval; /**Periodicinterval(nanoseconds,0==oneshot).*/ xnticks_tinterval_ns; /**Countoftimerticksinperiodicmode.*/ xnticks_tperiodic_ticks; /**Firsttickdateinperiodicmode.*/ xnticks_tstart_date; /**Dateofnextperiodicreleasepoint(timerticks).*/ xnticks_tpexpect_ticks; /** Sched structure to which the timer is attached. 附加计时器的Sched结构。*/ structxnsched*sched; /**Timeouthandler.*/ void(*handler)(structxntimer*timer); #ifdefCONFIG_XENO_OPT_STATS #ifdefCONFIG_XENO_OPT_EXTCLOCK structxnclock*tracker; #endif /**Timernametobedisplayed.*/ charname[XNOBJECT_NAME_LEN]; /**Timerholderintimebase.*/ structlist_headnext_stat; /**Numberoftimerschedules.*/ xnstat_counter_tscheduled; /**Numberoftimerevents.*/ xnstat_counter_tfired; #endif/*CONFIG_XENO_OPT_STATS*/ };

clock

:当自定义外部时钟源时,使用外部时钟时的xnclock.aplink:上面介绍新clock时说到timerdata,当xntimer启动是,aplink就会插入到所在cpu的timerdata中,当timerdata为红黑树时,aplink就是一个rb节点,否则是一个链表节点。分别如下: //优先级链表结构 structxntlholder{ structlist_headlink; xnticks_tkey; intprio; }; typedefstructxntlholderxntimerh_t; //树结构 typedefstruct{ unsignedlonglongdate; unsignedprio; structrb_nodelink; }xntimerh_t;

系统默认配置以红黑树形式管理xntimer,date表示定时器的多久后到期;prio表示该定时器的优先级,当加入链表时先date来排序,如果几个定时器date相同就看优先级,优先级高的先处理link为红黑树节点。

揭开xenomai双核系统下clock机制的面纱-双内核技术

status:定时器状态,所有状态为如下: #defineXNTIMER_DEQUEUED0x00000001/*没有挂在xnclock上*/ #defineXNTIMER_KILLED0x00000002/*该定时器已经被取消*/ #defineXNTIMER_PERIODIC0x00000004/*该定时器是一个周期定时器*/ #defineXNTIMER_REALTIME0x00000008/*定时器相对于Linuxwalltime定时*/ #defineXNTIMER_FIRED0x00000010/*定时已经到期*/ #defineXNTIMER_NOBLCK0x00000020/*非阻塞定时器*/ #defineXNTIMER_RUNNING0x00000040/*定时器已经start*/ #defineXNTIMER_KGRAVITY0x00000080/*该timer是一个内核态timer*/ #defineXNTIMER_UGRAVITY0x00000100/*该timer是一个用户态timer*/ #defineXNTIMER_IGRAVITY0/*该timer是一个中断timer*/

interval、interval_ns:周期定时器的定时周期,分别是tick 和ns,0表示这个xntimer 是单次定时的。

handler:定时器到期后执行的函数。

sched:该timer所在的sched,每个cpu核上有一个sched,管理本cpu上的线程调度,timer又需要本cpu的lapic定时,所以指定了sched就指定了该timer所属cpu。

xntimer 使用需要先调用xntimer_init()初始化xntimer结构成员,然后xntimer_start()启动这个xntimer,启动timer就是将它插入xnclock管理的红黑树。

xntimer_init()是一个宏,内部调用__xntimer_init初始化timer,参数timer:需要初始化的timer;clock:该timer是依附于哪个xnclock,也就是说哪个xnclock来处理我是否触发,没有自定义就是xnclock,在timer_start的时候就会将这个timer挂到对应的xnclock上去;handler:该timer到期后执行的hanler;sched:timer所属的sched;flags:指定该timer标志。 #definexntimer_init(__timer,__clock,__handler,__sched,__flags) do{ __xntimer_init(__timer,__clock,__handler,__sched,__flags); xntimer_set_name(__timer,#__handler); }while(0) void__xntimer_init(structxntimer*timer, structxnclock*clock, void(*handler)(structxntimer*timer), structxnsched*sched, intflags) { spl_ts__maybe_unused; #ifdefCONFIG_XENO_OPT_EXTCLOCK timer->clock=clock; #endif xntimerh_init(&timer->aplink); xntimerh_date(&timer->aplink)=XN_INFINITE;//0 xntimer_set_priority(timer,XNTIMER_STDPRIO); timer->status=(XNTIMER_DEQUEUED|(flags&XNTIMER_INIT_MASK));//(0x01|flags&0x000001A0) timer->handler=handler; timer->interval_ns=0; timer->sched=NULL; /* *Setthetimeraffinity,preferablytoxnsched_cpu(sched)if *schedwasgiven,CPU0otherwise. */ if(sched==NULL) sched=xnsched_struct(0); xntimer_set_affinity(timer,sched); #ifdefCONFIG_XENO_OPT_STATS #ifdefCONFIG_XENO_OPT_EXTCLOCK timer->tracker=clock; #endif ksformat(timer->name,XNOBJECT_NAME_LEN,”%d/%s”, task_pid_nr(current),current->comm); xntimer_reset_stats(timer); xnlock_get_irqsave(&nklock,s); list_add_tail(&timer->next_stat,&clock->timerq); clock->nrtimers++; xnvfile_touch(&clock->timer_vfile); xnlock_put_irqrestore(&nklock,s); #endif/*CONFIG_XENO_OPT_STATS*/ }

前面几行都是初始化xntimer 结构体指针,xntimer_set_affinity(timer, sched)表示将timer移到sched上(timer->shced=sched)。后面将这个初始化的time加到xnclock 的timerq队列,nrtimers加1。基本成员初始化完了,还有优先级没有设置,aplink中的优先级就代表了该timer的优先级: staticinlinevoidxntimer_set_priority(structxntimer*timer, intprio) { xntimerh_prio(&timer->aplink)=prio;/*设置timer节点优先级*/ }

启动一个定时器xntimer_start()代码如下: intxntimer_start(structxntimer*timer, xnticks_tvalue,xnticks_tinterval, xntmode_tmode) { structxnclock*clock=xntimer_clock(timer); xntimerq_t*q=xntimer_percpu_queue(timer); xnticks_tdate,now,delay,period; unsignedlonggravity; intret=0; trace_cobalt_timer_start(timer,value,interval,mode); if((timer->status&XNTIMER_DEQUEUED)==0) xntimer_dequeue(timer,q); now=xnclock_read_raw(clock); timer->status&=~(XNTIMER_REALTIME|XNTIMER_FIRED|XNTIMER_PERIODIC); switch(mode){ caseXN_RELATIVE: if((xnsticks_t)value< 0)             return -ETIMEDOUT;         date = xnclock_ns_to_ticks(clock, value) + now;         break;     case XN_REALTIME:         timer->status|=XNTIMER_REALTIME; value-=xnclock_get_offset(clock); /*fallthrough*/ default:/*XN_ABSOLUTE||XN_REALTIME*/ date=xnclock_ns_to_ticks(clock,value); if((xnsticks_t)(date-now)<= 0) {             if (interval == XN_INFINITE)                 return -ETIMEDOUT;             /*              * We are late on arrival for the first              * delivery, wait for the next shot on the              * periodic time line.              */             delay = now – date;             period = xnclock_ns_to_ticks(clock, interval);             date += period * (xnarch_div64(delay, period) + 1);         }         break;     }     /*      * To cope with the basic system latency, we apply a clock      * gravity value, which is the amount of time expressed in      * clock ticks by which we should anticipate the shot for any      * outstanding timer. The gravity value varies with the type      * of context the timer wakes up, i.e. irq handler, kernel or      * user thread.      */     gravity = xntimer_gravity(timer);     xntimerh_date(&timer->aplink)=date-gravity; if(now>=xntimerh_date(&timer->aplink)) xntimerh_date(&timer->aplink)+=gravity/2; timer->interval_ns=XN_INFINITE; timer->interval=XN_INFINITE; if(interval!=XN_INFINITE){ timer->interval_ns=interval; timer->interval=xnclock_ns_to_ticks(clock,interval); timer->periodic_ticks=0; timer->start_date=date; timer->pexpect_ticks=0; timer->status|=XNTIMER_PERIODIC; } timer->status|=XNTIMER_RUNNING; xntimer_enqueue_and_program(timer,q); returnret; }

启动一个timer即将该timer插入xnclock 红黑树xntimerq_t。参数value表示定时时间、interval为0表示这个timer是单次触发,非0表示周期定时器定时间隔,value和interval的单位由mode决定,当mode设置为XN_RELATIVE表示相对定时定时、XN_REALTIME为相对linux时间定时,时间都为ns,其他则为绝对定时单位为timer的tick。

首先取出红黑树根节点q,如果这个timer的状态是从队列删除(其他地方取消了这个定时器),就先把他从红黑树中删除。读取tsc得到此时tsc的tick值now,然后根据参数计算timer的到期时间date,中间将单位转换为ticks。下面开始设置红黑树中的最终值,xntimer_gravity(timer)根据这个timer为谁服务取出对应的gravity。 staticinlineunsignedlongxntimer_gravity(structxntimer*timer) { structxnclock*clock=xntimer_clock(timer); if(timer->status&XNTIMER_KGRAVITY)/*内核空间定时器*/ returnclock->gravity.kernel; if(timer->status&XNTIMER_UGRAVITY)/*用户空间定时器*/ returnclock->gravity.user; returnclock->gravity.irq;/*中断*/ }

为什么要设置gravity呢?xenomai是个实时系统必须保证定时器的精确,xntimer都是由硬件timer产生中断后处理的,如果没有gravity,对于用户空间实时任务RT:假如此时时间刻度是0,该任务定时10us后触发定时器,10us后,产生了中断,此时时间刻度为10us,开始处理xntimer,然后切换回内核空间执行调度,最后切换回用户空间,从定时器到期到最后切换回RT也是需要时间的,已经超过RT所定的10us,因此,需要得到定时器超时->回到用户空间的这段时间gravity;不同空间的任务经过的路径不一样,所以针对kernel、user和irq分别计算gravity,当任务定时,定时器到期时间date-gravity才是xntimer的触发时间。当切换回原来的任务时刚好是定时时间。

gravity是怎样计算的,xenomai初始化相关文章分析;

最后将timer状态设置为XNTIMER_RUNNING,调用xntimer_enqueue_and_program(timer, q)将timer按超时时间date和优先级插入该CPU红黑树timedata,新加入了一个timer就需要重新看看,最近超时的timer是哪一个,然后设置底层硬件timer的下一个event时间,为最近一个要超时的timer date: voidxntimer_enqueue_and_program(structxntimer*timer,xntimerq_t*q) { xntimer_enqueue(timer,q);/*添加到红黑树*/ if(xntimer_heading_p(timer)){/*这个timer处于第一个节点或者需要重新调度的sched的第二个节点*/ structxnsched*sched=xntimer_sched(timer);/*timer所在的sched*/ structxnclock*clock=xntimer_clock(timer);/*当前存数所在的CPU*/ if(sched!=xnsched_current())/*不是当前CPU任务的定时器*/ xnclock_remote_shot(clock,sched);/*给当前CPU发送ipipe_send_ipi(IPIPE_HRTIMER_IPI),让sched对应CPU重新调度*/ else xnclock_program_shot(clock,sched);/*设置下一个oneshot*/ } } intxntimer_heading_p(structxntimer*timer) { structxnsched*sched=timer->sched; xntimerq_t*q; xntimerh_t*h; q=xntimer_percpu_queue(timer); h=xntimerq_head(q); if(h==&timer->aplink)/*timer就是第一个*/ return1; if(sched->lflags&XNHDEFER){/*处于重新调度状态*/ h=xntimerq_second(q,h);/*这个timer处于重新调度状态下红黑树下*/ if(h==&timer->aplink) return1; } return0; }

由于head始终指向时间最小的timer,xntimer_heading_p()中先看head是不是刚刚插入的这个timer,如果是并且是本CPU上的timer就直接设置这timer的时间为lapic-timer的中断时间,对应22行返回->执行10行。

如果是最小但是不是本CPU上的就需要通过ipipe向timer所在CPU发送一个中断信号IPIPE_HRTIMER_IPI,告诉那个cpu,那个cpu就会执行中断处理函数xnintr_core_clock_handler(),对应22行返回->执行8行,为什么是IPIPE_HRTIMER_IPI?相当于模拟底层lapic-timer 产生了一个event事件,ipipe会让那个cpu 执行xnintr_core_clock_handler()对timer进行一个刷新,重新对底层硬件timer编程。

如果新插入的timer不是最小的,但是所在的sched处于XNHDEFER状态,说明第一个timer虽然最小,但是这个最小的如果到期暂时不需要处理,那就取出定时时间第二小的timer,看是不是新插入的timer,如果是,返回1,继续决定是编程还是发中断信号。

如果其他情况,那就不用管了,启动定时器流程完毕。一个一个timer到期后总会处理到新插入的这个的。

其中的向某个cpu发送中断信号函数如下,IPIPE_HRTIMER_IPI是注册到xnsched_realtime_domain的中断,底层硬件timer产生中断的中断号就是IPIPE_HRTIMER_VECTOR,这里的发送中断是通过中断控制器APIC来完成的,APIC会给对应cpu产生一个中断,然后就会被ipipe通过ipipeline,优先给xnsched_realtime_domain处理,ipipe domain管理说过: voidxnclock_core_remote_shot(structxnsched*sched) { ipipe_send_ipi(IPIPE_HRTIMER_IPI,*cpumask_of(xnsched_cpu(sched))); } intxntimer_setup_ipi(void) { returnipipe_request_irq(&xnsched_realtime_domain, IPIPE_HRTIMER_IPI, (ipipe_irq_handler_t)xnintr_core_clock_handler, NULL,NULL); }

对底层timer编程的函调用xnclock_core_local_shot()函数,最后调用ipipe_timer_set(delay)进行设置,event时间: staticinlinevoidxnclock_program_shot(structxnclock*clock, structxnsched*sched) { xnclock_core_local_shot(sched); } voidxnclock_core_local_shot(structxnsched*sched) { ……. delay=xntimerh_date(&timer->aplink)-xnclock_core_read_raw(); if(delay< 0)         delay = 0;     else if (delay >ULONG_MAX) delay=ULONG_MAX; ipipe_timer_set(delay); }

ipipe_timer_set()中先获取这个cpu的percpu_timer t,然后将定时时间转换为硬件的tick数,最后调用t->set(tdelay, t->timer_set)进行设置。这里的percpu_timer 与ipipe 相关下面解析,这里只用知道最后是调用了percpu_timer 的set函数,这个set函数是直接设置硬件lapic-timer的。 voidipipe_timer_set(unsignedlongcdelay) { unsignedlongtdelay; structipipe_timer*t; t=__ipipe_raw_cpu_read(percpu_timer); ……. /*将时间转换定时器频率数*/ tdelay=cdelay; if(t->c2t_integ!=1) tdelay*=t->c2t_integ; if(t->c2t_frac) tdelay+=((unsignedlonglong)cdelay*t->c2t_frac)>>32; if(tdelay< t->min_delay_ticks) tdelay=t->min_delay_ticks; if(tdelay>t->max_delay_ticks) tdelay=t->max_delay_ticks; if(t->set(tdelay,t->timer_set)< 0)         ipipe_raise_irq(t->irq); }

总结:启动一个xntimer,首先确定属于哪个cpu,然后将它插入到该cpu的xntimer管理结构timerdata,插入时按定时长短和优先级来决定,最后设置底层硬件timer产生下一个中断的时间点。

免责声明:文章内容来自互联网,本站不对其真实性负责,也不承担任何法律责任,如有侵权等情况,请与本站联系删除。
转载请注明出处:揭开xenomai双核系统下clock机制的面纱-双内核技术 https://www.yhzz.com.cn/a/6307.html

上一篇 2023-04-13 13:30:33
下一篇 2023-04-13 13:41:35

相关推荐

联系云恒

在线留言: 我要留言
客服热线:400-600-0310
工作时间:周一至周六,08:30-17:30,节假日休息。