0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

揭開(kāi)xenomai雙核系統(tǒng)下clock機(jī)制的面紗

Linux閱碼場(chǎng) ? 來(lái)源:Linux閱碼場(chǎng) ? 作者:王順剛 ? 2022-11-24 09:10 ? 次閱讀

clock可以說(shuō)是操作系統(tǒng)正常運(yùn)行的發(fā)動(dòng)機(jī),整個(gè)操作系統(tǒng)的活動(dòng)都受到它的激勵(lì)。系統(tǒng)利用時(shí)鐘中斷維持系統(tǒng)時(shí)間、促使任務(wù)調(diào)度,以保證所有進(jìn)程共享CPU資源;可以說(shuō),“時(shí)鐘中斷”是整個(gè)操作系統(tǒng)的脈搏。

那你是否好奇xenomai cobalt內(nèi)核和Linux內(nèi)核雙內(nèi)核共存的情況下,時(shí)間子系統(tǒng)是如何工作的?一個(gè)硬件時(shí)鐘如何為兩個(gè)操作系統(tǒng)內(nèi)核提供服務(wù)的?本文將揭開(kāi)xenomai雙核系統(tǒng)下clock機(jī)制的面紗。

首先回看一下之前的文章xenomai內(nèi)核解析之xenomai的組成結(jié)構(gòu)。

我們說(shuō)到:在內(nèi)核空間,在標(biāo)準(zhǔn)linux基礎(chǔ)上添加一個(gè)實(shí)時(shí)內(nèi)核Cobalt,得益于基于ADEOS(Adaptive Domain Environment for Operating System),使Cobalt內(nèi)核在內(nèi)核空間與linux內(nèi)核并存,并把標(biāo)準(zhǔn)的Linux內(nèi)核作為實(shí)時(shí)內(nèi)核中的一個(gè)idle進(jìn)程在實(shí)時(shí)內(nèi)核上調(diào)度。

eed69e9c-6b8b-11ed-8abf-dac502259ad0.png

“并把標(biāo)準(zhǔn)的Linux內(nèi)核作為實(shí)時(shí)內(nèi)核中的一個(gè)idle進(jìn)程在實(shí)時(shí)內(nèi)核上調(diào)度“,這句話(huà)是本文的重點(diǎn),接下我們先從Linux時(shí)間子系統(tǒng)介紹。

中間部分為個(gè)人分析代碼簡(jiǎn)單記錄,比較啰嗦,如果你只是想知道xenomai時(shí)鐘子系統(tǒng)與linux時(shí)鐘子系統(tǒng)之間的關(guān)系可直接到2.6 xenomai內(nèi)核下Linux時(shí)鐘工作流程查看總結(jié)。

一、linux時(shí)間子系統(tǒng)

linux時(shí)間子系統(tǒng)是一個(gè)很大的板塊,控制著linux的方方面面。這里只說(shuō)雙核相關(guān)的部分。即側(cè)重于Linux與底層硬件交互這一塊。

關(guān)于Linux時(shí)間子系統(tǒng)的詳細(xì)內(nèi)容,請(qǐng)移步蝸窩科技關(guān)系Linux 時(shí)間子系統(tǒng)專(zhuān)欄。文章中Linux時(shí)間子系統(tǒng)大部分內(nèi)容來(lái)自于此,在此謝過(guò)~

Linux時(shí)間子系統(tǒng)框架大致如下:

eefa02d8-6b8b-11ed-8abf-dac502259ad0.png

1.1 tick device

處理器采用時(shí)鐘定時(shí)器來(lái)周期性地提供系統(tǒng)脈搏。時(shí)鐘中斷是普通外設(shè)中斷的一種。調(diào)度器利用時(shí)鐘中斷來(lái)定時(shí)檢測(cè)當(dāng)前正在運(yùn)行的線(xiàn)程是否需要調(diào)度。提供時(shí)鐘中斷的設(shè)備就是tick device。

如今在多核架構(gòu)下,每個(gè)CPU形成了自己的一個(gè)小系統(tǒng),有自己的調(diào)度、自己的進(jìn)程統(tǒng)計(jì)等,這個(gè)小系統(tǒng)擁有自己的tick device,而且每個(gè)CPU上tick device是唯一的,tick device可以工作在periodic mode或者one shot mode,這是和系統(tǒng)配置有關(guān)(由于中斷的處理會(huì)影響實(shí)時(shí)性,一般將xenomai所在CPU的tick device配置工作在one shot mode模式)。

因此,整個(gè)系統(tǒng)中,在tick device layer,有多少個(gè)cpu,就會(huì)有多少個(gè)tick device,稱(chēng)為local tick device。當(dāng)然,有些事情(例如整個(gè)系統(tǒng)的負(fù)荷計(jì)算)不適合在local tick驅(qū)動(dòng)下進(jìn)行,因此,所有的local tick device中會(huì)有一個(gè)被選擇做global tick device,該device負(fù)責(zé)維護(hù)整個(gè)系統(tǒng)的jiffies,更新wall clock,計(jì)算全局負(fù)荷什么的。

tick_device 數(shù)據(jù)結(jié)構(gòu)如下:

/*tick device可以工作在兩種模式下,一種是周期性tick模式,另外一種是one shot模式。*/
enumtick_device_mode{
TICKDEV_MODE_PERIODIC,
TICKDEV_MODE_ONESHOT,/*oneshot模式主要和tickless系統(tǒng)以及高精度timer有關(guān)*/
};
structtick_device{
structclock_event_device*evtdev;
enumtick_device_modemode;
};

1.2 clock event和clock source

tick device依賴(lài)于底層硬件產(chǎn)生定時(shí)事件來(lái)推動(dòng)運(yùn)行,這些產(chǎn)生定時(shí)事件的硬件是timer,除此之外還需要一個(gè)在指定輸入頻率的clock下工作的一個(gè)counter來(lái)提供計(jì)時(shí)。對(duì)形形色色的timer和counter硬件,linux kernel抽象出了通用clock event layer和通用clock source模塊,這兩個(gè)模塊和硬件無(wú)關(guān)。

所謂clock source是用來(lái)抽象一個(gè)在指定輸入頻率的clock下工作的一個(gè)counter。clock event提供的是一定周期的event,如果應(yīng)用程序需要讀取當(dāng)前的時(shí)間,比如ns精度時(shí),就需要通過(guò)timekeeping從clock source中獲取與上個(gè)tick之間的時(shí)間后返回此時(shí)時(shí)間。

底層的clock source chip驅(qū)動(dòng)通過(guò)調(diào)用通用clock event和clock source模塊的接口函數(shù),注冊(cè)clock source和clock event設(shè)備。

intclocksource_register(structclocksource*cs)
voidclockevents_register_device(structclock_event_device*dev)

1.3 clock event 設(shè)備注冊(cè)

每個(gè)CPU上tick device是唯一的,但為T(mén)ick device提供tick event的timer硬件并不唯一,如上圖中有Lapic-timer、lapic-deadline、Hpet等,有多少個(gè)timer硬件就注冊(cè)多少個(gè)clock event device,各個(gè)cpu的tick device會(huì)選擇自己適合的那個(gè)clock event設(shè)備。

clock_event_devic結(jié)構(gòu)如下:

structclock_event_device{
void(*event_handler)(structclock_event_device*);
int(*set_next_event)(unsignedlongevt,structclock_event_device*);
int(*set_next_ktime)(ktime_texpires,structclock_event_device*);
ktime_tnext_event;
u64max_delta_ns;
u64min_delta_ns;
u32mult;
u32shift;
enumclock_event_statestate_use_accessors;
unsignedintfeatures;
unsignedlongretries;

int(*set_state_periodic)(structclock_event_device*);
int(*set_state_oneshot)(structclock_event_device*);
int(*set_state_oneshot_stopped)(structclock_event_device*);
int(*set_state_shutdown)(structclock_event_device*);
int(*tick_resume)(structclock_event_device*);

void(*broadcast)(conststructcpumask*mask);
void(*suspend)(structclock_event_device*);
void(*resume)(structclock_event_device*);
unsignedlongmin_delta_ticks;
unsignedlongmax_delta_ticks;

constchar*name;
intrating;
intirq;
intbound_on;
conststructcpumask*cpumask;
structlist_headlist;
......
}____cacheline_aligned;

簡(jiǎn)要說(shuō)下各成員變量的含義:

event_handler產(chǎn)生了clock event的時(shí)候調(diào)用的handler,硬件timer中斷到來(lái)的時(shí)候調(diào)用該timer中斷handler,而在這個(gè)中斷handler中再調(diào)用event_handler。

set_next_event設(shè)定產(chǎn)生下一個(gè)event。一般是clock的counter的cycle數(shù)值,一般的timer硬件都是用cycle值設(shè)定會(huì)比較方便,當(dāng)然,不排除有些奇葩可以直接使用ktime(秒、納秒),這時(shí)候clock event device的features成員要打上CLOCK_EVT_FEAT_KTIME的標(biāo)記使用set_next_ktime()函數(shù)設(shè)置。

set_state_periodic、set_state_oneshot、set_state_shutdown設(shè)置各個(gè)模式的配置函數(shù)。

broadcast上面說(shuō)到每個(gè)cpu有一個(gè)tcik device外還需要一個(gè)全局的clock event,為各CPU提供喚醒等功能。

rating該clock evnet的精度等級(jí),在選做tick device時(shí)做參考。

irq 該clock event對(duì)應(yīng)的系統(tǒng)中斷號(hào)。

voidclockevents_register_device(structclock_event_device*dev)
{
unsignedlongflags;

......
if(!dev->cpumask){
WARN_ON(num_possible_cpus()>1);
dev->cpumask=cpumask_of(smp_processor_id());
}
list_add(&dev->list,&clockevent_devices);/*加入clockevent設(shè)備全局列表*/
tick_check_new_device(dev);/*讓上層軟件知道底層又注冊(cè)一個(gè)新的clockdevice,當(dāng)然,是否上層軟件要使用這個(gè)新的clockeventdevice是上層軟件的事情*/
clockevents_notify_released();
......
}

clock event device的cpumask指明該設(shè)備為哪一個(gè)CPU工作,如果沒(méi)有設(shè)定并且cpu的個(gè)數(shù)大于1的時(shí)候要給出warning信息并進(jìn)行設(shè)定(設(shè)定為當(dāng)前運(yùn)行該代碼的那個(gè)CPU core)。在multi core的環(huán)境下,底層driver在調(diào)用該接口函數(shù)注冊(cè)clock event設(shè)備之前就需要設(shè)定cpumask成員,畢竟一個(gè)timer硬件附著在哪一個(gè)cpu上底層硬件最清楚。這里只是對(duì)未做設(shè)定的的設(shè)定為當(dāng)前CPU。

將新注冊(cè)的clockevent device添加到全局鏈表clockevent_devices,然后調(diào)用tick_check_new_device()讓上層軟件知道底層又注冊(cè)一個(gè)新的clock device,當(dāng)然,是否上層軟件會(huì)通過(guò)一系列判斷后來(lái)決定是否使用這個(gè)clock event作為tick device。如果被選作tick device 會(huì)為該clock event設(shè)置回調(diào)函數(shù)event_handler,如上圖所示:event_handler不同的模式會(huì)被設(shè)置為tick_handle_periodic()、hrtimer_interrupt()或tick_nohz_handler()。代碼詳細(xì)解析,后面會(huì)簡(jiǎn)要說(shuō)明;

對(duì)應(yīng)x86平臺(tái),clock event device有APIC-timer、hept,hept的rating沒(méi)有l(wèi)apic timer高。所以每個(gè)CPU上的loacl-apic timer作為該CPU的tick device。

//archx86kernelhpet.c
staticstructclock_event_devicelapic_clockevent={
.name="lapic",
.features=CLOCK_EVT_FEAT_PERIODIC|
CLOCK_EVT_FEAT_ONESHOT|CLOCK_EVT_FEAT_C3STOP
|CLOCK_EVT_FEAT_DUMMY,
.shift=32,
.set_state_shutdown=lapic_timer_shutdown,
.set_state_periodic=lapic_timer_set_periodic,
.set_state_oneshot=lapic_timer_set_oneshot,
.set_state_oneshot_stopped=lapic_timer_shutdown,
.set_next_event=lapic_next_event,
.broadcast=lapic_timer_broadcast,
.rating=100,
.irq=-1,
};
//archx86kernelapicapic.c
staticstructclock_event_devicehpet_clockevent={
.name="hpet",
.features=CLOCK_EVT_FEAT_PERIODIC|
CLOCK_EVT_FEAT_ONESHOT,
.set_state_periodic=hpet_legacy_set_periodic,
.set_state_oneshot=hpet_legacy_set_oneshot,
.set_state_shutdown=hpet_legacy_shutdown,
.tick_resume=hpet_legacy_resume,
.set_next_event=hpet_legacy_next_event,
.irq=0,
.rating=50,
};

apic的中斷函數(shù)smp_apic_timer_interrupt(),然后調(diào)用local_apic_timer_interrupt():

__visiblevoid__irq_entrysmp_apic_timer_interrupt(structpt_regs*regs)
{
structpt_regs*old_regs=set_irq_regs(regs);

/*
*NOTE!We'dbetterACKtheirqimmediately,
*becausetimerhandlingcanbeslow.
*
*update_process_times()expectsustohavedoneirq_enter().
*Besides,ifwedon'ttimerinterruptsignoretheglobal
*interruptlock,whichistheWrongThing(tm)todo.
*/
entering_ack_irq();
trace_local_timer_entry(LOCAL_TIMER_VECTOR);
local_apic_timer_interrupt();/*執(zhí)行handle*/
trace_local_timer_exit(LOCAL_TIMER_VECTOR);
exiting_irq();

set_irq_regs(old_regs);
}
staticvoidlocal_apic_timer_interrupt(void)
{
structclock_event_device*evt=this_cpu_ptr(&lapic_events);

if(!evt->event_handler){
pr_warning("SpuriousLAPICtimerinterruptoncpu%d
",
smp_processor_id());
/*Switchitoff*/
lapic_timer_shutdown(evt);
return;
}

inc_irq_stat(apic_timer_irqs);

evt->event_handler(evt);/*執(zhí)行event_handler*/
}

local_apic_timer_interrupt()先獲得產(chǎn)生該中斷的clock_event_device,然后執(zhí)行event_handler()。

1.4 clock source設(shè)備注冊(cè)

linux 中clock source主要與timekeeping模塊關(guān)聯(lián),這里不細(xì)說(shuō),查看系統(tǒng)中的可用的clock source:

$cat/sys/devices/system/clocksource/clocksource0/available_clocksource
tschpetacpi_pm

查看系統(tǒng)中當(dāng)前使用的clock source的信息:

$cat/sys/devices/system/clocksource/clocksource0/current_clocksource
tsc

這里主要說(shuō)一下與xenomai相關(guān)的clock source 設(shè)備TSC(Time Stamp Counter),x86處理器提供的TSC是一個(gè)高分辨率計(jì)數(shù)器,以恒定速率運(yùn)行(在較舊的處理器上,TSC計(jì)算內(nèi)部處理器的時(shí)鐘周期,這意味著當(dāng)處理器的頻率縮放比例改變時(shí),TSC的頻率也會(huì)改變,現(xiàn)今的TSC在處理器的所有操作狀態(tài)下均以恒定的速率運(yùn)行,其頻率遠(yuǎn)遠(yuǎn)超過(guò)了處理器的頻率),可以用單指令RDTSC讀取。

structclocksourceclocksource_tsc={
.name="tsc",
.rating=300,
.read=read_tsc,
.mask=CLOCKSOURCE_MASK(64),
.flags=CLOCK_SOURCE_IS_CONTINUOUS|
CLOCK_SOURCE_MUST_VERIFY,
.archdata={.vclock_mode=VCLOCK_TSC},
.resume=tsc_resume,
.mark_unstable=tsc_cs_mark_unstable,
.tick_stable=tsc_cs_tick_stable,
};

tsc在init_tsc_clocksource()中調(diào)用int clocksource_register(struct clocksource *cs)注冊(cè),流程如下:

1.調(diào)用__clocksource_update_freq_scale(cs, scale, freq),根據(jù)tsc頻率計(jì)算mult和shift,具體計(jì)算流程文章實(shí)時(shí)內(nèi)核與linux內(nèi)核時(shí)鐘漂移過(guò)大原因.docx已分析過(guò)。

2.調(diào)用clocksource_enqueue(cs)根據(jù)clock source按照rating的順序插入到全局鏈表clock source list中

3.選擇一個(gè)合適的clock source。kernel當(dāng)然是選用一個(gè)rating最高的clocksource作為當(dāng)前的正在使用的那個(gè)clock source。每當(dāng)注冊(cè)一個(gè)新的clock source的時(shí)候調(diào)用clocksource_select進(jìn)行選擇,畢竟有可能注冊(cè)了一個(gè)精度更高的clock source。X86系統(tǒng)中tsc rating最高,為300。

到此clock source注冊(cè)就注冊(cè)完了。

1.5 時(shí)間子系統(tǒng)的數(shù)據(jù)流和控制流

上面說(shuō)到tick device的幾種模式,下面結(jié)合整個(gè)系統(tǒng)模式說(shuō)明。高精度的timer需要高精度的clock event,工作在one shot mode的tick device工提供高精度的clock event(clockeventHandler中處理高精度timer)。因此,基于one shot mode下的tick device,系統(tǒng)實(shí)現(xiàn)了高精度timer,系統(tǒng)的各個(gè)模塊可以使用高精度timer的接口來(lái)完成定時(shí)服務(wù)。

雖然有了高精度timer的出現(xiàn), 內(nèi)核并沒(méi)有拋棄老的低精度timer機(jī)制(內(nèi)核開(kāi)發(fā)人員試圖整合高精度timer和低精度的timer,不過(guò)失敗了,所以目前內(nèi)核中,兩種timer是同時(shí)存在的)。當(dāng)系統(tǒng)處于高精度timer的時(shí)候(tick device處于one shot mode),系統(tǒng)會(huì)setup一個(gè)特別的高精度timer(可以稱(chēng)之sched timer),該高精度timer會(huì)周期性的觸發(fā),從而模擬的傳統(tǒng)的periodic tick,從而推動(dòng)了傳統(tǒng)低精度timer的運(yùn)轉(zhuǎn)。

因此,一些傳統(tǒng)的內(nèi)核模塊仍然可以調(diào)用經(jīng)典的低精度timer模塊的接口。系統(tǒng)可根據(jù)需要配置為以下幾種模式,具體配置見(jiàn)其他文檔:

1、使用低精度timer + 周期tick

根據(jù)當(dāng)前系統(tǒng)的配置情況(周期性tick),會(huì)調(diào)用tick_setup_periodic函數(shù),這時(shí)候,該tick device對(duì)應(yīng)的clock event device的clock event handler被設(shè)置為tick_handle_periodic。底層硬件會(huì)周期性的產(chǎn)生中斷,從而會(huì)周期性的調(diào)用tick_handle_periodic從而驅(qū)動(dòng)整個(gè)系統(tǒng)的運(yùn)轉(zhuǎn)。

這時(shí)候高精度timer模塊是運(yùn)行在低精度的模式,也就是說(shuō)這些hrtimer雖然是按照高精度timer的紅黑樹(shù)進(jìn)行組織,但是系統(tǒng)只是在每一周期性tick到來(lái)的時(shí)候調(diào)用hrtimer_run_queues函數(shù),來(lái)檢查是否有expire的hrtimer。毫無(wú)疑問(wèn),這里的高精度timer也就是沒(méi)有意義了。

2、低精度timer + Dynamic Tick

系統(tǒng)開(kāi)始的時(shí)候并不是直接進(jìn)入Dynamic tick mode的,而是經(jīng)歷一個(gè)切換過(guò)程。開(kāi)始的時(shí)候,系統(tǒng)運(yùn)行在周期tick的模式下,各個(gè)cpu對(duì)應(yīng)的tick device的(clock event device的)event handler是tick_handle_periodic。在timer的軟中斷上下文中,會(huì)調(diào)用tick_check_oneshot_change進(jìn)行是否切換到one shot模式的檢查,如果系統(tǒng)中有支持one-shot的clock event device,并且沒(méi)有配置高精度timer的話(huà),那么就會(huì)發(fā)生tick mode的切換(調(diào)用tick_nohz_switch_to_nohz),這時(shí)候,tick device會(huì)切換到one shot模式,而event handler被設(shè)置為tick_nohz_handler。

由于這時(shí)候的clock event device工作在one shot模式,因此當(dāng)系統(tǒng)正常運(yùn)行的時(shí)候,在event handler中每次都要reprogram clock event,以便正常產(chǎn)生tick。當(dāng)cpu運(yùn)行idle進(jìn)程的時(shí)候,clock event device不再reprogram產(chǎn)生下次的tick信號(hào),這樣,整個(gè)系統(tǒng)的周期性的tick就停下來(lái)。

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

3、高精度timer + Dynamic Tick

同樣的,系統(tǒng)開(kāi)始的時(shí)候并不是直接進(jìn)入Dynamic tick mode的,而是經(jīng)歷一個(gè)切換過(guò)程。系統(tǒng)開(kāi)始的時(shí)候是運(yùn)行在周期tick的模式下,event handler是tick_handle_periodic。在周期tick的軟中斷上下文中(參考run_timer_softirq),如果滿(mǎn)足條件,會(huì)調(diào)用hrtimer_switch_to_hres將hrtimer從低精度模式切換到高精度模式上。這時(shí)候,系統(tǒng)會(huì)有下面的動(dòng)作:

(1)Tick device的clock event設(shè)備切換到oneshot mode(參考tick_init_highres函數(shù))

(2)Tick device的clock event設(shè)備的event handler會(huì)更新為hrtimer_interrupt(參考tick_init_highres函數(shù))

(3)設(shè)定sched timer(即模擬周期tick那個(gè)高精度timer,參考tick_setup_sched_timer函數(shù))這樣,當(dāng)下一次tick到來(lái)的時(shí)候,系統(tǒng)會(huì)調(diào)用hrtimer_interrupt來(lái)處理這個(gè)tick(該tick是通過(guò)sched timer產(chǎn)生的)。

在Dynamic tick的模式下,各個(gè)cpu的tick device工作在one shot模式,該tick device對(duì)應(yīng)的clock event設(shè)備也工作在one shot的模式,這時(shí)候,硬件Timer的中斷不會(huì)周期性的產(chǎn)生,但是linux kernel中很多的模塊是依賴(lài)于周期性的tick的,因此,在這種情況下,系統(tǒng)使用hrtime模擬了一個(gè)周期性的tick。

在切換到dynamic tick模式的時(shí)候會(huì)初始化這個(gè)高精度timer,該高精度timer的回調(diào)函數(shù)是tick_sched_timer。這個(gè)函數(shù)執(zhí)行的函數(shù)類(lèi)似周期性tick中event handler執(zhí)行的內(nèi)容。不過(guò)在最后會(huì)reprogram該高精度timer,以便可以周期性的產(chǎn)生clock event。當(dāng)系統(tǒng)進(jìn)入idle的時(shí)候,就會(huì)stop這個(gè)高精度timer,這樣,當(dāng)沒(méi)有用戶(hù)事件的時(shí)候,CPU可以持續(xù)在idle狀態(tài),從而減少功耗。

4、高精度timer + 周期性Tick

這種配置不多見(jiàn),多半是由于硬件無(wú)法支持one shot的clock event device,這種情況下,整個(gè)系統(tǒng)仍然是運(yùn)行在周期tick的模式下。

總結(jié)一下:linux啟動(dòng)過(guò)程中初始化時(shí)鐘系統(tǒng),當(dāng)xenomai內(nèi)核未啟動(dòng)時(shí),linux直接對(duì)底層硬件lapic-timer編程,底層硬件lapic-timer產(chǎn)生中斷推動(dòng)整個(gè)Linux中的各個(gè)時(shí)鐘及調(diào)度運(yùn)行。

我們可以將Linux抽出如下圖,只需要為L(zhǎng)inux提供設(shè)置下一個(gè)時(shí)鐘事件set_next_event()和提供event觸發(fā)eventHandler()執(zhí)行兩個(gè)接口就能推動(dòng)整個(gè)linux時(shí)間子系統(tǒng)運(yùn)轉(zhuǎn),下面解析Xenomai是怎樣為linux提供這兩個(gè)接口的,達(dá)到控制整個(gè)時(shí)鐘系統(tǒng)的。

ef22a544-6b8b-11ed-8abf-dac502259ad0.png

二、xenomai時(shí)間子系統(tǒng)

2.1 xnclock

我們知道x86下每個(gè)cpu核有一個(gè)lapic,lapic中有定時(shí)硬件lapic-timer和hpet。tsc作為timeline,提供計(jì)時(shí),lapic-timer用來(lái)產(chǎn)生clock event。對(duì)于現(xiàn)今X86 CPU 操作系統(tǒng)一般都是使用TSC和lapic-timer作為clock source和clock event,因?yàn)榫茸罡?Atom 系列處理器可能會(huì)有區(qū)別).

xenomai的默認(rèn)時(shí)間管理對(duì)象是xnclock,xnclock管理著xenomai整個(gè)系統(tǒng)的時(shí)間、任務(wù)定時(shí)、調(diào)度等,xnclok的默認(rèn)時(shí)鐘源為T(mén)SC。當(dāng)然我們可以自定義clocksource。比如在TSC不可靠的系統(tǒng)上,可以使用外部定時(shí)硬件來(lái)作為時(shí)鐘源,當(dāng)自定義時(shí)鐘時(shí)需要實(shí)現(xiàn)結(jié)構(gòu)體中的宏CONFIG_XENO_OPT_EXTCLOCK包含的幾個(gè)必要函數(shù),且編譯配置使能CONFIG_XENO_OPT_EXTCLOCK。

注意:這里的自定義時(shí)鐘源只是將TSC替換為其他時(shí)鐘源,產(chǎn)生event的還是lapic-timer.

structxnclock{
/**(ns)*/
xnticks_twallclock_offset;/*獲取時(shí)鐘偏移:timekeeping - tsc*/
/**(ns)*/
xnticks_tresolution;
/**(rawclockticks).*/
structxnclock_gravitygravity;
/**Clockname.*/
constchar*name;
struct{
#ifdefCONFIG_XENO_OPT_EXTCLOCK
xnticks_t(*read_raw)(structxnclock*clock);
xnticks_t(*read_monotonic)(structxnclock*clock);
int(*set_time)(structxnclock*clock,
conststructtimespec*ts);
xnsticks_t(*ns_to_ticks)(structxnclock*clock,
xnsticks_tns);
xnsticks_t(*ticks_to_ns)(structxnclock*clock,
xnsticks_tticks);
xnsticks_t(*ticks_to_ns_rounded)(structxnclock*clock,
xnsticks_tticks);
void(*program_local_shot)(structxnclock*clock,
structxnsched*sched);
void(*program_remote_shot)(structxnclock*clock,
structxnsched*sched);
#endif
int(*set_gravity)(structxnclock*clock,
conststructxnclock_gravity*p);
void(*reset_gravity)(structxnclock*clock);
#ifdefCONFIG_XENO_OPT_VFILE
void(*print_status)(structxnclock*clock,
structxnvfile_regular_iterator*it);
#endif
}ops;
/*Privatesection.*/
structxntimerdata*timerdata;
intid;
#ifdefCONFIG_SMP
/**PossibleCPUaffinityofclockbeat.*/
cpumask_taffinity;
#endif
#ifdefCONFIG_XENO_OPT_STATS
structxnvfile_snapshottimer_vfile;
structxnvfile_rev_tagtimer_revtag;
structlist_headtimerq;
intnrtimers;/*統(tǒng)計(jì)掛在xnclockxntimer的數(shù)量*/
#endif/*CONFIG_XENO_OPT_STATS*/
#ifdefCONFIG_XENO_OPT_VFILE
structxnvfile_regularvfile;//vfile.ops=&clock_ops
#endif
};

wallclock_offset:linux系統(tǒng)wall time(1970開(kāi)始的時(shí)間值)與系統(tǒng)TSC cycle轉(zhuǎn)換為時(shí)間的偏移

resolution:該xnclock的精度

struct xnclock_gravity gravity:該xnclok下,中斷、內(nèi)核、用戶(hù)空間程序定時(shí)器的調(diào)整量,對(duì)系統(tǒng)精確定時(shí)很重要,后面會(huì)說(shuō)到。

structxnclock_gravity{
unsignedlongirq;
unsignedlongkernel;
unsignedlonguser;
};

ops:該xnclok的各操作函數(shù)。

timerdata:xntimer 管理結(jié)構(gòu)頭節(jié)點(diǎn),當(dāng)系統(tǒng)中使用紅黑樹(shù)來(lái)管理xntimer時(shí),他是紅黑樹(shù)head節(jié)點(diǎn),當(dāng)系統(tǒng)使用優(yōu)先級(jí)鏈表來(lái)管理時(shí)它是鏈表頭節(jié)點(diǎn),系統(tǒng)會(huì)為每個(gè)cpu分配一個(gè)timerdata,管理著本CPU上已啟動(dòng)的xntimer,當(dāng)為紅黑樹(shù)時(shí)head始終指向最近到期的xntimer,當(dāng)某個(gè)cpu上一個(gè)clockevent到來(lái)時(shí),xnclock會(huì)從該CPU timerdata取出head指向的那個(gè)timer看是否到期,然后進(jìn)一步處理。

#ifdefined(CONFIG_XENO_OPT_TIMER_RBTREE)
typedefstruct{
structrb_rootroot;
xntimerh_t*head;
}xntimerq_t;
#else
typedefstructlist_headxntimerq_t;
#endif

structxntimerdata{
xntimerq_tq;
};

timerq:不論是屬于哪個(gè)cpu的xntimer初始化后都會(huì)掛到這個(gè)鏈表上,nrtimers掛在timerq上x(chóng)ntimer的個(gè)數(shù)

vfile:proc文件系統(tǒng)操作接口,可通過(guò)proc查看xenomai clock信息。

cat/proc/xenomai/clock/coreclok

gravity:irq=99kernel=1334user=1334

devices:timer=lapic-deadline,clock=tsc

status:on

setup:99

ticks:376931548560(0057c2defd90)

gravity即xnclock中的結(jié)構(gòu)體gravity的值,devices表示xenomai用于產(chǎn)生clock event的硬件timer,clock為xnclock計(jì)時(shí)的時(shí)鐘源。

xenomai 內(nèi)核默認(rèn)定義xnclock如下,名字和結(jié)構(gòu)體名一樣,至于xnclock怎么和硬件timer 、tsc聯(lián)系起來(lái)后面分析:

structxnclocknkclock={
.name="coreclk",
.resolution=1,/*nanosecond.*/
.ops={
.set_gravity=set_core_clock_gravity,
.reset_gravity=reset_core_clock_gravity,
.print_status=print_core_clock_status,
},
.id=-1,
};

2.2 xntimer

實(shí)時(shí)任務(wù)的所有定時(shí)行為最后都會(huì)落到內(nèi)核中的xntimer上,而xnclock管理著硬件clock event,xntimer要完成定時(shí)就需要xnclock來(lái)獲取起始時(shí)間,xntimer結(jié)構(gòu)如下:

structxntimer{
#ifdefCONFIG_XENO_OPT_EXTCLOCK
structxnclock*clock;
#endif
/**Linkintimerslist.*/
xntimerh_taplink;
structlist_headadjlink;
/**Timerstatus.*/
unsignedlongstatus;
/**Periodicinterval(clockticks,0==oneshot).*/
xnticks_tinterval;
/**Periodicinterval(nanoseconds,0==oneshot).*/
xnticks_tinterval_ns;
/**Countoftimerticksinperiodicmode.*/
xnticks_tperiodic_ticks;
/**Firsttickdateinperiodicmode.*/
xnticks_tstart_date;
/**Dateofnextperiodicreleasepoint(timerticks).*/
xnticks_tpexpect_ticks;
/** Sched structure to which the timer is attached. 附加計(jì)時(shí)器的Sched結(jié)構(gòu)。*/
structxnsched*sched;
/**Timeouthandler.*/
void(*handler)(structxntimer*timer);
#ifdefCONFIG_XENO_OPT_STATS
#ifdefCONFIG_XENO_OPT_EXTCLOCK
structxnclock*tracker;
#endif
/**Timernametobedisplayed.*/
charname[XNOBJECT_NAME_LEN];
/**Timerholderintimebase.*/
structlist_headnext_stat;
/**Numberoftimerschedules.*/
xnstat_counter_tscheduled;
/**Numberoftimerevents.*/
xnstat_counter_tfired;
#endif/*CONFIG_XENO_OPT_STATS*/
};

clock:當(dāng)自定義外部時(shí)鐘源時(shí),使用外部時(shí)鐘時(shí)的xnclock.

aplink:上面介紹新clock時(shí)說(shuō)到timerdata,當(dāng)xntimer啟動(dòng)是,aplink就會(huì)插入到所在cpu的timerdata中,當(dāng)timerdata為紅黑樹(shù)時(shí),aplink就是一個(gè)rb節(jié)點(diǎn),否則是一個(gè)鏈表節(jié)點(diǎn)。分別如下:

//優(yōu)先級(jí)鏈表結(jié)構(gòu)
structxntlholder{
structlist_headlink;
xnticks_tkey;
intprio;
};
typedefstructxntlholderxntimerh_t;
//樹(shù)結(jié)構(gòu)
typedefstruct{
unsignedlonglongdate;
unsignedprio;
structrb_nodelink;
}xntimerh_t;

系統(tǒng)默認(rèn)配置以紅黑樹(shù)形式管理xntimer,date表示定時(shí)器的多久后到期;prio表示該定時(shí)器的優(yōu)先級(jí),當(dāng)加入鏈表時(shí)先date來(lái)排序,如果幾個(gè)定時(shí)器date相同就看優(yōu)先級(jí),優(yōu)先級(jí)高的先處理;link為紅黑樹(shù)節(jié)點(diǎn)。

ef467cee-6b8b-11ed-8abf-dac502259ad0.png

status:定時(shí)器狀態(tài),所有狀態(tài)為如下:

#defineXNTIMER_DEQUEUED0x00000001/*沒(méi)有掛在xnclock上*/
#defineXNTIMER_KILLED0x00000002/*該定時(shí)器已經(jīng)被取消*/
#defineXNTIMER_PERIODIC0x00000004/*該定時(shí)器是一個(gè)周期定時(shí)器*/
#defineXNTIMER_REALTIME0x00000008/*定時(shí)器相對(duì)于Linuxwalltime定時(shí)*/
#defineXNTIMER_FIRED0x00000010/*定時(shí)已經(jīng)到期*/
#defineXNTIMER_NOBLCK0x00000020/*非阻塞定時(shí)器*/
#defineXNTIMER_RUNNING0x00000040/*定時(shí)器已經(jīng)start*/
#defineXNTIMER_KGRAVITY0x00000080/*該timer是一個(gè)內(nèi)核態(tài)timer*/
#defineXNTIMER_UGRAVITY0x00000100/*該timer是一個(gè)用戶(hù)態(tài)timer*/
#defineXNTIMER_IGRAVITY0/*該timer是一個(gè)中斷timer*/

interval、interval_ns:周期定時(shí)器的定時(shí)周期,分別是tick 和ns,0表示這個(gè)xntimer 是單次定時(shí)的。

handler:定時(shí)器到期后執(zhí)行的函數(shù)。

sched:該timer所在的sched,每個(gè)cpu核上有一個(gè)sched,管理本cpu上的線(xiàn)程調(diào)度,timer又需要本cpu的lapic定時(shí),所以指定了sched就指定了該timer所屬cpu。

xntimer 使用需要先調(diào)用xntimer_init()初始化xntimer結(jié)構(gòu)成員,然后xntimer_start()啟動(dòng)這個(gè)xntimer,啟動(dòng)timer就是將它插入xnclock管理的紅黑樹(shù)。

xntimer_init()是一個(gè)宏,內(nèi)部調(diào)用__xntimer_init初始化timer,參數(shù)timer:需要初始化的timer;clock:該timer是依附于哪個(gè)xnclock,也就是說(shuō)哪個(gè)xnclock來(lái)處理我是否觸發(fā),沒(méi)有自定義就是xnclock,在timer_start的時(shí)候就會(huì)將這個(gè)timer掛到對(duì)應(yīng)的xnclock上去;handler:該timer到期后執(zhí)行的hanler;sched:timer所屬的sched;flags:指定該timer標(biāo)志。

#definexntimer_init(__timer,__clock,__handler,__sched,__flags)
do{
__xntimer_init(__timer,__clock,__handler,__sched,__flags);
xntimer_set_name(__timer,#__handler);
}while(0)

void__xntimer_init(structxntimer*timer,
structxnclock*clock,
void(*handler)(structxntimer*timer),
structxnsched*sched,
intflags)
{
spl_ts__maybe_unused;

#ifdefCONFIG_XENO_OPT_EXTCLOCK
timer->clock=clock;
#endif
xntimerh_init(&timer->aplink);
xntimerh_date(&timer->aplink)=XN_INFINITE;//0
xntimer_set_priority(timer,XNTIMER_STDPRIO);
timer->status=(XNTIMER_DEQUEUED|(flags&XNTIMER_INIT_MASK));//(0x01|flags&0x000001A0)
timer->handler=handler;
timer->interval_ns=0;
timer->sched=NULL;
/*
*Setthetimeraffinity,preferablytoxnsched_cpu(sched)if
*schedwasgiven,CPU0otherwise.
*/
if(sched==NULL)
sched=xnsched_struct(0);

xntimer_set_affinity(timer,sched);

#ifdefCONFIG_XENO_OPT_STATS
#ifdefCONFIG_XENO_OPT_EXTCLOCK
timer->tracker=clock;
#endif
ksformat(timer->name,XNOBJECT_NAME_LEN,"%d/%s",
task_pid_nr(current),current->comm);
xntimer_reset_stats(timer);
xnlock_get_irqsave(&nklock,s);
list_add_tail(&timer->next_stat,&clock->timerq);
clock->nrtimers++;
xnvfile_touch(&clock->timer_vfile);
xnlock_put_irqrestore(&nklock,s);
#endif/*CONFIG_XENO_OPT_STATS*/
}

前面幾行都是初始化xntimer 結(jié)構(gòu)體指針,xntimer_set_affinity(timer, sched)表示將timer移到sched上(timer->shced=sched)。后面將這個(gè)初始化的time加到xnclock 的timerq隊(duì)列,nrtimers加1。基本成員初始化完了,還有優(yōu)先級(jí)沒(méi)有設(shè)置,aplink中的優(yōu)先級(jí)就代表了該timer的優(yōu)先級(jí):

staticinlinevoidxntimer_set_priority(structxntimer*timer,
intprio)
{
xntimerh_prio(&timer->aplink)=prio;/*設(shè)置timer節(jié)點(diǎn)優(yōu)先級(jí)*/
}

啟動(dòng)一個(gè)定時(shí)器xntimer_start()代碼如下:

intxntimer_start(structxntimer*timer,
xnticks_tvalue,xnticks_tinterval,
xntmode_tmode)
{
structxnclock*clock=xntimer_clock(timer);
xntimerq_t*q=xntimer_percpu_queue(timer);
xnticks_tdate,now,delay,period;
unsignedlonggravity;
intret=0;

trace_cobalt_timer_start(timer,value,interval,mode);
if((timer->status&XNTIMER_DEQUEUED)==0)
xntimer_dequeue(timer,q);

now=xnclock_read_raw(clock);

timer->status&=~(XNTIMER_REALTIME|XNTIMER_FIRED|XNTIMER_PERIODIC);
switch(mode){
caseXN_RELATIVE:
if((xnsticks_t)valuestatus|=XNTIMER_REALTIME;
value-=xnclock_get_offset(clock);
/*fallthrough*/
default:/*XN_ABSOLUTE||XN_REALTIME*/
date=xnclock_ns_to_ticks(clock,value);
if((xnsticks_t)(date-now)<=?0)?{
????????????if?(interval?==?XN_INFINITE)
????????????????return?-ETIMEDOUT;
????????????/*
?????????????*?We?are?late?on?arrival?for?the?first
?????????????*?delivery,?wait?for?the?next?shot?on?the
?????????????*?periodic?time?line.
?????????????*/
????????????delay?=?now?-?date;
????????????period?=?xnclock_ns_to_ticks(clock,?interval);
????????????date?+=?period?*?(xnarch_div64(delay,?period)?+?1);
????????}
????????break;
????}

????/*
?????*?To?cope?with?the?basic?system?latency,?we?apply?a?clock
?????*?gravity?value,?which?is?the?amount?of?time?expressed?in
?????*?clock?ticks?by?which?we?should?anticipate?the?shot?for?any
?????*?outstanding?timer.?The?gravity?value?varies?with?the?type
?????*?of?context?the?timer?wakes?up,?i.e.?irq?handler,?kernel?or
?????*?user?thread.
?????*/
????gravity?=?xntimer_gravity(timer);
????xntimerh_date(&timer->aplink)=date-gravity;
if(now>=xntimerh_date(&timer->aplink))
xntimerh_date(&timer->aplink)+=gravity/2;

timer->interval_ns=XN_INFINITE;
timer->interval=XN_INFINITE;
if(interval!=XN_INFINITE){
timer->interval_ns=interval;
timer->interval=xnclock_ns_to_ticks(clock,interval);
timer->periodic_ticks=0;
timer->start_date=date;
timer->pexpect_ticks=0;
timer->status|=XNTIMER_PERIODIC;
}

timer->status|=XNTIMER_RUNNING;
xntimer_enqueue_and_program(timer,q);

returnret;
}

啟動(dòng)一個(gè)timer即將該timer插入xnclock 紅黑樹(shù)xntimerq_t。參數(shù)value表示定時(shí)時(shí)間、interval為0表示這個(gè)timer是單次觸發(fā),非0表示周期定時(shí)器定時(shí)間隔,value和interval的單位由mode決定,當(dāng)mode設(shè)置為XN_RELATIVE表示相對(duì)定時(shí)定時(shí)、XN_REALTIME為相對(duì)linux時(shí)間定時(shí),時(shí)間都為ns,其他則為絕對(duì)定時(shí)單位為timer的tick。

首先取出紅黑樹(shù)根節(jié)點(diǎn)q,如果這個(gè)timer的狀態(tài)是從隊(duì)列刪除(其他地方取消了這個(gè)定時(shí)器),就先把他從紅黑樹(shù)中刪除。讀取tsc得到此時(shí)tsc的tick值now,然后根據(jù)參數(shù)計(jì)算timer的到期時(shí)間date,中間將單位轉(zhuǎn)換為ticks。下面開(kāi)始設(shè)置紅黑樹(shù)中的最終值,xntimer_gravity(timer)根據(jù)這個(gè)timer為誰(shuí)服務(wù)取出對(duì)應(yīng)的gravity。

staticinlineunsignedlongxntimer_gravity(structxntimer*timer)
{
structxnclock*clock=xntimer_clock(timer);

if(timer->status&XNTIMER_KGRAVITY)/*內(nèi)核空間定時(shí)器*/
returnclock->gravity.kernel;

if(timer->status&XNTIMER_UGRAVITY)/*用戶(hù)空間定時(shí)器*/
returnclock->gravity.user;

returnclock->gravity.irq;/*中斷*/
}

為什么要設(shè)置gravity呢?xenomai是個(gè)實(shí)時(shí)系統(tǒng)必須保證定時(shí)器的精確,xntimer都是由硬件timer產(chǎn)生中斷后處理的,如果沒(méi)有g(shù)ravity,對(duì)于用戶(hù)空間實(shí)時(shí)任務(wù)RT:假如此時(shí)時(shí)間刻度是0,該任務(wù)定時(shí)10us后觸發(fā)定時(shí)器,10us后,產(chǎn)生了中斷,此時(shí)時(shí)間刻度為10us,開(kāi)始處理xntimer,然后切換回內(nèi)核空間執(zhí)行調(diào)度,最后切換回用戶(hù)空間,從定時(shí)器到期到最后切換回RT也是需要時(shí)間的,已經(jīng)超過(guò)RT所定的10us,因此,需要得到定時(shí)器超時(shí)->回到用戶(hù)空間的這段時(shí)間gravity;不同空間的任務(wù)經(jīng)過(guò)的路徑不一樣,所以針對(duì)kernel、user和irq分別計(jì)算gravity,當(dāng)任務(wù)定時(shí),定時(shí)器到期時(shí)間date-gravity才是xntimer的觸發(fā)時(shí)間。當(dāng)切換回原來(lái)的任務(wù)時(shí)剛好是定時(shí)時(shí)間。

gravity是怎樣計(jì)算的,xenomai初始化相關(guān)文章分析;

最后將timer狀態(tài)設(shè)置為XNTIMER_RUNNING,調(diào)用xntimer_enqueue_and_program(timer, q)將timer按超時(shí)時(shí)間date和優(yōu)先級(jí)插入該CPU紅黑樹(shù)timedata,新加入了一個(gè)timer就需要重新看看,最近超時(shí)的timer是哪一個(gè),然后設(shè)置底層硬件timer的下一個(gè)event時(shí)間,為最近一個(gè)要超時(shí)的timer date:

voidxntimer_enqueue_and_program(structxntimer*timer,xntimerq_t*q)
{
xntimer_enqueue(timer,q);/*添加到紅黑樹(shù)*/
if(xntimer_heading_p(timer)){/*這個(gè)timer處于第一個(gè)節(jié)點(diǎn)或者需要重新調(diào)度的sched的第二個(gè)節(jié)點(diǎn)*/
structxnsched*sched=xntimer_sched(timer);/*timer所在的sched*/
structxnclock*clock=xntimer_clock(timer);/*當(dāng)前存數(shù)所在的CPU*/
if(sched!=xnsched_current())/*不是當(dāng)前CPU任務(wù)的定時(shí)器*/
xnclock_remote_shot(clock,sched);/*給當(dāng)前CPU發(fā)送ipipe_send_ipi(IPIPE_HRTIMER_IPI),讓sched對(duì)應(yīng)CPU重新調(diào)度*/
else
xnclock_program_shot(clock,sched);/*設(shè)置下一個(gè)oneshot*/
}
}
intxntimer_heading_p(structxntimer*timer)
{
structxnsched*sched=timer->sched;
xntimerq_t*q;
xntimerh_t*h;

q=xntimer_percpu_queue(timer);
h=xntimerq_head(q);
if(h==&timer->aplink)/*timer就是第一個(gè)*/
return1;

if(sched->lflags&XNHDEFER){/*處于重新調(diào)度狀態(tài)*/
h=xntimerq_second(q,h);/*這個(gè)timer處于重新調(diào)度狀態(tài)下紅黑樹(shù)下*/
if(h==&timer->aplink)
return1;
}

return0;
}

由于head始終指向時(shí)間最小的timer,xntimer_heading_p()中先看head是不是剛剛插入的這個(gè)timer,如果是并且是本CPU上的timer就直接設(shè)置這timer的時(shí)間為lapic-timer的中斷時(shí)間,對(duì)應(yīng)22行返回->執(zhí)行10行。

如果是最小但是不是本CPU上的就需要通過(guò)ipipe向timer所在CPU發(fā)送一個(gè)中斷信號(hào)IPIPE_HRTIMER_IPI,告訴那個(gè)cpu,那個(gè)cpu就會(huì)執(zhí)行中斷處理函數(shù)xnintr_core_clock_handler(),對(duì)應(yīng)22行返回->執(zhí)行8行,為什么是IPIPE_HRTIMER_IPI?相當(dāng)于模擬底層lapic-timer 產(chǎn)生了一個(gè)event事件,ipipe會(huì)讓那個(gè)cpu 執(zhí)行xnintr_core_clock_handler()對(duì)timer進(jìn)行一個(gè)刷新,重新對(duì)底層硬件timer編程。

如果新插入的timer不是最小的,但是所在的sched處于XNHDEFER狀態(tài),說(shuō)明第一個(gè)timer雖然最小,但是這個(gè)最小的如果到期暫時(shí)不需要處理,那就取出定時(shí)時(shí)間第二小的timer,看是不是新插入的timer,如果是,返回1,繼續(xù)決定是編程還是發(fā)中斷信號(hào)。

如果其他情況,那就不用管了,啟動(dòng)定時(shí)器流程完畢。一個(gè)一個(gè)timer到期后總會(huì)處理到新插入的這個(gè)的。

其中的向某個(gè)cpu發(fā)送中斷信號(hào)函數(shù)如下,IPIPE_HRTIMER_IPI是注冊(cè)到xnsched_realtime_domain的中斷,底層硬件timer產(chǎn)生中斷的中斷號(hào)就是IPIPE_HRTIMER_VECTOR,這里的發(fā)送中斷是通過(guò)中斷控制器APIC來(lái)完成的,APIC會(huì)給對(duì)應(yīng)cpu產(chǎn)生一個(gè)中斷,然后就會(huì)被ipipe通過(guò)ipipeline,優(yōu)先給xnsched_realtime_domain處理,ipipe domain管理說(shuō)過(guò):

voidxnclock_core_remote_shot(structxnsched*sched)
{
ipipe_send_ipi(IPIPE_HRTIMER_IPI,*cpumask_of(xnsched_cpu(sched)));
}
intxntimer_setup_ipi(void)
{
returnipipe_request_irq(&xnsched_realtime_domain,
IPIPE_HRTIMER_IPI,
(ipipe_irq_handler_t)xnintr_core_clock_handler,
NULL,NULL);
}

對(duì)底層timer編程的函調(diào)用xnclock_core_local_shot()函數(shù),最后調(diào)用ipipe_timer_set(delay)進(jìn)行設(shè)置,event時(shí)間:

staticinlinevoidxnclock_program_shot(structxnclock*clock,
structxnsched*sched)
{
xnclock_core_local_shot(sched);
}
voidxnclock_core_local_shot(structxnsched*sched)
{
.......
delay=xntimerh_date(&timer->aplink)-xnclock_core_read_raw();
if(delayULONG_MAX)
delay=ULONG_MAX;

ipipe_timer_set(delay);
}

ipipe_timer_set()中先獲取這個(gè)cpu的percpu_timer t,然后將定時(shí)時(shí)間轉(zhuǎn)換為硬件的tick數(shù),最后調(diào)用t->set(tdelay, t->timer_set)進(jìn)行設(shè)置。這里的percpu_timer 與ipipe 相關(guān)下面解析,這里只用知道最后是調(diào)用了percpu_timer 的set函數(shù),這個(gè)set函數(shù)是直接設(shè)置硬件lapic-timer的。

voidipipe_timer_set(unsignedlongcdelay)
{
unsignedlongtdelay;
structipipe_timer*t;

t=__ipipe_raw_cpu_read(percpu_timer);
.......
/*將時(shí)間轉(zhuǎn)換定時(shí)器頻率數(shù)*/
tdelay=cdelay;
if(t->c2t_integ!=1)
tdelay*=t->c2t_integ;
if(t->c2t_frac)
tdelay+=((unsignedlonglong)cdelay*t->c2t_frac)>>32;
if(tdelaymin_delay_ticks)
tdelay=t->min_delay_ticks;
if(tdelay>t->max_delay_ticks)
tdelay=t->max_delay_ticks;

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

總結(jié):?jiǎn)?dòng)一個(gè)xntimer,首先確定屬于哪個(gè)cpu,然后將它插入到該cpu的xntimer管理結(jié)構(gòu)timerdata,插入時(shí)按定時(shí)長(zhǎng)短和優(yōu)先級(jí)來(lái)決定,最后設(shè)置底層硬件timer產(chǎn)生下一個(gè)中斷的時(shí)間點(diǎn)。

2.3 ipipe tick設(shè)備管理

linux時(shí)間系統(tǒng)中說(shuō)到有多少個(gè)硬件timer,就會(huì)注冊(cè)多少個(gè)clock event device,最后linux會(huì)為每個(gè)cpu選擇一個(gè)合適的clock event來(lái)為tick device產(chǎn)生event。xenomai系統(tǒng)的運(yùn)行也需要這么一個(gè)合適的硬件timer來(lái)產(chǎn)生event,由于xenomai需要的硬件都是由ipipe來(lái)提供,所以ipipe需要知道系統(tǒng)中有哪些clock event device被注冊(cè),然后ipipe為每一個(gè)cpu核選擇一個(gè)合適的。

ipipe將linux中clock event device按xenomai系統(tǒng)需要重新抽象為結(jié)構(gòu)體struct ipipe_timer,系統(tǒng)中有一個(gè)全局鏈表timer,當(dāng)?shù)讓域?qū)動(dòng)調(diào)用clockevents_register_device,注冊(cè)clock event設(shè)備時(shí)ipipe對(duì)應(yīng)的創(chuàng)建一個(gè)ipipe_timer插入鏈表timer。struct ipipe_timer如下:

structipipe_timer{
intirq;
void(*request)(structipipe_timer*timer,intsteal);
int(*set)(unsignedlongticks,void*timer);
void(*ack)(void);
void(*release)(structipipe_timer*timer);

/*Onlyifregisteringatimerdirectly*/
constchar*name;
unsignedrating;
unsignedlongfreq;
unsignedlongmin_delay_ticks;
unsignedlongmax_delay_ticks;
conststructcpumask*cpumask;

/*Forinternaluse*/
void*timer_set;/*pointerpassedto->set()callback*/
structclock_event_device*host_timer;/*依賴(lài)的clockevent*/
structlist_headlink;

unsignedc2t_integ;
unsignedc2t_frac;

/*Forclockeventinterception*/
u32real_mult;
u32real_shift;
void(*mode_handler)(enumclock_event_modemode,
structclock_event_device*);
intorig_mode;
int(*orig_set_state_periodic)(structclock_event_device*);
int(*orig_set_state_oneshot)(structclock_event_device*);
int(*orig_set_state_oneshot_stopped)(structclock_event_device*);
int(*orig_set_state_shutdown)(structclock_event_device*);
int(*orig_set_next_event)(unsignedlongevt,
structclock_event_device*cdev);
unsignedint(*refresh_freq)(void);
};

irq:該ipipe_timer所依賴(lài)的clock_event_device的中斷號(hào),產(chǎn)生中斷時(shí)ipipe將中斷分配給誰(shuí)處理用到;
request:設(shè)定clock_event_device模式的函數(shù)
set:設(shè)置下一個(gè)定時(shí)中斷的函數(shù),這個(gè)就是上面啟動(dòng)xntimer時(shí)的那個(gè)函數(shù)
ack:產(chǎn)生中斷后中斷清除函數(shù)
rating:該clock_event_device的raning級(jí)別
freq:該clock_event_device的運(yùn)行頻率
min_delay_ticks、max_delay_ticks:最小、最大定時(shí)時(shí)間
cpumask:cpu掩碼,標(biāo)識(shí)可以為哪個(gè)cpu提供定時(shí)服務(wù)
host_timer:這個(gè)ipipe_timer對(duì)應(yīng)是哪個(gè)clock_event_device
link:鏈表節(jié)點(diǎn),加入全局鏈表timer時(shí)使用
orig_set_state_periodic、orig_set_state_oneshot、orig_set_state_oneshot_stopped、orig_set_next_event,為xenomai提供服務(wù)需要將clock_event_device中一些已經(jīng)設(shè)置的函數(shù)替換,這些用來(lái)備份原clock_event_device中的函數(shù)。

再來(lái)看一看clock xevent注冊(cè)函數(shù)clockevents_register_device(),ipipe補(bǔ)丁在其中插入了一個(gè)注冊(cè)函數(shù)ipipe_host_timer_register()先把clock xevent管理起來(lái):

voidclockevents_register_device(structclock_event_device*dev)
{
unsignedlongflags;

......
ipipe_host_timer_register(dev);
....
}
staticintget_dev_mode(structclock_event_device*evtdev)
{
if(clockevent_state_oneshot(evtdev)||
clockevent_state_oneshot_stopped(evtdev))
returnCLOCK_EVT_MODE_ONESHOT;

if(clockevent_state_periodic(evtdev))
returnCLOCK_EVT_MODE_PERIODIC;

if(clockevent_state_shutdown(evtdev))
returnCLOCK_EVT_MODE_SHUTDOWN;

returnCLOCK_EVT_MODE_UNUSED;
}

voidipipe_host_timer_register(structclock_event_device*evtdev)
{
structipipe_timer*timer=evtdev->ipipe_timer;

if(timer==NULL)
return;

timer->orig_mode=CLOCK_EVT_MODE_UNUSED;

if(timer->request==NULL)
timer->request=ipipe_timer_default_request;/*設(shè)置request函數(shù)*/

/*
*Bydefault,usethesamemethodaslinuxtimer,onARMat
*least,mostset_next_eventmethodsaresafetobecalled
*fromXenomaidomainanyway.
*/
if(timer->set==NULL){
timer->timer_set=evtdev;
timer->set=(typeof(timer->set))evtdev->set_next_event;/*設(shè)定的counter的cycle數(shù)值*/
}

if(timer->release==NULL)
timer->release=ipipe_timer_default_release;

if(timer->name==NULL)
timer->name=evtdev->name;

if(timer->rating==0)
timer->rating=evtdev->rating;

timer->freq=(1000000000ULL*evtdev->mult)>>evtdev->shift;/*1G*mult>>shift*/

if(timer->min_delay_ticks==0)
timer->min_delay_ticks=
(evtdev->min_delta_ns*evtdev->mult)>>evtdev->shift;

if(timer->max_delay_ticks==0)
timer->max_delay_ticks=
(evtdev->max_delta_ns*evtdev->mult)>>evtdev->shift;

if(timer->cpumask==NULL)
timer->cpumask=evtdev->cpumask;

timer->host_timer=evtdev;

ipipe_timer_register(timer);
}

這里面通過(guò)evtdev直接將一些結(jié)構(gòu)體成員賦值,這里需要注意的的是timer->set = (typeof(timer->set))evtdev->set_next_event;對(duì)于lapic-timer來(lái)說(shuō)timer->set=lapic_next_event,如果CPU支持tsc deadline特性則是timer->set=lapic_next_deadline,TSC-deadline模式允許軟件使用本地APIC timer 在絕對(duì)時(shí)間發(fā)出中斷信號(hào),使用tsc來(lái)設(shè)置deadline,為了全文統(tǒng)一,使用apic-timer,這決定了xenomai是否能直接控制硬件,然后調(diào)用ipipe_timer_register()將ipipe_timer添加到鏈表timer完成注冊(cè):

voidipipe_timer_register(structipipe_timer*timer)
{
structipipe_timer*t;
unsignedlongflags;

if(timer->timer_set==NULL)
timer->timer_set=timer;

if(timer->cpumask==NULL)
timer->cpumask=cpumask_of(smp_processor_id());

raw_spin_lock_irqsave(&lock,flags);

list_for_each_entry(t,&timers,link){/*按插入鏈表*/
if(t->rating<=?timer->rating){
__list_add(&timer->link,t->link.prev,&t->link);
gotodone;
}
}
list_add_tail(&timer->link,&timers);/*按插入全局鏈表尾*/
done:
raw_spin_unlock_irqrestore(&lock,flags);
}

xenomai在每一個(gè)cpu核都需要一個(gè)ipipe_timer 來(lái)推動(dòng)調(diào)度、定時(shí)等,ipipe為每個(gè)CPU分配了一個(gè)ipipe_timer指針percpu_timer,鏈表timers記錄了所有ipipe_timer,這樣就可以從鏈表中選擇可供xenomai使用的ipipe_timer:

staticDEFINE_PER_CPU(structipipe_timer*,percpu_timer);

另外,在3.ipipe domian管理說(shuō)到每個(gè)cpu上管理不同域的結(jié)構(gòu)體ipipe_percpu_data,里面有一個(gè)成員變量int hrtimer_irq,這個(gè)hrtimer_irq是用來(lái)存放為這個(gè)cpu提供event的硬件timer的中斷號(hào)的,用于將ipipe_percpu_data與ipipe_timer聯(lián)系起來(lái),介紹完相關(guān)數(shù)據(jù)結(jié)構(gòu)下面來(lái)看xenomai 時(shí)鐘系統(tǒng)初始化流程。

DECLARE_PER_CPU(structipipe_percpu_data,ipipe_percpu);

2.4 xenomai 時(shí)鐘系統(tǒng)初始化流程

xenomai內(nèi)核系統(tǒng)初始化源碼文件:kernelxenomaiinit.c,時(shí)鐘系統(tǒng)在xenomai初始化流程中調(diào)用mach_setup()完成硬件相關(guān)初始化:

xenomai_init(void)
->mach_setup()
staticint__initmach_setup(void)
{
structipipe_sysinfosysinfo;
intret,virq;

ret=ipipe_select_timers(&xnsched_realtime_cpus);
...
ipipe_get_sysinfo(&sysinfo);/*獲取系統(tǒng)ipipe信息*/

if(timerfreq_arg==0)
timerfreq_arg=sysinfo.sys_hrtimer_freq;

if(clockfreq_arg==0)
clockfreq_arg=sysinfo.sys_hrclock_freq;

cobalt_pipeline.timer_freq=timerfreq_arg;
cobalt_pipeline.clock_freq=clockfreq_arg;

if(cobalt_machine.init){
ret=cobalt_machine.init();/*mach_x86_init*/
if(ret)
returnret;
}

ipipe_register_head(&xnsched_realtime_domain,"Xenomai");
......

ret=xnclock_init(cobalt_pipeline.clock_freq);/*初始化xnclock,為Cobalt提供clock服務(wù)時(shí)鐘*/
return0;

首先調(diào)用ipipe_select_timers()來(lái)為每個(gè)cpu選擇一個(gè)ipipe_timer。

intipipe_select_timers(conststructcpumask*mask)
{
unsignedhrclock_freq;
unsignedlonglongtmp;
structipipe_timer*t;
structclock_event_device*evtdev;
unsignedlongflags;
unsignedcpu;
cpumask_tfixup;

.......
if(__ipipe_hrclock_freq>UINT_MAX){
tmp=__ipipe_hrclock_freq;
do_div(tmp,1000);
hrclock_freq=tmp;
}else
hrclock_freq=__ipipe_hrclock_freq;/*1000ULL*cpu_khz*/

.......
for_each_cpu(cpu,mask){/*從timers為每一個(gè)CPU選擇一個(gè)percpu_timer*/
list_for_each_entry(t,&timers,link){/*遍歷ipipe全局timer鏈表*/
if(!cpumask_test_cpu(cpu,t->cpumask))
continue;

evtdev=t->host_timer;
if(evtdev&&clockevent_state_shutdown(evtdev))/*該CPUtimer被軟件shutdown則跳過(guò)*/
continue;
gotofound;
}
....
gotoerr_remove_all;
found:
install_pcpu_timer(cpu,hrclock_freq,t);/*設(shè)置每一個(gè)CPU的timer*/
}
.......
flags=ipipe_critical_enter(ipipe_timer_request_sync);
ipipe_timer_request_sync();/*如果支持,則切換到單觸發(fā)模式。*/
ipipe_critical_exit(flags);
.......
}

先得到從全局變量cpu_khz得到tsc頻率保存到hrclock_freq,然后為xenomai運(yùn)行的每一個(gè)cpu核進(jìn)行ippie_timer選擇,對(duì)每一個(gè)遍歷全局鏈表timers,取出evtdev,看是否能為該cpu服務(wù),并且沒(méi)有處于關(guān)閉狀態(tài)。evtdev在Linux沒(méi)有被使用就會(huì)被Linux關(guān)閉。最后選出來(lái)的也就是lapic-timer 。
找到合適的tevtdev后調(diào)用install_pcpu_timer(cpu, hrclock_freq, t),為該cpu設(shè)置ipipe_timer:

staticvoidinstall_pcpu_timer(unsignedcpu,unsignedhrclock_freq,
structipipe_timer*t)
{
per_cpu(ipipe_percpu.hrtimer_irq,cpu)=t->irq;
per_cpu(percpu_timer,cpu)=t;
config_pcpu_timer(t,hrclock_freq);
}

主要是設(shè)置幾個(gè)xenomai相關(guān)的precpu變量,ipipe_percpu.hrtimer_irq設(shè)置為該evtdev的irq,percpu_timer為該evtdev對(duì)應(yīng)的ipipe_timer,然后計(jì)算ipipe_timer中l(wèi)apic-timer與tsc頻率之間的轉(zhuǎn)換因子c2t_integ、c2t_frac;

回到ipipe_select_timers(),通過(guò)ipipe給每一個(gè)cpu發(fā)送一個(gè)中斷IPIPE_CRITICAL_IPI,將每一個(gè)lapic-timer通過(guò)ipipe_timer->request設(shè)置為oneshot模式。

回到mach_setup(),為每個(gè)cpu選出ipipe_timer后獲取此時(shí)系統(tǒng)信息:ipipe_get_sysinfo(&sysinfo)

intipipe_get_sysinfo(structipipe_sysinfo*info)
{
info->sys_nr_cpus=num_online_cpus();/*運(yùn)行的cpu數(shù)據(jù)*/
info->sys_cpu_freq=__ipipe_cpu_freq;/*1000ULL*cpu_khz*/
info->sys_hrtimer_irq=per_cpu(ipipe_percpu.hrtimer_irq,0);/*cpu0的ipipe_timer中斷號(hào)*/
info->sys_hrtimer_freq=__ipipe_hrtimer_freq;/*time的頻率*/
info->sys_hrclock_freq=__ipipe_hrclock_freq;/*1000ULL*cpu_khz*/

return0;
}

在這里還是覺(jué)得有問(wèn)題,CPU和TSC、timer三者頻率不一定相等。

這幾個(gè)變量在接下來(lái)初始化xnclock中使用。xnclock_init(cobalt_pipeline.clock_freq):

int__initxnclock_init(unsignedlonglongfreq)
{
xnclock_update_freq(freq);
nktimerlat=xnarch_timer_calibrate();
xnclock_reset_gravity(&nkclock);/*reset_core_clock_gravity*/
xnclock_register(&nkclock,&xnsched_realtime_cpus);

return0;
}

xnclock_update_freq(freq)計(jì)算出tsc頻率與時(shí)間ns單位的轉(zhuǎn)換因子tsc_scale,tsc_shift,計(jì)算流程可參考文檔實(shí)時(shí)內(nèi)核與linux內(nèi)核時(shí)鐘漂移過(guò)大原因.docx

xnarch_timer_calibrate()計(jì)算出每次對(duì)硬件timer編程這個(gè)執(zhí)行過(guò)程需要多長(zhǎng)時(shí)間,也就是測(cè)量ipipe_timer_set()這個(gè)函數(shù)的執(zhí)行時(shí)間nktimerlat,計(jì)算方法是這樣先確保測(cè)量這段時(shí)間timer不會(huì)觸發(fā)中斷干擾,所以先用ipipe_timer_set()給硬件timer設(shè)置一個(gè)很長(zhǎng)的超時(shí)值,然后開(kāi)始測(cè)量,先從TSC讀取現(xiàn)在的時(shí)間tick值t0,然后循環(huán)執(zhí)行100次ipipe_timer_set(),接著從TSC讀取現(xiàn)在的時(shí)間tick值t1,ipipe_timer_set()平均每次的執(zhí)行時(shí)間是ef6973c0-6b8b-11ed-8abf-dac502259ad0.png,為了算上其他可能的延遲5%,nktimerlat=(t1?t0)/105;

下面計(jì)算對(duì)kernel、user、irq xntimer精確定時(shí)的gravity,上面已經(jīng)說(shuō)過(guò)為甚需要這個(gè),xnclock_reset_gravity(&nkclock)調(diào)用執(zhí)行xnclock->ops.reset_gravity(),也就是reset_core_clock_gravity()函數(shù):

staticvoidreset_core_clock_gravity(structxnclock*clock)
{
structxnclock_gravitygravity;

xnarch_get_latencies(&gravity);
gravity.user+=nktimerlat;
if(gravity.kernel==0)
gravity.kernel=gravity.user;
if(gravity.irq==0)
gravity.irq=nktimerlat;
set_core_clock_gravity(clock,&gravity);
}

首先通過(guò)xnarch_get_latencies()函數(shù)來(lái)計(jì)算各空間的gravity,其實(shí)這個(gè)函數(shù)里沒(méi)有具體的計(jì)算流程,給的都是一些經(jīng)驗(yàn)值,要么我們自己編譯時(shí)配置:

staticinlinevoidxnarch_get_latencies(structxnclock_gravity*p)
{
unsignedlongsched_latency;

#ifCONFIG_XENO_OPT_TIMING_SCHEDLAT!=0
sched_latency=CONFIG_XENO_OPT_TIMING_SCHEDLAT;
#else/*!CONFIG_XENO_OPT_TIMING_SCHEDLAT*/

if(strcmp(ipipe_timer_name(),"lapic")==0){
#ifdefCONFIG_SMP
if(num_online_cpus()>1)
sched_latency=3350;
else
sched_latency=2000;
#else/*!SMP*/
sched_latency=1000;
#endif/*!SMP*/
}elseif(strcmp(ipipe_timer_name(),"pit")){/*HPET*/
#ifdefCONFIG_SMP
if(num_online_cpus()>1)
sched_latency=3350;
else
sched_latency=1500;
#else/*!SMP*/
sched_latency=1000;
#endif/*!SMP*/
}else{
sched_latency=(__get_bogomips()user=sched_latency;
p->kernel=CONFIG_XENO_OPT_TIMING_KSCHEDLAT;
p->irq=CONFIG_XENO_OPT_TIMING_IRQLAT;
}

首先判斷宏CONFIG_XENO_OPT_TIMING_SCHEDLAT 如果不等于0,說(shuō)明我們自己配置了這個(gè)數(shù),直接賦值就行,否則的話(huà),根據(jù)xenomai使用的定時(shí)器是lapic 還是hept給不同的一些經(jīng)驗(yàn)值了:

p->user=CONFIG_XENO_OPT_TIMING_SCHEDLAT;
p->kernel=CONFIG_XENO_OPT_TIMING_KSCHEDLAT;
p->irq=CONFIG_XENO_OPT_TIMING_IRQLAT;

CONFIG_XENO_OPT_TIMING_SCHEDLAT宏在內(nèi)核編譯時(shí)設(shè)置,默認(rèn)為0,使用已有的經(jīng)驗(yàn)值:

[*]Xenomai/cobalt--->
Latencysettings--->
(0)Userschedulinglatency(ns)
(0)Intra-kernelschedulinglatency(ns)
(0)Interruptlatency(ns)

實(shí)際使用后發(fā)現(xiàn)這個(gè)經(jīng)驗(yàn)值也不太準(zhǔn),從測(cè)試數(shù)據(jù)看i5處理器與賽揚(yáng)就存在差別,如果開(kāi)啟內(nèi)核trace,就更不準(zhǔn)了.

計(jì)算出gravity后加上ipipe_timer_set()執(zhí)行需要的時(shí)間nktimerlat,就是最終的gravity。以用戶(hù)空間實(shí)時(shí)程序定時(shí)為例如下(圖中時(shí)間段與比例無(wú)關(guān)):

ef793602-6b8b-11ed-8abf-dac502259ad0.png

到此mach_setup()函數(shù)中上層軟件時(shí)鐘相關(guān)初始化完了,但xenomai還不能直接對(duì)硬件timer,此時(shí)xenomai進(jìn)程調(diào)度還沒(méi)初始化,硬件timer與內(nèi)核調(diào)度等息息相關(guān),xenomai內(nèi)核還不能掌管硬件timer,不能保證linux愉快運(yùn)行,硬搶過(guò)來(lái)只能一起陣亡。等xenomai內(nèi)核任務(wù)管理等初始化完畢,給Linux舒適的運(yùn)行空間,就可以直接控制硬件timer了,下面繼續(xù)解析這個(gè)函數(shù)sys_init();

2.5 xenomai接管lapic-timer

sys_init()涉及每個(gè)CPU上的調(diào)度結(jié)構(gòu)體初始化等。先插以點(diǎn)內(nèi)容,每個(gè)cpu上的xenomai 調(diào)度由對(duì)象xnsched來(lái)管理,xnsched對(duì)象每個(gè)cpu有一個(gè),其中包含各類(lèi)sched class,還包含兩個(gè)xntimer,一個(gè)host timer ---htimer,主要給linux定時(shí),另一個(gè)循環(huán)計(jì)時(shí)timer rrbtimer;一個(gè)xnthead結(jié)構(gòu)rootcb,xenomai調(diào)度的是線(xiàn)程,每個(gè)實(shí)時(shí)線(xiàn)程使用xnthead結(jié)構(gòu)表示,這個(gè)rootcb表示本cpu上x(chóng)enomai調(diào)度的linux,在雙核下linux只是xenomai的一個(gè)idle任務(wù),cpu0上x(chóng)nsched結(jié)構(gòu)如下:

ef96dfb8-6b8b-11ed-8abf-dac502259ad0.png

詳細(xì)的結(jié)構(gòu)后面會(huì)分析,這里只解析時(shí)鐘相關(guān)部分。

static__initintsys_init(void)
{
structxnsched*sched;
void*heapaddr;
intret,cpu;

if(sysheap_size_arg==0)
sysheap_size_arg=CONFIG_XENO_OPT_SYS_HEAPSZ;/**/

heapaddr=xnheap_vmalloc(sysheap_size_arg*1024);/*256*1024*/
.....
xnheap_set_name(&cobalt_heap,"systemheap");

for_each_online_cpu(cpu){
sched=&per_cpu(nksched,cpu);
xnsched_init(sched,cpu);
}

#ifdefCONFIG_SMP
ipipe_request_irq(&xnsched_realtime_domain,
IPIPE_RESCHEDULE_IPI,
(ipipe_irq_handler_t)__xnsched_run_handler,
NULL,NULL);
#endif
xnregistry_init();

/*
*Ifstartinginstoppedmode,doallinitializations,butdo
*notenablethecoretimer.
*/
if(realtime_core_state()==COBALT_STATE_WARMUP){
ret=xntimer_grab_hardware();/*霸占硬件host定時(shí)器*/
.....
set_realtime_core_state(COBALT_STATE_RUNNING);/*更新實(shí)時(shí)內(nèi)核狀態(tài)*/
}

return0;
}

sys_init()中先初始化內(nèi)核堆空間,初始化每個(gè)CPU上的調(diào)度結(jié)構(gòu)體xnsched、創(chuàng)建idle線(xiàn)程,也就是上面說(shuō)到的roottcb,多cpu核調(diào)度等,經(jīng)過(guò)這一些步驟,LInux已經(jīng)變成xenomai的一個(gè)idle線(xiàn)程了,最后調(diào)用xntimer_grab_hardware(),接管硬件timer:

intxntimer_grab_hardware(void)
{
structxnsched*sched;
intret,cpu,_cpu;
spl_ts;

.......
nkclock.wallclock_offset=
xnclock_get_host_time()-xnclock_read_monotonic(&nkclock);

ret=xntimer_setup_ipi();ipipe_request_irq(&xnsched_realtime_domain,IPIPE_HRTIMER_IPI,
(ipipe_irq_handler_t)xnintr_core_clock_handler,NULL,NULL);

for_each_realtime_cpu(cpu){
ret=grab_hardware_timer(cpu);
if(ret1)
xntimer_start(&sched->htimer,ret,ret,XN_RELATIVE);
elseif(ret==1)
xntimer_start(&sched->htimer,0,0,XN_RELATIVE);

#ifdefCONFIG_XENO_OPT_WATCHDOG/*啟動(dòng)看門(mén)狗定時(shí)器*/
xntimer_start(&sched->wdtimer,1000000000UL,1000000000UL,XN_RELATIVE);
xnsched_reset_watchdog(sched);
#endif
xnlock_put_irqrestore(&nklock,s);
}

......
returnret;
}

注冊(cè)xnclock時(shí)nkclock.wallclock_offset沒(méi)有設(shè)置,現(xiàn)在設(shè)置也就是walltime的時(shí)間與tsc 的時(shí)間偏移。然后注冊(cè)IPIPE_HRTIMER_IPI中斷到xnsched_realtime_domain,9.2xntimer那一節(jié)啟動(dòng)一個(gè)xntimer需要通知其他cpu處理時(shí)發(fā)送的IPIPE_HRTIMER_IPI:

intxntimer_setup_ipi(void)
{
returnipipe_request_irq(&xnsched_realtime_domain,
IPIPE_HRTIMER_IPI,
(ipipe_irq_handler_t)xnintr_core_clock_handler,
NULL,NULL);
}

接下來(lái)就是重要的為每個(gè)cpu接管硬件timer了,其實(shí)過(guò)程也簡(jiǎn)單,就是將原來(lái)lcock event的一些操作函數(shù)替換來(lái)達(dá)到目的,每個(gè)cpu上x(chóng)enomai調(diào)度管理結(jié)構(gòu)xnsched,每個(gè)xnsched中有一個(gè)定時(shí)器htimer,這個(gè)xntimer就是為linux服務(wù)的,根據(jù)底層timer的類(lèi)型,后啟動(dòng)htimer,htimer 推動(dòng)linux繼時(shí)間子系統(tǒng)運(yùn)行。這些后面會(huì)詳細(xì)解析?;氐浇庸躷imer函數(shù)grab_hardware_timer(cpu):

staticintgrab_hardware_timer(intcpu)
{
inttickval,ret;

ret=ipipe_timer_start(xnintr_core_clock_handler,
switch_htick_mode,program_htick_shot,cpu);
switch(ret){
caseCLOCK_EVT_MODE_PERIODIC:
/*
*Oneshottickemulationcallbackwon'tbeused,ask
*thecallertostartaninternaltimerforemulating
*aperiodictick.
*/
tickval=1000000000UL/HZ;
break;

caseCLOCK_EVT_MODE_ONESHOT:
/*oneshottickemulation*/
tickval=1;
break;

caseCLOCK_EVT_MODE_UNUSED:
/*wedon'tneedtoemulatethetickatall.*/
tickval=0;
break;

caseCLOCK_EVT_MODE_SHUTDOWN:
return-ENODEV;

default:
returnret;
}

returntickval;
}

主要的操作在ipipe_timer_start(xnintr_core_clock_handler,switch_htick_mode, program_htick_shot, cpu),后面的就是判斷這個(gè)timer工作在什么模式,相應(yīng)的返回好根據(jù)模式設(shè)置htimer為linux服務(wù);

ipipe_timer_start(xnintr_core_clock_handler,switch_htick_mode, program_htick_shot, cpu)其中的xnintr_core_clock_handler是lapic-timer 產(chǎn)生中斷時(shí)xenomai內(nèi)核的處理函數(shù),里面會(huì)去處理每個(gè)xntimer以及xenomai調(diào)度;switch_htick_mode是lapic-timer工作模式切換函數(shù),program_htick_shot函數(shù)是對(duì)sched->htimer重新定時(shí)的函數(shù),這個(gè)函數(shù)對(duì)linux來(lái)說(shuō)特別重要,以后linux就不直接對(duì)硬件timer設(shè)置定時(shí)了,而是給xenomai中的sched->htimer設(shè)置。下面是ipipe_timer_start代碼:

intipipe_timer_start(void(*tick_handler)(void),
void(*emumode)(enumclock_event_modemode,
structclock_event_device*cdev),
int(*emutick)(unsignedlongevt,
structclock_event_device*cdev),
unsignedintcpu)
{
structgrab_timer_datadata;
intret;

data.tick_handler=tick_handler;/*xnintr_core_clock_handler*/
data.emutick=emutick;/*program_htick_shot*/
data.emumode=emumode;/*switch_htick_mode*/
data.retval=-EINVAL;
ret=smp_call_function_single(cpu,grab_timer,&data,true);/*執(zhí)行g(shù)rab_timer*/

returnret?:data.retval;
}

先將傳入的幾個(gè)函數(shù)指正存到結(jié)構(gòu)體data,然后調(diào)用smp_call_function_single傳給函數(shù)grab_timer處理,smp_call_function_single中的smp表示給指定的cpu去執(zhí)行g(shù)rab_timer,對(duì)應(yīng)的cpu執(zhí)行g(shù)rab_timer(&data):

staticvoidgrab_timer(void*arg)
{
structgrab_timer_data*data=arg;
structclock_event_device*evtdev;
structipipe_timer*timer;
structirq_desc*desc;
unsignedlongflags;
intsteal,ret;

flags=hard_local_irq_save();

timer=this_cpu_read(percpu_timer);
evtdev=timer->host_timer;

ret=ipipe_request_irq(ipipe_head_domain,timer->irq,
(ipipe_irq_handler_t)data->tick_handler,
NULL,__ipipe_ack_hrtimer_irq);
if(retretval=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)){/*啟動(dòng)oneshot*/
ret=clockevents_program_event(evtdev,
evtdev->next_event,true);
if(ret)
data->retval=ret;
}
}

首先從percpu_timer取出我們?cè)趇pipe_select_timers選擇的那個(gè)clockevent device evtdev,現(xiàn)在要這個(gè)evtdev為xenomai服務(wù),所以將它的中斷注冊(cè)到ipipe_head_domain,當(dāng)中斷來(lái)的時(shí)候后ipipe會(huì)交給ipipe_head_domain調(diào)用data->tick_handler也就是xnintr_core_clock_handler處理,xnintr_core_clock_handler中處理xenomai在本CPU當(dāng)上的調(diào)度、定時(shí)等。

在struct clock_event_device中ipipe添加了一個(gè)標(biāo)志位ipipe_stolen用來(lái)表示該evtdev是不是已經(jīng)為實(shí)時(shí)系統(tǒng)服務(wù),是就是1,否則為0,這里當(dāng)然為0,先將原來(lái)evtdev的操作函數(shù)備份到’orig_‘打頭的成員變量中,設(shè)置ipipe_timer的real_mult、real_shift為evtdev的mult、shift,原evtdev的mult、shift設(shè)置為1、0,linux計(jì)算的時(shí)候才能與xntimer定時(shí)時(shí)間對(duì)應(yīng)起來(lái)。

最重要的是把原來(lái)evtdev->set_next_event設(shè)置成了program_htick_shot,program_htick_shot如下,從此linux就是對(duì)shched->htimer 定時(shí)器設(shè)置定時(shí),來(lái)替代原來(lái)的evtdev:

staticintprogram_htick_shot(unsignedlongdelay,
structclock_event_device*cdev)
{
structxnsched*sched;
intret;
spl_ts;

xnlock_get_irqsave(&nklock,s);
sched=xnsched_current();
ret=xntimer_start(&sched->htimer,delay,XN_INFINITE,XN_RELATIVE);/*相對(duì),單次定時(shí)*/
xnlock_put_irqrestore(&nklock,s);

returnret?-ETIME:0;
}

其余的最后如果evtdev中斷沒(méi)有使能就使能中斷,evtdev是oneshot狀態(tài)啟動(dòng)oneshot,到此xenomai掌管了lpic-tiemr,從此xenomai內(nèi)核直接設(shè)置lpic-tiemr,lpic-tiemr到時(shí)產(chǎn)生中斷,ipipe調(diào)用執(zhí)行xnintr_core_clock_handler處理lpic-tiemr中斷,xnintr_core_clock_handler處理xenomai時(shí)鐘系統(tǒng):

voidxnintr_core_clock_handler(void)
{
structxnsched*sched=xnsched_current();
intcpu__maybe_unused=xnsched_cpu(sched);
xnstat_exectime_t*prev;

if(!xnsched_supported_cpu(cpu)){
#ifdefXNARCH_HOST_TICK_IRQ
ipipe_post_irq_root(XNARCH_HOST_TICK_IRQ);
#endif
return;
}

......

++sched->inesting;/*中斷嵌套++*/
sched->lflags|=XNINIRQ;/*在中斷上下文狀態(tài)*/

xnlock_get(&nklock);
xnclock_tick(&nkclock);/*處理一個(gè)時(shí)鐘tick*/
xnlock_put(&nklock);

trace_cobalt_clock_exit(per_cpu(ipipe_percpu.hrtimer_irq,cpu));
xnstat_exectime_switch(sched,prev);

if(--sched->inesting==0){/*如果沒(méi)有其他中斷嵌套,執(zhí)行從新調(diào)度*/
sched->lflags&=~XNINIRQ;
xnsched_run();/*調(diào)度*/
sched=xnsched_current();
}
/*
*Ifthecoreclockinterruptpreemptedareal-timethread,
*anytransitiontotherootthreadhasalreadytriggereda
*hosttickpropagationfromxnsched_run(),soatthispoint,
*weonlyneedtopropagatethehosttickincasethe
*interruptpreemptedtherootthread.
*/
if((sched->lflags&XNHTICK)&&
xnthread_test_state(sched->curr,XNROOT))
xnintr_host_tick(sched);
}

xnintr_core_clock_handler中,首先判斷產(chǎn)生這個(gè)中斷的cpu屬不屬于實(shí)時(shí)調(diào)度cpu,如果不屬于,那就把中斷post到root域后直接返回,ipipe會(huì)在root域上掛起這個(gè)中斷給linux處理。

如果這是運(yùn)行xenomai的cpu,接下來(lái)調(diào)用xnclock_tick(&nkclock),來(lái)處理一個(gè)時(shí)鐘tick,里面就是看該cpu上哪些xntimer到期了做相應(yīng)處理:

voidxnclock_tick(structxnclock*clock)
{
structxnsched*sched=xnsched_current();
structxntimer*timer;
xnsticks_tdelta;
xntimerq_t*tmq;
xnticks_tnow;
xntimerh_t*h;

atomic_only();
......
tmq=&xnclock_this_timerdata(clock)->q;/**/

/*
*Optimisation:anylocaltimerreprogrammingtriggeredby
*invokedtimerhandlerscanwaituntilweleavethetick
*handler.Usethisstatusflagashinttoxntimer_start().
*/
sched->status|=XNINTCK;
now=xnclock_read_raw(clock);
while((h=xntimerq_head(tmq))!=NULL){
timer=container_of(h,structxntimer,aplink);
delta=(xnsticks_t)(xntimerh_date(&timer->aplink)-now);
if(delta>0)
break;

trace_cobalt_timer_expire(timer);

xntimer_dequeue(timer,tmq);
xntimer_account_fired(timer);/*timer->fired++*/

/*
*Bypostponingthepropagationofthelow-priority
*hostticktotheinterruptepilogue(see
*xnintr_irq_handler()),wesavesomeI-cache,which
*translatesintopreciousmicrosecsonlow-endhw.
*/
if(unlikely(timer==&sched->htimer)){
sched->lflags|=XNHTICK;
sched->lflags&=~XNHDEFER;
if(timer->status&XNTIMER_PERIODIC)
gotoadvance;
continue;
}

/*Checkforalockedclockstate(i.e.ptracing).*/
if(unlikely(nkclock_lock>0)){
if(timer->status&XNTIMER_NOBLCK)
gotofire;
if(timer->status&XNTIMER_PERIODIC)
gotoadvance;
/*
*Wehavenoperiodforthisblockedtimer,
*sohaveittickagainatareasonablyclose
*dateinthefuture,waitingfortheclock
*tobeunlockedatsomepoint.Sinceclocks
*areblockedwhensingle-steppingintoan
*applicationusingadebugger,itisfineto
*waitfor250msfortheusertocontinue
*programexecution.
*/
xntimerh_date(&timer->aplink)+=
xnclock_ns_to_ticks(xntimer_clock(timer),
250000000);
gotorequeue;
}
fire:
timer->handler(timer);/******************************/
now=xnclock_read_raw(clock);
timer->status|=XNTIMER_FIRED;
/*
*Onlyrequeueperiodictimerswhichhavenotbeen
*requeued,stoppedorkilled.
*/
if((timer->status&
(XNTIMER_PERIODIC|XNTIMER_DEQUEUED|XNTIMER_KILLED|XNTIMER_RUNNING))!=
(XNTIMER_PERIODIC|XNTIMER_DEQUEUED|XNTIMER_RUNNING))
continue;
advance:
do{
timer->periodic_ticks++;
xntimer_update_date(timer);
}while(xntimerh_date(&timer->aplink)sched!=sched))
continue;
#endif
xntimer_enqueue(timer,tmq);
}

sched->status&=~XNINTCK;

xnclock_program_shot(clock,sched);
}

xnclock_tick里主要處理各種類(lèi)型的xntimer,首先取出本cpu上管理xntimer紅黑樹(shù)的根節(jié)點(diǎn)xntimerq_t,然后開(kāi)始處理,為了安全設(shè)置sched狀態(tài)標(biāo)識(shí)status為XNINTCK,標(biāo)識(shí)該sched正在處理tick,得到現(xiàn)在tsc值now,然后一個(gè)while循環(huán),取出紅黑樹(shù)上定時(shí)最小的那個(gè)xntimer,得到這個(gè)xntimer的時(shí)間date,如果date減去now大于0,說(shuō)明最短定時(shí)的xntimer都沒(méi)有到期,那就不需要繼續(xù)處理,直接跳出循環(huán),執(zhí)行xnclock_program_shot(clock, sched)設(shè)置定時(shí)器下一個(gè)中斷觸發(fā)時(shí)間。

如果有xntimer到期,date減去now小于等于0,首先從紅黑樹(shù)中刪除,然后xntimer.fire加1,表示xntimer到期次數(shù),然后處理,這里邏輯有點(diǎn)繞:

1.如果是sched->htimer,就是為L(zhǎng)inux定時(shí)的,先設(shè)置sched->lflags |= XNHTICK,這個(gè)標(biāo)志設(shè)置的是lflags不是status,因?yàn)閘inux的不是緊急的,后面本cpu沒(méi)有高優(yōu)先級(jí)實(shí)時(shí)任務(wù)運(yùn)行才會(huì)給linux處理。接著判斷是不是一個(gè)周期timer,如果是,goto到advance更新timer時(shí)間date,可能已將過(guò)去幾個(gè)周期時(shí)間了,所有使用循環(huán)一個(gè)一個(gè)周期的增加直到現(xiàn)在時(shí)間now,然后重新插入紅黑樹(shù)。

2.如果這個(gè)xntimer是一個(gè)非阻塞timer,直接跳轉(zhuǎn)fire執(zhí)行handler,并設(shè)置狀態(tài)已經(jīng)FIRED。

3.如果這是一個(gè)非htimer的周期定時(shí)器,那同樣更新時(shí)間后重新加入紅黑樹(shù)。

4.以上都不是就將xntimer重新定時(shí)250ms,加入紅黑樹(shù)。

xnclock_tick執(zhí)行返回后,xnstat_exectime_switch()更新該cpu上每個(gè)域的執(zhí)行時(shí)間,然后如果沒(méi)有其他中斷嵌套則進(jìn)行任務(wù)調(diào)度xnsched_run();

不知經(jīng)過(guò)多少個(gè)rt任務(wù)切換后回到這個(gè)上下文,并且當(dāng)前cpu運(yùn)行l(wèi)inux,上次離開(kāi)這linux的定時(shí)器htimer還沒(méi)處理呢,檢查如果當(dāng)前cpu上運(yùn)行l(wèi)inux,并且sched->lflags中有XNHTICK標(biāo)志,那將中斷通過(guò)ipipe post給linux處理,并清除lflags中的XNHTICK,linux中斷子系統(tǒng)就會(huì)去只執(zhí)行eventhandler,處理linux時(shí)間子系統(tǒng)。

voidxnintr_host_tick(structxnsched*sched)/*Interruptsoff.*/
{
sched->lflags&=~XNHTICK;
#ifdefXNARCH_HOST_TICK_IRQ
ipipe_post_irq_root(XNARCH_HOST_TICK_IRQ);
#endif
}

efb6a19a-6b8b-11ed-8abf-dac502259ad0.png

2.6 xenomai內(nèi)核下Linux時(shí)鐘工作流程

到此時(shí)鐘系統(tǒng)中除調(diào)度相關(guān)的外,一個(gè)CPU上雙核系統(tǒng)時(shí)鐘流程如下圖所示:

efd2643e-6b8b-11ed-8abf-dac502259ad0.png

總結(jié):xenomai內(nèi)核啟動(dòng)時(shí),grab_timer()結(jié)合ipipe通過(guò)替換回調(diào)函數(shù)將原linux系統(tǒng)timer lapic-timer作為xenomai 系統(tǒng)timer,xenomai直接對(duì)層硬件lapic-timer編程,linux退化為xenomai的idle任務(wù),idle任務(wù)的主時(shí)鐘就變成linux的時(shí)鐘來(lái)源,由linux直接對(duì)層硬件lapic-timer編程變成對(duì)idle hrtimer編程。idle hrtimer依附于xenomai時(shí)鐘xnclock,xnclock運(yùn)作來(lái)源于底層硬件lapic-timer。

2.7 gravity

為什么要設(shè)置gravity呢?

xenomai是個(gè)實(shí)時(shí)系統(tǒng)必須保證定時(shí)器的精確,xntimer都是由硬件timer產(chǎn)生中斷后處理的,如果沒(méi)有g(shù)ravity,對(duì)于用戶(hù)空間實(shí)時(shí)任務(wù)RT:假如此時(shí)時(shí)間刻度是0,該任務(wù)定時(shí)10us后觸發(fā)定時(shí)器,10us后,產(chǎn)生了中斷,此時(shí)時(shí)間刻度為10us,開(kāi)始處理xntimer,然后切換回內(nèi)核空間執(zhí)行調(diào)度,最后切換回用戶(hù)空間,從定時(shí)器到期到最后切換回RT也是需要時(shí)間的,已經(jīng)超過(guò)RT所定的10us,因此,需要得到定時(shí)器超時(shí)->回到用戶(hù)空間的這段時(shí)間gravity;不同空間的任務(wù)經(jīng)過(guò)的路徑不一樣,所以針對(duì)kernel、user和irq分別計(jì)算gravity,當(dāng)任務(wù)定時(shí),定時(shí)器到期時(shí)間date-gravity才是xntimer的觸發(fā)時(shí)間。當(dāng)切換回原來(lái)的任務(wù)時(shí)剛好是定時(shí)時(shí)間。

ef793602-6b8b-11ed-8abf-dac502259ad0.png

總結(jié)來(lái)說(shuō)是,CPU執(zhí)行代碼需要時(shí)間,調(diào)度度上下切換需要時(shí)間,中斷、內(nèi)核態(tài)、用戶(hù)態(tài)需要的時(shí)間不一樣,需要將中間的這些時(shí)間排除,這些時(shí)間就是gravity。

2.8 autotune

gravity可以使用xenomai 內(nèi)核代碼中的經(jīng)驗(yàn)值,還可以?xún)?nèi)核編譯時(shí)自定義,除這兩種之外,xenomai還提供了一種自動(dòng)計(jì)算的程序autotune,它的使用需要配合內(nèi)核模塊autotune,編譯內(nèi)核時(shí)選中編譯:

[]Xenomai/cobalt--->
Corefeatures--->
<>Auto-tuning

程序autotune位于/usr/xenomai/sbin目錄下,直接執(zhí)行會(huì)分別計(jì)算irq、kernel、user的gravity;






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10702

    瀏覽量

    209356
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    315

    瀏覽量

    21556
  • 時(shí)鐘中斷
    +關(guān)注

    關(guān)注

    0

    文章

    4

    瀏覽量

    7675

原文標(biāo)題:xenomai+linux雙內(nèi)核下的時(shí)鐘管理機(jī)制

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    [分享]揭開(kāi)zzz 手機(jī)神秘面紗

    揭開(kāi)zzz 手機(jī)神秘面紗zzz 手機(jī)的基礎(chǔ)模型可與諾基亞的 N95 相媲美,歐美的客戶(hù)都為該手機(jī)奇妙的個(gè)性化功能興奮不已。個(gè)性化手機(jī)從外殼的顏色到硬件的配置都可由客戶(hù)自行設(shè)定,甚至?xí)?http
    發(fā)表于 05-31 09:25

    Arduino為什么只有l(wèi)oop和setup函數(shù),揭開(kāi)Arduino的神秘面紗--運(yùn)行機(jī)制

    。。。。。。。。。。。。。。。。。。?運(yùn)行機(jī)制是怎么樣??一系列問(wèn)題。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。然后咱們揭開(kāi)它神秘的面紗-----首先大家可以用代碼瀏覽神器Source
    發(fā)表于 10-30 17:33

    揭開(kāi)深記憶示波器的神秘面紗

    揭開(kāi)深記憶示波器的神秘面紗
    發(fā)表于 09-23 07:56

    蘋(píng)果iPhone手機(jī)神秘面紗揭開(kāi)

    蘋(píng)果iPhone手機(jī)神秘面紗揭開(kāi)       據(jù)國(guó)外媒體報(bào)道,日前,BoyGeniusReport網(wǎng)站公布了號(hào)稱(chēng)是下一代iPhone手機(jī)所用背蓋的照
    發(fā)表于 04-17 16:22 ?470次閱讀

    揭開(kāi)實(shí)時(shí)以太網(wǎng)神秘的面紗

      說(shuō)到實(shí)時(shí)以太網(wǎng),大多數(shù)工程師都比較陌生,什么是實(shí)時(shí)?為什么實(shí)時(shí)?其實(shí)就是一層面紗,今天我們來(lái)給您揭開(kāi)!
    發(fā)表于 05-05 11:48 ?1593次閱讀
    <b class='flag-5'>揭開(kāi)</b>實(shí)時(shí)以太網(wǎng)神秘的<b class='flag-5'>面紗</b>

    一加5神秘面紗揭開(kāi)!外觀圓潤(rùn),攝2000萬(wàn)

    明日Oneplus公司的年度大作一加5將要在北京發(fā)布!數(shù)小時(shí)前,爆料大神evleaks也正式曬出了一加手機(jī)5的官方海報(bào),徹底揭開(kāi)Oneplus5的神秘面紗
    發(fā)表于 06-20 10:44 ?757次閱讀

    Duskers - 揭開(kāi)科幻生存游戲中的神秘面紗

    飛行員無(wú)人駕駛太空船,找到生存的手段,揭開(kāi)宇宙如何成為這個(gè)科幻生存游戲中的巨大墓地的神秘面紗。
    的頭像 發(fā)表于 11-08 06:37 ?3011次閱讀

    蘋(píng)果16寸MacBook Pro將于本周揭開(kāi)神秘面紗

    11月12日消息,消息稱(chēng),已經(jīng)傳聞了半年的16寸MacBook Pro,可能最早在本周揭開(kāi)面紗
    的頭像 發(fā)表于 11-12 15:40 ?3022次閱讀

    MT-001: 揭開(kāi)公式(SNR = 6.02N + 1.76dB)的神秘面紗

    MT-001: 揭開(kāi)公式(SNR = 6.02N + 1.76dB)的神秘面紗
    發(fā)表于 03-20 09:50 ?25次下載
    MT-001: <b class='flag-5'>揭開(kāi)</b>公式(SNR = 6.02N + 1.76dB)的神秘<b class='flag-5'>面紗</b>

    AN40-揭開(kāi)開(kāi)關(guān)電容器過(guò)濾的神秘面紗

    AN40-揭開(kāi)開(kāi)關(guān)電容器過(guò)濾的神秘面紗
    發(fā)表于 04-27 16:42 ?2次下載
    AN40-<b class='flag-5'>揭開(kāi)</b>開(kāi)關(guān)電容器過(guò)濾的神秘<b class='flag-5'>面紗</b>

    如何區(qū)分xenomai、linux系統(tǒng)調(diào)用/服務(wù)

    對(duì)于同一個(gè)POSIX接口應(yīng)用程序,可能既需要xenomai內(nèi)核提供服務(wù)(xenomai 系統(tǒng)調(diào)用),又需要調(diào)用linux內(nèi)核提供服務(wù)(linux內(nèi)核系統(tǒng)調(diào)用),或者既有l(wèi)ibcobal
    的頭像 發(fā)表于 05-10 10:28 ?1912次閱讀

    揭開(kāi)數(shù)字健康應(yīng)用的AI和機(jī)器學(xué)習(xí)的神秘面紗

    在本文中,我們將仔細(xì)研究用于處理生理信號(hào)的算法的整體架構(gòu),并揭開(kāi)其操作的神秘面紗。
    的頭像 發(fā)表于 12-01 15:17 ?544次閱讀

    新能源汽車(chē)的“隱形護(hù)衛(wèi)”,揭開(kāi)WAYON維安Auto TVS的神秘面紗

    新能源汽車(chē)的“隱形護(hù)衛(wèi)”,揭開(kāi)WAYON維安Auto TVS的神秘面紗
    的頭像 發(fā)表于 01-06 13:00 ?836次閱讀
    新能源汽車(chē)的“隱形護(hù)衛(wèi)”,<b class='flag-5'>揭開(kāi)</b>WAYON維安Auto TVS的神秘<b class='flag-5'>面紗</b>

    揭開(kāi)高性能多路復(fù)用數(shù)據(jù)采集系統(tǒng)面紗

    電子發(fā)燒友網(wǎng)站提供《揭開(kāi)高性能多路復(fù)用數(shù)據(jù)采集系統(tǒng)面紗.pdf》資料免費(fèi)下載
    發(fā)表于 11-23 10:06 ?0次下載
    <b class='flag-5'>揭開(kāi)</b>高性能多路復(fù)用數(shù)據(jù)采集<b class='flag-5'>系統(tǒng)</b><b class='flag-5'>面紗</b>

    用智能DAC揭開(kāi)醫(yī)療報(bào)警設(shè)計(jì)的神秘面紗

    電子發(fā)燒友網(wǎng)站提供《用智能DAC揭開(kāi)醫(yī)療報(bào)警設(shè)計(jì)的神秘面紗.pdf》資料免費(fèi)下載
    發(fā)表于 09-14 10:50 ?0次下載
    用智能DAC<b class='flag-5'>揭開(kāi)</b>醫(yī)療報(bào)警設(shè)計(jì)的神秘<b class='flag-5'>面紗</b>