當系統響應時間很重要時,要使用可剝奪型內核,uc/OS_II是可剝奪型的實時內核,搶佔式的多任務實時內核
任務的三個重要部分
- 程序代碼
- **私有堆棧:**保存的是任務上下文的信息
- **任務控制塊:**保存了任務堆棧指針,任務當前狀態標誌,任務的優先級別
uC/OS_II的任務調度
任務調度的思想是:“近似地每時每刻總是讓優先級最高的就緒任務處於運行狀態”
即任務調度採用的就是最高優先級調度算法:
- 找到具有最高優先級的任務
- 進行任務切換
**最高優先級判斷:**系統將64個優先級分爲8組,每組8個,即OSRdyGrp表示組,OSRdytbl[]表示每一組
優先級prio來設置OSRdyGrp和OSRdytbl[],其中的核心代碼爲:
任務prio置爲就緒狀態
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdytbl[prio>>3] |= OSMapTbl[prio&0x07];
任務prio脫離就緒狀態
if((OSRdytbl[prio>>3] &= ~OSMapTbl[prio&0x07]) == 0)
{
OSRdyGrp &= ~OSMapTbl[prio>>3];
}
任務調度中判斷最高優先級:因爲優先級存放在RSRdyGrp, OSRdytbl中,低位代表的是高優先級,故先檢查組,由低位到高位檢查,檢查到後,再在該組中由低位到高位檢查,第一個爲1的就是最高優先級
獲取最高的就緒任務算法:
y = OSUnMapTal[OSRdyGrp]; //D5,D4,D3位
x = USUnMapTal[OSRdyTbl[y]]; //D2,D1,D0位
prio = (y<<3)+x; //優先級別
或
y = USUnMapTbl[OSRdyGrp];
prio = (INT8U)((y<<3)+OSUnMapTbl[OSRdyTbl[y]]);
在OS_SchedNew()函數中完成:
static void OS_SchedNew (void)
{
#if OS_LOWEST_PRIO <= 63u /* See if we support up to 64 tasks */
INT8U y;
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
#else /* We support up to 256 tasks */
INT8U y;
OS_PRIO *ptbl;
if ((OSRdyGrp & 0xFFu) != 0u) {
y = OSUnMapTbl[OSRdyGrp & 0xFFu];
} else {
y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
}
ptbl = &OSRdyTbl[y];
if ((*ptbl & 0xFFu) != 0u) {
OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
} else {
OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
}
#endif
}
其中OSUnMaptbl[256]數組的功能主要就是尋找OSRdyGrp裏爲1的最低位,然後在OSRdyTbl[]數組裏面對應的爲1的最低位就是最高任務優先級
其中生成OSUnMapTbl[]數組的算法參考:
int main()
{
uint8 redgrp;
uint8 OSUnMapTbl[256] = {0};
int i;
for(redgrp=1;regdrp<255;regdrp++)
{
uint8 temp = regdrp;
for(i=0;i<8;i++)
{
if((temp&0x01) != 0)
{
OSUnMapTbl[regdrp] = i;
break;
}
else
{
temp >>= 1;
}
}
}
}
任務切換
調度有兩種方式:任務級的調度是由OSSched()函數完成,中斷級的調度是由OSIntExt()函數完成的
函數OSSched():
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) { /* Schedule only if all ISRs done and ... */
if (OSLockNesting == 0u) { /* ... scheduler is not locked */
OS_SchedNew(); /*Find Highest priority task ready to run------global variable 'OSPrioHighRdy'*/
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
}
OS_EXIT_CRITICAL();
}
- 若調度是禁止的(調度上鎖),或當前處於中斷服務子程序中,任務的調度是不允許的,任務調度函數OSSched()將退出。
- OSLockNesting–調度鎖定嵌套計數器用於跟蹤調度上鎖次數,當OSLockNesting>0時,表明調度是禁止的;只有OSLockNesting=0時調度纔是允許的,也稱爲調度開鎖
- OSIntNesting–中斷嵌套計數器用於跟蹤中斷嵌套層數,只要一進入中斷服務,就有OSIntNesting>0;只有所有中斷都退出時,纔有OSIntNesting=0
- 若調度是開放的且程序不處於中斷服務之中,則調用OS_SchedNew()函數找出準備就緒且優先級最高的任務
- 任務切換通過一個宏調用OS_TASK_SW()來完成的,實質上是_OSCtxSW的堆棧操作
- 在調用_OSCtxSw中斷服務程序前,CPU將PSW和任務斷點指針CS,IP壓入當前任務堆棧
- 將CPU寄存器AX、CX、DX、BX、SP、BP、SI、及DI、壓入當前任務堆棧
- 將當前CPU的堆棧指針SS和SP存入當前任務控制塊的任務堆棧指針變量(至此,當前任務的斷點數據已經全部保存,所以每一個新任務運行之前,uc/os都會將舊任務的斷點數據全部按照順序壓入私有堆棧中,同時每一個新任務的任務堆棧都已經保存初始化好的或以前任務中止時,CPU保存的斷點數據)
- OSTCBCur = OSTCBHighRdy、OSPrioCur = OSPrioHighRdy
- 使CPU的SS和SP堆棧指針指向新的任務堆棧
- 將新的任務堆棧的內容彈出到CPU寄存器,順序爲DS、ES、DI、SI、BP、SP、BX、DX、CX、AX。
- 運行IRET,將新任務斷點指針彈出到CPU的CS、IP指針寄存器,PSW彈出到CPU的PSW寄存器
- uc/os開始運行新任務
任務切換實際上就是分兩步:首先將被掛起的任務的CPU寄存器推入任務堆棧,然後將準備就緒的最高優先級任務的寄存器值從任務棧中恢復到CPU寄存器中
CPU寄存器中的CS,IP寄存器內有出棧和入棧指令,所以只能引發一次中斷,自動將CS,IP寄存器壓入堆棧,再利用中斷返回,將新任務的任務斷點指針彈出到CPU的CS,IP寄存器中,實現任務切換
中斷爲INT 0x80;即128號中斷
程序計數器PC是系統進行程序切換動作的關鍵
任務的切換就是任務運行環境的切換
保存在任務堆棧中的內容有:---------也叫任務上下文
- 程序的斷點地址(PC)
- 任務的堆棧指針(SP)
- 程序狀態字寄存器(PSW)
- 通用寄存器內容
- 函數調用信息(以存在於堆棧)
uC/OS_II的任務狀態
- **運行狀態:**處於就緒狀態的任務如果經調度器判斷獲得了CPU的使用權,則任務就進入運行狀態
- **就緒狀態:**系統爲任務配備了任務控制塊且在任務就緒表中進行了就緒登記,這時任務的狀態叫做就緒狀態
- **等待狀態:**正在運行的任務,需要等待一段時間或需要等待一個事件發生再運行時,該任務就會把CPU的使用權讓給別的任務從而使任務進入等待狀態
- **睡眠狀態:**任務在沒有被配備任務控制塊或剝奪了任務控制塊時的狀態叫做任務的睡眠狀態
- **中斷服務狀態:**一個正在運行的任務一旦相應中斷申請就會中止運行而去執行中斷服務程序,這時任務的狀態叫做中斷服務狀態
uC/OS_II的通信機制
- 信號量
- 互斥型信號量
- 消息郵箱
- 消息隊列
uC/OS_II的任務優先級反轉現象
互斥型信號量是一個二值信號,它可以使任務以獨佔方式使用共享資源,但在可剝奪型內核中,當任務以獨佔方式使用共享資源時,會出現低優先級任務先於高優先級任務而被運行的現象
即一個優先級別較低的任務在獲得了信號量使用共享資源期間被具有較高優先級任務所打斷而不能釋放信號量,從而使正在等待這個信號量的更高級別的任務因得不到信號量而被迫處於等待狀態,在這個等待期間,就讓優先級別低於她而高於佔據信號量的任務的任務先運行了。
**解決方法:**使獲得信號量任務的優先級別在使用共享資源期間暫時提升到所有任務最高優先級別的高一級別,以使該任務不被其他任務所打斷,從而能儘快地完成共享資源釋源放並釋放信號量,然後再釋放了信號量之後再恢復該任務原來的優先級別—低8位存放信號量值,高8位存放需要提升的優先級別prio
uC/OS_II的移植
OS_CPU_H
該文件中修改定義與編譯器無關的數據類型
設置定義棧的增長方向-------OS_STK_GROWTH 1 //棧是由高地址向低地址增長
堆棧的數據類型 OS_STK
修改定義進入臨界段的方法
#define OS_CRITICAL_METHOD 3
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#endif
定義任務切換宏
#define OS_TASK_SW() OSCtxSw()
OS_CPU_C.c
主要是初始化任務堆棧函數—OSTaskStkInit()
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */
stk = ptos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x01000000L; /* xPSR---T位(24位)置1,否則第一次執行任務爲Fault */
*(--stk) = (INT32U)task; /* Entry Point */
*(--stk) = (INT32U)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/
*(--stk) = (INT32U)0x12121212L; /* R12 */
*(--stk) = (INT32U)0x03030303L; /* R3 */
*(--stk) = (INT32U)0x02020202L; /* R2 */
*(--stk) = (INT32U)0x01010101L; /* R1 */
*(--stk) = (INT32U)p_arg; /* R0 : argument用於傳遞任務函數的參數 */
/* Remaining registers saved on process stack */
*(--stk) = (INT32U)0x11111111L; /* R11 */
*(--stk) = (INT32U)0x10101010L; /* R10 */
*(--stk) = (INT32U)0x09090909L; /* R9 */
*(--stk) = (INT32U)0x08080808L; /* R8 */
*(--stk) = (INT32U)0x07070707L; /* R7 */
*(--stk) = (INT32U)0x06060606L; /* R6 */
*(--stk) = (INT32U)0x05050505L; /* R5 */
*(--stk) = (INT32U)0x04040404L; /* R4 */
return (stk);
}
OS_CPU_A.asm
主要是四個與處理器相關的函數和兩個開中斷和關中斷的函數
- OSStartHighRdy()
- OSCtxSw()
- OSIntCtxSw()
- OSTickISR()
- OS_CPU_SR_Save()
- OS_CPU_SR_Restore()
(1) OS_SR_Save函數
OS_CPU_SR_Save
MRS R0, PRIMASK ;讀取PRIMASK到R0中,R0爲返回值
CPSID 1 ;PRIMASK=1,關中斷(NMI 和硬FAULT可以響應)
BX LR ;返回
(2) OS_CPU_SR_Restore函數
OS_CPU_SR_Restore
MSR PRIMASK, R0 ;讀取R0到PRIMASK中,R0爲參數
BX LR ;返回
PRIMASK是個只有1位的寄存器,當它置1時,就關閉所有可屏蔽的異常和中斷,只剩下NMI和硬fault可以響應,通過MRS和MSR方式進行訪問
MRS , //將程序狀態寄存器的內容傳送到通用寄存器中
MSR , //將Rm中的內容傳送到程序狀態寄存器的特定域中
(3) OSCtxSw函數
OSCtxSw
;觸發PendSV中斷
LDR R0, =NVIC_INT_CTRL ;R0=NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET ;R1=NVIC_PENDSVSET
STR R1, [R0] ;*(uint32_t*)NVIC_INT_CTRL = NVIC_PENDSVSET
BX LR ;返回
(4) OSIntCtxSw函數
OSIntCtxSw
;觸發PendSV中斷
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
OSCtxSw()做的是任務之間的切換,如任務 A 因爲等待某個資源或是做延時切換到任務 B,而 OSIntCtxSw()則是中斷退出時,由中斷狀態切換到另一個任務。由中斷切換到任務時,CPU 寄存器入棧的工作已經做完了,所以無需做第二次了
uC/OS_II的啓動過程
(1)SystemInit()函數:進行時鐘配置,選擇外部時鐘,進行分頻,倍頻配置
(2)進入main()函數:進行局部變量初始化,完成硬件初始化,定時中斷
- OSInit()函數:作業系統的初始化,初始化任務控制塊,事件控制表,創建最低優先級的空閒任務
- OSTaskCreate()函數:任務的創建,分配任務的工作堆棧,爲函數提供存放和訪問空間,任務優先級,以及對應簡歷的工作函數載體,任務創建成功之後,就啓動任務調度程序,執行就緒任務優先級最高的任務。