优先级
ucos3使用64个优先级,每个优先级中可以有多个不同的任务(除了0和末尾优先级)
优先级表初始化过程
void OS_PrioInit (void)
{
CPU_DATA i; //无符号整形32
for (i = 0u; i < OS_PRIO_TBL_SIZE; i++) {
OSPrioTbl[i] = (CPU_DATA)0;//清空任务表中的任务OS_PRIO_TBL_SIZE=2,相当于只有64个优先级,可以使用cpu函数CPU_CntLeadZeros()快速获取对应的优先级
}
}
系统可以在OSPrioTbl数组中快速获取最高的优先级,通过使用cpu函数CPU_CntLeadZeros()即可快速实现。该函数实现返回数据从左起0的数量,例如对于0x00F01234(32位),则返回8,对于0xFF001234,则返回0,对于32位处理器,优化后的代码为
OS_PRIO OS_PrioGetHightest(void)
{
OS_PRIO prio;
if(OSPrioTbl[0]!=(OS_PRIO_BIMAP)0){
prio=OS_CntLeadZeros(OSPrioTbl[0]);
}else{
prio=OS_CntLeadZeros(OSPrioTb1[1])+32;
}
return(prio);
}
在源码中则使用没有优化的函数
//返回高优先级的任务
OS_PRIO OS_PrioGetHighest (void)
{
CPU_DATA *p_tbl;//32u
OS_PRIO prio;//8u
prio = (OS_PRIO)0;
p_tbl = &OSPrioTbl[0];//获取优先级表的位置指针
//Os PrioGetHighest()从osPrioTbl[o]开始扫描表,直到找到一个非零的表项为止。循环将总是终止,因为由于空闲任务,表中总是有一个非零的条目。
while (*p_tbl == (CPU_DATA)0) { //循环判断当前优先级状态/* Search the bitmap table for the highest priority */
prio += DEF_INT_CPU_NBR_BITS;//每一等级占32位,判断后一32递增 /* Compute the step of each CPU_DATA entry */
p_tbl++;
}
//CPU CntLeadzeros()函数只是简单地计算一个CPU数据条目从左开始有多少个零(即最有效位)。例如,假设是32位,OxF0001234会得到0个前导0,Ox0OF01234会得到8个前导0。
prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl); /* Find the position of the first bit set at the entry */
return (prio);//返回当前闲置的最高优先级
}
就绪列表由两个数据结构组成:一个位图表,用来跟踪哪个优先级已经就绪,另一个表包含每个优先级上所有就绪任务的列表
- OSPrioTbl[] 存放优先级数组,当某优先级置1,则为闲置优先级
- OSRdyList[] 准备运行的任务,该数组的每一个位置代表一个优先级,即OSRdyList[0]代表优先级为0的任务,其他的以此类推,而每个数组成员则指向包含(NbrEntries,HeadPtr,TailPtr)参数的地址,NbrEntries代表该优先级中的任务总数,HeadPtr和TailPtr形成一个任务链,该任务链是当前优先级下的所有任务,易得前者指向首部,后者指向尾部。对于OSRdyList[0],将永远只有一个ISR Handler Task,对于OSRdyList[末尾],将永远只用于存放闲置任务。
任务堆栈
先前我们讨论过,ucos的任务堆栈是作为sram前段内存储存在sram中的,之后是设备堆栈,对于任务堆栈,其特性如下图
会发现作为任务堆栈,其增长方式和栈是一样的(高地址到低地址),堆栈的最下方(最高地址)储存着处理器cpu当前的寄存器值,我们知道ucos是一个抢占式系统,当任务切换时,任务会保存当前的cpu寄存器(CPU Registers)值并进入"休眠",然后切换到另一个优先级高的任务,执行结束后再返回,然后取出休眠前的cpu寄存器值(这些值记录了执行到代码的哪一行,各种临时变量的值和对应的指针),然后把值推回cpu就又能够从先前停下的地方继续运行。存储完这部份数据后的数据就是任务执行需要的其他堆栈数据的位置,没有用的内存值都为0,任务堆栈从高地址到低地址增长,其栈顶由任务的控制块TCB(Task Control Base)的SP指针指定,即标明栈顶。对于TCB,每个TCB都保存着任务初始化时的数据(eg:任务优先级,任务名称,任务状态,内部信息队列等等)。
中断处理
在任务运行过程中,如果遇到了ISR这样中断,系统将停下当前任务去执行中断任务,该中断任务使用的堆栈是MSP(stm32的栈),因此不会破坏原任务的数据结构,在执行结束后返回原任务时,读写任务堆栈中保存的CPU寄存器值就可以继续执行任务。
对于为什么不在ISR中进行任务的切换,网络上的解释是这将可能导致原有的context无法恢复,且如果有多种不同的信息恰好在ISR中释放时,很可能将导致关键信号的接收失败。为此在出现中断嵌套时停止任务切换等操作是更加省时的方式。对于不在ISR中使用任务调度还有的理解如下为什么不能在ISR中进行任务切换;
为了避免在ISR时系统的context被破坏,在ucos中,编写中断服务函数格式如下
void EXTI15_10_IRQHandler(void)
{
OSIntEnter();
/**裸机时候的中断服务函数**/
OSIntExit();
}
其中OSIntEnter()和OSIntExit()代码如下
void OSIntEnter (void)
{
if (OSRunning != OS_STATE_OS_RUNNING) { /* Is OS running? */
return; /* No */
}
if (OSIntNestingCtr >= (OS_NESTING_CTR)250u) { /* Have we nested past 250 levels? */
return; /* Yes */
}
OSIntNestingCtr++; /* Increment ISR nesting level */
}
void OSIntExit (void)
{
CPU_SR_ALLOC();
if (OSRunning != OS_STATE_OS_RUNNING) { /* Has the OS started? */
return; /* No */
}
CPU_INT_DIS();
if (OSIntNestingCtr == (OS_NESTING_CTR)0) { /* Prevent OSIntNestingCtr from wrapping */
CPU_INT_EN();
return;
}
OSIntNestingCtr--;
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */
CPU_INT_EN(); /* Yes */
return;
}
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler still locked? */
CPU_INT_EN(); /* Yes */
return;
}
OSPrioHighRdy = OS_PrioGetHighest(); /* Find highest priority */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; /* Get highest priority task ready-to-run */
if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task still the highest priority? */
CPU_INT_EN(); /* Yes */
return;
}
#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches for this new task */
#endif
OSTaskCtxSwCtr++; /* Keep track of the total number of ctx switches */
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif
OSIntCtxSw(); /* Perform interrupt level ctx switch */
CPU_INT_EN();
}
可以看到,在进出中断服务函数时,会判断当前中断的嵌套层数if (OSIntNestingCtr == (OS_NESTING_CTR)0)
,一直到中断嵌套层数为0时才会开始执行任务调度,这意味着在出现ISR嵌套时,系统的所有精力都在ISR上,直到所有ISR都完成。如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-438vfR0b-1616903544917)(/pic\嵌入式\系统\UCOS\ISR_SW.png)]
参考
ucos在M3中的中断嵌套机制
关于STM32-M3/M4的MSP和PSP
任务控制块(TCB)
这是相当多字段的一个结构,结构源码如下
struct os_tcb {
CPU_STK *StkPtr; /* Pointer to current top of stack,指向当前已用的栈底 */
void *ExtPtr; /* Pointer to user definable data for TCB extension,扩展储存FPU的数据(如果有FPU)*/
CPU_STK *StkLimitPtr; /* Pointer used to set stack 'watermark' limit */
OS_TCB *NextPtr; /* Pointer to next TCB in the TCB list */
OS_TCB *PrevPtr; /* Pointer to previous TCB in the TCB list */
OS_TCB *TickNextPtr;
OS_TCB *TickPrevPtr;
/*
TickNextPtr,TickPrevPtr这些指针用于在等待时间到期或等待pend调用超时的任务列表中双向链接os tcb。
同样,双链表允许从列表中快速插入和删除Os tcb。
*/
OS_TICK_SPOKE *TickSpokePtr; /* Pointer to tick spoke if task is in the tick list
该指针用于知道任务链接到滴答列表中的哪一项
*/
CPU_CHAR *NamePtr; /* Pointer to task name */
CPU_STK *StkBasePtr; /* Pointer to base address of stack 栈底(任务栈是由高地址到低地址)*/
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS TLS_Tbl[OS_CFG_TLS_TBL_SIZE];
#endif
OS_TASK_PTR TaskEntryAddr; /* Pointer to task entry point address任务函数代码的位置*/
void *TaskEntryArg; /* Argument passed to task when it was created任务函数代码使用的参数*/
OS_PEND_DATA *PendDataTblPtr; /* Pointer to list containing objects pended on */
OS_STATE PendOn; /* Indicates what task is pending on 表明等待的对象类型(标志,信号量,互斥锁...) */
OS_STATUS PendStatus; /* Pend status 表明等待的结果(ok,del,timeout,about) */
OS_STATE TaskState; /* See OS_TASK_STATE_xxx 当前任务状态(挂起,运行,删除....)*/
OS_PRIO Prio; /* Task priority (0 == highest) */
CPU_STK_SIZE StkSize; /* Size of task stack (in number of stack elements) 整个任务堆栈的大小*/
OS_OPT Opt; /* Task options as passed by OSTaskCreate() */
OS_OBJ_QTY PendDataTblEntries; /* Size of array of objects to pend on任务正在等待的对象数量*/
CPU_TS TS; /* Timestamp */
OS_SEM_CTR SemCtr; /* Task specific semaphore counter 任务收到的信号量的数量 */
/* DELAY / TIMEOUT */
OS_TICK TickCtrPrev; /* Previous time when task was ready */
OS_TICK TickCtrMatch; /* Absolute time when task is going to be ready */
OS_TICK TickRemain; /* Number of ticks remaining for a match (updated at ... */
/* ... run-time by OS_StatTask() 剩余的等待时间 */
OS_TICK TimeQuanta;
OS_TICK TimeQuantaCtr;
/*这些字段用于时间切片。当多个任务以相同优先级准备运行时,
. timequanta决定任务将执行多少时间(以ticks为单位),
直到它被uC/OS-III抢占,以便下一个具有相同优先级的任务有机会执行。
.TimeQuantactr将跟踪此任务发生的剩余滴答数,
并在任务的时间片开始时由.TimeQuanta加载*/
#if OS_MSG_EN > 0u
void *MsgPtr; /* Message received */
OS_MSG_SIZE MsgSize;
#endif
#if OS_CFG_TASK_Q_EN > 0u
OS_MSG_Q MsgQ; /* Message queue associated with task */
#if OS_CFG_TASK_PROFILE_EN > 0u
CPU_TS MsgQPendTime; /* Time it took for signal to be received 收到信息等待的时间*/
CPU_TS MsgQPendTimeMax; /* Max amount of time it took for signal to be received */
#endif
#endif
#if OS_CFG_TASK_REG_TBL_SIZE > 0u
OS_REG RegTbl[OS_CFG_TASK_REG_TBL_SIZE]; /* Task specific registers(可以用来储存任务相关的信息) */
#endif
#if OS_CFG_FLAG_EN > 0u
OS_FLAGS FlagsPend; /* Event flag(s) to wait on 任务挂起的事件标志 */
OS_FLAGS FlagsRdy; /* Event flags that made task ready to run */
OS_OPT FlagsOpt; /* Options (See OS_OPT_FLAG_xxx)当任务挂起事件标志时,此字段包含pend的类型*/
#endif
#if OS_CFG_TASK_SUSPEND_EN > 0u
OS_NESTING_CTR SuspendCtr; /* Nesting counter for OSTaskSuspend()记录任务被挂起的时间(可以嵌套)*/
#endif
#if OS_CFG_TASK_PROFILE_EN > 0u
OS_CPU_USAGE CPUUsage; /* CPU Usage of task (0.00-100.00%)CPUUsage包含任务的CPU使用率百分比(0到100%)。*/
OS_CPU_USAGE CPUUsageMax; /* CPU Usage of task (0.00-100.00%) - Peak */
OS_CTX_SW_CTR CtxSwCtr; /* Number of time the task was switched in记录任务运行的次数*/
CPU_TS CyclesDelta; /* value of OS_TS_GET() - .CyclesStart记录当前的时间戳 */
CPU_TS CyclesStart; /* Snapshot of cycle counter at start of task resumption切换到当前任务时的时间戳*/
OS_CYCLES CyclesTotal; /* Total number of # of cycles the task has been running任务总运行时间*/
OS_CYCLES CyclesTotalPrev; /* Snapshot of previous # of cycles上一次运行的时间? */
CPU_TS SemPendTime; /* Time it took for signal to be received 接收到信号量的时间*/
CPU_TS SemPendTimeMax; /* Max amount of time it took for signal to be received */
#endif
#if OS_CFG_STAT_TASK_STK_CHK_EN > 0u
CPU_STK_SIZE StkUsed; /* Number of stack elements used from the stack已用应用堆栈 */
CPU_STK_SIZE StkFree; /* Number of stack elements free on the stack空闲应用堆栈 */
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN
CPU_TS IntDisTimeMax; /* Maximum interrupt disable time */
#endif
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
CPU_TS SchedLockTimeMax; /* Maximum scheduler lock time该字段跟踪任务的最大调度程序锁定时间。 */
#endif
#if OS_CFG_DBG_EN > 0u
OS_TCB *DbgPrevPtr;
OS_TCB *DbgNextPtr;
CPU_CHAR *DbgNamePtr; //包含导致任务挂起的对象类型(信号量,互斥锁,消息队列...)
#endif
};