使用過ucosii的朋友應該都會知道,單片機+嵌入式實時操作系統(tǒng)能夠做到盡可能最大化的利用cpu資源,通過加入實時操作系統(tǒng)能夠做出更加強大的產(chǎn)品和應用。
不知道使用過ucosii的朋友有沒有去了解過它進行任務調(diào)度的原理和實現(xiàn)方式呢?
我個人結合ucosii的源碼和自己的理解,分享一些有關ucosii的任務管理和調(diào)度的實現(xiàn)。
1、ucos-ii 任務創(chuàng)建與任務調(diào)度
1.1、任務的創(chuàng)建
當你調(diào)用 OSTaskCreate( ) 進行任務的創(chuàng)建的時候,會初始化任務的堆棧、保存cpu的寄存器、創(chuàng)建任務的控制塊(OS_TCB)等的操作;
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */
OS_EXIT_CRITICAL();
}
return (err);
}
** 注意:ucosii不支持兩個及以上相同的任務優(yōu)先級的任務,ucosiii支持時間片輪轉。**
ucosii 的任務控制塊是任務中很重要,它記錄了任務的信息,包括優(yōu)先級、延時時間、狀態(tài)等信息??刂茐K定義如下:
typedef structos_tcb {
OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */
void *OSTCBExtPtr; /* Pointer to user definable data for TCB extension */
OS_STK *OSTCBStkBottom; /* Pointer to bottom of stack */
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) */
struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */
struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
OS_EVENT *OSTCBEventPtr; /* Pointer to event control block */
OS_EVENT **OSTCBEventMultiPtr; /* Pointer to multiple event control blocks */
void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */
OS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node */
OS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */
INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */
INT8U OSTCBStat; /* Task status */
INT8U OSTCBStatPend; /* Task PEND status */
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 */
OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */
OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */
INT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */
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 */
INT8U *OSTCBTaskName;
INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
} OS_TCB;
2、任務調(diào)度實現(xiàn)
2.1、將任務優(yōu)先級進行分組
因為ucosii最大優(yōu)先級數(shù)量為64個,所以可以分成8組,每組8個優(yōu)先級。
當一個任務被創(chuàng)建成功之后,它的組號由優(yōu)先級的高三位決定(bit5 bit4 bit3),它在組內(nèi)的編號由優(yōu)先級的低三位決定(bit2 bit1 bit0),如下:
ptcb->OSTCBY = (INT8U)(prio >> 3u); // 組
ptcb->OSTCBX = (INT8U)(prio & 0x07u); // 組內(nèi)編號
2.2、任務就緒表
ucosii對任務優(yōu)先級的調(diào)度管理是通過查詢?nèi)蝿站途w表進行的。任務就緒表里面保存著當前所有任務的就緒狀態(tài),如下:
OSRdyTbl[8]
說明:
1)它是uint8的數(shù)據(jù)類型。它的長度是8,每一個元素代表一個組,
比如 OSRdyTbl[0]代表第0組, OSRdyTbl[1]代表第1組,OSRdyTbl[2]代表第2組……以此類推。
2)每一個元素中的每一個位(bit)代表組內(nèi)的任務的就緒狀態(tài)(1為就緒,0為未就緒)。
說明:
1)當優(yōu)先級為12 的任務就緒時,那么對應的OSRdyTbl[1]的第4位bit,絕對等于1;
當整個系統(tǒng)中,當只有優(yōu)先級為12的任務就緒,其他所有任務都沒有就緒時,那么OSRdyTbl[1] 絕對等于0x10。
2)當優(yōu)先級為0和1的任務就緒時,那么對應的OSRdyTbl[0]的第0位bit以及第1位bit,都絕對等于1;
當整個系統(tǒng)中,當只有優(yōu)先級為0和1的任務就緒,其他所有任務都沒有就緒時,那么OSRdyTbl[0] 絕對等于0x03。
2.3、任務釋放CPU使用權
當任務中調(diào)用 OSTimeDly( ) 時,會讓任務進入休眠的狀態(tài),交出CPU的執(zhí)行權給到其他就緒任務去執(zhí)行,這個過程就發(fā)生了任務的切換。
簡單而言就是會把任務就緒表 OSRdyTbl 中對應的任務優(yōu)先級在組內(nèi)的編號狀態(tài)改變,從而使任務自身進入休眠狀態(tài)。代碼如下:
if (ticks > 0u) { /* 0 means no delay! */
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next task to run! */
}
在上面的代碼中發(fā)現(xiàn)了一個東西:OSRdyGrp。這個有什么用呢?
OSRdyGrp:管理任務就緒組的
OSRdyGrp是INT8U類型的,它每一個bit代表一個組,只要這個組內(nèi)有任何一個任務就緒了,那對應的這個bit就會被設置為1,表示這個組內(nèi)目前有就緒的任務。否者對應的位為0。
舉個例子,如下:
1)系統(tǒng)中只有任務0就緒了,那么OSRdyGrp 便等于 0x01(二進制00000001)。
2)系統(tǒng)中有任務0和任務63都就緒了,那么OSRdyGrp 便等于 0x81(二進制10000001)。
2.4、任務實現(xiàn)調(diào)度切換操作
發(fā)生一次任務調(diào)度是通過 OS_Sched() 進行的。源碼如下:
void OS_Sched (void)
{
OS_CPU_SR cpu_sr = 0u;
OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) { /* Schedule only if all ISRs done and ... */
if (OSLockNesting == 0u) { /* ... scheduler is not locked */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
}
OS_EXIT_CRITICAL();
}
這里的過程如下:
(1)先通過 OS_SchedNew() 找到當前處于就緒狀態(tài)的最高優(yōu)先級的任務,如下:
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
(2)然后通過 OS_TASK_SW() 進行任務切換,它的過程如下:
只是一個宏,它實際替換的是 OSCtxSw()
#define OS_TASK_SW() OSCtxSw()
2)OSCtxSw()是由匯編實現(xiàn)的
OSCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;觸發(fā)PendSV異常 (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
POP {R4, R5}
BX LR
就這樣,上下文就完成了一次切換。
評論
查看更多