nucleus plus深入學(xué)習(xí),這些知識還是挺有用的!
前言: 最近一直都在看nucleus plus,之前看過一些linux內(nèi)核的一些東西,但都是停留在文字上,代碼看的很少,這個nucleus plus內(nèi)核的代碼量不大,看過source code確實對很多OS的知識有了更深入的認(rèn)識,收獲還是挺多的,把學(xué)到的東西記錄下來。
內(nèi)容:
?一、nucleus plus特點: 1.內(nèi)核采用微內(nèi)核的設(shè)計,方便移植,資料寫著更reliable,但是我不這么認(rèn)為,與linux相比,以ARM平臺為例,NU只用到了SVC mode,內(nèi)核與用戶任務(wù)都運(yùn)行在同一個狀態(tài)下,也就是說所有的task都擁有訪問任何資源的權(quán)限,這樣很reliable么? 2.real-time OS,NU是一個軟實時操作系統(tǒng)(VxWorks是硬實時),thread control component支持多任務(wù)以及任務(wù)的搶占,對于中斷的處理定義了兩種服務(wù)方式,LISR和HISR,這個與linux中的上、下半部機(jī)制類似,linux中的下半部是通過軟中斷來實現(xiàn)的,NU的HISR只是作為一種優(yōu)先級總是高于task的任務(wù)出現(xiàn)。 3.NU是以library的方式應(yīng)用的,通過寫自己的app task與裁剪后的NU內(nèi)核及組件鏈接起來,NU并沒有CLI
二、組件
1.IN component 初始化組件由三個部分組成,硬件在reset后首先進(jìn)入INT_initialize(),進(jìn)行板級的相關(guān)初始化,首先設(shè)置SVC mode,關(guān)中斷,然后將內(nèi)核從rom中拷貝至ram中,建立bss段,依次建立sys stack, irq stack和fiq stack,最后初始化timer,建立timer HISR的棧空間,看了一下2410平臺的代碼,一個tick大概是15.8ms,完成板級的初始化后就進(jìn)入了INC_initialize,初始化各個組件,其中包括Application initialize,create task和HISR,最后將控制權(quán)交給schedule,主要看了一下RAM中地址空間的安排 |timer HISR stack = 1024| |FIQ stack = 512| |IRQ stack = 1024| |SVC stack = 1024| |.bss| |.data| |.text| 其中SVC stack的大小與中斷源的個數(shù)相關(guān),nested irq發(fā)生時,irq_context保存在SVC stack中,IRQ的stack只是做了臨時棧的作用。
2.thread control component TC組件是NU內(nèi)核的最重要組成部分,主要涵蓋了調(diào)度、中斷、任務(wù)的相關(guān)操作、鎖、時鐘幾個方面,下面分別介紹。
調(diào)度(schedule)
NU中的線程類型(在同一個地址空間內(nèi))有兩種,HISR和task,HISR可以理解為一種優(yōu)先級較高的task,但又不是task,HISR優(yōu)先級高于task的實現(xiàn)方式就是schdule時,先去查看當(dāng)前是否有active的HISR,再去查看task。task有suspend、ready、finished和terminated四種狀態(tài),而HISR只有executing和no-active這兩種狀態(tài)。
每一個task都有一個線程控制的數(shù)據(jù)結(jié)構(gòu)(TCB thread control block),其中包括了task的優(yōu)先級、狀態(tài)、時間片、task棧、protect信息、signal操作的標(biāo)志位和signal_handler等,task在創(chuàng)建時初始化這些信息,將task掛到一個create_list上,初始設(shè)定task為pure_suspend,如果設(shè)定auto start,調(diào)用resume_task()喚醒task,這里有個細(xì)節(jié),如果在application initialize中create_task(),則task不會自動運(yùn)行,因為初始化還未完成,控制權(quán)還沒有交給schedule,無法調(diào)度task。task被喚醒后狀態(tài)改變?yōu)閞eady,并掛在一個TCD_Priority_List[256]上,數(shù)組的每個元素是一個指向TCB環(huán)形雙向鏈表的指針,根據(jù)task的tc_priority找到對應(yīng)優(yōu)先級的TCB head pointer。
每一個HISR都有一個HISR控制的數(shù)據(jù)結(jié)構(gòu)(HCB HISR control block),其中只有優(yōu)先級,HISR棧和HISR entry信息,因此HISR是不可以suspend,同時也沒有time slice以及signal的相關(guān)操作,一般情況下當(dāng)發(fā)生了中斷后,HISR被activate,schedule就會調(diào)度HISR運(yùn)行,期間如果不發(fā)生中斷,HISR的執(zhí)行是不會被打斷的,HISR的優(yōu)先級只有0、1、2,timer的HISR優(yōu)先級為2,也就是說由外部中斷激活的HISR很難被搶占的,只有更高優(yōu)先級的中斷HISR才可以。與task不同,被激活的HISR使用head_list和tail_list將HCB掛在一個單項的鏈表上,因為相同優(yōu)先級的HISR不會搶占對方,因此不需要雙向鏈表,使用兩個指針目的是加快HISR執(zhí)行的速度。
一個實時操作系統(tǒng)的核心就是對于任務(wù)的調(diào)度,NU的調(diào)度策略是time slice和round robin的算法, 調(diào)度的部分主要有三個函數(shù)control_to_system()用于保存上下文,建立solicited stack,關(guān)中斷,關(guān)system time slice,并重置task的time slice為預(yù)設(shè)值,將sp更新為system_stack_pointer,調(diào)用schedule(),調(diào)度的過程是非常簡單的查詢,就是查看兩個全局的變量,TCD_Execute_HISR和TCD_Execute_Task,schedule部分的關(guān)鍵是打開了中斷,不然如果當(dāng)前沒有ready的task或是被激活的HISR,則shedule死循環(huán)下去,查詢到下一個應(yīng)該執(zhí)行的線程后跳轉(zhuǎn)至control_to_thread(),在這里重新開啟system time slice,然后將線程的tc_stack_ptr加入到sp中,切換至線程的棧中,依次pop出來,即完成了任務(wù)調(diào)度。
任務(wù)的切換主要是上下文的切換,也就是task棧的切換,函數(shù)的調(diào)用會保存部分regs和返回地址,這些動作都是編譯器來完成的,而OS中的任務(wù)切換是運(yùn)行時(runtime)的一種狀態(tài)變化,因此編譯器也無能為力,所以對于上下文的保存需要代碼來實現(xiàn)。
任務(wù)的搶占是異步的因此必須要通過中斷來實現(xiàn),一般每次timer的中斷決定當(dāng)前的task的slice time是否expired,然后設(shè)置TCT_Set_Execute_Task為相同優(yōu)先級的其他task或更高優(yōu)先級的task;高優(yōu)先級的task搶占低優(yōu)先級的task,一般是外部中斷觸發(fā),在HISR中resume_task()喚醒高優(yōu)先級的task,然后schedule到高優(yōu)先級的task中,因為timer的HISR是在系統(tǒng)初始化就已經(jīng)注冊的,只是執(zhí)行timeout和time slice超時后的操作,并沒有執(zhí)行resume_task的動作。
NU中的stack有兩種solicited stack和interrupt stack,solicited stack是一種minmum stack,而interrupt stack是對當(dāng)前所有寄存器全部保存,TCB中的minimum stack size = 申請得到stack size - solicited stack(在arm mode下占44字節(jié),thumb mode下占48字節(jié)),thumb標(biāo)志用來記錄上下文保存時的ARM的工作模式,c代碼編譯為thumb模式,這樣可以減小code size,提高代碼密度,assembly代碼編譯為arm模式提升代碼的效率,NU中內(nèi)核的代碼不多,主要是assembly代碼。stack的類型與其中PC指向的shell無關(guān),interrupt stack保存的是task或是HISR在執(zhí)行的過程中被中斷時的現(xiàn)場,solicited stack建立的地方包括 control_to_system()、schedule_protect()和send_signals()發(fā)送給占有protect資源的task的情況,HISR_Shell()執(zhí)行完后會建立solicited stack,再跳轉(zhuǎn)至schedule。
(Lower Address) Stack Top -> 1 (Interrupt stack type) CPSR Saved CPSR r0 Saved r0 r1 Saved r1 r2 Saved r2 r3 Saved r3 r4 Saved r4 r5 Saved r5 r6 Saved r6 r7 Saved r7 r8 Saved r8 r9 Saved r9 r10 Saved r10 r11 Saved r11 r12 Saved r12 sp Saved sp lr Saved lr (Higher Address) Stack Bottom-> pc Saved pc
(Lower Address) Stack Top -> 0 (Solicited stack type) !!FOR THUMB ONLY!! 0/0x20 Saved state mask r4 Saved r4 r5 Saved r5 r6 Saved r6 r7 Saved r7 r8 Saved r8 r9 Saved r9 r10 Saved r10 r11 Saved r11 r12 Saved r12 (Higher Address) Stack Bottom-> pc Saved pc 一個簡單的例子說明stack的情況,首先是一個task在ready(executing)的狀態(tài)下,而且time slice超時了,timer中斷發(fā)生后,保存task上下文interrupt_contex_save(),在task的tc_stack_ptr指向的地方建立中斷棧 taskA |interrupt stack|___tc_stack_ptr 棧頂端是pc=lr-4 ARM對于中斷的判定發(fā)生在當(dāng)前指令完成execute時,同時pipeline的原因pc=pc+8,入棧時就把lr-4首先放在stack的最高端(high)。
timer的LISR完成后激活了HISR,執(zhí)行TCC_Time_slice()將當(dāng)前task移到相同優(yōu)先級的尾端,并且設(shè)置下一個要執(zhí)行的task,HISR在棧頂端保存的是這個HISR_shell的入口地址,因為task的執(zhí)行完就finished,HISR是可重入的 HISR |solicited stack| 棧頂端是HISR_shell_entry
中斷(interrupt)
前面已經(jīng)提及了中斷的基本操作,這里就寫一些代碼路徑的細(xì)節(jié),中斷的執(zhí)行主要是兩個部分LISR和HISR,分成兩個部分的目的就是將關(guān)中斷的時間最小化,并且在LISR中開中斷允許中斷的嵌套,以及建立中斷優(yōu)先級,都可以減少中斷的延遲,保證OS的實時性。 NU的中斷模式是可重入的中斷處理方式,也就是基于中斷優(yōu)先級和嵌套的模式,中斷的嵌套在處理的過程中應(yīng)對lr_irq_mode寄存器進(jìn)行保存,因為高優(yōu)先級的中斷發(fā)生時會覆蓋掉低優(yōu)先級中斷的r14和spsr,因此要利用系統(tǒng)的棧來保存中斷棧。
NU對于中斷上下文的保存具體操作如下: (1)在中斷發(fā)生后執(zhí)行的入口函數(shù)INT_IRQ()中,將r0-r4保存至irq的棧中 (2)查找到對應(yīng)的interrupt_shell(),clear中斷源,更新全局的中斷計數(shù)器,然后進(jìn)行interrupt_contex_save (3)首先利用r1,r2,r3保存irq模式下的sp,lr,spsr,這里sp是用來切換至系統(tǒng)棧后拷貝lr和spsr的,這里保存lr和spsr是目的是task被搶占后,當(dāng)再次schedule時可以返回task之前的狀態(tài)。 (4)切換至SVC模式,如果是非嵌套的中斷則保存上下文至task stack中,將irq模式下的lr作為頂端PC的返回值入棧,將SVC模式下的r6-r14入棧,將irq模式下的sp保存至r4中入棧,最后將保存在irq_stack中的r0-r4入棧 (5)如果是嵌套中斷,中斷的嵌套發(fā)生在LISR中,在執(zhí)行LISR時已經(jīng)切換至system stack,因此嵌套中斷要將中斷的上下文保存至system stack中,與task stack中interrupt stack相比只是少了棧頂用來標(biāo)記嵌套的標(biāo)志(1 not nested) (6)有一個分支判斷,就是如果當(dāng)前線程是空,即TCD_Current_Thread == NULL,表明當(dāng)前是schedule中,因為初始化線程是關(guān)中斷的,這樣就不為schedule線程建立棧幀,因為schedule不需要保存上下文,在restore中斷上下文時直接跳轉(zhuǎn)至schedule。
中斷上下文的恢復(fù) 全局的中斷計數(shù)器INT_Count是否為0來判定當(dāng)前出棧的信息,如果是嵌套則返回LISR中,否則切換至system stack執(zhí)行schedule
timer timer與中斷緊密相關(guān),其實timer也是中斷的一種,只是發(fā)生中斷的頻率較高,且作用重大,一個實時操作系統(tǒng),時間是非常重要的一部分,NU中的timer主要有四個作用: (1)維護(hù)系統(tǒng)時鐘 TMD_system_clock (2)task的time slice (3)task的suspend timeout timer (4)application timer 其中(3)(4)共用一種機(jī)制,一個全局的時間軸TMD_timer,timeout timer和app timer都建立在一個TM_TCB的數(shù)據(jù)結(jié)構(gòu)上,通過tm_remaining_time來表征當(dāng)前timer的剩余時間,例如當(dāng)前有timer_list上有三個TM_TCB,依次是Ta = 5,Tb = 7, Tc = 20,那么建立的鏈表上剩余時間依次是5,2,8,如果現(xiàn)在要加入一個新的timer根據(jù)timer值插入至合適的位置,如果插入的timer為13,則安排在Tb后面,剩余時間為1,后面的8改為7,當(dāng)發(fā)生了timer expired,則觸發(fā)timer_HISR,如果是app timer則執(zhí)行timer callback,如果是task timeout timer,則執(zhí)行TCC_Task_Timeout喚醒task。
(2)的實現(xiàn)也是依賴于全局的time slice時間軸,每一個task在執(zhí)行時都會將自己的時間片信息更新至全局的時間軸上,當(dāng)一個task的time slice執(zhí)行完在timer HISR中調(diào)用TCC_task_Timeout將當(dāng)前的task放在相同優(yōu)先級list的最尾端,并設(shè)置下一個最高優(yōu)先級的任務(wù)。task在執(zhí)行的過程中只有被中斷后time slice會保存下來,其他讓出處理器的情況都會將time slice更新為預(yù)設(shè)值。
protect protect與linux的鎖機(jī)制類似,互斥訪問,利用開關(guān)中斷來實現(xiàn),并且擁有protect的task是不可以suspend的,必須要將protect釋放后才可以掛起,當(dāng)一個優(yōu)先級較低的task占有protect資源,如果被搶占,一個高優(yōu)先級的task或HISR在請求protect資源時會執(zhí)行TCC_schedule_protect()讓出處理器給低優(yōu)先級的task執(zhí)行,直到低優(yōu)先級的task執(zhí)行unprotect()為止,此時task或HISR建立的是solicited stack,同時在control_to_thread前開關(guān)中斷一次,這樣可以減少一次上下文的切換。NU中常用到的是system_protect,它就是一把大鎖,保護(hù)內(nèi)核中所有全局?jǐn)?shù)據(jù)結(jié)構(gòu)的順序訪問,粒度很大。
LISR中不可以請求protect資源,因為LISR是中斷task后執(zhí)行,如果task占有protect資源,這時LISR又去請求protect資源,會發(fā)生死鎖,因為LISR讓出處理器后,schedule沒辦法再次調(diào)度到LISR執(zhí)行,則發(fā)生死鎖錯誤,因此在LISR中除了activate_HISR()以外不可以使用system call,例如resume_task等等,這寫系統(tǒng)調(diào)用都會請求protect資源。
對于protect的請求按照一定的順序可以防止死鎖,NU的源碼中一般將system_protect資源的請求放在后面,其他如DM_protect先請求。