就绪优先级为映像响表
在UCOSIII内,任务调度是要先找到优先级最高的任务,然后执行。理论上对于UCOSIII可以有无数个优先级,每个优先级又可以有无数个任务但是对于这么多的任务如何快速查到到当先就绪的最高优先级的任务是那个,为了完成这个功能ucos的设计了就绪优先级为映像响表组合任务就绪表来实现这个功能。对于32位的CPU就绪优先级为映像响表的数据结构如下:
位 数组元素 | 31 | 30 | 29 | ...... | 2 | 1 | 0 |
OSPrioTbl[0] | 优先级0 | 优先级1 | 优先级2 | ... | 优先级29 | 优先级30 | 优先级31 |
OSPrioTbl[1] | 优先级32 | 33 | 34 | ... | 61 | 62 | 63 |
OSPrioTbl[2] | ... | ... | ... | ... | ... | ... | ... |
...... | ... | ... | ... | ... | ... | ... | ... |
OSPrioTbl[n] | ... | ... | ... | ... | ... | ... | ... |
相关的函数有查找就绪优先级为映像响表中最高的优先级:
OS_PRIO OS_PrioGetHighest (void)
{
CPU_DATA *p_tbl;
OS_PRIO prio;
prio = (OS_PRIO)0;
p_tbl = &OSPrioTbl[0];
while (*p_tbl == (CPU_DATA)0) { /* Search the bitmap table for the highest priority */
prio += DEF_INT_CPU_NBR_BITS; /* Compute the step of each CPU_DATA entry */
p_tbl++;
}
prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl); /* Find the position of the first bit set at the entry */
return (prio);
}
这里因为STM32提供了计算前导零的指令所以程序中直接调用了汇编函数CPU_CntLeadZeros来计算前导零,对于不支持这个指令的CPU程序中也给出了C计算前导零的函数.
CPU_CntLeadZeros
CLZ R0, R0 ; Count leading zeros
BX LR
C计算前导零(32bit)的函数.
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#if (CPU_CFG_DATA_SIZE_MAX >= CPU_WORD_SIZE_32)
CPU_DATA CPU_CntLeadZeros32 (CPU_INT32U val)
{
#if (!((defined(CPU_CFG_LEAD_ZEROS_ASM_PRESENT)) && \
(CPU_CFG_DATA_SIZE >= CPU_WORD_SIZE_32)))
CPU_DATA ix;
#endif
CPU_DATA nbr_lead_zeros;
/* ---------- ASM-OPTIMIZED ----------- */
#if ((defined(CPU_CFG_LEAD_ZEROS_ASM_PRESENT)) && \
(CPU_CFG_DATA_SIZE >= CPU_WORD_SIZE_32))
nbr_lead_zeros = CPU_CntLeadZeros((CPU_DATA)val);
nbr_lead_zeros -= (CPU_CFG_DATA_SIZE - CPU_WORD_SIZE_32) * DEF_OCTET_NBR_BITS;
#else /* ----------- C-OPTIMIZED ------------ */
if (val > 0x0000FFFFu) {
if (val > 0x00FFFFFFu) { /* Chk bits [31:24] : */
/* .. Nbr lead zeros = .. */
ix = (CPU_DATA)(val >> 24u); /* .. lookup tbl ix = 'val' >> 24 bits */
nbr_lead_zeros = (CPU_DATA)(CPU_CntLeadZerosTbl[ix] + 0u); /* .. plus nbr msb lead zeros = 0 bits.*/
} else { /* Chk bits [23:16] : */
/* .. Nbr lead zeros = .. */
ix = (CPU_DATA)(val >> 16u); /* .. lookup tbl ix = 'val' >> 16 bits */
nbr_lead_zeros = (CPU_DATA)(CPU_CntLeadZerosTbl[ix] + 8u); /* .. plus nbr msb lead zeros = 8 bits.*/
}
} else {
if (val > 0x000000FFu) { /* Chk bits [15:08] : */
/* .. Nbr lead zeros = .. */
ix = (CPU_DATA)(val >> 8u); /* .. lookup tbl ix = 'val' >> 8 bits */
nbr_lead_zeros = (CPU_DATA)(CPU_CntLeadZerosTbl[ix] + 16u); /* .. plus nbr msb lead zeros = 16 bits.*/
} else { /* Chk bits [07:00] : */
/* .. Nbr lead zeros = .. */
ix = (CPU_DATA)(val >> 0u); /* .. lookup tbl ix = 'val' >> 0 bits */
nbr_lead_zeros = (CPU_DATA)(CPU_CntLeadZerosTbl[ix] + 24u); /* .. plus nbr msb lead zeros = 24 bits.*/
}
}
#endif
return (nbr_lead_zeros);
}
#endif
同样8,16,64,位的都是相同的原理。
置位表中某个优先级处于就绪
void OS_PrioInsert (OS_PRIO prio)
{
CPU_DATA bit;
CPU_DATA bit_nbr;
OS_PRIO ix;
ix = prio / DEF_INT_CPU_NBR_BITS;
bit_nbr = (CPU_DATA)prio & (DEF_INT_CPU_NBR_BITS - 1u);
bit = 1u;
bit <<= (DEF_INT_CPU_NBR_BITS - 1u) - bit_nbr;
OSPrioTbl[ix] |= bit;
}
表中某个优先级处于就绪位清零:
void OS_PrioRemove (OS_PRIO prio)
{
CPU_DATA bit;
CPU_DATA bit_nbr;
OS_PRIO ix;
ix = prio / DEF_INT_CPU_NBR_BITS;
bit_nbr = (CPU_DATA)prio & (DEF_INT_CPU_NBR_BITS - 1u);
bit = 1u;
bit <<= (DEF_INT_CPU_NBR_BITS - 1u) - bit_nbr;
OSPrioTbl[ix] &= ~bit;
}
系统巧妙地设计了就绪优先级位映像表数据结构就是为了能够快速的查找到就绪列表中最高的优先级任务,同时搭配就绪列表就可以方便管理任务的调用了。
任务就绪列表
这是一个用来管理UCOS内就绪任务的列表,系统定义了一个数组OSRdyList[OS_CFG_PRIO_MAX],每个优先级对应其中一个元素,反过来说就是一个元素管理同一个优先级下的所有任务,OSRdyList的数据结构如图,很简单HeadPtr指向优先级上的第一个任务控制块,TailPtr指向最后一个,NbrEnries记录任务及上有多少任务。任务控制块里的NextPtr和PrePtr相互之间串成一个双向链表。如图:
初始化任务就绪表函数其实就是对OSRdyList[]每个结构体进行0值初始化。
使任务就绪函数其中OS_PrioInsert是将就绪优先级为映像响表对应位置1的操作。OS_RdyListInsertTail是将任务插入同优先级的就绪表的末尾,OS_RdyListInsertHead为插入开头,OS_RdyListMoveHeadToTail是将同优先级的任务的开头的任务移到末尾的操作,在时间轮片调度处调用。
void OS_RdyListInsert (OS_TCB *p_tcb)
{
OS_PrioInsert(p_tcb->Prio);
if (p_tcb->Prio == OSPrioCur) { /* Are we readying a task at the same prio? */
OS_RdyListInsertTail(p_tcb); /* Yes, insert readied task at the end of the list */
} else {
OS_RdyListInsertHead(p_tcb); /* No, insert readied task at the beginning of the list */
}
最后看到OSStart()函数内的一段代码就知道,系统内核是如何调度一个高优先级的任务开始运行的。
if (OSRunning == OS_STATE_OS_STOPPED) {
OSPrioHighRdy = OS_PrioGetHighest(); /* Find the highest priority */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING;
OSStartHighRdy(); /* Execute target specific code to start task */
*p_err = OS_ERR_FATAL_RETURN; /* OSStart() is not supposed to return */
其中OSStartHighRdy()为一个汇编函数是任务管理相关的函数,就知道他的功能就是开始当前最高优先级的任务的运行就可以。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
OSStartHighRdy LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority LDR R1, =NVIC_PENDSV_PRI STRB R1, [R0] MOVS R0, #0 ; Set the PSP to 0 for initial context switch call MSR PSP, R0 LDR R0, =OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase LDR R1, [R0] MSR MSP, R1 LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0] CPSIE I ; Enable interrupts at processor level OSStartHang B OSStartHang ; Should never get here
任务轮片调度:
在创建任务时设置任务轮片的时间节拍数,当任务执行到一定的时钟节拍后就会将任务同优先级的任务进行调度运行,这个函数在系统时钟的中断函数里有调用。
void OSTimeTick (void)
{
OS_ERR err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
CPU_TS ts;
#endif
OSTimeTickHook(); /* Call user definable hook */
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
ts = OS_TS_GET(); /* Get timestamp */
OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK, /* Post to ISR queue */
(void *)&OSRdyList[OSPrioCur],
(void *) 0,
(OS_MSG_SIZE) 0u,
(OS_FLAGS ) 0u,
(OS_OPT ) 0u,
(CPU_TS ) ts,
(OS_ERR *)&err);
#else
(void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB, /* Signal tick task */
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
void OS_SchedRoundRobin (OS_RDY_LIST *p_rdy_list)
{
OS_TCB *p_tcb;
CPU_SR_ALLOC();
if (OSSchedRoundRobinEn != DEF_TRUE) { /* Make sure round-robin has been enabled */
return;
}
CPU_CRITICAL_ENTER();
p_tcb = p_rdy_list->HeadPtr; /* Decrement time quanta counter */
if (p_tcb == (OS_TCB *)0) {
CPU_CRITICAL_EXIT();
return;
}
if (p_tcb == &OSIdleTaskTCB) {
CPU_CRITICAL_EXIT();
return;
}
if (p_tcb->TimeQuantaCtr > (OS_TICK)0) {
p_tcb->TimeQuantaCtr--;
}
if (p_tcb->TimeQuantaCtr > (OS_TICK)0) { /* Task not done with its time quanta */
CPU_CRITICAL_EXIT();
return;
}
if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2) { /* See if it's time to time slice current task */
CPU_CRITICAL_EXIT(); /* ... only if multiple tasks at same priority */
return;
}
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't round-robin if the scheduler is locked */
CPU_CRITICAL_EXIT();
return;
}
OS_RdyListMoveHeadToTail(p_rdy_list); /* Move current OS_TCB to the end of the list */
p_tcb = p_rdy_list->HeadPtr; /* Point to new OS_TCB at head of the list */
if (p_tcb->TimeQuanta == (OS_TICK)0) { /* See if we need to use the default time slice */
p_tcb->TimeQuantaCtr = OSSchedRoundRobinDfltTimeQuanta;
} else {
p_tcb->TimeQuantaCtr = p_tcb->TimeQuanta; /* Load time slice counter with new time */
}
CPU_CRITICAL_EXIT();
}
轮片调度就是将同一优先级就绪列表的的任务从头到尾的轮换执行。