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

1.3 clock event 设备注册

每个CPU上tick device是唯一的,但为Tick device提供tick event的timer硬件并不唯一,如上图中有Lapic-timer、lapic-deadline、Hpet等,有多少个timer硬件就注册多少个clock event device,各个cpu的tick device会选择自己适合的那个clock event设备。 clock_event_devic结构如下: structclock_event_device{ void(*event_handler)(structclock_event_device*); int(*set_next_event)(unsignedlongevt,structclock_event_device*); int(*set_next_ktime)(ktime_texpires,structclock_event_device*); ktime_tnext_event; u64max_delta_ns; u64min_delta_ns; u32mult; u32shift; enumclock_event_statestate_use_accessors; unsignedintfeatures; unsignedlongretries; int(*set_state_periodic)(structclock_event_device*); int(*set_state_oneshot)(structclock_event_device*); int(*set_state_oneshot_stopped)(structclock_event_device*); int(*set_state_shutdown)(structclock_event_device*); int(*tick_resume)(structclock_event_device*); void(*broadcast)(conststructcpumask*mask); void(*suspend)(structclock_event_device*); void(*resume)(structclock_event_device*); unsignedlongmin_delta_ticks; unsignedlongmax_delta_ticks; constchar*name; intrating; intirq; intbound_on; conststructcpumask*cpumask; structlist_headlist; …… }____cacheline_aligned;

简要说下各成员变量的含义:

event_handler产生了clock event的时候调用的handler,硬件timer中断到来的时候调用该timer中断handler,而在这个中断handler中再调用event_handler。

set_next_event设定产生下一个event。一般是clock的counter的cycle数值,一般的timer硬件都是用cycle值设定会比较方便,当然,不排除有些奇葩可以直接使用ktime(秒、纳秒),这时候clock event device的features成员要打上CLOCK_EVT_FEAT_KTIME的标记使用set_next_ktime()函数设置。

set_state_periodic、set_state_oneshot、set_state_shutdown设置各个模式的配置函数。

broadcast上面说到每个cpu有一个tcik device外还需要一个全局的clock event,为各CPU提供唤醒等功能。

rating该clock evnet的精度等级,在选做tick device时做参考。

irq 该clock event对应的系统中断号。 voidclockevents_register_device(structclock_event_device*dev) { unsignedlongflags; …… if(!dev->cpumask){ WARN_ON(num_possible_cpus()>1); dev->cpumask=cpumask_of(smp_processor_id()); } list_add(&dev->list,&clockevent_devices);/*加入clockevent设备全局列表*/ tick_check_new_device(dev);/*让上层软件知道底层又注册一个新的clockdevice,当然,是否上层软件要使用这个新的clockeventdevice是上层软件的事情*/ clockevents_notify_released(); …… }

clock event device的cpumask指明该设备为哪一个CPU工作,如果没有设定并且cpu的个数大于1的时候要给出warning信息并进行设定(设定为当前运行该代码的那个CPU core)。在multi core的环境下,底层driver在调用该接口函数注册clock event设备之前就需要设定cpumask成员,毕竟一个timer硬件附着在哪一个cpu上底层硬件最清楚。这里只是对未做设定的的设定为当前CPU。

将新注册的clockevent device添加到全局链表clockevent_devices,然后调用tick_check_new_device()让上层软件知道底层又注册一个新的clock device,当然,是否上层软件会通过一系列判断后来决定是否使用这个clock event作为tick device。如果被选作tick device 会为该clock event设置回调函数event_handler,如上图所示:event_handler不同的模式会被设置为tick_handle_periodic()、hrtimer_interrupt()或tick_nohz_handler()。代码详细解析,后面会简要说明;

对应x86平台,clock event device有APIC-timer、hept,hept的rating没有lapic timer高。所以每个CPU上的loacl-apic timer作为该CPU的tick device。 //archx86kernelhpet.c staticstructclock_event_devicelapic_clockevent={ .name=”lapic”, .features=CLOCK_EVT_FEAT_PERIODIC| CLOCK_EVT_FEAT_ONESHOT|CLOCK_EVT_FEAT_C3STOP |CLOCK_EVT_FEAT_DUMMY, .shift=32, .set_state_shutdown=lapic_timer_shutdown, .set_state_periodic=lapic_timer_set_periodic, .set_state_oneshot=lapic_timer_set_oneshot, .set_state_oneshot_stopped=lapic_timer_shutdown, .set_next_event=lapic_next_event, .broadcast=lapic_timer_broadcast, .rating=100, .irq=-1, }; //archx86kernelapicapic.c staticstructclock_event_devicehpet_clockevent={ .name=”hpet”, .features=CLOCK_EVT_FEAT_PERIODIC| CLOCK_EVT_FEAT_ONESHOT, .set_state_periodic=hpet_legacy_set_periodic, .set_state_oneshot=hpet_legacy_set_oneshot, .set_state_shutdown=hpet_legacy_shutdown, .tick_resume=hpet_legacy_resume, .set_next_event=hpet_legacy_next_event, .irq=0, .rating=50, };

apic的中断函数smp_apic_timer_interrupt(),然后调用local_apic_timer_interrupt(): __visiblevoid__irq_entrysmp_apic_timer_interrupt(structpt_regs*regs) { structpt_regs*old_regs=set_irq_regs(regs); /* *NOTE!WedbetterACKtheirqimmediately, *becausetimerhandlingcanbeslow. * *update_process_times()expectsustohavedoneirq_enter(). *Besides,ifwedonttimerinterruptsignoretheglobal *interruptlock,whichistheWrongThing(tm)todo. */ entering_ack_irq(); trace_local_timer_entry(LOCAL_TIMER_VECTOR); local_apic_timer_interrupt();/*执行handle*/ trace_local_timer_exit(LOCAL_TIMER_VECTOR); exiting_irq(); set_irq_regs(old_regs); } staticvoidlocal_apic_timer_interrupt(void) { structclock_event_device*evt=this_cpu_ptr(&lapic_events); if(!evt->event_handler){ pr_warning(“SpuriousLAPICtimerinterruptoncpu%d “, smp_processor_id()); /*Switchitoff*/ lapic_timer_shutdown(evt); return; } inc_irq_stat(apic_timer_irqs); evt->event_handler(evt);/*执行event_handler*/ }

local_apic_timer_interrupt()先获得产生该中断的clock_event_device,然后执行event_handler()。

1.4 clock source设备注册 linux 中clock source主要与timekeeping模块关联,这里不细说,查看系统中的可用的clock source: $cat/sys/devices/system/clocksource/clocksource0/available_clocksource tschpetacpi_pm

查看系统中当前使用的clock source的信息: $cat/sys/devices/system/clocksource/clocksource0/current_clocksource tsc

这里主要说一下与xenomai相关的clock source 设备TSC(Time Stamp Counter),x86处理器提供的TSC是一个高分辨率计数器,以恒定速率运行(在较旧的处理器上,TSC计算内部处理器的时钟周期,这意味着当处理器的频率缩放比例改变时,TSC的频率也会改变,现今的TSC在处理器的所有操作状态下均以恒定的速率运行,其频率远远超过了处理器的频率),可以用单指令RDTSC读取。 structclocksourceclocksource_tsc={ .name=”tsc”, .rating=300, .read=read_tsc, .mask=CLOCKSOURCE_MASK(64), .flags=CLOCK_SOURCE_IS_CONTINUOUS| CLOCK_SOURCE_MUST_VERIFY, .archdata={.vclock_mode=VCLOCK_TSC}, .resume=tsc_resume, .mark_unstable=tsc_cs_mark_unstable, .tick_stable=tsc_cs_tick_stable, };

tsc在init_tsc_clocksource()中调用int clocksource_register(struct clocksource *cs)注册,流程如下:

1.调用__clocksource_update_freq_scale(cs, scale, freq),根据tsc频率计算mult和shift,具体计算流程文章实时内核与linux内核时钟漂移过大原因.docx已分析过。

2.调用clocksource_enqueue(cs)根据clock source按照rating的顺序插入到全局链表clock source list中

3.选择一个合适的clock source。kernel当然是选用一个rating最高的clocksource作为当前的正在使用的那个clock source。每当注册一个新的clock source的时候调用clocksource_select进行选择,毕竟有可能注册了一个精度更高的clock source。X86系统中tsc rating最高,为300。

到此clock source注册就注册完了。

1.5 时间子系统的数据流和控制流

上面说到tick device的几种模式,下面结合整个系统模式说明。高精度的timer需要高精度的clock event,工作在one shot mode的tick device工提供高精度的clock event(clockeventHandler中处理高精度timer)。因此,基于one shot mode下的tick device,系统实现了高精度timer,系统的各个模块可以使用高精度timer的接口来完成定时服务。

虽然有了高精度timer的出现, 内核并没有抛弃老的低精度timer机制(内核开发人员试图整合高精度timer和低精度的timer,不过失败了,所以目前内核中,两种timer是同时存在的)。当系统处于高精度timer的时候(tick device处于one shot mode),系统会setup一个特别的高精度timer(可以称之sched timer),该高精度timer会周期性的触发,从而模拟的传统的periodic tick,从而推动了传统低精度timer的运转。

因此,一些传统的内核模块仍然可以调用经典的低精度timer模块的接口。系统可根据需要配置为以下几种模式,具体配置见其他文档:1、使用低精度timer + 周期tick

根据当前系统的配置情况(周期性tick),会调用tick_setup_periodic函数,这时候,该tick device对应的clock event device的clock event handler被设置为tick_handle_periodic。底层硬件会周期性的产生中断,从而会周期性的调用tick_handle_periodic从而驱动整个系统的运转。

这时候高精度timer模块是运行在低精度的模式,也就是说这些hrtimer虽然是按照高精度timer的红黑树进行组织,但是系统只是在每一周期性tick到来的时候调用hrtimer_run_queues函数,来检查是否有expire的hrtimer。毫无疑问,这里的高精度timer也就是没有意义了。

2、低精度timer + Dynamic Tick

系统开始的时候并不是直接进入Dynamic tick mode的,而是经历一个切换过程。开始的时候,系统运行在周期tick的模式下,各个cpu对应的tick device的(clock event device的)event handler是tick_handle_periodic。在timer的软中断上下文中,会调用tick_check_oneshot_change进行是否切换到one shot模式的检查,如果系统中有支持one-shot的clock event device,并且没有配置高精度timer的话,那么就会发生tick mode的切换(调用tick_nohz_switch_to_nohz),这时候,tick device会切换到one shot模式,而event handler被设置为tick_nohz_handler。 由于这时候的clock event device工作在one shot模式,因此当系统正常运行的时候,在event handler中每次都要reprogram clock event,以便正常产生tick。当cpu运行idle进程的时候,clock event device不再reprogram产生下次的tick信号,这样,整个系统的周期性的tick就停下来。

高精度timer和低精度timer的工作原理同上。

3、高精度timer + Dynamic Tick

同样的,系统开始的时候并不是直接进入Dynamic tick mode的,而是经历一个切换过程。系统开始的时候是运行在周期tick的模式下,event handler是tick_handle_periodic。在周期tick的软中断上下文中(参考run_timer_softirq),如果满足条件,会调用hrtimer_switch_to_hres将hrtimer从低精度模式切换到高精度模式上。这时候,系统会有下面的动作:

(1)Tick device的clock event设备切换到oneshot mode(参考tick_init_highres函数)

(2)Tick device的clock event设备的event handler会更新为hrtimer_interrupt(参考tick_init_highres函数)

(3)设定sched timer(即模拟周期tick那个高精度timer,参考tick_setup_sched_timer函数)这样,当下一次tick到来的时候,系统会调用hrtimer_interrupt来处理这个tick(该tick是通过sched timer产生的)。

在Dynamic tick的模式下,各个cpu的tick device工作在one shot模式,该tick device对应的clock event设备也工作在one shot的模式,这时候,硬件Timer的中断不会周期性的产生,但是linux kernel中很多的模块是依赖于周期性的tick的,因此,在这种情况下,系统使用hrtime模拟了一个周期性的tick。 在切换到dynamic tick模式的时候会初始化这个高精度timer,该高精度timer的回调函数是tick_sched_timer。这个函数执行的函数类似周期性tick中event handler执行的内容。不过在最后会reprogram该高精度timer,以便可以周期性的产生clock event。当系统进入idle的时候,就会stop这个高精度timer,这样,当没有用户事件的时候,CPU可以持续在idle状态,从而减少功耗。

免责声明:文章内容来自互联网,本站不对其真实性负责,也不承担任何法律责任,如有侵权等情况,请与本站联系删除。
转载请注明出处:揭开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,节假日休息。