從零開始學習UCOSII操作系統2--UCOSII的內核實現
參考書籍:《嵌入式實時操作系統μCOS-II原理及應用》、《嵌入式實時操作系統uCOS-II 邵貝貝(第二版)》
1、任務的結構--任務控制塊
首先這個任務控制塊是非常的大的,這裏面使用很多的宏定義,估計是可以讓使用者使用的時候按需配置。
所以這裏只是整理一些必須要用到的功能,不常用的不講,講了就會變成一本書了。
(1)任務的關鍵 OS_STK == 任務的堆棧,用於保存任務的信息,最主要的是保存在程序的運行的SP指針。
任務切換的實質就是SP指針的變化,通過SP指針的變化,可以跳轉到你想要去的任何的一塊不受保護的地址去。
(2)任務的鏈表: struct os_tcb *OSTCBNext; 指向下一個任務,
此處使用鏈表是可以通過指針訪問下一個任務的內容,可以使用這個雙向鏈表放置到某些隊列當中,
實現同優先級的多任務。
(3)事件控制塊:OS_EVENT *OSTCBEventPtr;
是一個技術組件,用於後面的消息和消息隊列,郵箱和信號量等的設計。
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; /* 指向當前任務堆棧棧頂的指針 */
#if OS_TASK_CREATE_EXT_EN > 0
void *OSTCBExtPtr; /* 指向用戶定義的任務控制塊擴展,這個數據結構包括了任務的名字 */
OS_STK *OSTCBStkBottom; /* 以跟蹤某個任務的執行時間,或者跟蹤到某個任務的次 */
INT32U OSTCBStkSize; /* Size of task stack (in number of stack elements) */
INT16U OSTCBOpt; /* Task options as passed by OSTaskCreateExt() */
INT16U OSTCBId; /* Task ID (0..65535) */
#endif
struct os_tcb *OSTCBNext; /* 任務之間的雙向鏈表的使用 */
struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
#if OS_EVENT_EN
OS_EVENT *OSTCBEventPtr; /* 指向任務的事件控制塊 */
#endif
#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) || (OS_MBOX_EN > 0)
void *OSTCBMsg; /* 指向傳遞給任務的消息的指針 */
#endif
#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
#if OS_TASK_DEL_EN > 0
OS_FLAG_NODE *OSTCBFlagNode; /* 指向事件標誌組的指針 */
#endif
OS_FLAGS OSTCBFlagsRdy; /* 當任務等待事件標誌組時候, OSTCBFlagRdy是使任務進入就緒態的事件標誌*/
#endif
INT16U OSTCBDly; /* 當需要把任務延時諾幹個節拍時,或者需要把任務掛起一段時間等待某個事件的發生,需要使用這個變量 */
INT8U OSTCBStat; /* 任務的狀態 */
BOOLEAN OSTCBPendTO; /* 等待標誌組超時 */
INT8U OSTCBPrio; /* Task priority (0 == highest) */
INT8U OSTCBX; /* Bit position in group corresponding to task priority */
INT8U OSTCBY; /* Index into ready table corresponding to task priority */
#if OS_LOWEST_PRIO <= 63
INT8U OSTCBBitX; /* Bit mask to access bit position in ready table */
INT8U OSTCBBitY; /* Bit mask to access bit position in ready group */
#else
INT16U OSTCBBitX; /* Bit mask to access bit position in ready table */
INT16U OSTCBBitY; /* Bit mask to access bit position in ready group */
#endif
#if OS_TASK_DEL_EN > 0
INT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */
#endif
#if OS_TASK_PROFILE_EN > 0
INT32U OSTCBCtxSwCtr; /* Number of time the task was switched in */
INT32U OSTCBCyclesTot; /* Total number of clock cycles the task has been running */
INT32U OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_STK *OSTCBStkBase; /* Pointer to the beginning of the task stack */
INT32U OSTCBStkUsed; /* Number of bytes used from the stack */
#endif
#if OS_TASK_NAME_SIZE > 1
INT8U OSTCBTaskName[OS_TASK_NAME_SIZE];
#endif
} OS_TCB;
2、如何得到最高的優先級的任務---就緒表機制
這個表的原理非常的簡單,就是通過查表的原理,不斷的從X軸到Y軸的不斷的累加來計算的。
讓某一個任務進入就緒態的話,僅僅只需要在這份表格中填入1即可。
具體代碼:
OSRdyGrp |= OSMapTbl[prio >> 3];
OSRdyTbl[prio>>3] |= OSMapTbl[prio & 0x07];
計算實例:假設我們需要讓優先級爲24的任務置1的話。
任務的優先級組中填入的數是 24 >> 3 也就是24 >> 3 = 3
那麼在任務的第3個優先級組中我們應該填入是24 & 0x07 = 0,也就是在第0位上面填入1,即可把優先級爲24的任務喚醒。
把相應的任務掛起的計算公式爲:
OSRdyGrp &= ~OSMapTbl[prio >> 3];
3、如何通過最高優先級的任務進行任務切換--進入中斷,切換任務堆棧實現
(1)首先我們可以通過上面的機制得到當前系統中的最高的優先級任務是什麼?
但是我們怎麼通過這個最高級的優先級任務,把當前任務切換到最高級的優先級任務呢?
(2)裏面涉及到一個重要的概念,每一種CPU中都有一些對應的CPU的寄存器。裏面
有一個十分關鍵的程序指針,是用來跳轉到相應的程序裏面的。
上節說到每個任務都是一個無類型無返回值的函數,也就是可以通過函數指針的方式
跳轉到你想要跳轉的任務裏面去執行。
(3)要實現上面的過程是通過一個函數來實現的OS_TASK_SW();
(4)下面根據下面的圖進行任務切換的分析過程
此過程主要是分析,當程序中遇到更高的優先級的時候,CPU應該是怎麼運行的。
(1)假設當前運行的任務是低優先級的任務,CPU程序寄存器中存在的一些寄存器都是低優先級的任務
(2)當程序運行到檢測到高優先級的任務進入就緒狀態的時候,此時CPU發送一些命令,把CPU當前的一些程序寄存器的內容複製到低優先級任務的堆棧中。也就是1過程。
(3)此時通過剛剛的就緒表的機制,可以從程序中得到最高優先級的任務,也就是2過程
(4)最後的過程3就是把剛剛的高優先級任務的堆棧指針複製到CPU的程序寄存器當中,實現任務的切換。
3、如何實現時間片的輪詢的方法?
(1)根據上面的過程是實現可剝奪型內核的基礎,但是有些是可以進行時間片輪詢的方式的。
UCOSII本身是不支持同優先級有多個任務的,UCOSIII是支持的,所以實現這個機制的方案
就是剛剛上面提到的事件控制塊的靈活使用。
(2)剛剛的位圖是指向某一個任務的,但是UCOSIII的位圖是指向一個隊列,在同一個隊列中優先級
相同,也就是說,同一優先級的任務應該是按時間片輪詢的方式的。
(3)每個處理器中都會有一個時鐘節拍,在時鐘節拍中調用任務切換的核心函數,
在同一個優先級不斷的進行輪詢即可實現時間片輪詢。