作者簡介:
順剛(網名:沐多),一線碼農,從事工控行業,目前在一家工業自動化公司從事工業實時現場總線開發工作,喜歡鉆研Linux內核及xenomai,個人博客 wsg1100,歡迎大家關注!
clock可以說是操作系統正常運行的發動機,整個操作系統的活動都受到它的激勵。系統利用時鐘中斷維持系統時間、促使任務調度,以保證所有進程共享CPU資源;可以說,“時鐘中斷”是整個操作系統的脈搏。
那你是否好奇xenomai cobalt內核和Linux內核雙內核共存的情況下,時間子系統是如何工作的?一個硬件時鐘如何為兩個操作系統內核提供服務的?本文將揭開xenomai雙核系統下clock機制的面紗。
首先回看一下之前的文章xenomai內核解析之xenomai的組成結構。
我們說到:在內核空間,在標準linux基礎上添加一個實時內核Cobalt,得益于基于ADEOS(Adaptive Domain Environment for Operating System),使Cobalt內核在內核空間與linux內核并存,并把標準的Linux內核作為實時內核中的一個idle進程在實時內核上調度。
“并把標準的Linux內核作為實時內核中的一個idle進程在實時內核上調度“,這句話是本文的重點,接下我們先從Linux時間子系統介紹。
中間部分為個人分析代碼簡單記錄,比較啰嗦,如果你只是想知道xenomai時鐘子系統與linux時鐘子系統之間的關系可直接到2.6 xenomai內核下Linux時鐘工作流程查看總結。
一、linux時間子系統
linux時間子系統是一個很大的板塊,控制著linux的方方面面。這里只說雙核相關的部分。即側重于Linux與底層硬件交互這一塊。
關于Linux時間子系統的詳細內容,請移步蝸窩科技關系Linux 時間子系統專欄。文章中Linux時間子系統大部分內容來自于此,在此謝過~
Linux時間子系統框架大致如下:

1.1 tick device
處理器采用時鐘定時器來周期性地提供系統脈搏。時鐘中斷是普通外設中斷的一種。調度器利用時鐘中斷來定時檢測當前正在運行的線程是否需要調度。提供時鐘中斷的設備就是tick device。
如今在多核架構下,每個CPU形成了自己的一個小系統,有自己的調度、自己的進程統計等,這個小系統擁有自己的tick device,而且每個CPU上tick device是唯一的,tick device可以工作在periodic mode或者one shot mode,這是和系統配置有關(由于中斷的處理會影響實時性,一般將xenomai所在CPU的tick device配置工作在one shot mode模式)。因此,整個系統中,在tick device layer,有多少個cpu,就會有多少個tick device,稱為local tick device。當然,有些事情(例如整個系統的負荷計算)不適合在local tick驅動下進行,因此,所有的local tick device中會有一個被選擇做global tick device,該device負責維護整個系統的jiffies,更新wall clock,計算全局負荷什么的。
tick_device 數據結構如下:
?
/*tick device可以工作在兩種模式下,一種是周期性tick模式,另外一種是one shot模式。*/
enum?tick_device_mode?{
????TICKDEV_MODE_PERIODIC,
????TICKDEV_MODE_ONESHOT,/*one?shot模式主要和tickless系統以及高精度timer有關*/
};
struct?tick_device?{
????struct?clock_event_device?*evtdev;
????enum?tick_device_mode?mode;
};
?
1.2 clock event和clock source
tick device依賴于底層硬件產生定時事件來推動運行,這些產生定時事件的硬件是timer,除此之外還需要一個在指定輸入頻率的clock下工作的一個counter來提供計時。對形形色色的timer和counter硬件,linux kernel抽象出了通用clock event layer和通用clock source模塊,這兩個模塊和硬件無關。所謂clock source是用來抽象一個在指定輸入頻率的clock下工作的一個counter。clock event提供的是一定周期的event,如果應用程序需要讀取當前的時間,比如ns精度時,就需要通過timekeeping從clock source中獲取與上個tick之間的時間后返回此時時間。
底層的clock source chip驅動通過調用通用clock event和clock source模塊的接口函數,注冊clock source和clock event設備。
?
int?clocksource_register(struct?clocksource?*cs)? void?clockevents_register_device(struct?clock_event_device?*dev)
?
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結構如下:
?
struct?clock_event_device?{
????void????????????(*event_handler)(struct?clock_event_device?*);
????int?????????(*set_next_event)(unsigned?long?evt,?struct?clock_event_device?*);
????int?????????(*set_next_ktime)(ktime_t?expires,?struct?clock_event_device?*);
????ktime_t?????????next_event;
????u64?????????max_delta_ns;
????u64?????????min_delta_ns;
????u32?????????mult;
????u32?????????shift;
????enum?clock_event_state??state_use_accessors;
????unsigned?int????????features;
????unsigned?long???????retries;
????int?????????(*set_state_periodic)(struct?clock_event_device?*);
????int?????????(*set_state_oneshot)(struct?clock_event_device?*);
????int?????????(*set_state_oneshot_stopped)(struct?clock_event_device?*);
????int?????????(*set_state_shutdown)(struct?clock_event_device?*);
????int?????????(*tick_resume)(struct?clock_event_device?*);
????void????????????(*broadcast)(const?struct?cpumask?*mask);
????void????????????(*suspend)(struct?clock_event_device?*);
????void????????????(*resume)(struct?clock_event_device?*);
????unsigned?long???????min_delta_ticks;
????unsigned?long???????max_delta_ticks;
????const?char??????*name;
????int?????????rating;
????int?????????irq;
????int?????????bound_on;
????const?struct?cpumask????*cpumask;
????struct?list_head????list;
????......
}?____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對應的系統中斷號。
?
void?clockevents_register_device(struct?clock_event_device?*dev)
{
????unsigned?long?flags;
????......
????if?(!dev->cpumask)?{
????????WARN_ON(num_possible_cpus()?>?1);
????????dev->cpumask?=?cpumask_of(smp_processor_id());
????}
????list_add(&dev->list,?&clockevent_devices);/*加入clock?event設備全局列表?*/
????tick_check_new_device(dev);/*讓上層軟件知道底層又注冊一個新的clock?device,當然,是否上層軟件要使用這個新的clock?event?device是上層軟件的事情*/
????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
static?struct?clock_event_device?lapic_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
static?struct?clock_event_device?hpet_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():
?
__visible?void?__irq_entry?smp_apic_timer_interrupt(struct?pt_regs?*regs)
{
????struct?pt_regs?*old_regs?=?set_irq_regs(regs);
????/*
?????*?NOTE!?We'd?better?ACK?the?irq?immediately,
?????*?because?timer?handling?can?be?slow.
?????*
?????*?update_process_times()?expects?us?to?have?done?irq_enter().
?????*?Besides,?if?we?don't?timer?interrupts?ignore?the?global
?????*?interrupt?lock,?which?is?the?WrongThing?(tm)?to?do.
?????*/
????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);
}
static?void?local_apic_timer_interrupt(void)
{
????struct?clock_event_device?*evt?=?this_cpu_ptr(&lapic_events);
????if?(!evt->event_handler)?{
????????pr_warning("Spurious?LAPIC?timer?interrupt?on?cpu?%d
",
???????????????smp_processor_id());
????????/*?Switch?it?off?*/
????????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 tsc?hpet?acpi_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讀取。
?
struct?clocksource?clocksource_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狀態,從而減少功耗。
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時間子系統
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.
?
struct?xnclock?{
????/**?(ns)?*/
????xnticks_t?wallclock_offset;?/*獲取時鐘偏移:timekeeping - tsc*/
????/**?(ns)?*/
????xnticks_t?resolution;
????/**?(raw?clock?ticks).?*/
????struct?xnclock_gravity?gravity;
????/**?Clock?name.?*/
????const?char?*name;
????struct?{
#ifdef?CONFIG_XENO_OPT_EXTCLOCK
????????xnticks_t?(*read_raw)(struct?xnclock?*clock);
????????xnticks_t?(*read_monotonic)(struct?xnclock?*clock);
????????int?(*set_time)(struct?xnclock?*clock,
????????????????const?struct?timespec?*ts);
????????xnsticks_t?(*ns_to_ticks)(struct?xnclock?*clock,
??????????????????????xnsticks_t?ns);
????????xnsticks_t?(*ticks_to_ns)(struct?xnclock?*clock,
??????????????????????xnsticks_t?ticks);
????????xnsticks_t?(*ticks_to_ns_rounded)(struct?xnclock?*clock,
??????????????????????????xnsticks_t?ticks);
????????void?(*program_local_shot)(struct?xnclock?*clock,
???????????????????????struct?xnsched?*sched);
????????void?(*program_remote_shot)(struct?xnclock?*clock,
????????????????????????struct?xnsched?*sched);
#endif
????????int?(*set_gravity)(struct?xnclock?*clock,
???????????????????const?struct?xnclock_gravity?*p);
????????void?(*reset_gravity)(struct?xnclock?*clock);
#ifdef?CONFIG_XENO_OPT_VFILE
????????void?(*print_status)(struct?xnclock?*clock,
?????????????????????struct?xnvfile_regular_iterator?*it);
#endif
????}?ops;
????/*?Private?section.?*/
????struct?xntimerdata?*timerdata;
????int?id;
#ifdef?CONFIG_SMP
????/**?Possible?CPU?affinity?of?clock?beat.?*/
????cpumask_t?affinity;
#endif
#ifdef?CONFIG_XENO_OPT_STATS
????struct?xnvfile_snapshot?timer_vfile;
????struct?xnvfile_rev_tag?timer_revtag;
????struct?list_head?timerq;
????int?nrtimers;???/*統計掛在xnclock?xntimer?的數量*/
#endif?/*?CONFIG_XENO_OPT_STATS?*/
#ifdef?CONFIG_XENO_OPT_VFILE
????struct?xnvfile_regular?vfile;//vfile.ops=?&clock_ops
#endif
};
?
wallclock_offset:linux系統wall time(1970開始的時間值)與系統TSC cycle轉換為時間的偏移
resolution:該xnclock的精度
struct xnclock_gravity gravity:該xnclok下,中斷、內核、用戶空間程序定時器的調整量,對系統精確定時很重要,后面會說到。
?
struct?xnclock_gravity?{
????unsigned?long?irq;
????unsigned?long?kernel;
????unsigned?long?user;
};
?
ops:該xnclok的各操作函數。
timerdata:xntimer 管理結構頭節點,當系統中使用紅黑樹來管理xntimer時,他是紅黑樹head節點,當系統使用優先級鏈表來管理時它是鏈表頭節點,系統會為每個cpu分配一個timerdata,管理著本CPU上已啟動的xntimer,當為紅黑樹時head始終指向最近到期的xntimer,當某個cpu上一個clockevent到來時,xnclock會從該CPU timerdata取出head指向的那個timer看是否到期,然后進一步處理。
?
#if?defined(CONFIG_XENO_OPT_TIMER_RBTREE)
typedef?struct?{
????struct?rb_root?root;
????xntimerh_t?*head;
}?xntimerq_t;
#else
typedef?struct?list_head?xntimerq_t;
#endif
struct?xntimerdata?{
????xntimerq_t?q;
};
?
timerq:不論是屬于哪個cpu的xntimer初始化后都會掛到這個鏈表上,nrtimers掛在timerq上xntimer的個數
vfile:proc文件系統操作接口,可通過proc查看xenomai clock信息。
?
cat?/proc/xenomai/clock/coreclok gravity:?irq=99?kernel=1334?user=1334 devices:?timer=lapic-deadline,?clock=tsc status:?on setup:?99 ticks:?376931548560?(0057?c2defd90)
?
gravity即xnclock中的結構體gravity的值,devices表示xenomai用于產生clock event的硬件timer,clock為xnclock計時的時鐘源。
xenomai 內核默認定義xnclock如下,名字和結構體名一樣,至于xnclock怎么和硬件timer 、tsc聯系起來后面分析:
?
struct?xnclock?nkclock?=?{
????.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結構如下:
?
struct?xntimer?{
#ifdef?CONFIG_XENO_OPT_EXTCLOCK
????struct?xnclock?*clock;
#endif
????/**?Link?in?timers?list.?*/
????xntimerh_t?aplink;
????struct?list_head?adjlink;
????/**?Timer?status.?*/
????unsigned?long?status;
????/**?Periodic?interval?(clock?ticks,?0?==?one?shot).?*/
????xnticks_t?interval;
????/**?Periodic?interval?(nanoseconds,?0?==?one?shot).?*/
????xnticks_t?interval_ns;
????/**?Count?of?timer?ticks?in?periodic?mode.?*/
????xnticks_t?periodic_ticks;
????/**?First?tick?date?in?periodic?mode.?*/
????xnticks_t?start_date;
????/**?Date?of?next?periodic?release?point?(timer?ticks).?*/
????xnticks_t?pexpect_ticks;
????/** Sched structure to which the timer is attached. 附加計時器的Sched結構。*/
????struct?xnsched?*sched;
????/**?Timeout?handler.?*/
????void?(*handler)(struct?xntimer?*timer);
#ifdef?CONFIG_XENO_OPT_STATS
#ifdef?CONFIG_XENO_OPT_EXTCLOCK
????struct?xnclock?*tracker;
#endif
????/**?Timer?name?to?be?displayed.?*/
????char?name[XNOBJECT_NAME_LEN];
????/**?Timer?holder?in?timebase.?*/
????struct?list_head?next_stat;
????/**?Number?of?timer?schedules.?*/
????xnstat_counter_t?scheduled;
????/**?Number?of?timer?events.?*/
????xnstat_counter_t?fired;
#endif?/*?CONFIG_XENO_OPT_STATS?*/
};
?
clock:當自定義外部時鐘源時,使用外部時鐘時的xnclock.
aplink:上面介紹新clock時說到timerdata,當xntimer啟動是,aplink就會插入到所在cpu的timerdata中,當timerdata為紅黑樹時,aplink就是一個rb節點,否則是一個鏈表節點。分別如下:
?
//優先級鏈表結構
struct?xntlholder?{
????struct?list_head?link;
????xnticks_t?key;
????int?prio;
};
typedef?struct?xntlholder?xntimerh_t;
//樹結構
typedef?struct?{
????unsigned?long?long?date;
????unsigned?prio;
????struct?rb_node?link;
}?xntimerh_t;
?
系統默認配置以紅黑樹形式管理xntimer,date表示定時器的多久后到期;prio表示該定時器的優先級,當加入鏈表時先date來排序,如果幾個定時器date相同就看優先級,優先級高的先處理;link為紅黑樹節點。

status:定時器狀態,所有狀態為如下:
?
#define?XNTIMER_DEQUEUED??0x00000001????/*沒有掛在xnclock上*/ #define?XNTIMER_KILLED????0x00000002????/*該定時器已經被取消*/ #define?XNTIMER_PERIODIC??0x00000004????/*該定時器是一個周期定時器*/ #define?XNTIMER_REALTIME??0x00000008????/*定時器相對于Linux?walltime定時*/ #define?XNTIMER_FIRED?????0x00000010????/*定時已經到期*/ #define?XNTIMER_NOBLCK????0x00000020????/*非阻塞定時器*/ #define?XNTIMER_RUNNING???0x00000040????/*定時器已經start*/ #define?XNTIMER_KGRAVITY??0x00000080????/*該timer是一個內核態timer*/ #define?XNTIMER_UGRAVITY??0x00000100????/*該timer是一個用戶態timer*/ #define?XNTIMER_IGRAVITY??0?????????/*該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標志。
?
#define?xntimer_init(__timer,?__clock,?__handler,?__sched,?__flags)????
do?{????????????????????????????????????
????__xntimer_init(__timer,?__clock,?__handler,?__sched,?__flags);??
????xntimer_set_name(__timer,?#__handler);??
}?while?(0)
void?__xntimer_init(struct?xntimer?*timer,
????????????struct?xnclock?*clock,
????????????void?(*handler)(struct?xntimer?*timer),
????????????struct?xnsched?*sched,
????????????int?flags)
{
????spl_t?s?__maybe_unused;
#ifdef?CONFIG_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;
????/*
?????*?Set?the?timer?affinity,?preferably?to?xnsched_cpu(sched)?if
?????*?sched?was?given,?CPU0?otherwise.
?????*/
????if?(sched?==?NULL)
????????sched?=?xnsched_struct(0);
????xntimer_set_affinity(timer,?sched);
#ifdef?CONFIG_XENO_OPT_STATS
#ifdef?CONFIG_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的優先級:
?
static?inline?void?xntimer_set_priority(struct?xntimer?*timer,
????????????????????int?prio)
{
????xntimerh_prio(&timer->aplink)?=?prio;/*設置timer節點優先級*/
}
?
啟動一個定時器xntimer_start()代碼如下:
?
int?xntimer_start(struct?xntimer?*timer,
??????????xnticks_t?value,?xnticks_t?interval,
??????????xntmode_t?mode)
{
????struct?xnclock?*clock?=?xntimer_clock(timer);
????xntimerq_t?*q?=?xntimer_percpu_queue(timer);
????xnticks_t?date,?now,?delay,?period;
????unsigned?long?gravity;
????int?ret?=?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)?{
????case?XN_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);
????????/*?fall?through?*/
????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);
????return?ret;
}
?
啟動一個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。
?
static?inline?unsigned?long?xntimer_gravity(struct?xntimer?*timer)
{
????struct?xnclock?*clock?=?xntimer_clock(timer);
????if?(timer->status?&?XNTIMER_KGRAVITY)/*內核空間定時器*/
????????return?clock->gravity.kernel;
????if?(timer->status?&?XNTIMER_UGRAVITY)/*用戶空間定時器*/
????????return?clock->gravity.user;
????return?clock->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:
?
void?xntimer_enqueue_and_program(struct?xntimer?*timer,?xntimerq_t?*q)
{
????xntimer_enqueue(timer,?q);/*添加到紅黑樹*/
????if?(xntimer_heading_p(timer))?{/*這個timer處于第一個節點或者需要重新調度的sched的第二個節點*/
????????struct?xnsched?*sched?=?xntimer_sched(timer);/*timer所在的sched*/
????????struct?xnclock?*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);/*設置下一個one?shot*/
????}
}
int?xntimer_heading_p(struct?xntimer?*timer)
{
????struct?xnsched?*sched?=?timer->sched;
????xntimerq_t?*q;
????xntimerh_t?*h;
????q?=?xntimer_percpu_queue(timer);
????h?=?xntimerq_head(q);
????if?(h?==?&timer->aplink)/*timer?就是第一個*/
????????return?1;
????if?(sched->lflags?&?XNHDEFER)?{/*處于重新調度狀態*/
????????h?=?xntimerq_second(q,?h);/*這個timer?處于重新調度狀態下紅黑樹下?*/
????????if?(h?==?&timer->aplink)
????????????return?1;
????}
????return?0;
}
?
由于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管理說過:
?
void?xnclock_core_remote_shot(struct?xnsched?*sched)
{
????ipipe_send_ipi(IPIPE_HRTIMER_IPI,?*cpumask_of(xnsched_cpu(sched)));
}
int?xntimer_setup_ipi(void)
{
????return?ipipe_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時間:
?
static?inline?void?xnclock_program_shot(struct?xnclock?*clock,
????????????????????struct?xnsched?*sched)
{
????xnclock_core_local_shot(sched);
}
void?xnclock_core_local_shot(struct?xnsched?*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的。
?
void?ipipe_timer_set(unsigned?long?cdelay)
{
????unsigned?long?tdelay;
????struct?ipipe_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?+=?((unsigned?long?long)cdelay?*?t->c2t_frac)?>>?32;
????if?(tdelay?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產生下一個中斷的時間點。
2.3 ipipe tick設備管理
linux時間系統中說到有多少個硬件timer,就會注冊多少個clock event device,最后linux會為每個cpu選擇一個合適的clock event來為tick device產生event。xenomai系統的運行也需要這么一個合適的硬件timer來產生event,由于xenomai需要的硬件都是由ipipe來提供,所以ipipe需要知道系統中有哪些clock event device被注冊,然后ipipe為每一個cpu核選擇一個合適的。
ipipe將linux中clock event device按xenomai系統需要重新抽象為結構體struct ipipe_timer,系統中有一個全局鏈表timer,當底層驅動調用clockevents_register_device,注冊clock event設備時ipipe對應的創建一個ipipe_timer插入鏈表timer。struct ipipe_timer如下:
?
struct?ipipe_timer?{
????int?irq;
????void?(*request)(struct?ipipe_timer?*timer,?int?steal);
????int?(*set)(unsigned?long?ticks,?void?*timer);
????void?(*ack)(void);
????void?(*release)(struct?ipipe_timer?*timer);
????/*?Only?if?registering?a?timer?directly?*/
????const?char?*name;
????unsigned?rating;
????unsigned?long?freq;
????unsigned?long?min_delay_ticks;
????unsigned?long?max_delay_ticks;
????const?struct?cpumask?*cpumask;
????/*?For?internal?use?*/
????void?*timer_set;????/*?pointer?passed?to?->set()?callback?*/
????struct?clock_event_device?*host_timer;/*依賴的clock?event*/
????struct?list_head?link;
????unsigned?c2t_integ;
????unsigned?c2t_frac;
????/*?For?clockevent?interception??*/
????u32?real_mult;
????u32?real_shift;
????void?(*mode_handler)(enum?clock_event_mode?mode,
?????????????????struct?clock_event_device?*);
????int?orig_mode;
????int?(*orig_set_state_periodic)(struct?clock_event_device?*);
????int?(*orig_set_state_oneshot)(struct?clock_event_device?*);
????int?(*orig_set_state_oneshot_stopped)(struct?clock_event_device?*);
????int?(*orig_set_state_shutdown)(struct?clock_event_device?*);
????int?(*orig_set_next_event)(unsigned?long?evt,
???????????????????struct?clock_event_device?*cdev);
????unsigned?int?(*refresh_freq)(void);
};
?
irq:該ipipe_timer所依賴的clock_event_device的中斷號,產生中斷時ipipe將中斷分配給誰處理用到;
request:設定clock_event_device模式的函數
set:設置下一個定時中斷的函數,這個就是上面啟動xntimer時的那個函數
ack:產生中斷后中斷清除函數
rating:該clock_event_device的raning級別
freq:該clock_event_device的運行頻率
min_delay_ticks、max_delay_ticks:最小、最大定時時間
cpumask:cpu掩碼,標識可以為哪個cpu提供定時服務
host_timer:這個ipipe_timer對應是哪個clock_event_device
link:鏈表節點,加入全局鏈表timer時使用
orig_set_state_periodic、orig_set_state_oneshot、orig_set_state_oneshot_stopped、orig_set_next_event,為xenomai提供服務需要將clock_event_device中一些已經設置的函數替換,這些用來備份原clock_event_device中的函數。
再來看一看clock xevent注冊函數clockevents_register_device(),ipipe補丁在其中插入了一個注冊函數ipipe_host_timer_register()先把clock xevent管理起來:
?
void?clockevents_register_device(struct?clock_event_device?*dev)
{
????unsigned?long?flags;
????......
????ipipe_host_timer_register(dev);
????....
}
static?int?get_dev_mode(struct?clock_event_device?*evtdev)
{
????if?(clockevent_state_oneshot(evtdev)?||
????????clockevent_state_oneshot_stopped(evtdev))
????????return?CLOCK_EVT_MODE_ONESHOT;
????if?(clockevent_state_periodic(evtdev))
????????return?CLOCK_EVT_MODE_PERIODIC;
????if?(clockevent_state_shutdown(evtdev))
????????return?CLOCK_EVT_MODE_SHUTDOWN;
????return?CLOCK_EVT_MODE_UNUSED;
}
void?ipipe_host_timer_register(struct?clock_event_device?*evtdev)
{
????struct?ipipe_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;/*設置request函數*/
????/*
?????*?By?default,?use?the?same?method?as?linux?timer,?on?ARM?at
?????*?least,?most?set_next_event?methods?are?safe?to?be?called
?????*?from?Xenomai?domain?anyway.
?????*/
????if?(timer->set?==?NULL)?{
????????timer->timer_set?=?evtdev;
????????timer->set?=?(typeof(timer->set))evtdev->set_next_event;/*設定的counter的cycle數值*/
????}
????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直接將一些結構體成員賦值,這里需要注意的的是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 在絕對時間發出中斷信號,使用tsc來設置deadline,為了全文統一,使用apic-timer,這決定了xenomai是否能直接控制硬件,然后調用ipipe_timer_register()將ipipe_timer添加到鏈表timer完成注冊:
?
void?ipipe_timer_register(struct?ipipe_timer?*timer)
{
????struct?ipipe_timer?*t;
????unsigned?long?flags;
????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);
????????????goto?done;
????????}
????}
????list_add_tail(&timer->link,?&timers);/*按插入全局鏈表尾*/
??done:
????raw_spin_unlock_irqrestore(&lock,?flags);
}
?
xenomai在每一個cpu核都需要一個ipipe_timer 來推動調度、定時等,ipipe為每個CPU分配了一個ipipe_timer指針percpu_timer,鏈表timers記錄了所有ipipe_timer,這樣就可以從鏈表中選擇可供xenomai使用的ipipe_timer:
?
static?DEFINE_PER_CPU(struct?ipipe_timer?*,?percpu_timer);
?
另外,在3.ipipe domian管理說到每個cpu上管理不同域的結構體ipipe_percpu_data,里面有一個成員變量int hrtimer_irq,這個hrtimer_irq是用來存放為這個cpu提供event的硬件timer的中斷號的,用于將ipipe_percpu_data與ipipe_timer聯系起來,介紹完相關數據結構下面來看xenomai 時鐘系統初始化流程。
?
DECLARE_PER_CPU(struct?ipipe_percpu_data,?ipipe_percpu);
?
2.4 xenomai 時鐘系統初始化流程
xenomai內核系統初始化源碼文件:kernelxenomaiinit.c,時鐘系統在xenomai初始化流程中調用mach_setup()完成硬件相關初始化:
?
xenomai_init(void)
->mach_setup()
static?int?__init?mach_setup(void)
{
????struct?ipipe_sysinfo?sysinfo;
????int?ret,?virq;
????ret?=?ipipe_select_timers(&xnsched_realtime_cpus);
????...
????ipipe_get_sysinfo(&sysinfo);/*獲取?系統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)
????????????return?ret;
????}
????ipipe_register_head(&xnsched_realtime_domain,?"Xenomai");
????......
????ret?=?xnclock_init(cobalt_pipeline.clock_freq);/*初始化xnclock,為Cobalt提供clock服務時鐘*/
????return?0;
?
首先調用ipipe_select_timers()來為每個cpu選擇一個ipipe_timer。
?
int?ipipe_select_timers(const?struct?cpumask?*mask)
{
????unsigned?hrclock_freq;
????unsigned?long?long?tmp;
????struct?ipipe_timer?*t;
????struct?clock_event_device?*evtdev;
????unsigned?long?flags;
????unsigned?cpu;
????cpumask_t?fixup;
????.......
????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))/*該CPU?timer?被軟件shutdown則跳過*/
????????????????continue;
????????????goto?found;
????????}
????????....
????????goto?err_remove_all;
found:
????????install_pcpu_timer(cpu,?hrclock_freq,?t);/*設置每一個CPU的timer*/
????}
????.......
????flags?=?ipipe_critical_enter(ipipe_timer_request_sync);
????ipipe_timer_request_sync();/*如果支持,則切換到單觸發模式。*/
????ipipe_critical_exit(flags);
????.......
}
?
先得到從全局變量cpu_khz得到tsc頻率保存到hrclock_freq,然后為xenomai運行的每一個cpu核進行ippie_timer選擇,對每一個遍歷全局鏈表timers,取出evtdev,看是否能為該cpu服務,并且沒有處于關閉狀態。evtdev在Linux沒有被使用就會被Linux關閉。最后選出來的也就是lapic-timer 。
找到合適的tevtdev后調用install_pcpu_timer(cpu, hrclock_freq, t),為該cpu設置ipipe_timer:
?
static?void?install_pcpu_timer(unsigned?cpu,?unsigned?hrclock_freq,
??????????????????struct?ipipe_timer?*t)
{
????per_cpu(ipipe_percpu.hrtimer_irq,?cpu)?=?t->irq;
????per_cpu(percpu_timer,?cpu)?=?t;
????config_pcpu_timer(t,?hrclock_freq);
}
?
主要是設置幾個xenomai相關的precpu變量,ipipe_percpu.hrtimer_irq設置為該evtdev的irq,percpu_timer為該evtdev對應的ipipe_timer,然后計算ipipe_timer中lapic-timer與tsc頻率之間的轉換因子c2t_integ、c2t_frac;
回到ipipe_select_timers(),通過ipipe給每一個cpu發送一個中斷IPIPE_CRITICAL_IPI,將每一個lapic-timer通過ipipe_timer->request設置為oneshot模式。
回到mach_setup(),為每個cpu選出ipipe_timer后獲取此時系統信息:ipipe_get_sysinfo(&sysinfo)
?
int?ipipe_get_sysinfo(struct?ipipe_sysinfo?*info)
{
????info->sys_nr_cpus?=?num_online_cpus();/*運行的cpu數據*/
????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*/
????return?0;
}
?
在這里還是覺得有問題,CPU和TSC、timer三者頻率不一定相等。
這幾個變量在接下來初始化xnclock中使用。xnclock_init(cobalt_pipeline.clock_freq):
?
int?__init?xnclock_init(unsigned?long?long?freq)
{
????xnclock_update_freq(freq);?
????nktimerlat?=?xnarch_timer_calibrate();
????xnclock_reset_gravity(&nkclock);??????/*?reset_core_clock_gravity?*/
????xnclock_register(&nkclock,?&xnsched_realtime_cpus);
????return?0;
}
?
xnclock_update_freq(freq)計算出tsc頻率與時間ns單位的轉換因子tsc_scale,tsc_shift,計算流程可參考文檔實時內核與linux內核時鐘漂移過大原因.docx
xnarch_timer_calibrate()計算出每次對硬件timer編程這個執行過程需要多長時間,也就是測量ipipe_timer_set()這個函數的執行時間nktimerlat,計算方法是這樣先確保測量這段時間timer不會觸發中斷干擾,所以先用ipipe_timer_set()給硬件timer設置一個很長的超時值,然后開始測量,先從TSC讀取現在的時間tick值t0,然后循環執行100次ipipe_timer_set(),接著從TSC讀取現在的時間tick值t1,ipipe_timer_set()平均每次的執行時間是
,為了算上其他可能的延遲5%,nktimerlat=(t1?t0)/105;
下面計算對kernel、user、irq xntimer精確定時的gravity,上面已經說過為甚需要這個,xnclock_reset_gravity(&nkclock)調用執行xnclock->ops.reset_gravity(),也就是reset_core_clock_gravity()函數:
?
static?void?reset_core_clock_gravity(struct?xnclock?*clock)
{
????struct?xnclock_gravity?gravity;
????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()函數來計算各空間的gravity,其實這個函數里沒有具體的計算流程,給的都是一些經驗值,要么我們自己編譯時配置:
?
static?inline?void?xnarch_get_latencies(struct?xnclock_gravity?*p)
{
????unsigned?long?sched_latency;
#if?CONFIG_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)?{
#ifdef?CONFIG_SMP
????????if?(num_online_cpus()?>?1)
????????????sched_latency?=?3350;
????????else
????????????sched_latency?=?2000;
#else?/*?!SMP?*/
????????sched_latency?=?1000;
#endif?/*?!SMP?*/
????}?else?if?(strcmp(ipipe_timer_name(),?"pit"))?{?/*?HPET?*/
#ifdef?CONFIG_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,說明我們自己配置了這個數,直接賦值就行,否則的話,根據xenomai使用的定時器是lapic 還是hept給不同的一些經驗值了:
?
????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宏在內核編譯時設置,默認為0,使用已有的經驗值:
?
[*]?Xenomai/cobalt?---> Latency?settings?---> (0)?User?scheduling?latency?(ns) (0)?Intra-kernel?scheduling?latency?(ns) (0)?Interrupt?latency?(ns)
?
實際使用后發現這個經驗值也不太準,從測試數據看i5處理器與賽揚就存在差別,如果開啟內核trace,就更不準了.
計算出gravity后加上ipipe_timer_set()執行需要的時間nktimerlat,就是最終的gravity。以用戶空間實時程序定時為例如下(圖中時間段與比例無關):
到此mach_setup()函數中上層軟件時鐘相關初始化完了,但xenomai還不能直接對硬件timer,此時xenomai進程調度還沒初始化,硬件timer與內核調度等息息相關,xenomai內核還不能掌管硬件timer,不能保證linux愉快運行,硬搶過來只能一起陣亡。等xenomai內核任務管理等初始化完畢,給Linux舒適的運行空間,就可以直接控制硬件timer了,下面繼續解析這個函數sys_init();
2.5 xenomai接管lapic-timer
sys_init()涉及每個CPU上的調度結構體初始化等。先插以點內容,每個cpu上的xenomai 調度由對象xnsched來管理,xnsched對象每個cpu有一個,其中包含各類sched class,還包含兩個xntimer,一個host timer ---htimer,主要給linux定時,另一個循環計時timer rrbtimer;一個xnthead結構rootcb,xenomai調度的是線程,每個實時線程使用xnthead結構表示,這個rootcb表示本cpu上xenomai調度的linux,在雙核下linux只是xenomai的一個idle任務,cpu0上xnsched結構如下:

詳細的結構后面會分析,這里只解析時鐘相關部分。
?
static?__init?int?sys_init(void)
{
????struct?xnsched?*sched;
????void?*heapaddr;
????int?ret,?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,?"system?heap");
????for_each_online_cpu(cpu)?{
????????sched?=?&per_cpu(nksched,?cpu);
????????xnsched_init(sched,?cpu);?
????}
#ifdef?CONFIG_SMP
????ipipe_request_irq(&xnsched_realtime_domain,
??????????????IPIPE_RESCHEDULE_IPI,
??????????????(ipipe_irq_handler_t)__xnsched_run_handler,
??????????????NULL,?NULL);?????
#endif
????xnregistry_init();
????/*
?????*?If?starting?in?stopped?mode,?do?all?initializations,?but?do
?????*?not?enable?the?core?timer.
?????*/
????if?(realtime_core_state()?==?COBALT_STATE_WARMUP)?{
????????ret?=?xntimer_grab_hardware();?/*霸占硬件host定時器*/
????????.....
????????set_realtime_core_state(COBALT_STATE_RUNNING);?/*更新實時內核狀態*/
????}
????return?0;
}
?
sys_init()中先初始化內核堆空間,初始化每個CPU上的調度結構體xnsched、創建idle線程,也就是上面說到的roottcb,多cpu核調度等,經過這一些步驟,LInux已經變成xenomai的一個idle線程了,最后調用xntimer_grab_hardware(),接管硬件timer:
?
int?xntimer_grab_hardware(void)
{
????struct?xnsched?*sched;
????int?ret,?cpu,?_cpu;
????spl_t?s;
????.......
????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?(ret?0)
????????????goto?fail;
????????xnlock_get_irqsave(&nklock,?s);
????????sched?=?xnsched_struct(cpu);
????????if?(ret?>?1)
????????????xntimer_start(&sched->htimer,?ret,?ret,?XN_RELATIVE);
????????else?if?(ret?==?1)
????????????xntimer_start(&sched->htimer,?0,?0,?XN_RELATIVE);
#ifdef?CONFIG_XENO_OPT_WATCHDOG?/*啟動看門狗定時器*/
????????xntimer_start(&sched->wdtimer,?1000000000UL,?1000000000UL,?XN_RELATIVE);
????????xnsched_reset_watchdog(sched);
#endif
????????xnlock_put_irqrestore(&nklock,?s);
????}
????......
????return?ret;
}
?
注冊xnclock時nkclock.wallclock_offset沒有設置,現在設置也就是walltime的時間與tsc 的時間偏移。然后注冊IPIPE_HRTIMER_IPI中斷到xnsched_realtime_domain,9.2xntimer那一節啟動一個xntimer需要通知其他cpu處理時發送的IPIPE_HRTIMER_IPI:
?
int?xntimer_setup_ipi(void)
{
????return?ipipe_request_irq(&xnsched_realtime_domain,
?????????????????IPIPE_HRTIMER_IPI,
?????????????????(ipipe_irq_handler_t)xnintr_core_clock_handler,
?????????????????NULL,?NULL);
}
?
接下來就是重要的為每個cpu接管硬件timer了,其實過程也簡單,就是將原來lcock event的一些操作函數替換來達到目的,每個cpu上xenomai調度管理結構xnsched,每個xnsched中有一個定時器htimer,這個xntimer就是為linux服務的,根據底層timer的類型,后啟動htimer,htimer 推動linux繼時間子系統運行。這些后面會詳細解析。回到接管timer函數grab_hardware_timer(cpu):
?
static?int?grab_hardware_timer(int?cpu)
{
????int?tickval,?ret;
????ret?=?ipipe_timer_start(xnintr_core_clock_handler,
????????????????switch_htick_mode,?program_htick_shot,?cpu);
????switch?(ret)?{
????case?CLOCK_EVT_MODE_PERIODIC:
????????/*
?????????*?Oneshot?tick?emulation?callback?won't?be?used,?ask
?????????*?the?caller?to?start?an?internal?timer?for?emulating
?????????*?a?periodic?tick.
?????????*/
????????tickval?=?1000000000UL?/?HZ;
????????break;
????case?CLOCK_EVT_MODE_ONESHOT:
????????/*?oneshot?tick?emulation?*/
????????tickval?=?1;
????????break;
????case?CLOCK_EVT_MODE_UNUSED:
????????/*?we?don't?need?to?emulate?the?tick?at?all.?*/
????????tickval?=?0;
????????break;
????case?CLOCK_EVT_MODE_SHUTDOWN:
????????return?-ENODEV;
????default:
????????return?ret;
????}
????return?tickval;
}
?
主要的操作在ipipe_timer_start(xnintr_core_clock_handler,switch_htick_mode, program_htick_shot, cpu),后面的就是判斷這個timer工作在什么模式,相應的返回好根據模式設置htimer為linux服務;
ipipe_timer_start(xnintr_core_clock_handler,switch_htick_mode, program_htick_shot, cpu)其中的xnintr_core_clock_handler是lapic-timer 產生中斷時xenomai內核的處理函數,里面會去處理每個xntimer以及xenomai調度;switch_htick_mode是lapic-timer工作模式切換函數,program_htick_shot函數是對sched->htimer重新定時的函數,這個函數對linux來說特別重要,以后linux就不直接對硬件timer設置定時了,而是給xenomai中的sched->htimer設置。下面是ipipe_timer_start代碼:
?
int?ipipe_timer_start(void?(*tick_handler)(void),
??????????????void?(*emumode)(enum?clock_event_mode?mode,
??????????????????????struct?clock_event_device?*cdev),
??????????????int?(*emutick)(unsigned?long?evt,
?????????????????????struct?clock_event_device?*cdev),
??????????????unsigned?int?cpu)
{
????struct?grab_timer_data?data;
????int?ret;
????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);/*執行grab_timer*/
????return?ret??:?data.retval;
}
?
先將傳入的幾個函數指正存到結構體data,然后調用smp_call_function_single傳給函數grab_timer處理,smp_call_function_single中的smp表示給指定的cpu去執行grab_timer,對應的cpu執行grab_timer(&data):
?
static?void?grab_timer(void?*arg)
{
????struct?grab_timer_data?*data?=?arg;
????struct?clock_event_device?*evtdev;
????struct?ipipe_timer?*timer;
????struct?irq_desc?*desc;
????unsigned?long?flags;
????int?steal,?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?(ret?0?&&?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,現在要這個evtdev為xenomai服務,所以將它的中斷注冊到ipipe_head_domain,當中斷來的時候后ipipe會交給ipipe_head_domain調用data->tick_handler也就是xnintr_core_clock_handler處理,xnintr_core_clock_handler中處理xenomai在本CPU當上的調度、定時等。
在struct clock_event_device中ipipe添加了一個標志位ipipe_stolen用來表示該evtdev是不是已經為實時系統服務,是就是1,否則為0,這里當然為0,先將原來evtdev的操作函數備份到’orig_‘打頭的成員變量中,設置ipipe_timer的real_mult、real_shift為evtdev的mult、shift,原evtdev的mult、shift設置為1、0,linux計算的時候才能與xntimer定時時間對應起來。
最重要的是把原來evtdev->set_next_event設置成了program_htick_shot,program_htick_shot如下,從此linux就是對shched->htimer 定時器設置定時,來替代原來的evtdev:
?
static?int?program_htick_shot(unsigned?long?delay,
??????????????????struct?clock_event_device?*cdev)
{
????struct?xnsched?*sched;
????int?ret;
????spl_t?s;
????xnlock_get_irqsave(&nklock,?s);
????sched?=?xnsched_current();?
????ret?=?xntimer_start(&sched->htimer,?delay,?XN_INFINITE,?XN_RELATIVE);?/*相對,單次定時*/
????xnlock_put_irqrestore(&nklock,?s);
????return?ret???-ETIME?:?0;
}
?
其余的最后如果evtdev中斷沒有使能就使能中斷,evtdev是oneshot狀態啟動oneshot,到此xenomai掌管了lpic-tiemr,從此xenomai內核直接設置lpic-tiemr,lpic-tiemr到時產生中斷,ipipe調用執行xnintr_core_clock_handler處理lpic-tiemr中斷,xnintr_core_clock_handler處理xenomai時鐘系統:
?
void?xnintr_core_clock_handler(void)
{
????struct?xnsched?*sched?=?xnsched_current();
????int?cpu??__maybe_unused?=?xnsched_cpu(sched);
????xnstat_exectime_t?*prev;
????if?(!xnsched_supported_cpu(cpu))?{?
#ifdef?XNARCH_HOST_TICK_IRQ
????????ipipe_post_irq_root(XNARCH_HOST_TICK_IRQ);??
#endif
????????return;
????}
????......
????++sched->inesting;??/*中斷嵌套++*/
????sched->lflags?|=?XNINIRQ;?/*在中斷上下文狀態*/
????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)?{?/*如果沒有其他中斷嵌套,執行從新調度*/
????????sched->lflags?&=?~XNINIRQ;
????????xnsched_run();?????/*調度*/
????????sched?=?xnsched_current();
????}
????/*
?????*?If?the?core?clock?interrupt?preempted?a?real-time?thread,
?????*?any?transition?to?the?root?thread?has?already?triggered?a
?????*?host?tick?propagation?from?xnsched_run(),?so?at?this?point,
?????*?we?only?need?to?propagate?the?host?tick?in?case?the
?????*?interrupt?preempted?the?root?thread.
?????*/
????if?((sched->lflags?&?XNHTICK)?&&
????????xnthread_test_state(sched->curr,?XNROOT))
????????xnintr_host_tick(sched);
}
?
xnintr_core_clock_handler中,首先判斷產生這個中斷的cpu屬不屬于實時調度cpu,如果不屬于,那就把中斷post到root域后直接返回,ipipe會在root域上掛起這個中斷給linux處理。
如果這是運行xenomai的cpu,接下來調用xnclock_tick(&nkclock),來處理一個時鐘tick,里面就是看該cpu上哪些xntimer到期了做相應處理:
?
void?xnclock_tick(struct?xnclock?*clock)
{
????struct?xnsched?*sched?=?xnsched_current();
????struct?xntimer?*timer;
????xnsticks_t?delta;
????xntimerq_t?*tmq;
????xnticks_t?now;
????xntimerh_t?*h;
????atomic_only();
????......
????tmq?=?&xnclock_this_timerdata(clock)->q;/**/
????/*
?????*?Optimisation:?any?local?timer?reprogramming?triggered?by
?????*?invoked?timer?handlers?can?wait?until?we?leave?the?tick
?????*?handler.?Use?this?status?flag?as?hint?to?xntimer_start().
?????*/
????sched->status?|=?XNINTCK;
????now?=?xnclock_read_raw(clock);
????while?((h?=?xntimerq_head(tmq))?!=?NULL)?{
????????timer?=?container_of(h,?struct?xntimer,?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?++*/
????????/*
?????????*?By?postponing?the?propagation?of?the?low-priority
?????????*?host?tick?to?the?interrupt?epilogue?(see
?????????*?xnintr_irq_handler()),?we?save?some?I-cache,?which
?????????*?translates?into?precious?microsecs?on?low-end?hw.
?????????*/
????????if?(unlikely(timer?==?&sched->htimer))?{
????????????sched->lflags?|=?XNHTICK;???
????????????sched->lflags?&=?~XNHDEFER;
????????????if?(timer->status?&?XNTIMER_PERIODIC)
????????????????goto?advance;
????????????continue;
????????}
????????/*?Check?for?a?locked?clock?state?(i.e.?ptracing).*/
????????if?(unlikely(nkclock_lock?>?0))?{
????????????if?(timer->status?&?XNTIMER_NOBLCK)
????????????????goto?fire;
????????????if?(timer->status?&?XNTIMER_PERIODIC)
????????????????goto?advance;
????????????/*
?????????????*?We?have?no?period?for?this?blocked?timer,
?????????????*?so?have?it?tick?again?at?a?reasonably?close
?????????????*?date?in?the?future,?waiting?for?the?clock
?????????????*?to?be?unlocked?at?some?point.?Since?clocks
?????????????*?are?blocked?when?single-stepping?into?an
?????????????*?application?using?a?debugger,?it?is?fine?to
?????????????*?wait?for?250?ms?for?the?user?to?continue
?????????????*?program?execution.
?????????????*/
????????????xntimerh_date(&timer->aplink)?+=
????????????????xnclock_ns_to_ticks(xntimer_clock(timer),
????????????????????????250000000);
????????????goto?requeue;
????????}
????fire:
????????timer->handler(timer);/******************************/
????????now?=?xnclock_read_raw(clock);
????????timer->status?|=?XNTIMER_FIRED;
????????/*
?????????*?Only?requeue?periodic?timers?which?have?not?been
?????????*?requeued,?stopped?or?killed.
????????*/
????????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紅黑樹的根節點xntimerq_t,然后開始處理,為了安全設置sched狀態標識status為XNINTCK,標識該sched正在處理tick,得到現在tsc值now,然后一個while循環,取出紅黑樹上定時最小的那個xntimer,得到這個xntimer的時間date,如果date減去now大于0,說明最短定時的xntimer都沒有到期,那就不需要繼續處理,直接跳出循環,執行xnclock_program_shot(clock, sched)設置定時器下一個中斷觸發時間。
如果有xntimer到期,date減去now小于等于0,首先從紅黑樹中刪除,然后xntimer.fire加1,表示xntimer到期次數,然后處理,這里邏輯有點繞:
1.如果是sched->htimer,就是為Linux定時的,先設置sched->lflags |= XNHTICK,這個標志設置的是lflags不是status,因為linux的不是緊急的,后面本cpu沒有高優先級實時任務運行才會給linux處理。接著判斷是不是一個周期timer,如果是,goto到advance更新timer時間date,可能已將過去幾個周期時間了,所有使用循環一個一個周期的增加直到現在時間now,然后重新插入紅黑樹。
2.如果這個xntimer是一個非阻塞timer,直接跳轉fire執行handler,并設置狀態已經FIRED。
3.如果這是一個非htimer的周期定時器,那同樣更新時間后重新加入紅黑樹。
4.以上都不是就將xntimer重新定時250ms,加入紅黑樹。
xnclock_tick執行返回后,xnstat_exectime_switch()更新該cpu上每個域的執行時間,然后如果沒有其他中斷嵌套則進行任務調度xnsched_run();
不知經過多少個rt任務切換后回到這個上下文,并且當前cpu運行linux,上次離開這linux的定時器htimer還沒處理呢,檢查如果當前cpu上運行linux,并且sched->lflags中有XNHTICK標志,那將中斷通過ipipe post給linux處理,并清除lflags中的XNHTICK,linux中斷子系統就會去只執行eventhandler,處理linux時間子系統。
?
void?xnintr_host_tick(struct?xnsched?*sched)?/*?Interrupts?off.?*/
{
????sched->lflags?&=?~XNHTICK;
#ifdef?XNARCH_HOST_TICK_IRQ
????ipipe_post_irq_root(XNARCH_HOST_TICK_IRQ);
#endif
}
?

2.6 xenomai內核下Linux時鐘工作流程
到此時鐘系統中除調度相關的外,一個CPU上雙核系統時鐘流程如下圖所示:

總結:xenomai內核啟動時,grab_timer()結合ipipe通過替換回調函數將原linux系統timer lapic-timer作為xenomai 系統timer,xenomai直接對層硬件lapic-timer編程,linux退化為xenomai的idle任務,idle任務的主時鐘就變成linux的時鐘來源,由linux直接對層硬件lapic-timer編程變成對idle hrtimer編程。idle hrtimer依附于xenomai時鐘xnclock,xnclock運作來源于底層硬件lapic-timer。
2.7 gravity
為什么要設置gravity呢?
xenomai是個實時系統必須保證定時器的精確,xntimer都是由硬件timer產生中斷后處理的,如果沒有gravity,對于用戶空間實時任務RT:假如此時時間刻度是0,該任務定時10us后觸發定時器,10us后,產生了中斷,此時時間刻度為10us,開始處理xntimer,然后切換回內核空間執行調度,最后切換回用戶空間,從定時器到期到最后切換回RT也是需要時間的,已經超過RT所定的10us,因此,需要得到定時器超時->回到用戶空間的這段時間gravity;不同空間的任務經過的路徑不一樣,所以針對kernel、user和irq分別計算gravity,當任務定時,定時器到期時間date-gravity才是xntimer的觸發時間。當切換回原來的任務時剛好是定時時間。

總結來說是,CPU執行代碼需要時間,調度度上下切換需要時間,中斷、內核態、用戶態需要的時間不一樣,需要將中間的這些時間排除,這些時間就是gravity。
2.8 autotune
gravity可以使用xenomai 內核代碼中的經驗值,還可以內核編譯時自定義,除這兩種之外,xenomai還提供了一種自動計算的程序autotune,它的使用需要配合內核模塊autotune,編譯內核時選中編譯:
?
[]?Xenomai/cobalt?---> Core?features?---> <>?Auto-tuning
?
程序autotune位于/usr/xenomai/sbin目錄下,直接執行會分別計算irq、kernel、user的gravity;
審核編輯:湯梓紅
電子發燒友App






























評論