目录
之前的章节,FreeRTOS还没有支持多优先级,只支持两个任务互相切换,从本章开始,任务中我们开始加入优先级的功能。在FreeRTOS中,数字优先级越小,逻辑优先级也越小,这与隔壁的RT-Thread和μC/OS刚好相反。
1. 如何支持多优先级
就绪列表pxReadyTasksLists[ configMAX_PRIORITIES ]是一个数组,数组里面存的是就绪任务的TCB(准确来说是TCB里面的xStateListItem节点),数组的下标对应任务的优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为0的链表。图 演示的是就绪列表中有两个任务就绪,优先级分别为1和2,其中空闲任务没有画出来,空闲任务自系统启动后会一直就绪,因为系统至少得保证有一个任务可以运行。
configMAX_PRIORITIES 为任务优先级数量,若 配置为 5,则优先级范围【0,4】,其余以此类推。
任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置。相同优先级的任务插入到就绪列表里面的同一条链表中,这就是我们下一章要讲解的支持时间片轮转。
pxCurrenTCB是一个全局的TCB指针,用于指向优先级最高的就绪任务的TCB,即当前正在运行的TCB。那么我们要想让任务支持优先级,即只要解决在任务切换(taskYIELD)的时候,让pxCurrenTCB指向最高优先级的就绪任务的TCB就可以,前面的章节我们是手动地让pxCurrenTCB在任务1、任务2和空闲任务中轮转,现在我们要改成pxCurrenTCB在任务切换的时候指向最高优先级的就绪任务的TCB即可,那问题的关键就是:如果找到最高优先级的就绪任务的TCB。
FreeRTOS提供了两套方法,一套是通用的,一套是根据特定的处理器优化过的,接下来我们重点讲解下这两个方法。
2. 查找最高优先级的就绪任务
寻找最高优先级的就绪任务相关代码在task.c中定义
/* 查找最高优先级的就绪任务:通用方法 */
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
/* uxTopReadyPriority 存的是就绪任务的最高优先级 */
#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
} /* taskRECORD_READY_PRIORITY */
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
\
/* 寻找包含就绪任务的最高优先级的队列 */ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
--uxTopPriority; \
} \
\
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
/* 更新uxTopReadyPriority */ \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
/*-----------------------------------------------------------*/
/* 这两个宏定义只有在选择优化方法时才用,这里定义为空 */
#define taskRESET_READY_PRIORITY( uxPriority )
#define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
/* 查找最高优先级的就绪任务:根据处理器架构优化后的方法 */
#else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* 寻找最高优先级 */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
/*-----------------------------------------------------------*/
#if 0
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
#else
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
}
#endif
#endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
找最高优先级的就绪任务有两种方法,具体由configUSE_PORT_OPTIMISED_TASK_SELECTION这个宏控制,定义为0选择通用方法,定义为1选择根据处理器优化的方法,该宏默认在portmacro.h中定义为1,即使用优化过的方法。
2.1 通用方法(普适性)
taskRECORD_READY_PRIORITY()用于更新uxTopReadyPriority的值。uxTopReadyPriority是一个在task.c中定义的静态变量,用于表示创建的任务的最高优先级,默认初始化为0,即空闲任务的优先级.
taskSELECT_HIGHEST_PRIORITY_TASK()用于寻找优先级最高的就绪任务,实质就是更新uxTopReadyPriority和pxCurrentTCB的值。
从最高优先级对应的就绪列表数组下标开始寻找当前链表下是否有任务存在,如果没有,则uxTopPriority减一操作,继续寻找下一个优先级对应的链表中是否有任务存在,如果有则跳出while循环,表示找到了最高优先级的就绪任务。之所以可以采用从最高优先级往下搜索,是因为任务的优先级与就绪列表的下标是一一对应的,优先级越高,对应的就绪列表数组的下标越大。
2.2 优化方法(专业性)
优化的方法,这得益于Cortex-M内核有一个计算前导零的指令CLZ,所谓前导零就是计算一个变量(Cortex-M内核单片机的变量为32位)从高位开始第一次出现1的位的前面的零的个数。
比如:一个32位的变量uxTopReadyPriority,其位0、位24和位25均置1,其余位为0,具体见。那么使用前导零指令__CLZ (uxTopReadyPriority)可以很快的计算出uxTopReadyPriority的前导零的个数为6。
如果uxTopReadyPriority的每个位号对应的是任务的优先级,任务就绪时,则将对应的位置1,反之则清零。那么图 10-2就表示优先级0、优先级24和优先级25这三个任务就绪,其中优先级为25的任务优先级最高。利用前导零计算指令可以很快计算出就绪任务中的最高优先级为:( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) ) = ( 31UL - ( uint32_t ) 6 ) = 25。
故此,正常情况下,对Cotex-M的内核,优先级数最好不要超过32,这样处理速度相对使用通用方法要快不少。
3. 代码修改
参照源码,参考工程例程,很好理解,不做赘述