一、就绪任务列表
就绪列表 pxReadyTasksLists[ configMAX_PRIORITIES ]
是一个数组, 数组里面存的是就绪任务的 TCB(准确来说是 TCB 里面的 xStateListItem 节点) ,数组的下标对应任务的优先级,优先级越低对应的数组下标越小(即数字优先级越小,逻辑优先级也越小)。空闲任务的优先级最低,对应的是下标为 0 的链表。
pxReadyTasksLists就绪任务列表定义在tasks.c中:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
configMAX_PRIORITIES 该宏定义在FreeRTOSConfig.h中,代表支持的最大优先级。我们看到configMAX_PRIORITIES 即为就绪任务列表的数组元素个数。那么我们实际可用的任务优先级只能是0到configMAX_PRIORITIES-1,因为pxReadyTasksLists的元素下标是0到configMAX_PRIORITIES-1。
通常在FreeRTOSConfig.h中对于软件定时器的优先级定义为(configMAX_PRIORITIES-1)即相当于最高优先级了。
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) //软件定时器优先级
pxReadyTasksLists是List_t 类型的数组,看看List_t 是什么:
struct xLIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
void * pvOwner;
void * configLIST_VOLATILE pvContainer;
};
typedef struct xLIST_ITEM ListItem_t;
struct xMINI_LIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
typedef struct xLIST
{
configLIST_VOLATILE UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex;
MiniListItem_t xListEnd;
} List_t;
每个List_t元素可以看成链表,每个链表中指向的是当前处于就绪态的同一优先级的任务TCB
任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置,相同优先级的任务插入到就绪列表里面的同一条链表中。
二、查找最高优先级的就绪任务方法
1、通用方法
从最高优先级对应的就绪列表数组下标开始寻找当前链表下是否有任务存在,如果没有,则 uxTopPriority 减一操作,继续寻找下一个优先级对应的链表中是否有任务存在,如果有则跳出 while 循环,表示找到了最高优先级的就绪任务。之所以可以采用从最高优先级往下搜索,是因为任务的优先级与就绪列表的下标是一一对应的,优先级越高,对应的就绪列表数组的下标越大。获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB。
2、优化方法
Cortex-M 内核有一个计算前导零的指令CLZ。前导零就是计算一个变量(Cortex-M 内核单片机的变量为 32位)从高位开始第一次出现 1 的位的前面的零的个数。比如:一个 32 位的变量 uxTopReadyPriority,其位 0、位 24 和 位 25 均 置 1 , 其 余 位 为 0 。 那 么 使 用 前 导 零 指 令 __CLZ (uxTopReadyPriority)可以很快的计算出 uxTopReadyPriority 的前导零的个数为 6。利用前导零计算指令可以很快计算出就绪任务中的最高优先级。
三、源代码分析
两种查找最高优先级的就绪任务方法定义在task.c文件中。
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
}
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
\
/* Find the highest priority queue that contains ready tasks. */ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
configASSERT( uxTopPriority ); \
--uxTopPriority; \
} \
\
/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \
the same priority get an equal share of the processor time. */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
/*-----------------------------------------------------------*/
#define taskRESET_READY_PRIORITY( uxPriority )
#define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
#else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
/* If configUSE_PORT_OPTIMISED_TASK_SELECTION is 1 then task selection is
performed in a way that is tailored to the particular microcontroller
architecture being used. */
/* A port optimised version is provided. Call the port defined macros. */
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* Find the highest priority list that contains ready tasks. */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
/*-----------------------------------------------------------*/
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
#endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
查找最高优先级的就绪任务有两种方法 ,具体由 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏控制,定义为 0 选择通用方法, 定义为 1 选择根据处理器优化的方法。
该宏默认在 portmacro.h 中定义为 1,即使用优化过的方法。
/* Port specific optimisations. */
#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif
通用方法:
taskRECORD_READY_PRIORITY()
用于更新 uxTopReadyPriority的值。uxTopReadyPriority 是一个在 task.c 中定义的静态变量,用于表示创建的任务的最高优先级,默认初始化为 0,即空闲任务的优先级。
#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
}
taskSELECT_HIGHEST_PRIORITY_TASK()
用于寻找优先级最高的就绪任务,实质就是更新 uxTopReadyPriority 和 pxCurrentTCB 的值。
通用方法查找优先级最高的就绪任务的方法是通过while循环,从已知的最高优先级对应的就绪列表数组下标开始顺序往下寻找当前链表下是否有任务存在,直到找到最高优先级的就绪任务。 之所以可以采用从最高优先级往下搜索,是因为任务的优先级与就绪列表的下标是一一对应的,优先级越高,对应的就绪列表数组的下标越大。
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
\
/* Find the highest priority queue that contains ready tasks. */ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
configASSERT( uxTopPriority ); \
--uxTopPriority; \
} \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
/*-----------------------------------------------------------*/
#define taskRESET_READY_PRIORITY( uxPriority )
#define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
优化方法:
taskRECORD_READY_PRIORITY()
用于根据传进来的形参(通常形参就是任务的优先级)将变量 uxTopReadyPriority 的某个位置 1。uxTopReadyPriority 是一个在 task.c 中定义的静态变量,默认初始化为 0。与通用方法中用来表示创建的任务的最高优先级不一样,它在优化方法中担任的是一个优先级位图表的角色,即该变量的每个位对应任务的优先级,如果任务就绪,则将对应的位置 1,反之清零。根据这个原理,只需要计算出 uxTopReadyPriority 的前导零个数就算找到了就绪任务的最高优先级。与taskRECORD_READY_PRIORITY() 作 用 相 反 的 是taskRESET_READY_PRIORITY() 。
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
taskRESET_READY_PRIORITY()
用于根据传进来的形参(通常形参就是任务的优先级)将变量 uxTopReadyPriority 的某个位清零。
有个if判断条件是要先确保就绪列表中对应该优先级下的链表没有任务才能改变uxTopReadyPriority对应的位。
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
portRECORD_READY_PRIORITY和portRESET_READY_PRIORITY定义在portmacro.h 中:
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
taskSELECT_HIGHEST_PRIORITY_TASK()
用于寻找优先级最高的就绪任务,实质就是更新 uxTopReadyPriority 和 pxCurrentTCB 的值。
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* Find the highest priority list that contains ready tasks. */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
}
portGET_HIGHEST_PRIORITY在 portmacro.h 中定义
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )\
uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
在第二种优化方法中,变量uxTopReadyPriority 的每个位号对应的是任务的优先级,任务就绪时,则将对应的位置 1,反之则清零。
使 用 前 导 零 指 令 __CLZ(uxTopReadyPriority)可以很快的计算出 uxTopReadyPriority 的前导零的个数,通过前导零的个数能定位到uxTopReadyPriority最高非0位,也就能快速定位到最高就绪任务优先级。
调用:
任务切换函数vTaskSwitchContext()直接调用函数taskSELECT_HIGHEST_PRIORITY_TASK()寻找到优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB。