【原創】xenomai+linux雙核心下的時鐘管理機制

沐多發表於2022-04-05

【原創】xenomai+linux雙核心下的時鐘管理機制

版權宣告:本文為本文為博主原創文章,轉載請註明出處。如有問題,歡迎指正。部落格地址:https://www.cnblogs.com/wsg1100/

clock可以說是作業系統正常執行的發動機,整個作業系統的活動都受到它的激勵。系統利用時鐘中斷維持系統時間、促使任務排程,以保證所有程式共享CPU資源;可以說,“時鐘中斷”是整個作業系統的脈搏。

那你是否好奇xenomai cobalt核心和Linux核心雙核心共存的情況下,時間子系統是如何工作的?一個硬體時鐘如何為兩個作業系統提供服務的?本文將揭開xenomai雙核系統時間機制的面紗。

首先回看一下之前的文章[xenomai核心解析之xenomai的組成結構](https://www.cnblogs.com/wsg1100/p/12833126.html)。

我們說到:在核心空間,在標準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時間子系統框架大致如下:

linux_time_subsystem-m

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_periodicset_state_oneshotset_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。

//arch\x86\kernel\hpet.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,
};
//arch\x86\kernel\apic\apic.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\n",
			   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提供這兩個介面的,達到控制整個時鐘系統的。

abs-linux-time-m

二、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表示週期定時器定時間隔,valueinterval的單位由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 < t->min_delay_ticks)
		tdelay = t->min_delay_ticks;
	if (tdelay > t->max_delay_ticks)
		tdelay = t->max_delay_ticks;

	if (t->set(tdelay, t->timer_set) < 0)
		ipipe_raise_irq(t->irq); 
}

總結:啟動一個xntimer,首先確定屬於哪個cpu,然後將它插入到該cpu的xntimer管理結構timerdata,插入時按定時長短和優先順序來決定,最後設定底層硬體timer產生下一個中斷的時間點。

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_dataipipe_timer聯絡起來,介紹完相關資料結構下面來看xenomai 時鐘系統初始化流程。

DECLARE_PER_CPU(struct ipipe_percpu_data, ipipe_percpu);

2.4 xenomai 時鐘系統初始化流程

xenomai核心系統初始化原始碼檔案:kernel\xenomai\init.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值\(t_0\),然後迴圈執行100次ipipe_timer_set(),接著從TSC讀取現在的時間tick值\(t_1\)ipipe_timer_set()平均每次的執行時間是$(t_1-t_0)/100 \(單位\)tick$,為了算上其他可能的延遲\(5\%\),\(nktimerlat = (t_1-t_0)/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_domain9.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) < now);
	requeue:
#ifdef CONFIG_SMP
		/*
		 * If the timer was migrated over its timeout handler,
		 * xntimer_migrate() re-queued it already.
		 */
		if (unlikely(timer->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;

相關文章