嵌入式学习笔记 - freeRTOS任务优先级抢占,时间片抢占的实现机制
如果是大于的情况,那么使用listGET_OWNER_OF_NEXT_ENTRY也是指向当前到期的任务,因为taskSELECT_HIGHEST_PRIORITY_TASK()函数实现里关于listGET_OWNER_OF_NEXT_ENTRY()的函数实现里( pxConstList )->pxIndex总是指向end节点之前,而时间到期插入就绪任务时使用的是insertEnd();第564行是
一 任务优先级、时间片抢占
已经知道freeRTOS的任务切换函数发生在systick中断中,如下图

由上图可见,任务切换有个先决条件TaskIncrementTick()函数返回为1,这个函数如下图,

函数返回值为xSwitchRequired,有两处赋值的地方
第① 处:总体有两个条件,一个是延时列表有任务到期,一个是到期任务优先级>=当前任务优先级,分别是划红线的语句526行以及568行,然后就使能切换任务,感觉这个自然是正常逻辑。
其中等于的情况也包含了:想象下系统只有一个优先级有两个任务的情况,如果任务中没有主动切换释放CPU的话,如果这里再不切换的话就会一直执行一个任务,配合任务切换函数用的是listGET_OWNER_OF_NEXT_ENTRY,总是指向下一个节点任务,既使不使能时间片抢占,同优先级的另一个任务到期也可以依次切换执行,这里其实不是时间片抢占,只是同优先级任务到期抢占。
如果是大于的情况:那么使用listGET_OWNER_OF_NEXT_ENTRY也是指向当前到期的任务,因为taskSELECT_HIGHEST_PRIORITY_TASK()函数实现里关于listGET_OWNER_OF_NEXT_ENTRY()的函数实现里( pxConstList )->pxIndex总是指向end节点之前,而时间到期插入就绪任务时使用的是insertEnd();总是插入最后一个节点,即便前面还有一个节点也不会错过这个节点,这就是高优先级抢占。
小于的情况:如果是小于的情况不切换,既使到期,一直要等到高优先级的任务释放CPU。
为了便于梳理记忆,可以将优先级大于以及等于当前任务优先级的情况统一看成高优先级抢占模式(实际包含同优先级),下面要讲的额第②处看成同优先级时间片抢占模式。
另外说一下关于第557行关于这个宏configUSE_PREEMPTION,这个宏在配置文件中可被修改,由以上代码可知,如果这个宏关闭,高优先级抢占,以及同优先级抢占将被关闭,系统只能依赖自身任务通过vTaskDelay( )里的 taskYIELD()释放CPU,这种模式就是所谓的协作模式,就是一个任务一个任务交替执行,不管优先级大小。
第②处:第583行至第586行就是判断是否执行实现时间片抢占的部分,第580行是个宏定义在配置文件中,
第583行是判断条件,如果当先优先级链表里面有多于一个任务在运行,使能任务切换,实现时间片抢占,既使延时列表里面没有任务到期,这其实就是时间片抢占。
二 任务切换执行实现
下图是任务切换taskYIELD();的最终实现,依次调用:




最后一个函数第120行就实现了将想要切换的任务的任务控制块指针赋值给pxCurrentTCB.
pendSV 最终根据pxCurrentTCB 第一项指示的栈顶地址,将任务栈的内容更改到msp的指针以及PC的指针以及其他寄存器实现任务切换。
前面所有的准备,不管是任务从就绪列表删除加入延时列表,
或者是systick中断轮询到任务时间到期将任务从延时列表删除加入就绪列表,
都是为了在此处体现,此处是pendSV汇编代码切换上下文的最终执行之处,
只能有一个任务被切换到pxCurrentTCB,也只能在这被切换到pxCurrentTCB。
三 调试实例
通过以上理论分析,来调试一个调试freeRTOS系统中遇到的问题:
系统共有三个任务:
任务1优先级为1,任务2优先级为2,任务3为空闲任务优先级为0,
任务1中没有使用vTaskDelay( )函数释放CPU的语句
任务2中使用了vTaskDelay( )函数释放CPU,
按照道理任务1任务2应该可以有序的执行,但是通过单步运行以及仿真发现实际任务2一次也未得到执行,尽管任务2已经加入到就绪列表如下图,由本文第二节内容分析推断也就是从未进行过任务切换,不然pendSV中断中的vTaskSwitchContext()/taskSELECT_HIGHEST_PRIORITY_TASK();函数肯定会选择任务2至少执行一次,因为任务2优先级最高。

我们由第一节的内容分析知道:
切换任务的地方一个是任务通过vTaskDelay( )主动切换,如下,而任务一没有使用这个函数,是我们的本意,

一个是systick中条件达到时进行任务的切换,如下

这个切换的执行条件如下,第二个条件不可能因为一个优先级只有一个任务,那么只有第一个条件是允许的,而xNextTaskUnblockTime赋值的地方只有任务二中有延时释放,所以这个条件才不能满足,这就是任务二不能执行的原因了,
为什么任务二没有对xNextTaskUnblockTime赋值呢,任务二的优先级最高,至少创建任务的时候汇之星一次对其赋值,然后我们单步调试发现,在创建空闲任务的时候,也就是在三个任务初始话的最后多了一个错误语句 pxCurrentTCB = &Task1TCB; 既使任务二创建的时候已将pxCurrentTCB 赋值为任务2,pxCurrentTCB = &Task2TCB;所以任务二一开始就没有得到执行,xNextTaskUnblockTime也从未得到赋值,任务二也从未有机会运行,注释掉此语句后一切正常。

从此次调试也可以得到一个结论:
系统必须从高优先进入,因为至少要给高优先级赋值时间变量xNextTaskUnblockTime的机会,因为时间变量是任务中赋值的,
虽然创建任务的时候已经加入就绪列表而且赋值了优先级位如下:
/* 将任务添加到就绪列表 */
#define prvAddTaskToReadyList( pxTCB ) \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
但是少了xNextTaskUnblockTime也有可能得不到进入任务切换的机会,如果没有其他任务进行切换任务的执行语句taskYIELD();,永远不会进入执行切换任务的函数:
void vTaskSwitchContext( void )
{
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */
taskSELECT_HIGHEST_PRIORITY_TASK();
}
就永远不会有pendSV进行对最高优先级任务的选取操作。
就有可能得不到运行(xNextTaskUnblockTime是systick激活切换任务的条件,
如果低优先级任务中又没有释放/切换任务的语句的话,高优先级任务就有可能得不到执行)
四 总结一下任务得到执行的三个要素
①写入就绪列表,②优先级位赋值,这两条如下:
/* 将任务添加到就绪列表 */
#define prvAddTaskToReadyList( pxTCB ) \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
③任务得到执行,也就是能通过 taskYIELD();进入pendSV中断任务切换的函数从而选取到任务,如下,条件是systick选中(xNextTaskUnblockTime到期或者其他,具体见第一节),或者任务主动切换进入
void vTaskSwitchContext( void )
{
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */
taskSELECT_HIGHEST_PRIORITY_TASK();
}
五 题外话,关于主动释放CPU使用权:
同样可得知如果高优先级的任务没有使用vTaskDelay( )里的 taskYIELD();主动释放CPU(切换任务)的话,既使低优先级任务延时到期,systick中断里面的条件也不会满足,因为当前任务一直是高优先级,低优先级的任务也永远得不到执行。
如果高优先级使用了vTaskDelay( )里的 taskYIELD();主动释放CPU(切换任务)的话,既使低优先级没有就绪,会切换到空闲任务,然后低优先级任务就绪后,systick的判断才会满足(当前优先级大于pxCurrentTCB->uxPriority的优先级)的条件,才会进行任务切换。
如下图就是释放CPU的代码:

第509行将相应的优先级位去除,编译pendSV查找到低优先级的任务,
第512行pendSV执行切换将找到的低优先级任务切换到pxCurrentTCB。
更多推荐



所有评论(0)