(1). OSCtxSw()函數(shù)
uCOS-II實(shí)質(zhì)上是一個(gè)嵌入式操作系統(tǒng)內(nèi)核,,她只負(fù)責(zé)管理各個(gè)任務(wù),,為每個(gè)任務(wù)分配CPU時(shí)間,,并且負(fù)責(zé)任務(wù)之間的通訊,。內(nèi)核提供的基本服務(wù)是任務(wù)切換,。
一個(gè)任務(wù)通常是一個(gè)無(wú)限循環(huán),可以通過內(nèi)核的專用函數(shù)來(lái)建立,、刪除,、掛起、激活任務(wù),,任務(wù)是絕不會(huì)返回,。事實(shí)上任務(wù)也就是一個(gè)函數(shù),也正因她是一個(gè)特殊的函數(shù),,而且和內(nèi)核調(diào)度直接相關(guān),,所以不能隨便返回和被用戶調(diào)用,而要用內(nèi)核的專用函數(shù)來(lái)“建立”和“刪除”,。所謂的“建立任務(wù)”其實(shí)是在內(nèi)核處對(duì)該函數(shù)進(jìn)行注冊(cè)和相關(guān)數(shù)據(jù)結(jié)構(gòu)的填充,,比如該函數(shù)的入口地址、為函數(shù)分配專門的堆??臻g(為什么要為函數(shù)分配專門的地址空間呢,?我們馬上就會(huì)談到)。“任務(wù)調(diào)度”就是根據(jù)情況(比如時(shí)間片被用完),,來(lái)調(diào)用另一個(gè)被稱為任務(wù)的函數(shù)(我們暫時(shí)稱之為函數(shù)TA),,同時(shí)停止當(dāng)前的一個(gè)任務(wù)(其實(shí)也是一個(gè)函數(shù),我們稱之為TB),。
問題出來(lái)了,,若內(nèi)核象普通函數(shù)那樣直接調(diào)用TA,那么當(dāng)內(nèi)核要重新調(diào)用TB時(shí)怎么知道剛才TB執(zhí)行到哪里了呢?若內(nèi)核為TA和TB分配專用的兩塊空間,,當(dāng)內(nèi)核要調(diào)用其他任務(wù)(其實(shí)就是函數(shù))的時(shí)候先將當(dāng)前任務(wù)(函數(shù))運(yùn)行的地址和狀態(tài)保存起來(lái),,然后當(dāng)要返回前再恢復(fù),當(dāng)然每個(gè)被稱之為任務(wù)的函數(shù)都要有自己獨(dú)立的保存運(yùn)行地址和狀態(tài)的空間,,以免混亂,。那問題就很好解決了。這也就是為什么任務(wù)都有自己的堆??臻g的原因,。
那么新的問題來(lái)了,內(nèi)核是如何調(diào)度的呢,?在這里我們只關(guān)心內(nèi)核要進(jìn)行任務(wù)調(diào)度時(shí)發(fā)生的情況,,而不關(guān)心內(nèi)核為什么及何時(shí)要調(diào)度任務(wù)。這是因?yàn)檫@和移植關(guān)系不大,,各種內(nèi)核對(duì)任務(wù)的調(diào)度算法是不同的,,解決方案也不同。但這些只是些算法上的區(qū)別,,和平臺(tái)關(guān)系不大,。我們只需要將精力集中在內(nèi)核決定要調(diào)度時(shí)會(huì)發(fā)生的事情。在uc/OS-II中若內(nèi)核決定要對(duì)任務(wù)實(shí)行調(diào)度時(shí)最終會(huì)調(diào)用這個(gè)關(guān)鍵的函數(shù)void OSCtxSw(void),,該函數(shù)位于os_cpu_a.asm中,。它其實(shí)是一個(gè)軟件中斷或陷阱。因此有必要在中斷矢量表里分配一個(gè)軟件中斷向量或陷阱給向量該函數(shù),。在我例子中的Vector.c文件中可以很清楚的看到我分配了一個(gè)軟件中斷向量給該函數(shù),。在os_cpu_a.asm文件中除了OSCtxSw()函數(shù)外你還看到了三個(gè)用匯編編寫的函數(shù),我會(huì)依次介紹,。如下是OSCtxSw()函數(shù)的源代碼,。 OSCtxSw: pushn %r15 ; 將r1~r15寄存器壓入當(dāng)前任務(wù)堆棧,(r1~r15是C33中的CPU寄存器) ld.w %r0,%ahr ; 將狀態(tài)寄存器的內(nèi)容轉(zhuǎn)存入r0,r1寄存器 ld.w %r1,%alr ; pushn %r1 ; 將狀態(tài)寄存器壓入堆棧 ld.w %r4,%sp ; 將當(dāng)前的SP指針內(nèi)容保存入r4 xld.w %r5,[OSTCBCur] ; 將當(dāng)前SP指針內(nèi)容存入uc/OS-II的一個(gè)數(shù)據(jù)結(jié)構(gòu): ld.w [%r5],%r4 ; OSTCBCur->OSTCBStkPtr中 xcall OSTaskSwHook ; 調(diào)用用戶接口函數(shù),,允許用戶在任務(wù)切換時(shí)做一些工作 xld.w %r4,[OSTCBHighRdy] ; 得到要切換的任務(wù)的TCB塊 xld.w %r5,OSTCBCur ; 將要切換到的任務(wù)TCB塊放到當(dāng)前TCB塊 xld.w [%r5], %r4 ; xld.w %r5,OSPrioHighRdy ; OSPrioCur = OSPrioHighRdy,,保存要切換到的任務(wù)優(yōu)先級(jí) ld.b %r4,[%r5] xld.w %r5,OSPrioCur xld.b [%r5],%r4 xld.w %r5,[OSTCBHighRdy] ; SP = OSTCBHighRdy->OSTCBStkPtr,得到要切換到的 ld.w %r4,[%r5] ; 任務(wù)SP指針 ld.w %sp, %r4 popn %r1 ld.w %alr,%r1 ; 從要切換到的任務(wù)SP指針中恢復(fù)狀態(tài)寄存器 ld.w %ahr,%r0 popn %r15 ; 從要切換到的任務(wù)SP指針中恢復(fù)r1~r15寄存器 reti ; 從要切換到的任務(wù)SP指針中中斷返回,,這時(shí)自然就回到了要切換到的任務(wù) 該函數(shù)是用匯編寫的,,這就很直接的說明了一個(gè)問題——這個(gè)函數(shù)和uCOS-II的移植直接相關(guān)。OSCtxSw()人為的模仿了一次中斷,,大多數(shù)MCU提供軟件中斷或陷阱指令來(lái)實(shí)現(xiàn)這樣的操作,。必須提供中斷向量給匯編語(yǔ)言函數(shù)OSCtxSw()。任務(wù)切換很簡(jiǎn)單,,將被掛起任務(wù)的微處理器寄存器推入堆棧,,然后將較高優(yōu)先級(jí)的任務(wù)的寄存器值從堆棧中恢復(fù)到寄存器中,。在uCOS-II中,就緒任務(wù)的堆棧結(jié)構(gòu)總是看起來(lái)跟剛剛發(fā)生過中斷一樣,,所有的微處理器的寄存器都保存在堆棧中,。 (2). OSStartHighRdy()函數(shù)
在掌握了最關(guān)鍵的一個(gè)匯編函數(shù)后我們?cè)賮?lái)看看其他匯編函數(shù)。OSStartHighRdy(),,顧名思義是操作系統(tǒng)開始工作時(shí)調(diào)用最高優(yōu)先級(jí)任務(wù)的函數(shù),。它是在OSStart ()中被調(diào)用的,其實(shí)它的原理很簡(jiǎn)單,,你只要理解了OSCtxSw()函數(shù)就能很輕易的理解它,。我們先回顧一下剛才的話“就緒任務(wù)的堆棧結(jié)構(gòu)總是看起來(lái)跟剛剛發(fā)生過中斷一樣”,,那么在操作系統(tǒng)初始化結(jié)束,,但還未進(jìn)行調(diào)度時(shí)任務(wù)的堆棧結(jié)構(gòu)又是什么樣子的呢?uCOS-II是這樣做的,,她在初始化時(shí)將所有已建立任務(wù)的堆棧結(jié)構(gòu)初始化,,并把任務(wù)的首地址放在堆棧中。同時(shí)任務(wù)的堆棧指針指向棧頂,。當(dāng)系統(tǒng)啟動(dòng)開始執(zhí)行第一個(gè)任務(wù)(當(dāng)然是最高優(yōu)先級(jí)的任務(wù))時(shí)就調(diào)用OSStartHighRdy(),,該函數(shù)會(huì)恢復(fù)要執(zhí)行的任務(wù)的狀態(tài)。在它返回時(shí)并沒有使用普通的ret指令而是利用reti指令將初始化時(shí)由操作系統(tǒng)添入的任務(wù)的首地址和狀態(tài)寄存器彈彈出,,(單片機(jī)在進(jìn)入中斷是一般會(huì)自動(dòng)將狀態(tài)寄存器和PC指針同時(shí)入棧,,所以在中斷返回時(shí)要調(diào)用專用的reti指令,它會(huì)將狀態(tài)寄存器和PC指針同時(shí)出棧,。而正常的函數(shù)調(diào)用時(shí)狀態(tài)寄存器是不會(huì)自動(dòng)保存的,,所以ret函數(shù)也不會(huì)同時(shí)恢復(fù)狀態(tài)寄存器)這樣第一個(gè)任務(wù)就啟動(dòng)了。從中你應(yīng)該可以看出OSCtxSw()和OSStartHighRdy()的相似之處了吧,?OSCtxSw()是要將掛起任務(wù)的狀態(tài)保存,,然后恢復(fù)要運(yùn)行的任務(wù)的狀態(tài)。而OSStartHighRdy()只需要將要運(yùn)行的任務(wù)的狀態(tài)恢復(fù)就行了,。所以這部分源代碼也非常相似,,你自己也一定看得懂。 (3). OSIntCtxSw()函數(shù) 接下來(lái)我們來(lái)看看OSIntCtxSw()函數(shù),,該函數(shù)是在中斷中對(duì)任務(wù)進(jìn)行切換時(shí)被OSIntExit()調(diào)用的,。注意因?yàn)槭窃谥袛嘀斜徽{(diào)用的,所以O(shè)SIntCtxSw()認(rèn)為所有狀態(tài)寄存器已經(jīng)被保存,。用戶在中斷中要進(jìn)行任務(wù)調(diào)度時(shí)尤其要注意這點(diǎn),。還有,要強(qiáng)調(diào)OSIntCtxSw()是在OSIntExit()中被調(diào)用的,,而OSIntExit()要和OSIntEnter()成對(duì)使用,,即用戶想在中斷函數(shù)中調(diào)度任務(wù)的話一定要在進(jìn)入中斷時(shí)調(diào)用OSIntEnter()在離開中斷前調(diào)用OSIntExit(),。別忘了!還要在一進(jìn)入中斷是最先調(diào)用OS_SAVEALL()它會(huì)幫你把所有的寄存器都保存起來(lái),,在即將退出中斷前調(diào)用OS_RESTOREALL(),。OSIntCtxSw()的原理也和OSCtxSw()相似,只是少了保存狀態(tài)寄存器這一環(huán)而已,。值得一提的是OSIntCtxSw()是在OSIntExit()中被調(diào)用的,,而在OSIntCtxSw()返回時(shí)就進(jìn)入了新的任務(wù),并不是從中斷返回時(shí)再進(jìn)入新的任務(wù)的,,因此在OSIntCtxSw()里首先要調(diào)整堆棧指針的位置,。 (4). OSTickISR()函數(shù) 接下來(lái)我們來(lái)看看最后一個(gè)函數(shù)OSTickISR(),這個(gè)函數(shù)其實(shí)就是一個(gè)時(shí)鐘中斷函數(shù),,就是它為系統(tǒng)提供所謂的時(shí)間片,。既然作為一個(gè)中斷函數(shù)你就必須給他分配中斷向量。在我的Vector.c文件中你也能看到,。它還會(huì)為一個(gè)稱之為OSIntNesting的全局變量加一,,為什么加一我們就不討論了。反正你要移植的時(shí)候也別忘了給OSIntNesting變量加一就行了,。 2. os_cpu_c.c的說明 和os_cpu_a.asm一樣,,os_cpu_c.c也是和移植密切相關(guān)的一個(gè)文件,只不過是用C語(yǔ)言寫的,。在該文件中最重要的是如下這個(gè)函數(shù): OS_STK *OSTaskStkInit (INT32U *pd, void *pdata, INT32U *ptos, INT16U opt) { INT32U *stk; opt = opt; /* 'opt' is not used, prevent warning */ stk = (INT32U)ptos; /* Load stack pointer */ *stk-- = (INT32U)pd;//return address *stk-- = (INT32U)0x10;//psr, Interrupts enabled *stk-- = (INT32U)0;//r15 *stk-- = (INT32U)0;//r14 *stk-- = (INT32U)0;//r13 *stk-- = (INT32U)0;//r12 *stk-- = (INT32U)0;//r11 *stk-- = (INT32U)0;//r10 *stk-- = (INT32U)0;//r9 *stk-- = (INT32U)0;//r8 *stk-- = (INT32U)0;//r7 *stk-- = (INT32U)0;//r6 *stk-- = (INT32U)0;//r5 *stk-- = (INT32U)0;//r4 *stk-- = (INT32U)0;//r3 *stk-- = (INT32U)0;//r2 *stk-- = (INT32U)0;//r1 *stk-- = (INT32U)0;//r0 *stk-- = (INT32U)0;//alr *stk = (INT32U)0;//ahr return ((void *)stk); } 我們?cè)俅位仡櫼幌?#8220;就緒任務(wù)的堆棧結(jié)構(gòu)總是看起來(lái)跟剛剛發(fā)生過中斷一樣”這句話,,那么在你要移植的系統(tǒng)初始化時(shí)要將任務(wù)堆棧變成什么樣子呢?通過這個(gè)函數(shù),!操作系統(tǒng)在調(diào)用這個(gè)函數(shù)時(shí)會(huì)傳遞一個(gè)堆棧指針給它,,利用這個(gè)堆棧指針你就可以根據(jù)你系統(tǒng)的要求將堆棧初始化。并且最后返回該堆棧指針,。比如在我的MCU中有16個(gè)通用寄存器,、1個(gè)狀態(tài)寄存器和2個(gè)專用寄存器。每次中斷(或任務(wù)調(diào)度)時(shí)要將她們?nèi)咳霔?,而且我的MCU的堆棧生長(zhǎng)方向是向下的,。參數(shù)pd就是任務(wù)的首地址,通常它應(yīng)該放在棧底,,緊接著任務(wù)首地址的是狀態(tài)寄存器,。再接下來(lái)就是16個(gè)通用寄存器和2個(gè)專用寄存器。16個(gè)通用寄存器的和2個(gè)專用寄存器的保存順序一旦固定,,那么你在進(jìn)入中斷時(shí)的入棧順序就要和這個(gè)函數(shù)一致當(dāng)然出棧順序也要匹配,。在這里要提醒你一點(diǎn),我的MCU是32位的,,對(duì)堆棧操作也是4字節(jié)對(duì)齊的,,所以要將stk指針定義成INT32U,。你的MCU若是16位,且對(duì)堆棧訪問也是2字節(jié)對(duì)齊的,,就要將stk定義成INT16U,。否則,呵呵呵……,! 3. os_cpu.h的說明 在該頭文件中定義了許多操作系統(tǒng)要用到的基本的數(shù)據(jù)類型和變量,。最重要的是你要實(shí)現(xiàn)如下宏: #define OS_TASK_SW() 該宏是操作系統(tǒng)在進(jìn)行任務(wù)切換時(shí)調(diào)用的,一般都定義成一個(gè)軟件中斷,。在我的系統(tǒng)中定義成如下形式: #define OS_TASK_SW() asm("int 3") //任務(wù)切換時(shí)調(diào)用軟件中斷 還有就是要定義堆棧的生長(zhǎng)方向: #define OS_STK_GROWTH 1 //1 表示從高到低方向生長(zhǎng),,0表示從低到高方向生長(zhǎng) StatusReg變量是我自己添加的,它被OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()使用,,主要為了解決通過狀態(tài)寄存器開關(guān)中斷的問題,。 4. os_cfg.h的說明 該文件是用來(lái)配置操作系統(tǒng)的,每個(gè)配置后面都有比較清晰的注解,,我就不羅嗦了,。而且一般情況下你不必修改,。當(dāng)然還是應(yīng)該看看,! 5. ext.s的說明 ext.s文件是我自己添加的,它實(shí)現(xiàn)了OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()函數(shù),,因?yàn)槲业南到y(tǒng)沒有辦法用一條指令來(lái)屏蔽中斷,。用戶只要能實(shí)現(xiàn)OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()這兩個(gè)函數(shù)對(duì)中斷進(jìn)行屏蔽和開放就行了。 |
|
來(lái)自: 歐陽(yáng)小昭 > 《ucos》