蘇州永節電子科技有限公司
設為首頁 | 收藏本站
新聞詳情

嵌入式開發,單片機開發 UC/OS 的移植

瀏覽數:3
文章附圖

第八章 移植μC/OS-Ⅱ

這一章介紹如何將μC/OS-Ⅱ移植到不同的單片機開發和嵌入式系統上。所謂移植,就是使一個實時內核能在某個微處理器或微控制器上運行。為了方便移植,大部分的μC/OS-Ⅱ代碼是用C語言寫的;但仍需要用C和匯編語言寫一些與處理器相關的代碼,這是因為μC/OS-Ⅱ在讀寫處理器寄存器時只能通過匯編語言來實現。由于μC/OS-Ⅱ在設計時就已經充分考慮了可移植性,所以μC/OS-Ⅱ的移植相對來說是比較容易的。如果已經有人在您使用的處理器上成功地移植了μC/OS-Ⅱ,您也得到了相關代碼,就不必看本章了。當然,本章介紹的內容將有助于用戶了解μC/OS-Ⅱ中與處理器相關的代碼。

要使μC/OS-Ⅱ正常運行,處理器必須滿足以下要求:

1. 處理器的C編譯器能產生可重入代碼。

2
用C語言就可以打開和關閉中斷。

3
處理器支持中斷,并且能產生定時中斷(通常在10至100Hz之間)。

4
處理器支持能夠容納一定量數據(可能是幾千字節)的硬件堆棧。

5
處理器有將堆棧指針和其它CPU寄存器讀出和存儲到堆棧或內存中的指令。

像Motorola 6805系列的處理器不能滿足上面的第4條和第5條要求,所以μC/OS-Ⅱ不能在這類處理器上運行。

圖8.1說明了μC/OS-Ⅱ的結構以及它與硬件的關系。由于μC/OS-Ⅱ為自由軟件,當用戶用到μC/OS-Ⅱ時,有責任公開應用軟件和μC/OS-Ⅱ的配置代碼。這本書和磁盤包含了所有與處理器無關的代碼和Intel 80x86實模式下的與處理器相關的代碼(C編譯器大模式下編譯)。如果用戶打算在其它處理器上使用μC/OS-Ⅱ,最好能找到一個現成的移植實例,如果沒有只好自己編寫了。用戶可以在正式的μC/OS-Ⅱ網站www. μCOS-Ⅱ.com中查找一些移植實例。

圖 8.1 μC/OS-II 硬件和軟件體系結構



如果用戶理解了處理器和C編譯器的技術細節,移植μC/OS-Ⅱ的工作實際上是非常簡單的。前提是您的處理器和編譯器滿足了μC/OS-Ⅱ的要求,并且已經有了必要工具。移植工作包括以下幾個內容:

?用#define設置一個常量的值(OS_CPU.H)

?聲明10個數據類型(OS_CPU.H)

?用#define聲明三個宏(OS_CPU.H)

?用C語言編寫六個簡單的函數(OS_CPU_C.C)

?編寫四個匯編語言函數(OS_CPU_A.ASM)

根據處理器的不同,一個移植實例可能需要編寫或改寫50至300行的代碼,需要的時間從幾個小時到一星期不等。

一旦代碼移植結束,下一步工作就是測試。測試一個象μC/OS-Ⅱ一樣的多任務實時內核并不復雜。甚至可以在沒有應用程序的情況下測試。換句話說,就是讓內核自己測試自己。這樣做有兩個好處:第一,避免使本來就復雜的事情更加復雜;第二,如果出現問題,可以知道問題出在內核代碼上而不是應用程序。剛開始的時候可以運行一些簡單的任務和時鐘節拍中斷服務例程。一旦多任務調度成功地運行了,再添加應用程序的任務就是非常簡單的工作了。

8.00 開發工具

如前所述,移植μC/OS-Ⅱ需要一個C編譯器,并且是針對用戶用的CPU的。因為μC/OS-Ⅱ是一個可剝奪型內核,用戶只有通過C編譯器來產生可重入代碼;C編譯器還要支持匯編語言程序。絕大部分的C編譯器都是為嵌入式系統設計的,它包括匯編器、連接器和定位器。連接器用來將不同的模塊(編譯過和匯編過的文件)連接成目標文件。定位器則允許用戶將代碼和數據放置在目標處理器的指定內存映射空間中。所用的C編譯器還必須提供一個機制來從C中打開和關閉中斷。一些編譯器允許用戶在C源代碼中插入匯編語言。這就使得插入合適的處理器指令來允許和禁止中斷變得非常容易了。還有一些編譯器實際上包括了語言擴展功能,可以直接從C中允許和禁止中斷。

8.01 目錄和文件

本書所付的磁盤中提供了μC/OS-Ⅱ的安裝程序,可在硬盤上安裝μC/OS-Ⅱ和移植實例代碼(Intel 80x86實模式,大模式編譯)。我設計了一個連續的目錄結構,使得用戶更容易找到目標處理器的文件。如果想增加一個其它處理器的移植實例,您可以考慮采取同樣的方法(包括目錄的建立和文件的命名等等)。

所有的移植實例都應放在用戶硬盤的\SOFTWARE\μCOS-Ⅱ目錄下。各個微處理器或微控制器的移植源代碼必須在以下兩個或三個文件中找到:OS_CPU.H,OS_CPU_C.C,OS_CPU_A.ASM。匯編語言文件OS_CPU_A.ASM是可選擇的,因為某些C編譯器允許用戶在C語言中插入匯編語言,所以用戶可以將所需的匯編語言代碼直接放到OS_CPU_C.C中。放置移植實例的目錄決定于用戶所用的處理器,例如在下面的表中所示的放置不同移植實例的目錄結構。注意,各個目錄雖然針對完全不同的目標處理器,但都包括了相同的文件名。



Intel/AMD 80186

\SOFTWARE\uCOS-II\Ix86S






\OS_CPU_A.ASM


\OS_CPU_C.C


\SOFTWARE\uCOS-II\Ix86L


\OS_CPU.H


\OS_CPU_A.ASM


\OS_CPU_C.C

Motorola 68HC11

\SOFTWARE\uCOS-II\68HC11   


\OS_CPU.H


\OS_CPU_A.ASM


\OS_CPU_C.C


8.02 INCLUDES.H

在第一章中曾提到過,INCLUDES.H是一個頭文件,它在所有.C文件的第一行被包含。


#include "includes.h"


INCLUDES.H使得用戶項目中的每個.C文件不用分別去考慮它實際上需要哪些頭文件。使用INCLUDES.H的唯一缺點是它可能會包含一些實際不相關的頭文件。這意味著每個文件的編譯時間可能會增加。但由于它增強了代碼的可移植性,所以我們還是決定使用這一方法。用戶可以通過編輯INCLUDES.H來增加自己的頭文件,但是用戶的頭文件必須添加在頭文件列表的最后。

8.03 OS_CPU.H

OS_CPU.H包括了用#defines定義的與處理器相關的常量,宏和類型定義。OS_CPU.H的大體結構如程序清單 L8.1所示。

程序清單 L 8.1 OS_CPU.H.

#ifdef   OS_CPU_GLOBALS

#define OS_CPU_EXT

#else

#define OS_CPU_EXT   extern

#endif


/*

************************************************************************

*                                數據類型

*                            (與編譯器相關)

************************************************************************

*/


typedef unsigned char   BOOLEAN;

typedef unsigned char   INT8U;      /* 無符號8位整數    */    (1)

typedef signed   char   INT8S;       /* 有符號8位整數    */

typedef unsigned int   INT16U;     /* 無符號16位整數   */

typedef signed   int   INT16S;      /* 有符號16位整數   */

typedef unsigned long   INT32U;     /* 無符號32位整數   */

typedef signed   long   INT32S;      /* 有符號32位整數   */

typedef float          FP32;         /* 單精度浮點數      */ (2)

typedef double         FP64;         /* 雙精度浮點數     */


typedef unsigned int   OS_STK;      /* 堆棧入口寬度為16位 */


/*

*************************************************************************

*                             與處理器相關的代碼

*************************************************************************

*/


#define   OS_ENTER_CRITICAL()   ???   /* 禁止中斷              */ (3)

#define   OS_EXIT_CRITICAL()   ???   /* 允許中斷              */


#define   OS_STK_GROWTH        1    /* 定義堆棧的增長方向: 1=向下, 0=向上 */ (4)


#define   OS_TASK_SW()         ??? (5)


8.03.01 與編譯器相關的數據類型

因為不同的微處理器有不同的字長,所以μC/OS-Ⅱ的移植包括了一系列的類型定義以確保其可移植性。尤其是,μC/OS-Ⅱ代碼從不使用C的short,int和long等數據類型,因為它們是與編譯器相關的,不可移植。相反的,我定義的整型數據結構既是可移植的又是直觀的[L8.1(2)]。為了方便,雖然μC/OS-Ⅱ不使用浮點數據,但我還是定義了浮點數據類型[L8.1(2)]。

例如,INT16U數據類型總是代表16位的無符號整數?,F在,μC/OS-Ⅱ和用戶的應用程序就可以估計出聲明為該數據類型的變量的數值范圍是0-65535。將μC/OS-Ⅱ移植到32位的處理器上也就意味著INT16U實際被聲明為無符號短整型數據結構而不是無符號整型數據結構。但是,μC/OS-Ⅱ所處理的仍然是INT16U。

用戶必須將任務堆棧的數據類型告訴給μC/OS-Ⅱ。這個過程是通過為OS_STK聲明正確的C數據類型來完成的。如果用戶的處理器上的堆棧成員是32位的,并且用戶的編譯文件指定整型為32位數,那么就應該將OS_STK聲明位無符號整型數據類型。所有的任務堆棧都必須用OS_STK來聲明數據類型。

用戶所必須要做的就是查看編譯器手冊,并找到對應于μC/OS-Ⅱ的標準C數據類型。


8.03.02 OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()

與所有的實時內核一樣,μC/OS-Ⅱ需要先禁止中斷再訪問代碼的臨界段,并且在訪問完畢后重新允許中斷。這就使得μC/OS-Ⅱ能夠保護臨界段代碼免受多任務或中斷服務例程(ISRs)的破壞。中斷禁止時間是商業實時內核公司提供的重要指標之一,因為它將影響到用戶的系統對實時事件的響應能力。雖然μC/OS-Ⅱ盡量使中斷禁止時間達到最短,但是μC/OS-Ⅱ的中斷禁止時間還主要依賴于處理器結構和編譯器產生的代碼的質量。通常每個處理器都會提供一定的指令來禁止/允許中斷,因此用戶的C編譯器必須要有一定的機制來直接從C中執行這些操作。有些編譯器能夠允許用戶在C源代碼中插入匯編語言聲明。這樣就使得插入處理器指令來允許和禁止中斷變得很容易了。其它一些編譯器實際上包括了語言擴展功能,可以直接從C中允許和禁止中斷。為了隱藏編譯器廠商提供的具體實現方法,μC/OS-Ⅱ定義了兩個宏來禁止和允許中斷:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()[L8.1(3)]。


{

    OS_ENTER_CRITICAL();

    /* ?μC/OS-II 臨界代碼段 */

    OS_EXIT_CRITICAL();

}


方法1

執行這兩個宏的第一個也是最簡單的方法是在OS_ENTER_CRITICAL()中調用處理器指令來禁止中斷,以及在OS_EXIT_CRITICAL()中調用允許中斷指令。但是,在這個過程中還存在著小小的問題。如果用戶在禁止中斷的情況下調用μC/OS-Ⅱ函數,在從μC/OS-Ⅱ返回的時候,中斷可能會變成是允許的了!如果用戶禁止中斷就表明用戶想在從μC/OS-Ⅱ函數返回的時候中斷還是禁止的。在這種情況下,光靠這種執行方法可能是不夠的。

方法2

執行OS_ENTER_CRITICAL()的第二個方法是先將中斷禁止狀態保存到堆棧中,然后禁止中斷。而執行OS_EXIT_CRITICAL()的時候只是從堆棧中恢復中斷狀態。如果用這個方法的話,不管用戶是在中斷禁止還是允許的情況下調用μC/OS-Ⅱ服務,在整個調用過程中都不會改變中斷狀態。如果用戶在中斷禁止的時候調用μC/OS-Ⅱ服務,其實用戶是在延長應用程序的中斷響應時間。用戶的應用程序還可以用OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()來保護代碼的臨界段。但是,用戶在使用這種方法的時候還得十分小心,因為如果用戶在調用象OSTimeDly()之類的服務之前就禁止中斷,很有可能用戶的應用程序會崩潰。發生這種情況的原因是任務被掛起直到時間期滿,而中斷是禁止的,因而用戶不可能獲得節拍中斷!很明顯,所有的PEND調用都會涉及到這個問題,用戶得十分小心。一個通用的辦法是用戶應該在中斷允許的情況下調用μC/OS-Ⅱ的系統服務!

問題是:哪種方法更好一點?這就得看用戶想犧牲些什么。如果用戶并不關心在調用μC/OS-Ⅱ服務后用戶的應用程序中中斷是否是允許的,那么用戶應該選擇第一種方法執行。如果用戶想在調用μC/OS-Ⅱ服務過程中保持中斷禁止狀態,那么很明顯用戶應該選擇第二種方法。

給用戶舉個例子吧,通過執行STI命令在Intel 80186上禁止中斷,并用CLI命令來允許中斷。用戶可以用下面的方法來執行這兩個宏:


#define OS_ENTER_CRITICAL()   asm CLI

#define OS_EXIT_CRITICAL()   asm STI


CLI和SCI指令都會在兩個時鐘周期內被馬上執行(總共為四個周期)。為了保持中斷狀態,用戶需要用下面的方法來執行宏:


#define OS_ENTER_CRITICAL()   asm PUSHF; CLI

#define OS_EXIT_CRITICAL()   asm POPF


在這種情況下,OS_ENTER_CRITICAL()需要12個時鐘周期,而OS_EXIT_CRITICAL()需要另外的8個時鐘周期(總共有20個周期)。這樣,保持中斷禁止狀態要比簡單的禁止/允許中斷多花16個時鐘周期的時間(至少在80186上是這樣的)。當然,如果用戶有一個速度比較快的處理器(如Intel Pentium Ⅱ),那么這兩種方法的時間差別會很小。

8.03.03 OS_STK_GROWTH

絕大多數的微處理器和微控制器的堆棧是從上往下長的。但是某些處理器是用另外一種方式工作的。μC/OS-Ⅱ被設計成兩種情況都可以處理,只要在結構常量OS_STK_GROWTH [L8.1(4)]中指定堆棧的生長方式(如下所示)就可以了。

置OS_STK_GROWTH為0表示堆棧從下往上長。

置OS_STK_GROWTH為1表示堆棧從上往下長。

8.03.04 OS_TASK_SW()

OS_TASK_SW()[L8.1(5)]是一個宏,它是在μC/OS-Ⅱ從低優先級任務切換到最高優先級任務時被調用的。OS_TASK_SW()總是在任務級代碼中被調用的。另一個函數OSIntExit()被用來在ISR使得更高優先級任務處于就緒狀態時,執行任務切換功能。任務切換只是簡單的將處理器寄存器保存到將被掛起的任務的堆棧中,并且將更高優先級的任務從堆棧中恢復出來。

在μC/OS-Ⅱ中,處于就緒狀態的任務的堆棧結構看起來就像剛發生過中斷并將所有的寄存器保存到堆棧中的情形一樣。換句話說,μC/OS-Ⅱ要運行處于就緒狀態的任務必須要做的事就是將所有處理器寄存器從任務堆棧中恢復出來,并且執行中斷的返回。為了切換任務可以通過執行OS_TASK_SW()來產生中斷。大部分的處理器會提供軟中斷或是陷阱(TRAP)指令來完成這個功能。ISR或是陷阱處理函數(也叫做異常處理函數)的向量地址必須指向匯編語言函數OSCtxSw()(參看8.04.02)。

例如,在Intel或者AMD 80x86處理器上可以使用INT指令。但是中斷處理向量需要指向OSCtxSw()。Motorola 68HC11處理器使用的是SWI指令,同樣,SWI的向量地址仍是OSCtxSw()。還有,Motorola 680x0/CPU32可能會使用16個陷阱指令中的一個。當然,選中的陷阱向量地址還是OSCtxSw()。

一些處理器如Zilog Z80并不提供軟中斷機制。在這種情況下,用戶需要盡自己的所能將堆棧結構設置成與中斷堆棧結構一樣。OS_TASK_SW()只會簡單的調用OSCtxSw()而不是將某個向量指向OSCtxSw()。μC/OS已經被移植到了Z80處理器上,μC/OS-Ⅱ也同樣可以。

8.04 OS_CPU_A.ASM

μC/OS-Ⅱ的移植實例要求用戶編寫四個簡單的匯編語言函數:

OSStartHighRdy()

OSCtxSw()

OSIntCtxSw()

OSTickISR()

如果用戶的編譯器支持插入匯編語言代碼的話,用戶就可以將所有與處理器相關的代碼放到OS_CPU_C.C文件中,而不必再擁有一些分散的匯編語言文件。

8.04.01 OSStartHighRdy()

使就緒狀態的任務開始運行的函數叫做OSStart(),如下所示。在用戶調用OSStart()之前,用戶必須至少已經建立了自己的一個任務(參看OSTaskCreate()和OSTaskCteateExt())。OSStartHighRdy()假設OSTCBHighRdy指向的是優先級最高的任務的任務控制塊。前面曾提到過,在μC/OS-Ⅱ中處于就緒狀態的任務的堆棧結構看起來就像剛發生過中斷并將所有的寄存器保存到堆棧中的情形一樣。要想運行最高優先級任務,用戶所要做的是將所有處理器寄存器按順序從任務堆棧中恢復出來,并且執行中斷的返回。為了簡單一點,堆棧指針總是儲存在任務控制塊(即它的OS_TCB)的開頭。換句話說,也就是要想恢復的任務堆棧指針總是儲存在OS_TCB的0偏址內存單元中。


void OSStartHighRdy (void)

{

    Call user definable OSTaskSwHook();                                   

    Get the stack pointer of the task to resume:                           

        Stack pointer = OSTCBHighRdy->OSTCBStkPtr;

    OSRunning = TRUE;

    Restore all processor registers from the new task's stack;             

    Execute a return from interrupt instruction;                           

}


注意,OSStartHighRdy()必須調用OSTaskSwHook(),因為用戶正在進行任務切換的部分工作——用戶在恢復最高優先級任務的寄存器。而OSTaskSwHook()可以通過檢查OSRunning來知道是OSStartHighRdy()在調用它(OSRunning為FALSE)還是正常的任務切換在調用它(OSRunning為TRUE).

OSStartHighRdy()還必須在最高優先級任務恢復之前和調用OSTaskSwHook()之后設置OSRunning為TRUE。

8.04.02 OSCtxSw()

如前面所述,任務級的切換問題是通過發軟中斷命令或依靠處理器執行陷阱指令來完成的。中斷服務例程,陷阱或異常處理例程的向量地址必須指向OSCtxSw()。

如果當前任務調用μC/OS-Ⅱ提供的系統服務,并使得更高優先級任務處于就緒狀態,μC/OS-Ⅱ就會借助上面提到的向量地址找到OSCtxSw()。在系統服務調用的最后,μC/OS-Ⅱ會調用OSSched(),并由此來推斷當前任務不再是要運行的最重要的任務了。OSSched()先將最高優先級任務的地址裝載到OSTCBHighRdy中,再通過調用OS_TASK_SW()來執行軟中斷或陷阱指令。注意,變量OSTCBCur早就包含了指向當前任務的任務控制塊(OS_TCB)的指針。軟中斷 (或陷阱) 指令會強制一些處理器寄存器(比如返回地址和處理器狀態字)到當前任務的堆棧中,并使處理器執行OSCtxSw()。OSCtxSw()的原型如程序清單 L8.2所示。這些代碼必須寫在匯編語言中,因為用戶不能直接從C中訪問CPU寄存器。注意在OSCtxSw()和用戶定義的函數OSTaskSwHook()的執行過程中,中斷是禁止的。


程序清單 L 8.2 OSCtxSw()的原型

void OSCtxSw(void)

{

    保存處理器寄存器;

    將當前任務的堆棧指針保存到當前任務的OS_TCB中:

        OSTCBCur->OSTCBStkPtr = Stack pointer;

    調用用戶定義的OSTaskSwHook();

    OSTCBCur   = OSTCBHighRdy;

    OSPrioCur = OSPrioHighRdy;

    得到需要恢復的任務的堆棧指針:

        Stack pointer = OSTCBHighRdy->OSTCBStkPtr;

    將所有處理器寄存器從新任務的堆棧中恢復出來;

    執行中斷返回指令;

}


8.04.03 OSIntCtxSw()

OSIntExit()通過調用OSIntCtxSw()來從ISR中執行切換功能。因為OSIntCtxSw()是在ISR中被調用的,所以可以斷定所有的處理器寄存器都被正確地保存到了被中斷的任務的堆棧之中。實際上除了我們需要的東西外,堆棧結構中還有其它的一些東西。OSIntCtxSw()必須要清理堆棧,這樣被中斷的任務的堆棧結構內容才能滿足我們的需要。

要想了解OSIntCtxSw(),用戶可以看看μC/OS-Ⅱ調用該函數的過程。用戶可以參看圖8.2來幫助理解下面的描述。假定中斷不能嵌套(即ISR不會被中斷),中斷是允許的,并且處理器正在執行任務級的代碼。當中斷來臨的時候,處理器會結束當前的指令,識別中斷并且初始化中斷處理過程,包括將處理器的狀態寄存器和返回被中斷的任務的地址保存到堆棧中[F8.2(1)]。至于究竟哪些寄存器保存到了堆棧上,以及保存的順序是怎樣的,并不重要。

圖 8.2 在ISR執行過程中的堆棧內容.



接著,CPU會調用正確的ISR。μC/OS-Ⅱ要求用戶的ISR在開始時要保存剩下的處理器寄存器[F8.2(2)]。一旦寄存器保存好了,μC/OS-Ⅱ就要求用戶或者調用OSIntEnter(),或者將變量OSIntNesting加1。在這個時候,被中斷任務的堆棧中只包含了被中斷任務的寄存器內容。現在,ISR可以執行中斷服務了。并且如果ISR發消息給任務(通過調用OSMboxPost()或OSQPost()),恢復任務(通過調用OSTaskResume()),或者調用OSTimeTick()或OSTimeDlyResume()的話,有可能使更高優先級的任務處于就緒狀態。

假設有一個更高優先級的任務處于就緒狀態。μC/OS-Ⅱ要求用戶的ISR在完成中斷服務的時候調用OSIntExit()。OSIntExit()會告訴μC/OS-Ⅱ到了返回任務級代碼的時間了。調用OSIntExit()會導致調用者的返回地址被保存到被中斷的任務的堆棧中[F8.2(3)]。

OSIntExit()剛開始時會禁止中斷,因為它需要執行臨界段的代碼。根據OS_ENTER_CRITICAL()的不同執行過程(參看8.03.02),處理器的狀態寄存器會被保存到被中斷的任務的堆棧中[F8.2(4)]。OSIntExit()注意到由于有更高優先級的任務處于就緒狀態,被中斷的任務已經不再是要繼續執行的任務了。在這種情況下,指針OSTCBHighRdy會被指向新任務的OS_TCB,并且OSIntExit()會調用OSIntCtxSw()來執行任務切換。調用OSIntCtxSw()也同樣使返回地址被保存到被中斷的任務的堆棧中[F8.2(5)]。

在用戶切換任務的時候,用戶只想將某些項([F8.2(1)]和[F8.2(2)])保留在堆棧中,并忽略其它項(F8.2(3),(4)和(5))。這是通過調整堆棧指針(加一個數在堆棧指針上)來完成的[F8.2(6)]。加在堆棧指針上的數必須是明確的,而這個數主要依賴于移植的目標處理器(地址空間可能是16,32或64位),所用的編譯器,編譯器選項,內存模式等等。另外,處理器狀態字可能是8,16,32甚至64位寬,并且OSIntExit()可能會分配局部變量。有些處理器允許用戶直接增加常量到堆棧指針中,而有些則不允許。在后一種情況下,可以通過簡單的執行一定數量的pop(出棧)指令來實現相同的功能。一旦堆棧指針完成調整,新的堆棧指針會被保存到被切換出去的任務的OS_TCB中[F8.2(7)]。

OSIntCtxSw()的原型如程序清單 L8.3所示。這些代碼必須寫在匯編語言中,因為用戶不能直接從C語言中訪問CPU寄存器。如果用戶的編譯器支持插入匯編語言代碼的話,用戶就可以將OSIntCtxSw()代碼放到OS_CPU_C.C文件中,而不放到OS_CPU_A.ASM文件中。正如用戶所看到的那樣,除了第一行以外,OSIntCtxSw()的代碼與OSCtxSw()是一樣的。這樣在移植實例中,用戶可以通過“跳轉”到OSCtxSw()中來減少OSIntCtxSw()代碼量。


程序清單 L 8.3 OSIntCtxSw()的原型

void OSIntCtxSw(void)

{

    調整堆棧指針來去掉在調用:

        OSIntExit(),

        OSIntCtxSw()過程中壓入堆棧的多余內容;

    將當前任務堆棧指針保存到當前任務的OS_TCB中:

        OSTCBCur->OSTCBStkPtr = 堆棧指針;

    調用用戶定義的OSTaskSwHook();

    OSTCBCur   = OSTCBHighRdy;

    OSPrioCur = OSPrioHighRdy;

    得到需要恢復的任務的堆棧指針:

        堆棧指針 = OSTCBHighRdy->OSTCBStkPtr;

    將所有處理器寄存器從新任務的堆棧中恢復出來;

    執行中斷返回指令;

}


8.04.04 OSTickISR()

μC/OS-Ⅱ要求用戶提供一個時鐘資源來實現時間的延時和期滿功能。時鐘節拍應該每秒鐘發生10-100次。為了完成該任務,可以使用硬件時鐘,也可以從交流電中獲得50/60Hz的時鐘頻率。

用戶必須在開始多任務調度后(即調用OSStart()后)允許時鐘節拍中斷。換句話說,就是用戶應該在OSStart()運行后,μC/OS-Ⅱ啟動運行的第一個任務中初始化節拍中斷。通常所犯的錯誤是在調用OSInit()和OSStart()之間允許時鐘節拍中斷(如程序清單 L8.4所示)。


程序清單 L 8.4 在不正確的位置啟動時鐘節拍中斷

void main(void)

{

    .

    .

    OSInit();               /* 初始化 ?μC/OS-II                 */

    .

    .

    /* 應用程序初始化代碼 ...                         */

    /* ... 調用OSTaskCreate()建立至少一個任務       */

    .

    .

    允許時鐘節拍中斷; /* 千萬不要在這里允許!!!            */

    .

    .

    OSStart();              /* 開始多任務調度                  */

}


有可能在μC/OS-Ⅱ開始執行第一個任務前時鐘節拍中斷就發生了。在這種情況下,μC/OS-Ⅱ的運行狀態不確定,用戶的應用程序也可能會崩潰。

時鐘節拍ISR的原型如程序清單 L8.5所示。這些代碼必須寫在匯編語言中,因為用戶不能直接從C語言中訪問CPU寄存器。如果用戶的處理器可以通過單條指令來增加OSIntNesting,那么用戶就沒必要調用OSIntEnter()了。增加OSIntNesting要比通過函數調用和返回快得多。OSIntEnter()只增加OSIntNesting,并且作為臨界段代碼中受到保護。


程序清單 L 8.5 時鐘節拍ISR的原型

void OSTickISR(void)

{

   保存處理器寄存器;

   調用OSIntEnter()或者直接將 OSIntNesting加1;


   調用OSTimeTick();


   調用OSIntExit();

   恢復處理器寄存器;

   執行中斷返回指令;

}


8.05 OS_CPU_C.C

μC/OS-Ⅱ的移植實例要求用戶編寫六個簡單的C函數:

OSTaskStkInit()

OSTaskCreateHook()

OSTaskDelHook()

OSTaskSwHook()

OSTaskStatHook()

OSTimeTickHook()

唯一必要的函數是OSTaskStkInit(),其它五個函數必須得聲明但沒必要包含代碼。

8.05.01 OSTaskStkInt()

OSTaskCreate()和OSTaskCreateExt()通過調用OSTaskStkInt()來初始化任務的堆棧結構,因此,堆??雌饋砭拖駝偘l生過中斷并將所有的寄存器保存到堆棧中的情形一樣。圖8.3顯示了OSTaskStkInt()放到正被建立的任務堆棧中的東西。注意,在這里我假定了堆棧是從上往下長的。下面的討論同樣適用于從下往上長的堆棧。

在用戶建立任務的時候,用戶會傳遞任務的地址,pdata指針,任務的堆棧棧頂和任務的優先級給OSTaskCreate()和OSTaskCreateExt()。雖然OSTaskCreateExt()還要求有其它的參數,但這些參數在討論OSTaskStkInt()的時候是無關緊要的。為了正確初始化堆棧結構,OSTaskStkInt()只要求剛才提到的前三個參數和一個附加的選項,這個選項只能在OSTaskCreateExt()中得到。

圖 8.3 堆棧初始化(pdata通過堆棧傳遞)




回顧一下,在μC/OS-Ⅱ中,無限循環的任務看起來就像其它的C函數一樣。當任務開始被μC/OS-Ⅱ執行時,任務就會收到一個參數,好像它被其它的任務調用一樣。


void MyTask (void *pdata)

{

    /* 對'pdata'做某些操作 */

    for (;;) {

        /* 任務代碼                      */

    }

}


如果我想從其它的函數中調用MyTask(),C編譯器就會先將調用MyTask()的函數的返回地址保存到堆棧中,再將參數保存到堆棧中。實際上有些編譯器會將pdata參數傳至一個或多個寄存器中。在后面我會討論這類情況。假定pdata會被編譯器保存到堆棧中,OSTaskStkInit()就會簡單的模仿編譯器的這種動作,將pdata保存到堆棧中[F8.3(1)]。但是結果表明,與C函數調用不一樣,調用者的返回地址是未知的。用戶所擁有的是任務的開始地址,而不是調用該函數(任務)的函數的返回地址!事實上用戶不必太在意這點,因為任務并不希望返回到其它函數中。

這時,用戶需要將寄存器保存到堆棧中,當處理器發現并開始執行中斷的時候,它會自動地完成該過程的。一些處理器會將所有的寄存器存入堆棧,而其它一些處理器只將部分寄存器存入堆棧。一般而言,處理器至少得將程序計數器的值(中斷返回地址)和處理器的狀態字存入堆棧[F8.3(2)]。很明顯,處理器是按一定的順序將寄存器存入堆棧的,而用戶在將寄存器存入堆棧的時候也就必須依照這一順序。

接著,用戶需要將剩下的處理器寄存器保存到堆棧中[F8.3(3)]。保存的命令依賴于用戶的處理器是否允許用戶保存它們。有些處理器用一個或多個指令就可以馬上將許多寄存器都保存起來。用戶必須用特定的指令來完成這一過程。例如,Intel 80x86使用PUSHA 指令將8個寄存器保存到堆棧中。對Motorola 68HC11處理器而言,在中斷響應期間,所有的寄存器都會按一定順序自動的保存到堆棧中,所以在用戶將寄存器存入堆棧的時候,也必須依照這一順序。

現在是時候討論這個問題了:如果用戶的C編譯器將pdata參數傳遞到寄存器中而不是堆棧中該作些什么?用戶需要從編譯器的文檔中找到pdata儲存在哪個寄存器中。pdata的內容就會隨著這個寄存器的儲存被放置在堆棧中。

圖 8.4 堆棧初始化(pdata通過寄存器傳遞)



一旦用戶初始化了堆棧,OSTaskStkInit()就需要返回堆棧指針所指的地址[F8.3(4)]。OSTaskCreate()和OSTaskCreateExt()會獲得該地址并將它保存到任務控制塊(OS_TCB)中。處理器文檔會告訴用戶堆棧指針會指向下一個堆棧空閑位置,還是會指向最后存入數據的堆棧單元位置。例如,對Intel 80x86處理器而言,堆棧指針會指向最后存入數據的堆棧單元位置,而對Motorola 68HC11處理器而言,堆棧指針會指向下一個空閑的位置。

8.05.02 OSTaskCreateHook()

當用OSTaskCreate()或OSTaskCreateExt()建立任務的時候就會調用OSTaskCreateHook()。該函數允許用戶或使用用戶的移植實例的用戶擴展μC/OS-Ⅱ的功能。當μC/OS-Ⅱ設置完了自己的內部結構后,會在調用任務調度程序之前調用OSTaskCreateHook()。該函數被調用的時候中斷是禁止的。因此用戶應盡量減少該函數中的代碼以縮短中斷的響應時間。

當OSTaskCreateHook()被調用的時候,它會收到指向已建立任務的OS_TCB的指針,這樣它就可以訪問所有的結構成員了。當使用OSTaskCreate()建立任務時,OSTaskCreateHook()的功能是有限的。但當用戶使用OSTaskCreateExt()建立任務時,用戶會得到OS_TCB中的擴展指針(OSTCBExtPtr),該指針可用來訪問任務的附加數據,如浮點寄存器,MMU寄存器,任務計數器的內容,以及調試信息。

只用當OS_CFG.H中的OS_CPU_HOOKS_EN被置為1時才會產生OSTaskCreateHook()的代碼。這樣,使用用戶的移植實例的用戶可以在其它的文件中重新定義hook函數。

8.05.03 OSTaskDelHook()

當任務被刪除的時候就會調用OSTaskDelHook()。該函數在把任務從μC/OS-Ⅱ的內部任務鏈表中解開之前被調用。當OSTaskDelHook()被調用的時候,它會收到指向正被刪除任務的OS_TCB的指針,這樣它就可以訪問所有的結構成員了。OSTaskDelHook()可以用來檢驗TCB擴展是否被建立了(一個非空指針)并進行一些清除操作。OSTaskDelHook()不返回任何值。

只用當OS_CFG.H中的OS_CPU_HOOKS_EN被置為1時才會產生OSTaskDelHook()的代碼。

8.05.04 OSTaskSwHook()

當發生任務切換的時候調用OSTaskSwHook()。不管任務切換是通過OSCtxSw()還是OSIntCtxSw()來執行的都會調用該函數。OSTaskSwHook()可以直接訪問OSTCBCur 和OSTCBHighRdy,因為它們是全局變量。OSTCBCur指向被切換出去的任務的OS_TCB,而OSTCBHighRdy指向新任務的OS_TCB。注意在調用OSTaskSwHook()期間中斷一直是被禁止的。因為代碼的多少會影響到中斷的響應時間,所以用戶應盡量使代碼簡化。OSTaskSwHook()沒有任何參數,也不返回任何值。

只用當OS_CFG.H中的OS_CPU_HOOKS_EN被置為1時才會產生 OSTaskSwHook()的代碼。

8.05.05 OSTaskStatHook()

OSTaskStatHook()每秒鐘都會被OSTaskStat()調用一次。用戶可以用OSTaskStatHook()來擴展統計功能。例如,用戶可以保持并顯示每個任務的執行時間,每個任務所用的CPU份額,以及每個任務執行的頻率等等。OSTaskStatHook()沒有任何參數,也不返回任何值。

只用當OS_CFG.H中的OS_CPU_HOOKS_EN被置為1時才會產生OSTaskStatHook()的代碼。

8.05.06 OSTimeTickHook()

OSTaskTimeHook()在每個時鐘節拍都會被OSTaskTick()調用。實際上,OSTaskTimeHook()是在節拍被μC/OS-Ⅱ真正處理,并通知用戶的移植實例或應用程序之前被調用的。OSTaskTimeHook()沒有任何參數,也不返回任何值。

只用當OS_CFG.H中的OS_CPU_HOOKS_EN被置為1時才會產生OSTaskTimeHook()的代碼。




OSTaskCreateHook()

void OSTaskCreateHook(OS_TCB *ptcb)


File

Called from

Code enabled by

OS_CPU_C.C

OSTaskCreate() and OSTaskCreateExt()

OS_CPU_HOOKS_EN



無論何時建立任務,在分配好和初始化TCB后就會調用該函數,當然任務的堆棧結構也已經初始化好了。OSTaskCreateHook()允許用戶用自己的方式來擴展任務建立函數的功能。例如用戶可以初始化和存儲與任務相關的浮點寄存器,MMU寄存器以及其它寄存器的內容。通常,用戶可以存儲用戶的應用程序所分配的附加的內存信息。用戶還可以通過使用OSTaskCreateHook()來觸發示波器或邏輯分析儀,以及設置斷點。

參數

ptcb是指向所創建任務的任務控制塊的指針。

返回值

注意事項

該函數在被調用的時候中斷是禁止的。因此用戶應盡量減少該函數中的代碼以縮短中斷的響應時間。

范例

該例子假定了用戶是用OSTaskCreateExt()建立任務的,因為它希望在任務OS_TCB中有.OSTCBExtPtr域,該域包含了指向浮點寄存器的指針。


Void OSTaskCreateHook (OS_TCB *ptcb)

{

    if (ptcb->OSTCBExtPtr != (void *)0) {

        /* 儲存浮點寄存器的內容到.. */

        /* ..TCB擴展域中                           */

      }

}




OSTaskDelHook()

void OSTaskDelHook(OS_TCB *ptcb)


File

Called from

Code enabled by

OS_CPU_C.C

OSTaskDel()

OS_CPU_HOOKS_EN


當用戶通過調用OSTaskDel()來刪除任務時都會調用該函數。這樣用戶就可以處理OSTaskCreateHook()所分配的內存。OSTaskDelHook()就在TCB從TCB鏈中被移除前被調用。用戶還可以通過使用OSTaskDelHook()來觸發示波器或邏輯分析儀,以及設置斷點。

參數

ptcb是指向所創建任務的任務控制塊的指針。

返回值

注意事項

該函數在被調用的時候中斷是禁止的。因此用戶應盡量減少該函數中的代碼以縮短中斷的響應時間。

范例


void OSTaskDelHook (OS_TCB *ptcb)

{

    /* 輸出信號觸發示波器           */

}




OSTaskSwHook()

void OSTaskSwHook(void)


File

Called from

Code enabled by

OS_CPU_C.C

OSCtxSw() and
OSIntCtxSw()

OS_CPU_HOOKS_EN



當執行任務切換時都會調用該函數。全局變量OSTCBHighRdy指向得到CPU的任務的TCB,而OSTCBCur指向被切換出去的任務的TCB。OSTaskSwHook()在保存好了任務的寄存器和保存好了指向當前任務TCB的堆棧指針后馬上被調用。用戶可以用該函數來保存或恢復浮點寄存器或MMU寄存器的內容,來得到任務執行時間的軌跡以及任務被切換進來的次數等等。

參數

返回值

注意事項

該函數在被調用的時候中斷是禁止的。因此用戶應盡量減少該函數中的代碼以縮短中斷的響應時間。

范例


void OSTaskSwHook (void)

{

    /* 將浮點寄存器的內容儲存在當前任務的TCB擴展域中。 */

    /* 用新任務的TCB擴展域中的值更新浮點寄存器的內容。   */

}





OSTaskStatHook()

void OSTaskStatHook(void)


File

Called from

Code enabled by

OS_CPU_C.C

OSTaskStat()

OS_CPU_HOOKS_EN


該函數每秒鐘都會被μC/OS-Ⅱ的統計任務調用。OSTaskStatHook()允許用戶加入自己的統計功能。

參數

返回值

注意事項

統計任務大概在調用OSStart()后再過5秒開始執行。注意,當OS_TASK_STAT_EN或者OS_TASK_CREATE_EXT_EN被置為0時,該函數不會被調用。

范例


void OSTaskStatHook (void)

{

    /* 計算所有任務執行的總時間     */

    /* 計算每個任務的執行時間在總時間內所占的百分比      */

}




OSTimeTickHook()

void OSTimeTickHook(void)


File

Called from

Code enabled by

OS_CPU_C.C

OSTimeTick()

OS_CPU_HOOKS_EN


只要發生時鐘節拍,該函數就會被OSTimeTick()調用。一旦進入OSTimeTick()就會馬上調用OSTimeTickHook()以允許執行用戶的應用程序中的與時間密切相關的代碼。用戶還可以通過使用該函數觸發示波器或邏輯分析儀來調試,或者為仿真器設置斷點。

參數

返回值

注意事項

OSTimeTick()通常是被ISR調用的,所以時鐘節拍ISR的執行時間會因為用戶在該函數中提供的代碼而增加。當OSTimeTick()被調用的時候,中斷可以是禁止的也可以是允許的,這主要取決于該處理器上的移植是怎樣進行的。如果中斷是禁止的,該函數將會影響到中斷響應時間。

范例


void OSTimeTickHook (void)

{

    /* 觸發示波器                               */

}