clock可以說是操作系統(tǒng)正常運行的發(fā)動機,整個操作系統(tǒng)的活動都受到它的激勵。系統(tǒng)利用時鐘中斷維持系統(tǒng)時間、促使任務(wù)調(diào)度,以保證所有進程共享CPU資源;可以說,“時鐘中斷”是整個操作系統(tǒng)的脈搏。
那你是否好奇xenomai cobalt內(nèi)核和Linux內(nèi)核雙內(nèi)核共存的情況下,時間子系統(tǒng)是如何工作的?一個硬件時鐘如何為兩個操作系統(tǒng)內(nèi)核提供服務(wù)的?本文將揭開xenomai雙核系統(tǒng)下clock機制的面紗。
首先回看一下之前的文章xenomai內(nèi)核解析之xenomai的組成結(jié)構(gòu)。
我們說到:在內(nèi)核空間,在標準linux基礎(chǔ)上添加一個實時內(nèi)核Cobalt,得益于基于ADEOS(Adaptive Domain Environment for Operating System),使Cobalt內(nèi)核在內(nèi)核空間與linux內(nèi)核并存,并把標準的Linux內(nèi)核作為實時內(nèi)核中的一個idle進程在實時內(nèi)核上調(diào)度。
“并把標準的Linux內(nèi)核作為實時內(nèi)核中的一個idle進程在實時內(nèi)核上調(diào)度“,這句話是本文的重點,接下我們先從Linux時間子系統(tǒng)介紹。
中間部分為個人分析代碼簡單記錄,比較啰嗦,如果你只是想知道xenomai時鐘子系統(tǒng)與linux時鐘子系統(tǒng)之間的關(guān)系可直接到2.6 xenomai內(nèi)核下Linux時鐘工作流程查看總結(jié)。
一、linux時間子系統(tǒng)
linux時間子系統(tǒng)是一個很大的板塊,控制著linux的方方面面。這里只說雙核相關(guān)的部分。即側(cè)重于Linux與底層硬件交互這一塊。
關(guān)于Linux時間子系統(tǒng)的詳細內(nèi)容,請移步蝸窩科技關(guān)系Linux 時間子系統(tǒng)專欄。文章中Linux時間子系統(tǒng)大部分內(nèi)容來自于此,在此謝過~
Linux時間子系統(tǒng)框架大致如下:
1.1 tick device
處理器采用時鐘定時器來周期性地提供系統(tǒng)脈搏。時鐘中斷是普通外設(shè)中斷的一種。調(diào)度器利用時鐘中斷來定時檢測當前正在運行的線程是否需要調(diào)度。提供時鐘中斷的設(shè)備就是tick device。
如今在多核架構(gòu)下,每個CPU形成了自己的一個小系統(tǒng),有自己的調(diào)度、自己的進程統(tǒng)計等,這個小系統(tǒng)擁有自己的tick device,而且每個CPU上tick device是唯一的,tick device可以工作在periodic mode或者one shot mode,這是和系統(tǒng)配置有關(guān)(由于中斷的處理會影響實時性,一般將xenomai所在CPU的tick device配置工作在one shot mode模式)。
因此,整個系統(tǒng)中,在tick device layer,有多少個cpu,就會有多少個tick device,稱為local tick device。當然,有些事情(例如整個系統(tǒng)的負荷計算)不適合在local tick驅(qū)動下進行,因此,所有的local tick device中會有一個被選擇做global tick device,該device負責維護整個系統(tǒng)的jiffies,更新wall clock,計算全局負荷什么的。
tick_device 數(shù)據(jù)結(jié)構(gòu)如下:
/*tick device可以工作在兩種模式下,一種是周期性tick模式,另外一種是one shot模式。*/ enumtick_device_mode{ TICKDEV_MODE_PERIODIC, TICKDEV_MODE_ONESHOT,/*oneshot模式主要和tickless系統(tǒng)以及高精度timer有關(guān)*/ }; structtick_device{ structclock_event_device*evtdev; enumtick_device_modemode; };
1.2 clock event和clock source
tick device依賴于底層硬件產(chǎn)生定時事件來推動運行,這些產(chǎn)生定時事件的硬件是timer,除此之外還需要一個在指定輸入頻率的clock下工作的一個counter來提供計時。對形形色色的timer和counter硬件,linux kernel抽象出了通用clock event layer和通用clock source模塊,這兩個模塊和硬件無關(guān)。
所謂clock source是用來抽象一個在指定輸入頻率的clock下工作的一個counter。clock event提供的是一定周期的event,如果應用程序需要讀取當前的時間,比如ns精度時,就需要通過timekeeping從clock source中獲取與上個tick之間的時間后返回此時時間。
底層的clock source chip驅(qū)動通過調(diào)用通用clock event和clock source模塊的接口函數(shù),注冊clock source和clock event設(shè)備。
intclocksource_register(structclocksource*cs) voidclockevents_register_device(structclock_event_device*dev)
1.3 clock event 設(shè)備注冊
每個CPU上tick device是唯一的,但為Tick device提供tick event的timer硬件并不唯一,如上圖中有Lapic-timer、lapic-deadline、Hpet等,有多少個timer硬件就注冊多少個clock event device,各個cpu的tick device會選擇自己適合的那個clock event設(shè)備。
clock_event_devic結(jié)構(gòu)如下:
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產(chǎn)生了clock event的時候調(diào)用的handler,硬件timer中斷到來的時候調(diào)用該timer中斷handler,而在這個中斷handler中再調(diào)用event_handler。
set_next_event設(shè)定產(chǎn)生下一個event。一般是clock的counter的cycle數(shù)值,一般的timer硬件都是用cycle值設(shè)定會比較方便,當然,不排除有些奇葩可以直接使用ktime(秒、納秒),這時候clock event device的features成員要打上CLOCK_EVT_FEAT_KTIME的標記使用set_next_ktime()函數(shù)設(shè)置。
set_state_periodic、set_state_oneshot、set_state_shutdown設(shè)置各個模式的配置函數(shù)。
broadcast上面說到每個cpu有一個tcik device外還需要一個全局的clock event,為各CPU提供喚醒等功能。
rating該clock evnet的精度等級,在選做tick device時做參考。
irq 該clock event對應的系統(tǒng)中斷號。
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設(shè)備全局列表*/ tick_check_new_device(dev);/*讓上層軟件知道底層又注冊一個新的clockdevice,當然,是否上層軟件要使用這個新的clockeventdevice是上層軟件的事情*/ clockevents_notify_released(); ...... }
clock event device的cpumask指明該設(shè)備為哪一個CPU工作,如果沒有設(shè)定并且cpu的個數(shù)大于1的時候要給出warning信息并進行設(shè)定(設(shè)定為當前運行該代碼的那個CPU core)。在multi core的環(huán)境下,底層driver在調(diào)用該接口函數(shù)注冊clock event設(shè)備之前就需要設(shè)定cpumask成員,畢竟一個timer硬件附著在哪一個cpu上底層硬件最清楚。這里只是對未做設(shè)定的的設(shè)定為當前CPU。
將新注冊的clockevent device添加到全局鏈表clockevent_devices,然后調(diào)用tick_check_new_device()讓上層軟件知道底層又注冊一個新的clock device,當然,是否上層軟件會通過一系列判斷后來決定是否使用這個clock event作為tick device。如果被選作tick device 會為該clock event設(shè)置回調(diào)函數(shù)event_handler,如上圖所示:event_handler不同的模式會被設(shè)置為tick_handle_periodic()、hrtimer_interrupt()或tick_nohz_handler()。代碼詳細解析,后面會簡要說明;
對應x86平臺,clock event device有APIC-timer、hept,hept的rating沒有l(wèi)apic 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的中斷函數(shù)smp_apic_timer_interrupt(),然后調(diào)用local_apic_timer_interrupt():
__visiblevoid__irq_entrysmp_apic_timer_interrupt(structpt_regs*regs) { structpt_regs*old_regs=set_irq_regs(regs); /* *NOTE!We'dbetterACKtheirqimmediately, *becausetimerhandlingcanbeslow. * *update_process_times()expectsustohavedoneirq_enter(). *Besides,ifwedon'ttimerinterruptsignoretheglobal *interruptlock,whichistheWrongThing(tm)todo. */ entering_ack_irq(); trace_local_timer_entry(LOCAL_TIMER_VECTOR); local_apic_timer_interrupt();/*執(zhí)行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);/*執(zhí)行event_handler*/ }
local_apic_timer_interrupt()先獲得產(chǎn)生該中斷的clock_event_device,然后執(zhí)行event_handler()。
1.4 clock source設(shè)備注冊
linux 中clock source主要與timekeeping模塊關(guān)聯(lián),這里不細說,查看系統(tǒng)中的可用的clock source:
$cat/sys/devices/system/clocksource/clocksource0/available_clocksource tschpetacpi_pm
查看系統(tǒng)中當前使用的clock source的信息:
$cat/sys/devices/system/clocksource/clocksource0/current_clocksource tsc
這里主要說一下與xenomai相關(guān)的clock source 設(shè)備TSC(Time Stamp Counter),x86處理器提供的TSC是一個高分辨率計數(shù)器,以恒定速率運行(在較舊的處理器上,TSC計算內(nèi)部處理器的時鐘周期,這意味著當處理器的頻率縮放比例改變時,TSC的頻率也會改變,現(xiàn)今的TSC在處理器的所有操作狀態(tài)下均以恒定的速率運行,其頻率遠遠超過了處理器的頻率),可以用單指令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()中調(diào)用int clocksource_register(struct clocksource *cs)注冊,流程如下:
1.調(diào)用__clocksource_update_freq_scale(cs, scale, freq),根據(jù)tsc頻率計算mult和shift,具體計算流程文章實時內(nèi)核與linux內(nèi)核時鐘漂移過大原因.docx已分析過。
2.調(diào)用clocksource_enqueue(cs)根據(jù)clock source按照rating的順序插入到全局鏈表clock source list中
3.選擇一個合適的clock source。kernel當然是選用一個rating最高的clocksource作為當前的正在使用的那個clock source。每當注冊一個新的clock source的時候調(diào)用clocksource_select進行選擇,畢竟有可能注冊了一個精度更高的clock source。X86系統(tǒng)中tsc rating最高,為300。
到此clock source注冊就注冊完了。
1.5 時間子系統(tǒng)的數(shù)據(jù)流和控制流
上面說到tick device的幾種模式,下面結(jié)合整個系統(tǒng)模式說明。高精度的timer需要高精度的clock event,工作在one shot mode的tick device工提供高精度的clock event(clockeventHandler中處理高精度timer)。因此,基于one shot mode下的tick device,系統(tǒng)實現(xiàn)了高精度timer,系統(tǒng)的各個模塊可以使用高精度timer的接口來完成定時服務(wù)。
雖然有了高精度timer的出現(xiàn), 內(nèi)核并沒有拋棄老的低精度timer機制(內(nèi)核開發(fā)人員試圖整合高精度timer和低精度的timer,不過失敗了,所以目前內(nèi)核中,兩種timer是同時存在的)。當系統(tǒng)處于高精度timer的時候(tick device處于one shot mode),系統(tǒng)會setup一個特別的高精度timer(可以稱之sched timer),該高精度timer會周期性的觸發(fā),從而模擬的傳統(tǒng)的periodic tick,從而推動了傳統(tǒng)低精度timer的運轉(zhuǎn)。
因此,一些傳統(tǒng)的內(nèi)核模塊仍然可以調(diào)用經(jīng)典的低精度timer模塊的接口。系統(tǒng)可根據(jù)需要配置為以下幾種模式,具體配置見其他文檔:
1、使用低精度timer + 周期tick
根據(jù)當前系統(tǒng)的配置情況(周期性tick),會調(diào)用tick_setup_periodic函數(shù),這時候,該tick device對應的clock event device的clock event handler被設(shè)置為tick_handle_periodic。底層硬件會周期性的產(chǎn)生中斷,從而會周期性的調(diào)用tick_handle_periodic從而驅(qū)動整個系統(tǒng)的運轉(zhuǎn)。
這時候高精度timer模塊是運行在低精度的模式,也就是說這些hrtimer雖然是按照高精度timer的紅黑樹進行組織,但是系統(tǒng)只是在每一周期性tick到來的時候調(diào)用hrtimer_run_queues函數(shù),來檢查是否有expire的hrtimer。毫無疑問,這里的高精度timer也就是沒有意義了。
2、低精度timer + Dynamic Tick
系統(tǒng)開始的時候并不是直接進入Dynamic tick mode的,而是經(jīng)歷一個切換過程。開始的時候,系統(tǒng)運行在周期tick的模式下,各個cpu對應的tick device的(clock event device的)event handler是tick_handle_periodic。在timer的軟中斷上下文中,會調(diào)用tick_check_oneshot_change進行是否切換到one shot模式的檢查,如果系統(tǒng)中有支持one-shot的clock event device,并且沒有配置高精度timer的話,那么就會發(fā)生tick mode的切換(調(diào)用tick_nohz_switch_to_nohz),這時候,tick device會切換到one shot模式,而event handler被設(shè)置為tick_nohz_handler。
由于這時候的clock event device工作在one shot模式,因此當系統(tǒng)正常運行的時候,在event handler中每次都要reprogram clock event,以便正常產(chǎn)生tick。當cpu運行idle進程的時候,clock event device不再reprogram產(chǎn)生下次的tick信號,這樣,整個系統(tǒng)的周期性的tick就停下來。
高精度timer和低精度timer的工作原理同上。
3、高精度timer + Dynamic Tick
同樣的,系統(tǒng)開始的時候并不是直接進入Dynamic tick mode的,而是經(jīng)歷一個切換過程。系統(tǒng)開始的時候是運行在周期tick的模式下,event handler是tick_handle_periodic。在周期tick的軟中斷上下文中(參考run_timer_softirq),如果滿足條件,會調(diào)用hrtimer_switch_to_hres將hrtimer從低精度模式切換到高精度模式上。這時候,系統(tǒng)會有下面的動作:
(1)Tick device的clock event設(shè)備切換到oneshot mode(參考tick_init_highres函數(shù))
(2)Tick device的clock event設(shè)備的event handler會更新為hrtimer_interrupt(參考tick_init_highres函數(shù))
(3)設(shè)定sched timer(即模擬周期tick那個高精度timer,參考tick_setup_sched_timer函數(shù))這樣,當下一次tick到來的時候,系統(tǒng)會調(diào)用hrtimer_interrupt來處理這個tick(該tick是通過sched timer產(chǎn)生的)。
在Dynamic tick的模式下,各個cpu的tick device工作在one shot模式,該tick device對應的clock event設(shè)備也工作在one shot的模式,這時候,硬件Timer的中斷不會周期性的產(chǎn)生,但是linux kernel中很多的模塊是依賴于周期性的tick的,因此,在這種情況下,系統(tǒng)使用hrtime模擬了一個周期性的tick。
在切換到dynamic tick模式的時候會初始化這個高精度timer,該高精度timer的回調(diào)函數(shù)是tick_sched_timer。這個函數(shù)執(zhí)行的函數(shù)類似周期性tick中event handler執(zhí)行的內(nèi)容。不過在最后會reprogram該高精度timer,以便可以周期性的產(chǎn)生clock event。當系統(tǒng)進入idle的時候,就會stop這個高精度timer,這樣,當沒有用戶事件的時候,CPU可以持續(xù)在idle狀態(tài),從而減少功耗。
4、高精度timer + 周期性Tick
這種配置不多見,多半是由于硬件無法支持one shot的clock event device,這種情況下,整個系統(tǒng)仍然是運行在周期tick的模式下。
總結(jié)一下:linux啟動過程中初始化時鐘系統(tǒng),當xenomai內(nèi)核未啟動時,linux直接對底層硬件lapic-timer編程,底層硬件lapic-timer產(chǎn)生中斷推動整個Linux中的各個時鐘及調(diào)度運行。
我們可以將Linux抽出如下圖,只需要為Linux提供設(shè)置下一個時鐘事件set_next_event()和提供event觸發(fā)eventHandler()執(zhí)行兩個接口就能推動整個linux時間子系統(tǒng)運轉(zhuǎn),下面解析Xenomai是怎樣為linux提供這兩個接口的,達到控制整個時鐘系統(tǒng)的。
二、xenomai時間子系統(tǒng)
2.1 xnclock
我們知道x86下每個cpu核有一個lapic,lapic中有定時硬件lapic-timer和hpet。tsc作為timeline,提供計時,lapic-timer用來產(chǎn)生clock event。對于現(xiàn)今X86 CPU 操作系統(tǒng)一般都是使用TSC和lapic-timer作為clock source和clock event,因為精度最高(Atom 系列處理器可能會有區(qū)別).
xenomai的默認時間管理對象是xnclock,xnclock管理著xenomai整個系統(tǒng)的時間、任務(wù)定時、調(diào)度等,xnclok的默認時鐘源為TSC。當然我們可以自定義clocksource。比如在TSC不可靠的系統(tǒng)上,可以使用外部定時硬件來作為時鐘源,當自定義時鐘時需要實現(xiàn)結(jié)構(gòu)體中的宏CONFIG_XENO_OPT_EXTCLOCK包含的幾個必要函數(shù),且編譯配置使能CONFIG_XENO_OPT_EXTCLOCK。
注意:這里的自定義時鐘源只是將TSC替換為其他時鐘源,產(chǎn)生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;/*統(tǒng)計掛在xnclockxntimer的數(shù)量*/ #endif/*CONFIG_XENO_OPT_STATS*/ #ifdefCONFIG_XENO_OPT_VFILE structxnvfile_regularvfile;//vfile.ops=&clock_ops #endif };
wallclock_offset:linux系統(tǒng)wall time(1970開始的時間值)與系統(tǒng)TSC cycle轉(zhuǎn)換為時間的偏移
resolution:該xnclock的精度
struct xnclock_gravity gravity:該xnclok下,中斷、內(nèi)核、用戶空間程序定時器的調(diào)整量,對系統(tǒng)精確定時很重要,后面會說到。
structxnclock_gravity{ unsignedlongirq; unsignedlongkernel; unsignedlonguser; };
ops:該xnclok的各操作函數(shù)。
timerdata:xntimer 管理結(jié)構(gòu)頭節(jié)點,當系統(tǒng)中使用紅黑樹來管理xntimer時,他是紅黑樹head節(jié)點,當系統(tǒng)使用優(yōu)先級鏈表來管理時它是鏈表頭節(jié)點,系統(tǒng)會為每個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的個數(shù)
vfile:proc文件系統(tǒng)操作接口,可通過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中的結(jié)構(gòu)體gravity的值,devices表示xenomai用于產(chǎn)生clock event的硬件timer,clock為xnclock計時的時鐘源。
xenomai 內(nèi)核默認定義xnclock如下,名字和結(jié)構(gòu)體名一樣,至于xnclock怎么和硬件timer 、tsc聯(lián)系起來后面分析:
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
實時任務(wù)的所有定時行為最后都會落到內(nèi)核中的xntimer上,而xnclock管理著硬件clock event,xntimer要完成定時就需要xnclock來獲取起始時間,xntimer結(jié)構(gòu)如下:
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結(jié)構(gòu)。*/ 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節(jié)點,否則是一個鏈表節(jié)點。分別如下:
//優(yōu)先級鏈表結(jié)構(gòu) structxntlholder{ structlist_headlink; xnticks_tkey; intprio; }; typedefstructxntlholderxntimerh_t; //樹結(jié)構(gòu) typedefstruct{ unsignedlonglongdate; unsignedprio; structrb_nodelink; }xntimerh_t;
系統(tǒng)默認配置以紅黑樹形式管理xntimer,date表示定時器的多久后到期;prio表示該定時器的優(yōu)先級,當加入鏈表時先date來排序,如果幾個定時器date相同就看優(yōu)先級,優(yōu)先級高的先處理;link為紅黑樹節(jié)點。
status:定時器狀態(tài),所有狀態(tài)為如下:
#defineXNTIMER_DEQUEUED0x00000001/*沒有掛在xnclock上*/ #defineXNTIMER_KILLED0x00000002/*該定時器已經(jīng)被取消*/ #defineXNTIMER_PERIODIC0x00000004/*該定時器是一個周期定時器*/ #defineXNTIMER_REALTIME0x00000008/*定時器相對于Linuxwalltime定時*/ #defineXNTIMER_FIRED0x00000010/*定時已經(jīng)到期*/ #defineXNTIMER_NOBLCK0x00000020/*非阻塞定時器*/ #defineXNTIMER_RUNNING0x00000040/*定時器已經(jīng)start*/ #defineXNTIMER_KGRAVITY0x00000080/*該timer是一個內(nèi)核態(tài)timer*/ #defineXNTIMER_UGRAVITY0x00000100/*該timer是一個用戶態(tài)timer*/ #defineXNTIMER_IGRAVITY0/*該timer是一個中斷timer*/
interval、interval_ns:周期定時器的定時周期,分別是tick 和ns,0表示這個xntimer 是單次定時的。
handler:定時器到期后執(zhí)行的函數(shù)。
sched:該timer所在的sched,每個cpu核上有一個sched,管理本cpu上的線程調(diào)度,timer又需要本cpu的lapic定時,所以指定了sched就指定了該timer所屬cpu。
xntimer 使用需要先調(diào)用xntimer_init()初始化xntimer結(jié)構(gòu)成員,然后xntimer_start()啟動這個xntimer,啟動timer就是將它插入xnclock管理的紅黑樹。
xntimer_init()是一個宏,內(nèi)部調(diào)用__xntimer_init初始化timer,參數(shù)timer:需要初始化的timer;clock:該timer是依附于哪個xnclock,也就是說哪個xnclock來處理我是否觸發(fā),沒有自定義就是xnclock,在timer_start的時候就會將這個timer掛到對應的xnclock上去;handler:該timer到期后執(zhí)行的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 結(jié)構(gòu)體指針,xntimer_set_affinity(timer, sched)表示將timer移到sched上(timer->shced=sched)。后面將這個初始化的time加到xnclock 的timerq隊列,nrtimers加1。基本成員初始化完了,還有優(yōu)先級沒有設(shè)置,aplink中的優(yōu)先級就代表了該timer的優(yōu)先級:
staticinlinevoidxntimer_set_priority(structxntimer*timer, intprio) { xntimerh_prio(&timer->aplink)=prio;/*設(shè)置timer節(jié)點優(yōu)先級*/ }
啟動一個定時器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)value0) ????????????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。參數(shù)value表示定時時間、interval為0表示這個timer是單次觸發(fā),非0表示周期定時器定時間隔,value和interval的單位由mode決定,當mode設(shè)置為XN_RELATIVE表示相對定時定時、XN_REALTIME為相對linux時間定時,時間都為ns,其他則為絕對定時單位為timer的tick。
首先取出紅黑樹根節(jié)點q,如果這個timer的狀態(tài)是從隊列刪除(其他地方取消了這個定時器),就先把他從紅黑樹中刪除。讀取tsc得到此時tsc的tick值now,然后根據(jù)參數(shù)計算timer的到期時間date,中間將單位轉(zhuǎn)換為ticks。下面開始設(shè)置紅黑樹中的最終值,xntimer_gravity(timer)根據(jù)這個timer為誰服務(wù)取出對應的gravity。
staticinlineunsignedlongxntimer_gravity(structxntimer*timer) { structxnclock*clock=xntimer_clock(timer); if(timer->status&XNTIMER_KGRAVITY)/*內(nèi)核空間定時器*/ returnclock->gravity.kernel; if(timer->status&XNTIMER_UGRAVITY)/*用戶空間定時器*/ returnclock->gravity.user; returnclock->gravity.irq;/*中斷*/ }
為什么要設(shè)置gravity呢?xenomai是個實時系統(tǒng)必須保證定時器的精確,xntimer都是由硬件timer產(chǎn)生中斷后處理的,如果沒有g(shù)ravity,對于用戶空間實時任務(wù)RT:假如此時時間刻度是0,該任務(wù)定時10us后觸發(fā)定時器,10us后,產(chǎn)生了中斷,此時時間刻度為10us,開始處理xntimer,然后切換回內(nèi)核空間執(zhí)行調(diào)度,最后切換回用戶空間,從定時器到期到最后切換回RT也是需要時間的,已經(jīng)超過RT所定的10us,因此,需要得到定時器超時->回到用戶空間的這段時間gravity;不同空間的任務(wù)經(jīng)過的路徑不一樣,所以針對kernel、user和irq分別計算gravity,當任務(wù)定時,定時器到期時間date-gravity才是xntimer的觸發(fā)時間。當切換回原來的任務(wù)時剛好是定時時間。
gravity是怎樣計算的,xenomai初始化相關(guān)文章分析;
最后將timer狀態(tài)設(shè)置為XNTIMER_RUNNING,調(diào)用xntimer_enqueue_and_program(timer, q)將timer按超時時間date和優(yōu)先級插入該CPU紅黑樹timedata,新加入了一個timer就需要重新看看,最近超時的timer是哪一個,然后設(shè)置底層硬件timer的下一個event時間,為最近一個要超時的timer date:
voidxntimer_enqueue_and_program(structxntimer*timer,xntimerq_t*q) { xntimer_enqueue(timer,q);/*添加到紅黑樹*/ if(xntimer_heading_p(timer)){/*這個timer處于第一個節(jié)點或者需要重新調(diào)度的sched的第二個節(jié)點*/ structxnsched*sched=xntimer_sched(timer);/*timer所在的sched*/ structxnclock*clock=xntimer_clock(timer);/*當前存數(shù)所在的CPU*/ if(sched!=xnsched_current())/*不是當前CPU任務(wù)的定時器*/ xnclock_remote_shot(clock,sched);/*給當前CPU發(fā)送ipipe_send_ipi(IPIPE_HRTIMER_IPI),讓sched對應CPU重新調(diào)度*/ else xnclock_program_shot(clock,sched);/*設(shè)置下一個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){/*處于重新調(diào)度狀態(tài)*/ h=xntimerq_second(q,h);/*這個timer處于重新調(diào)度狀態(tài)下紅黑樹下*/ if(h==&timer->aplink) return1; } return0; }
由于head始終指向時間最小的timer,xntimer_heading_p()中先看head是不是剛剛插入的這個timer,如果是并且是本CPU上的timer就直接設(shè)置這timer的時間為lapic-timer的中斷時間,對應22行返回->執(zhí)行10行。
如果是最小但是不是本CPU上的就需要通過ipipe向timer所在CPU發(fā)送一個中斷信號IPIPE_HRTIMER_IPI,告訴那個cpu,那個cpu就會執(zhí)行中斷處理函數(shù)xnintr_core_clock_handler(),對應22行返回->執(zhí)行8行,為什么是IPIPE_HRTIMER_IPI?相當于模擬底層lapic-timer 產(chǎn)生了一個event事件,ipipe會讓那個cpu 執(zhí)行xnintr_core_clock_handler()對timer進行一個刷新,重新對底層硬件timer編程。
如果新插入的timer不是最小的,但是所在的sched處于XNHDEFER狀態(tài),說明第一個timer雖然最小,但是這個最小的如果到期暫時不需要處理,那就取出定時時間第二小的timer,看是不是新插入的timer,如果是,返回1,繼續(xù)決定是編程還是發(fā)中斷信號。
如果其他情況,那就不用管了,啟動定時器流程完畢。一個一個timer到期后總會處理到新插入的這個的。
其中的向某個cpu發(fā)送中斷信號函數(shù)如下,IPIPE_HRTIMER_IPI是注冊到xnsched_realtime_domain的中斷,底層硬件timer產(chǎn)生中斷的中斷號就是IPIPE_HRTIMER_VECTOR,這里的發(fā)送中斷是通過中斷控制器APIC來完成的,APIC會給對應cpu產(chǎn)生一個中斷,然后就會被ipipe通過ipipeline,優(yōu)先給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編程的函調(diào)用xnclock_core_local_shot()函數(shù),最后調(diào)用ipipe_timer_set(delay)進行設(shè)置,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(delay0) ????????delay?=?0; ????else?if?(delay?>ULONG_MAX) delay=ULONG_MAX; ipipe_timer_set(delay); }
ipipe_timer_set()中先獲取這個cpu的percpu_timer t,然后將定時時間轉(zhuǎn)換為硬件的tick數(shù),最后調(diào)用t->set(tdelay, t->timer_set)進行設(shè)置。這里的percpu_timer 與ipipe 相關(guān)下面解析,這里只用知道最后是調(diào)用了percpu_timer 的set函數(shù),這個set函數(shù)是直接設(shè)置硬件lapic-timer的。
voidipipe_timer_set(unsignedlongcdelay) { unsignedlongtdelay; structipipe_timer*t; t=__ipipe_raw_cpu_read(percpu_timer); ....... /*將時間轉(zhuǎn)換定時器頻率數(shù)*/ tdelay=cdelay; if(t->c2t_integ!=1) tdelay*=t->c2t_integ; if(t->c2t_frac) tdelay+=((unsignedlonglong)cdelay*t->c2t_frac)>>32; if(tdelaymin_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); }
總結(jié):啟動一個xntimer,首先確定屬于哪個cpu,然后將它插入到該cpu的xntimer管理結(jié)構(gòu)timerdata,插入時按定時長短和優(yōu)先級來決定,最后設(shè)置底層硬件timer產(chǎn)生下一個中斷的時間點。
2.3 ipipe tick設(shè)備管理
linux時間系統(tǒng)中說到有多少個硬件timer,就會注冊多少個clock event device,最后linux會為每個cpu選擇一個合適的clock event來為tick device產(chǎn)生event。xenomai系統(tǒng)的運行也需要這么一個合適的硬件timer來產(chǎn)生event,由于xenomai需要的硬件都是由ipipe來提供,所以ipipe需要知道系統(tǒng)中有哪些clock event device被注冊,然后ipipe為每一個cpu核選擇一個合適的。
ipipe將linux中clock event device按xenomai系統(tǒng)需要重新抽象為結(jié)構(gòu)體struct ipipe_timer,系統(tǒng)中有一個全局鏈表timer,當?shù)讓域?qū)動調(diào)用clockevents_register_device,注冊clock event設(shè)備時ipipe對應的創(chuàng)建一個ipipe_timer插入鏈表timer。struct ipipe_timer如下:
structipipe_timer{ intirq; void(*request)(structipipe_timer*timer,intsteal); int(*set)(unsignedlongticks,void*timer); void(*ack)(void); void(*release)(structipipe_timer*timer); /*Onlyifregisteringatimerdirectly*/ constchar*name; unsignedrating; unsignedlongfreq; unsignedlongmin_delay_ticks; unsignedlongmax_delay_ticks; conststructcpumask*cpumask; /*Forinternaluse*/ void*timer_set;/*pointerpassedto->set()callback*/ structclock_event_device*host_timer;/*依賴的clockevent*/ structlist_headlink; unsignedc2t_integ; unsignedc2t_frac; /*Forclockeventinterception*/ u32real_mult; u32real_shift; void(*mode_handler)(enumclock_event_modemode, structclock_event_device*); intorig_mode; int(*orig_set_state_periodic)(structclock_event_device*); int(*orig_set_state_oneshot)(structclock_event_device*); int(*orig_set_state_oneshot_stopped)(structclock_event_device*); int(*orig_set_state_shutdown)(structclock_event_device*); int(*orig_set_next_event)(unsignedlongevt, structclock_event_device*cdev); unsignedint(*refresh_freq)(void); };
irq:該ipipe_timer所依賴的clock_event_device的中斷號,產(chǎn)生中斷時ipipe將中斷分配給誰處理用到;
request:設(shè)定clock_event_device模式的函數(shù)
set:設(shè)置下一個定時中斷的函數(shù),這個就是上面啟動xntimer時的那個函數(shù)
ack:產(chǎn)生中斷后中斷清除函數(shù)
rating:該clock_event_device的raning級別
freq:該clock_event_device的運行頻率
min_delay_ticks、max_delay_ticks:最小、最大定時時間
cpumask:cpu掩碼,標識可以為哪個cpu提供定時服務(wù)
host_timer:這個ipipe_timer對應是哪個clock_event_device
link:鏈表節(jié)點,加入全局鏈表timer時使用
orig_set_state_periodic、orig_set_state_oneshot、orig_set_state_oneshot_stopped、orig_set_next_event,為xenomai提供服務(wù)需要將clock_event_device中一些已經(jīng)設(shè)置的函數(shù)替換,這些用來備份原clock_event_device中的函數(shù)。
再來看一看clock xevent注冊函數(shù)clockevents_register_device(),ipipe補丁在其中插入了一個注冊函數(shù)ipipe_host_timer_register()先把clock xevent管理起來:
voidclockevents_register_device(structclock_event_device*dev) { unsignedlongflags; ...... ipipe_host_timer_register(dev); .... } staticintget_dev_mode(structclock_event_device*evtdev) { if(clockevent_state_oneshot(evtdev)|| clockevent_state_oneshot_stopped(evtdev)) returnCLOCK_EVT_MODE_ONESHOT; if(clockevent_state_periodic(evtdev)) returnCLOCK_EVT_MODE_PERIODIC; if(clockevent_state_shutdown(evtdev)) returnCLOCK_EVT_MODE_SHUTDOWN; returnCLOCK_EVT_MODE_UNUSED; } voidipipe_host_timer_register(structclock_event_device*evtdev) { structipipe_timer*timer=evtdev->ipipe_timer; if(timer==NULL) return; timer->orig_mode=CLOCK_EVT_MODE_UNUSED; if(timer->request==NULL) timer->request=ipipe_timer_default_request;/*設(shè)置request函數(shù)*/ /* *Bydefault,usethesamemethodaslinuxtimer,onARMat *least,mostset_next_eventmethodsaresafetobecalled *fromXenomaidomainanyway. */ if(timer->set==NULL){ timer->timer_set=evtdev; timer->set=(typeof(timer->set))evtdev->set_next_event;/*設(shè)定的counter的cycle數(shù)值*/ } if(timer->release==NULL) timer->release=ipipe_timer_default_release; if(timer->name==NULL) timer->name=evtdev->name; if(timer->rating==0) timer->rating=evtdev->rating; timer->freq=(1000000000ULL*evtdev->mult)>>evtdev->shift;/*1G*mult>>shift*/ if(timer->min_delay_ticks==0) timer->min_delay_ticks= (evtdev->min_delta_ns*evtdev->mult)>>evtdev->shift; if(timer->max_delay_ticks==0) timer->max_delay_ticks= (evtdev->max_delta_ns*evtdev->mult)>>evtdev->shift; if(timer->cpumask==NULL) timer->cpumask=evtdev->cpumask; timer->host_timer=evtdev; ipipe_timer_register(timer); }
這里面通過evtdev直接將一些結(jié)構(gòu)體成員賦值,這里需要注意的的是timer->set = (typeof(timer->set))evtdev->set_next_event;對于lapic-timer來說timer->set=lapic_next_event,如果CPU支持tsc deadline特性則是timer->set=lapic_next_deadline,TSC-deadline模式允許軟件使用本地APIC timer 在絕對時間發(fā)出中斷信號,使用tsc來設(shè)置deadline,為了全文統(tǒng)一,使用apic-timer,這決定了xenomai是否能直接控制硬件,然后調(diào)用ipipe_timer_register()將ipipe_timer添加到鏈表timer完成注冊:
voidipipe_timer_register(structipipe_timer*timer) { structipipe_timer*t; unsignedlongflags; if(timer->timer_set==NULL) timer->timer_set=timer; if(timer->cpumask==NULL) timer->cpumask=cpumask_of(smp_processor_id()); raw_spin_lock_irqsave(&lock,flags); list_for_each_entry(t,&timers,link){/*按插入鏈表*/ if(t->rating<=?timer->rating){ __list_add(&timer->link,t->link.prev,&t->link); gotodone; } } list_add_tail(&timer->link,&timers);/*按插入全局鏈表尾*/ done: raw_spin_unlock_irqrestore(&lock,flags); }
xenomai在每一個cpu核都需要一個ipipe_timer 來推動調(diào)度、定時等,ipipe為每個CPU分配了一個ipipe_timer指針percpu_timer,鏈表timers記錄了所有ipipe_timer,這樣就可以從鏈表中選擇可供xenomai使用的ipipe_timer:
staticDEFINE_PER_CPU(structipipe_timer*,percpu_timer);
另外,在3.ipipe domian管理說到每個cpu上管理不同域的結(jié)構(gòu)體ipipe_percpu_data,里面有一個成員變量int hrtimer_irq,這個hrtimer_irq是用來存放為這個cpu提供event的硬件timer的中斷號的,用于將ipipe_percpu_data與ipipe_timer聯(lián)系起來,介紹完相關(guān)數(shù)據(jù)結(jié)構(gòu)下面來看xenomai 時鐘系統(tǒng)初始化流程。
DECLARE_PER_CPU(structipipe_percpu_data,ipipe_percpu);
2.4 xenomai 時鐘系統(tǒng)初始化流程
xenomai內(nèi)核系統(tǒng)初始化源碼文件:kernelxenomaiinit.c,時鐘系統(tǒng)在xenomai初始化流程中調(diào)用mach_setup()完成硬件相關(guān)初始化:
xenomai_init(void) ->mach_setup() staticint__initmach_setup(void) { structipipe_sysinfosysinfo; intret,virq; ret=ipipe_select_timers(&xnsched_realtime_cpus); ... ipipe_get_sysinfo(&sysinfo);/*獲取系統(tǒng)ipipe信息*/ if(timerfreq_arg==0) timerfreq_arg=sysinfo.sys_hrtimer_freq; if(clockfreq_arg==0) clockfreq_arg=sysinfo.sys_hrclock_freq; cobalt_pipeline.timer_freq=timerfreq_arg; cobalt_pipeline.clock_freq=clockfreq_arg; if(cobalt_machine.init){ ret=cobalt_machine.init();/*mach_x86_init*/ if(ret) returnret; } ipipe_register_head(&xnsched_realtime_domain,"Xenomai"); ...... ret=xnclock_init(cobalt_pipeline.clock_freq);/*初始化xnclock,為Cobalt提供clock服務(wù)時鐘*/ return0;
首先調(diào)用ipipe_select_timers()來為每個cpu選擇一個ipipe_timer。
intipipe_select_timers(conststructcpumask*mask) { unsignedhrclock_freq; unsignedlonglongtmp; structipipe_timer*t; structclock_event_device*evtdev; unsignedlongflags; unsignedcpu; cpumask_tfixup; ....... if(__ipipe_hrclock_freq>UINT_MAX){ tmp=__ipipe_hrclock_freq; do_div(tmp,1000); hrclock_freq=tmp; }else hrclock_freq=__ipipe_hrclock_freq;/*1000ULL*cpu_khz*/ ....... for_each_cpu(cpu,mask){/*從timers為每一個CPU選擇一個percpu_timer*/ list_for_each_entry(t,&timers,link){/*遍歷ipipe全局timer鏈表*/ if(!cpumask_test_cpu(cpu,t->cpumask)) continue; evtdev=t->host_timer; if(evtdev&&clockevent_state_shutdown(evtdev))/*該CPUtimer被軟件shutdown則跳過*/ continue; gotofound; } .... gotoerr_remove_all; found: install_pcpu_timer(cpu,hrclock_freq,t);/*設(shè)置每一個CPU的timer*/ } ....... flags=ipipe_critical_enter(ipipe_timer_request_sync); ipipe_timer_request_sync();/*如果支持,則切換到單觸發(fā)模式。*/ ipipe_critical_exit(flags); ....... }
先得到從全局變量cpu_khz得到tsc頻率保存到hrclock_freq,然后為xenomai運行的每一個cpu核進行ippie_timer選擇,對每一個遍歷全局鏈表timers,取出evtdev,看是否能為該cpu服務(wù),并且沒有處于關(guān)閉狀態(tài)。evtdev在Linux沒有被使用就會被Linux關(guān)閉。最后選出來的也就是lapic-timer 。
找到合適的tevtdev后調(diào)用install_pcpu_timer(cpu, hrclock_freq, t),為該cpu設(shè)置ipipe_timer:
staticvoidinstall_pcpu_timer(unsignedcpu,unsignedhrclock_freq, structipipe_timer*t) { per_cpu(ipipe_percpu.hrtimer_irq,cpu)=t->irq; per_cpu(percpu_timer,cpu)=t; config_pcpu_timer(t,hrclock_freq); }
主要是設(shè)置幾個xenomai相關(guān)的precpu變量,ipipe_percpu.hrtimer_irq設(shè)置為該evtdev的irq,percpu_timer為該evtdev對應的ipipe_timer,然后計算ipipe_timer中l(wèi)apic-timer與tsc頻率之間的轉(zhuǎn)換因子c2t_integ、c2t_frac;
回到ipipe_select_timers(),通過ipipe給每一個cpu發(fā)送一個中斷IPIPE_CRITICAL_IPI,將每一個lapic-timer通過ipipe_timer->request設(shè)置為oneshot模式。
回到mach_setup(),為每個cpu選出ipipe_timer后獲取此時系統(tǒng)信息:ipipe_get_sysinfo(&sysinfo)
intipipe_get_sysinfo(structipipe_sysinfo*info) { info->sys_nr_cpus=num_online_cpus();/*運行的cpu數(shù)據(jù)*/ info->sys_cpu_freq=__ipipe_cpu_freq;/*1000ULL*cpu_khz*/ info->sys_hrtimer_irq=per_cpu(ipipe_percpu.hrtimer_irq,0);/*cpu0的ipipe_timer中斷號*/ info->sys_hrtimer_freq=__ipipe_hrtimer_freq;/*time的頻率*/ info->sys_hrclock_freq=__ipipe_hrclock_freq;/*1000ULL*cpu_khz*/ return0; }
在這里還是覺得有問題,CPU和TSC、timer三者頻率不一定相等。
這幾個變量在接下來初始化xnclock中使用。xnclock_init(cobalt_pipeline.clock_freq):
int__initxnclock_init(unsignedlonglongfreq) { xnclock_update_freq(freq); nktimerlat=xnarch_timer_calibrate(); xnclock_reset_gravity(&nkclock);/*reset_core_clock_gravity*/ xnclock_register(&nkclock,&xnsched_realtime_cpus); return0; }
xnclock_update_freq(freq)計算出tsc頻率與時間ns單位的轉(zhuǎn)換因子tsc_scale,tsc_shift,計算流程可參考文檔實時內(nèi)核與linux內(nèi)核時鐘漂移過大原因.docx
xnarch_timer_calibrate()計算出每次對硬件timer編程這個執(zhí)行過程需要多長時間,也就是測量ipipe_timer_set()這個函數(shù)的執(zhí)行時間nktimerlat,計算方法是這樣先確保測量這段時間timer不會觸發(fā)中斷干擾,所以先用ipipe_timer_set()給硬件timer設(shè)置一個很長的超時值,然后開始測量,先從TSC讀取現(xiàn)在的時間tick值t0,然后循環(huán)執(zhí)行100次ipipe_timer_set(),接著從TSC讀取現(xiàn)在的時間tick值t1,ipipe_timer_set()平均每次的執(zhí)行時間是,為了算上其他可能的延遲5%,nktimerlat=(t1?t0)/105;
下面計算對kernel、user、irq xntimer精確定時的gravity,上面已經(jīng)說過為甚需要這個,xnclock_reset_gravity(&nkclock)調(diào)用執(zhí)行xnclock->ops.reset_gravity(),也就是reset_core_clock_gravity()函數(shù):
staticvoidreset_core_clock_gravity(structxnclock*clock) { structxnclock_gravitygravity; xnarch_get_latencies(&gravity); gravity.user+=nktimerlat; if(gravity.kernel==0) gravity.kernel=gravity.user; if(gravity.irq==0) gravity.irq=nktimerlat; set_core_clock_gravity(clock,&gravity); }
首先通過xnarch_get_latencies()函數(shù)來計算各空間的gravity,其實這個函數(shù)里沒有具體的計算流程,給的都是一些經(jīng)驗值,要么我們自己編譯時配置:
staticinlinevoidxnarch_get_latencies(structxnclock_gravity*p) { unsignedlongsched_latency; #ifCONFIG_XENO_OPT_TIMING_SCHEDLAT!=0 sched_latency=CONFIG_XENO_OPT_TIMING_SCHEDLAT; #else/*!CONFIG_XENO_OPT_TIMING_SCHEDLAT*/ if(strcmp(ipipe_timer_name(),"lapic")==0){ #ifdefCONFIG_SMP if(num_online_cpus()>1) sched_latency=3350; else sched_latency=2000; #else/*!SMP*/ sched_latency=1000; #endif/*!SMP*/ }elseif(strcmp(ipipe_timer_name(),"pit")){/*HPET*/ #ifdefCONFIG_SMP if(num_online_cpus()>1) sched_latency=3350; else sched_latency=1500; #else/*!SMP*/ sched_latency=1000; #endif/*!SMP*/ }else{ sched_latency=(__get_bogomips()250???17000?: ?????????????????__get_bogomips()?2500???4200?: ?????????????????3500); #ifdef?CONFIG_SMP ????????sched_latency?+=?1000; #endif?/*?CONFIG_SMP?*/ ????} #endif?/*?!CONFIG_XENO_OPT_TIMING_SCHEDLAT?*/ ????p->user=sched_latency; p->kernel=CONFIG_XENO_OPT_TIMING_KSCHEDLAT; p->irq=CONFIG_XENO_OPT_TIMING_IRQLAT; }
首先判斷宏CONFIG_XENO_OPT_TIMING_SCHEDLAT 如果不等于0,說明我們自己配置了這個數(shù),直接賦值就行,否則的話,根據(jù)xenomai使用的定時器是lapic 還是hept給不同的一些經(jīng)驗值了:
p->user=CONFIG_XENO_OPT_TIMING_SCHEDLAT; p->kernel=CONFIG_XENO_OPT_TIMING_KSCHEDLAT; p->irq=CONFIG_XENO_OPT_TIMING_IRQLAT;
CONFIG_XENO_OPT_TIMING_SCHEDLAT宏在內(nèi)核編譯時設(shè)置,默認為0,使用已有的經(jīng)驗值:
[*]Xenomai/cobalt---> Latencysettings---> (0)Userschedulinglatency(ns) (0)Intra-kernelschedulinglatency(ns) (0)Interruptlatency(ns)
實際使用后發(fā)現(xiàn)這個經(jīng)驗值也不太準,從測試數(shù)據(jù)看i5處理器與賽揚就存在差別,如果開啟內(nèi)核trace,就更不準了.
計算出gravity后加上ipipe_timer_set()執(zhí)行需要的時間nktimerlat,就是最終的gravity。以用戶空間實時程序定時為例如下(圖中時間段與比例無關(guān)):
到此mach_setup()函數(shù)中上層軟件時鐘相關(guān)初始化完了,但xenomai還不能直接對硬件timer,此時xenomai進程調(diào)度還沒初始化,硬件timer與內(nèi)核調(diào)度等息息相關(guān),xenomai內(nèi)核還不能掌管硬件timer,不能保證linux愉快運行,硬搶過來只能一起陣亡。等xenomai內(nèi)核任務(wù)管理等初始化完畢,給Linux舒適的運行空間,就可以直接控制硬件timer了,下面繼續(xù)解析這個函數(shù)sys_init();
2.5 xenomai接管lapic-timer
sys_init()涉及每個CPU上的調(diào)度結(jié)構(gòu)體初始化等。先插以點內(nèi)容,每個cpu上的xenomai 調(diào)度由對象xnsched來管理,xnsched對象每個cpu有一個,其中包含各類sched class,還包含兩個xntimer,一個host timer ---htimer,主要給linux定時,另一個循環(huán)計時timer rrbtimer;一個xnthead結(jié)構(gòu)rootcb,xenomai調(diào)度的是線程,每個實時線程使用xnthead結(jié)構(gòu)表示,這個rootcb表示本cpu上xenomai調(diào)度的linux,在雙核下linux只是xenomai的一個idle任務(wù),cpu0上xnsched結(jié)構(gòu)如下:
詳細的結(jié)構(gòu)后面會分析,這里只解析時鐘相關(guān)部分。
static__initintsys_init(void) { structxnsched*sched; void*heapaddr; intret,cpu; if(sysheap_size_arg==0) sysheap_size_arg=CONFIG_XENO_OPT_SYS_HEAPSZ;/**/ heapaddr=xnheap_vmalloc(sysheap_size_arg*1024);/*256*1024*/ ..... xnheap_set_name(&cobalt_heap,"systemheap"); for_each_online_cpu(cpu){ sched=&per_cpu(nksched,cpu); xnsched_init(sched,cpu); } #ifdefCONFIG_SMP ipipe_request_irq(&xnsched_realtime_domain, IPIPE_RESCHEDULE_IPI, (ipipe_irq_handler_t)__xnsched_run_handler, NULL,NULL); #endif xnregistry_init(); /* *Ifstartinginstoppedmode,doallinitializations,butdo *notenablethecoretimer. */ if(realtime_core_state()==COBALT_STATE_WARMUP){ ret=xntimer_grab_hardware();/*霸占硬件host定時器*/ ..... set_realtime_core_state(COBALT_STATE_RUNNING);/*更新實時內(nèi)核狀態(tài)*/ } return0; }
sys_init()中先初始化內(nèi)核堆空間,初始化每個CPU上的調(diào)度結(jié)構(gòu)體xnsched、創(chuàng)建idle線程,也就是上面說到的roottcb,多cpu核調(diào)度等,經(jīng)過這一些步驟,LInux已經(jīng)變成xenomai的一個idle線程了,最后調(diào)用xntimer_grab_hardware(),接管硬件timer:
intxntimer_grab_hardware(void) { structxnsched*sched; intret,cpu,_cpu; spl_ts; ....... nkclock.wallclock_offset= xnclock_get_host_time()-xnclock_read_monotonic(&nkclock); ret=xntimer_setup_ipi();ipipe_request_irq(&xnsched_realtime_domain,IPIPE_HRTIMER_IPI, (ipipe_irq_handler_t)xnintr_core_clock_handler,NULL,NULL); for_each_realtime_cpu(cpu){ ret=grab_hardware_timer(cpu); if(ret0) ????????????goto?fail; ????????xnlock_get_irqsave(&nklock,?s); ????????sched?=?xnsched_struct(cpu); ????????if?(ret?>1) xntimer_start(&sched->htimer,ret,ret,XN_RELATIVE); elseif(ret==1) xntimer_start(&sched->htimer,0,0,XN_RELATIVE); #ifdefCONFIG_XENO_OPT_WATCHDOG/*啟動看門狗定時器*/ xntimer_start(&sched->wdtimer,1000000000UL,1000000000UL,XN_RELATIVE); xnsched_reset_watchdog(sched); #endif xnlock_put_irqrestore(&nklock,s); } ...... returnret; }
注冊xnclock時nkclock.wallclock_offset沒有設(shè)置,現(xiàn)在設(shè)置也就是walltime的時間與tsc 的時間偏移。然后注冊IPIPE_HRTIMER_IPI中斷到xnsched_realtime_domain,9.2xntimer那一節(jié)啟動一個xntimer需要通知其他cpu處理時發(fā)送的IPIPE_HRTIMER_IPI:
intxntimer_setup_ipi(void) { returnipipe_request_irq(&xnsched_realtime_domain, IPIPE_HRTIMER_IPI, (ipipe_irq_handler_t)xnintr_core_clock_handler, NULL,NULL); }
接下來就是重要的為每個cpu接管硬件timer了,其實過程也簡單,就是將原來lcock event的一些操作函數(shù)替換來達到目的,每個cpu上xenomai調(diào)度管理結(jié)構(gòu)xnsched,每個xnsched中有一個定時器htimer,這個xntimer就是為linux服務(wù)的,根據(jù)底層timer的類型,后啟動htimer,htimer 推動linux繼時間子系統(tǒng)運行。這些后面會詳細解析?;氐浇庸躷imer函數(shù)grab_hardware_timer(cpu):
staticintgrab_hardware_timer(intcpu) { inttickval,ret; ret=ipipe_timer_start(xnintr_core_clock_handler, switch_htick_mode,program_htick_shot,cpu); switch(ret){ caseCLOCK_EVT_MODE_PERIODIC: /* *Oneshottickemulationcallbackwon'tbeused,ask *thecallertostartaninternaltimerforemulating *aperiodictick. */ tickval=1000000000UL/HZ; break; caseCLOCK_EVT_MODE_ONESHOT: /*oneshottickemulation*/ tickval=1; break; caseCLOCK_EVT_MODE_UNUSED: /*wedon'tneedtoemulatethetickatall.*/ tickval=0; break; caseCLOCK_EVT_MODE_SHUTDOWN: return-ENODEV; default: returnret; } returntickval; }
主要的操作在ipipe_timer_start(xnintr_core_clock_handler,switch_htick_mode, program_htick_shot, cpu),后面的就是判斷這個timer工作在什么模式,相應的返回好根據(jù)模式設(shè)置htimer為linux服務(wù);
ipipe_timer_start(xnintr_core_clock_handler,switch_htick_mode, program_htick_shot, cpu)其中的xnintr_core_clock_handler是lapic-timer 產(chǎn)生中斷時xenomai內(nèi)核的處理函數(shù),里面會去處理每個xntimer以及xenomai調(diào)度;switch_htick_mode是lapic-timer工作模式切換函數(shù),program_htick_shot函數(shù)是對sched->htimer重新定時的函數(shù),這個函數(shù)對linux來說特別重要,以后linux就不直接對硬件timer設(shè)置定時了,而是給xenomai中的sched->htimer設(shè)置。下面是ipipe_timer_start代碼:
intipipe_timer_start(void(*tick_handler)(void), void(*emumode)(enumclock_event_modemode, structclock_event_device*cdev), int(*emutick)(unsignedlongevt, structclock_event_device*cdev), unsignedintcpu) { structgrab_timer_datadata; intret; data.tick_handler=tick_handler;/*xnintr_core_clock_handler*/ data.emutick=emutick;/*program_htick_shot*/ data.emumode=emumode;/*switch_htick_mode*/ data.retval=-EINVAL; ret=smp_call_function_single(cpu,grab_timer,&data,true);/*執(zhí)行g(shù)rab_timer*/ returnret?:data.retval; }
先將傳入的幾個函數(shù)指正存到結(jié)構(gòu)體data,然后調(diào)用smp_call_function_single傳給函數(shù)grab_timer處理,smp_call_function_single中的smp表示給指定的cpu去執(zhí)行g(shù)rab_timer,對應的cpu執(zhí)行g(shù)rab_timer(&data):
staticvoidgrab_timer(void*arg) { structgrab_timer_data*data=arg; structclock_event_device*evtdev; structipipe_timer*timer; structirq_desc*desc; unsignedlongflags; intsteal,ret; flags=hard_local_irq_save(); timer=this_cpu_read(percpu_timer); evtdev=timer->host_timer; ret=ipipe_request_irq(ipipe_head_domain,timer->irq, (ipipe_irq_handler_t)data->tick_handler, NULL,__ipipe_ack_hrtimer_irq); if(ret0?&&?ret?!=?-EBUSY)?{ ????????hard_local_irq_restore(flags); ????????data->retval=ret; return; } steal=evtdev!=NULL&&!clockevent_state_detached(evtdev); if(steal&&evtdev->ipipe_stolen==0){ timer->real_mult=evtdev->mult; timer->real_shift=evtdev->shift; timer->orig_set_state_periodic=evtdev->set_state_periodic; timer->orig_set_state_oneshot=evtdev->set_state_oneshot; timer->orig_set_state_oneshot_stopped=evtdev->set_state_oneshot_stopped; timer->orig_set_state_shutdown=evtdev->set_state_shutdown; timer->orig_set_next_event=evtdev->set_next_event; timer->mode_handler=data->emumode;/*switch_htick_mode*/ evtdev->mult=1; evtdev->shift=0; evtdev->max_delta_ns=UINT_MAX; if(timer->orig_set_state_periodic) evtdev->set_state_periodic=do_set_periodic; if(timer->orig_set_state_oneshot) evtdev->set_state_oneshot=do_set_oneshot; if(timer->orig_set_state_oneshot_stopped) evtdev->set_state_oneshot_stopped=do_set_oneshot_stopped; if(timer->orig_set_state_shutdown) evtdev->set_state_shutdown=do_set_shutdown; evtdev->set_next_event=data->emutick;/*program_htick_shot*/ evtdev->ipipe_stolen=1; } hard_local_irq_restore(flags); data->retval=get_dev_mode(evtdev); desc=irq_to_desc(timer->irq); if(desc&&irqd_irq_disabled(&desc->irq_data)) ipipe_enable_irq(timer->irq); if(evtdev->ipipe_stolen&&clockevent_state_oneshot(evtdev)){/*啟動oneshot*/ ret=clockevents_program_event(evtdev, evtdev->next_event,true); if(ret) data->retval=ret; } }
首先從percpu_timer取出我們在ipipe_select_timers選擇的那個clockevent device evtdev,現(xiàn)在要這個evtdev為xenomai服務(wù),所以將它的中斷注冊到ipipe_head_domain,當中斷來的時候后ipipe會交給ipipe_head_domain調(diào)用data->tick_handler也就是xnintr_core_clock_handler處理,xnintr_core_clock_handler中處理xenomai在本CPU當上的調(diào)度、定時等。
在struct clock_event_device中ipipe添加了一個標志位ipipe_stolen用來表示該evtdev是不是已經(jīng)為實時系統(tǒng)服務(wù),是就是1,否則為0,這里當然為0,先將原來evtdev的操作函數(shù)備份到’orig_‘打頭的成員變量中,設(shè)置ipipe_timer的real_mult、real_shift為evtdev的mult、shift,原evtdev的mult、shift設(shè)置為1、0,linux計算的時候才能與xntimer定時時間對應起來。
最重要的是把原來evtdev->set_next_event設(shè)置成了program_htick_shot,program_htick_shot如下,從此linux就是對shched->htimer 定時器設(shè)置定時,來替代原來的evtdev:
staticintprogram_htick_shot(unsignedlongdelay, structclock_event_device*cdev) { structxnsched*sched; intret; spl_ts; xnlock_get_irqsave(&nklock,s); sched=xnsched_current(); ret=xntimer_start(&sched->htimer,delay,XN_INFINITE,XN_RELATIVE);/*相對,單次定時*/ xnlock_put_irqrestore(&nklock,s); returnret?-ETIME:0; }
其余的最后如果evtdev中斷沒有使能就使能中斷,evtdev是oneshot狀態(tài)啟動oneshot,到此xenomai掌管了lpic-tiemr,從此xenomai內(nèi)核直接設(shè)置lpic-tiemr,lpic-tiemr到時產(chǎn)生中斷,ipipe調(diào)用執(zhí)行xnintr_core_clock_handler處理lpic-tiemr中斷,xnintr_core_clock_handler處理xenomai時鐘系統(tǒng):
voidxnintr_core_clock_handler(void) { structxnsched*sched=xnsched_current(); intcpu__maybe_unused=xnsched_cpu(sched); xnstat_exectime_t*prev; if(!xnsched_supported_cpu(cpu)){ #ifdefXNARCH_HOST_TICK_IRQ ipipe_post_irq_root(XNARCH_HOST_TICK_IRQ); #endif return; } ...... ++sched->inesting;/*中斷嵌套++*/ sched->lflags|=XNINIRQ;/*在中斷上下文狀態(tài)*/ xnlock_get(&nklock); xnclock_tick(&nkclock);/*處理一個時鐘tick*/ xnlock_put(&nklock); trace_cobalt_clock_exit(per_cpu(ipipe_percpu.hrtimer_irq,cpu)); xnstat_exectime_switch(sched,prev); if(--sched->inesting==0){/*如果沒有其他中斷嵌套,執(zhí)行從新調(diào)度*/ sched->lflags&=~XNINIRQ; xnsched_run();/*調(diào)度*/ sched=xnsched_current(); } /* *Ifthecoreclockinterruptpreemptedareal-timethread, *anytransitiontotherootthreadhasalreadytriggereda *hosttickpropagationfromxnsched_run(),soatthispoint, *weonlyneedtopropagatethehosttickincasethe *interruptpreemptedtherootthread. */ if((sched->lflags&XNHTICK)&& xnthread_test_state(sched->curr,XNROOT)) xnintr_host_tick(sched); }
xnintr_core_clock_handler中,首先判斷產(chǎn)生這個中斷的cpu屬不屬于實時調(diào)度cpu,如果不屬于,那就把中斷post到root域后直接返回,ipipe會在root域上掛起這個中斷給linux處理。
如果這是運行xenomai的cpu,接下來調(diào)用xnclock_tick(&nkclock),來處理一個時鐘tick,里面就是看該cpu上哪些xntimer到期了做相應處理:
voidxnclock_tick(structxnclock*clock) { structxnsched*sched=xnsched_current(); structxntimer*timer; xnsticks_tdelta; xntimerq_t*tmq; xnticks_tnow; xntimerh_t*h; atomic_only(); ...... tmq=&xnclock_this_timerdata(clock)->q;/**/ /* *Optimisation:anylocaltimerreprogrammingtriggeredby *invokedtimerhandlerscanwaituntilweleavethetick *handler.Usethisstatusflagashinttoxntimer_start(). */ sched->status|=XNINTCK; now=xnclock_read_raw(clock); while((h=xntimerq_head(tmq))!=NULL){ timer=container_of(h,structxntimer,aplink); delta=(xnsticks_t)(xntimerh_date(&timer->aplink)-now); if(delta>0) break; trace_cobalt_timer_expire(timer); xntimer_dequeue(timer,tmq); xntimer_account_fired(timer);/*timer->fired++*/ /* *Bypostponingthepropagationofthelow-priority *hostticktotheinterruptepilogue(see *xnintr_irq_handler()),wesavesomeI-cache,which *translatesintopreciousmicrosecsonlow-endhw. */ if(unlikely(timer==&sched->htimer)){ sched->lflags|=XNHTICK; sched->lflags&=~XNHDEFER; if(timer->status&XNTIMER_PERIODIC) gotoadvance; continue; } /*Checkforalockedclockstate(i.e.ptracing).*/ if(unlikely(nkclock_lock>0)){ if(timer->status&XNTIMER_NOBLCK) gotofire; if(timer->status&XNTIMER_PERIODIC) gotoadvance; /* *Wehavenoperiodforthisblockedtimer, *sohaveittickagainatareasonablyclose *dateinthefuture,waitingfortheclock *tobeunlockedatsomepoint.Sinceclocks *areblockedwhensingle-steppingintoan *applicationusingadebugger,itisfineto *waitfor250msfortheusertocontinue *programexecution. */ xntimerh_date(&timer->aplink)+= xnclock_ns_to_ticks(xntimer_clock(timer), 250000000); gotorequeue; } fire: timer->handler(timer);/******************************/ now=xnclock_read_raw(clock); timer->status|=XNTIMER_FIRED; /* *Onlyrequeueperiodictimerswhichhavenotbeen *requeued,stoppedorkilled. */ if((timer->status& (XNTIMER_PERIODIC|XNTIMER_DEQUEUED|XNTIMER_KILLED|XNTIMER_RUNNING))!= (XNTIMER_PERIODIC|XNTIMER_DEQUEUED|XNTIMER_RUNNING)) continue; advance: do{ timer->periodic_ticks++; xntimer_update_date(timer); }while(xntimerh_date(&timer->aplink)sched!=sched)) continue; #endif xntimer_enqueue(timer,tmq); } sched->status&=~XNINTCK; xnclock_program_shot(clock,sched); }
xnclock_tick里主要處理各種類型的xntimer,首先取出本cpu上管理xntimer紅黑樹的根節(jié)點xntimerq_t,然后開始處理,為了安全設(shè)置sched狀態(tài)標識status為XNINTCK,標識該sched正在處理tick,得到現(xiàn)在tsc值now,然后一個while循環(huán),取出紅黑樹上定時最小的那個xntimer,得到這個xntimer的時間date,如果date減去now大于0,說明最短定時的xntimer都沒有到期,那就不需要繼續(xù)處理,直接跳出循環(huán),執(zhí)行xnclock_program_shot(clock, sched)設(shè)置定時器下一個中斷觸發(fā)時間。
如果有xntimer到期,date減去now小于等于0,首先從紅黑樹中刪除,然后xntimer.fire加1,表示xntimer到期次數(shù),然后處理,這里邏輯有點繞:
1.如果是sched->htimer,就是為Linux定時的,先設(shè)置sched->lflags |= XNHTICK,這個標志設(shè)置的是lflags不是status,因為linux的不是緊急的,后面本cpu沒有高優(yōu)先級實時任務(wù)運行才會給linux處理。接著判斷是不是一個周期timer,如果是,goto到advance更新timer時間date,可能已將過去幾個周期時間了,所有使用循環(huán)一個一個周期的增加直到現(xiàn)在時間now,然后重新插入紅黑樹。
2.如果這個xntimer是一個非阻塞timer,直接跳轉(zhuǎn)fire執(zhí)行handler,并設(shè)置狀態(tài)已經(jīng)FIRED。
3.如果這是一個非htimer的周期定時器,那同樣更新時間后重新加入紅黑樹。
4.以上都不是就將xntimer重新定時250ms,加入紅黑樹。
xnclock_tick執(zhí)行返回后,xnstat_exectime_switch()更新該cpu上每個域的執(zhí)行時間,然后如果沒有其他中斷嵌套則進行任務(wù)調(diào)度xnsched_run();
不知經(jīng)過多少個rt任務(wù)切換后回到這個上下文,并且當前cpu運行l(wèi)inux,上次離開這linux的定時器htimer還沒處理呢,檢查如果當前cpu上運行l(wèi)inux,并且sched->lflags中有XNHTICK標志,那將中斷通過ipipe post給linux處理,并清除lflags中的XNHTICK,linux中斷子系統(tǒng)就會去只執(zhí)行eventhandler,處理linux時間子系統(tǒng)。
voidxnintr_host_tick(structxnsched*sched)/*Interruptsoff.*/ { sched->lflags&=~XNHTICK; #ifdefXNARCH_HOST_TICK_IRQ ipipe_post_irq_root(XNARCH_HOST_TICK_IRQ); #endif }
2.6 xenomai內(nèi)核下Linux時鐘工作流程
到此時鐘系統(tǒng)中除調(diào)度相關(guān)的外,一個CPU上雙核系統(tǒng)時鐘流程如下圖所示:
總結(jié):xenomai內(nèi)核啟動時,grab_timer()結(jié)合ipipe通過替換回調(diào)函數(shù)將原linux系統(tǒng)timer lapic-timer作為xenomai 系統(tǒng)timer,xenomai直接對層硬件lapic-timer編程,linux退化為xenomai的idle任務(wù),idle任務(wù)的主時鐘就變成linux的時鐘來源,由linux直接對層硬件lapic-timer編程變成對idle hrtimer編程。idle hrtimer依附于xenomai時鐘xnclock,xnclock運作來源于底層硬件lapic-timer。
2.7 gravity
為什么要設(shè)置gravity呢?
xenomai是個實時系統(tǒng)必須保證定時器的精確,xntimer都是由硬件timer產(chǎn)生中斷后處理的,如果沒有g(shù)ravity,對于用戶空間實時任務(wù)RT:假如此時時間刻度是0,該任務(wù)定時10us后觸發(fā)定時器,10us后,產(chǎn)生了中斷,此時時間刻度為10us,開始處理xntimer,然后切換回內(nèi)核空間執(zhí)行調(diào)度,最后切換回用戶空間,從定時器到期到最后切換回RT也是需要時間的,已經(jīng)超過RT所定的10us,因此,需要得到定時器超時->回到用戶空間的這段時間gravity;不同空間的任務(wù)經(jīng)過的路徑不一樣,所以針對kernel、user和irq分別計算gravity,當任務(wù)定時,定時器到期時間date-gravity才是xntimer的觸發(fā)時間。當切換回原來的任務(wù)時剛好是定時時間。
總結(jié)來說是,CPU執(zhí)行代碼需要時間,調(diào)度度上下切換需要時間,中斷、內(nèi)核態(tài)、用戶態(tài)需要的時間不一樣,需要將中間的這些時間排除,這些時間就是gravity。
2.8 autotune
gravity可以使用xenomai 內(nèi)核代碼中的經(jīng)驗值,還可以內(nèi)核編譯時自定義,除這兩種之外,xenomai還提供了一種自動計算的程序autotune,它的使用需要配合內(nèi)核模塊autotune,編譯內(nèi)核時選中編譯:
[]Xenomai/cobalt---> Corefeatures---> <>Auto-tuning
程序autotune位于/usr/xenomai/sbin目錄下,直接執(zhí)行會分別計算irq、kernel、user的gravity;
審核編輯:劉清
-
cpu
+關(guān)注
關(guān)注
68文章
11075瀏覽量
216971 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
317瀏覽量
22385 -
時鐘中斷
+關(guān)注
關(guān)注
0文章
4瀏覽量
7816
原文標題:xenomai+linux雙內(nèi)核下的時鐘管理機制
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
[分享]揭開zzz 手機神秘面紗
Arduino為什么只有l(wèi)oop和setup函數(shù),揭開Arduino的神秘面紗--運行機制
蘋果iPhone手機神秘面紗被揭開
揭開實時以太網(wǎng)神秘的面紗

一加5神秘面紗揭開!外觀圓潤,雙攝2000萬
Duskers - 揭開科幻生存游戲中的神秘面紗
蘋果16寸MacBook Pro將于本周揭開神秘面紗
MT-001: 揭開公式(SNR = 6.02N + 1.76dB)的神秘面紗

如何區(qū)分xenomai、linux系統(tǒng)調(diào)用/服務(wù)
揭開數(shù)字健康應用的AI和機器學習的神秘面紗
揭開高性能多路復用數(shù)據(jù)采集系統(tǒng)面紗

用智能DAC揭開醫(yī)療報警設(shè)計的神秘面紗

評論