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