在FreeRTOS中,数字优先级越小,逻辑优先级也越小;在任务创建时,会根据任务的优先级将任务插入就绪列表不同的位置。
相同优先级的任务插入就绪列表中的同一条链表中。
要想任务支持优先级,即只要实现在任务切换(taskYIELD)时,让pxCurrentTCB指向最高优先级的就绪任务的TCB既可以。
重要数据
0. 全局TCB指针
/* 当前正在运行的任务的任务控制块指针,默认初始化为NULL */
TCB_t * volatile pxCurrentTCB = NULL;
1. 静态变量:uxTopReadyPriority
位置:在task.c文件中定义;静态变量;
static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;
表示创建的任务的最高优先级,默认初始化为0,即空闲任务的优先级。
数值越大,优先级越高;
2. 修改任务控制块:增加一变量
增加了一个变量:UBaseType_t uxPriority;
描述任务块的优先级。
typedef struct tskTaskControlBlock
{volatile StackType_t *pxTopOfStack; /* 栈顶 */ListItem_t xStateListItem; /* 任务节点 */StackType_t *pxStack; /* 任务栈起始地址 *//* 任务名称,字符串形式 */char pcTaskName[ configMAX_TASK_NAME_LEN ];TickType_t xTicksToDelay;UBaseType_t uxPriority;
} tskTCB;
typedef tskTCB TCB_t;
此处有一个疑问:
3. 全局任务定时器
static volatile UBaseType_t uxCurrentNumberOfTasks = ( UBaseType_t ) 0U;
两种方法
查找最高优先级的就绪任务有两种方法,具体由宏configUSE_PORT_OPTIMISED_TASK_SELECTION控制,定义为0选择通用方法,定义为1选择根据处理器优化的方法,该宏默认在portmacro.h文件中定义为1,即使用优化过的方法。
#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif
该功能主要是通过四个宏来实现:
taskRECORD_READY_PRIORITY( uxPriority )
taskSELECT_HIGHEST_PRIORITY_TASK()
taskRESET_READY_PRIORITY( uxPriority )
portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
通用方法
在通用方法中实现了上面的宏1、宏2;宏3~4为空。
两个宏定义
taskRECORD_READY_PRIORITY( uxPriority )
位置:在task.c文件中定义;
实现记录当前运行任务的优先级,直接得到优先级的序号,并更新到静态变量 uxTopReadyPriority 中;
#define taskRECORD_READY_PRIORITY( uxPriority ) \{ \if( ( uxPriority ) > uxTopReadyPriority ) \{ \uxTopReadyPriority = ( uxPriority ); \} \} /* taskRECORD_READY_PRIORITY */
taskSELECT_HIGHEST_PRIORITY_TASK()
位置:在task.c文件中定义;
#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 */
1)用于寻找优先级最高的就绪任务、更新静态变量 uxTopReadyPriority;调用宏:listLIST_IS_EMPTY( pxList )
/* 判断链表是否为空 */
#define listLIST_IS_EMPTY( pxList ) ( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )
此处有一个疑问:链表中的变量计数器(uxNumberOfItems)用来做什么用?
/* 链表结构体定义 */
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 */
ListItem_t * pxIndex; /* 链表节点索引指针 */
MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;
现在可以回答这个问题了,也就是这列链表中节点的个数,其在
a. 将节点从链表中删除:
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) 函数会减1;
b. 将节点插入到链表的尾部 :void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ) 函数中加1;
2)更新全局变量pxCurrentTCB;调用宏:listGET_OWNER_OF_NEXT_ENTRY(..)
/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \List_t * const pxConstList = ( pxList ); \/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \/* 当前链表为空 */ \if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \{ \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \} \/* 获取节点的OWNER,即TCB */ \( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
/* 节点结构体定义 */
struct xLIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
void * pvOwner; /* 指向拥有该节点的内核对象,通常是TCB */
void * pvContainer; /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */
3)继续更新静态变量 uxTopReadyPriority;
优化方法
Cortex-M 内核有一个计算前导零的指令:CLZ。用于计算一个变量从高位开始第一次出现1的位前面的0的个数。
__clz( ( uxReadyPriorities ):
a. uxReadyPriorities 为32位,每一个位号对应一个任务的优先级,1就绪,反之为0;
b. 32个优先级的任务: ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )表示最高就绪任务的等级;
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
两个宏定义
taskRECORD_READY_PRIORITY( uxPriority )
位置:在task.c文件中定义;
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
里头又涉及到两个宏:portRECORD_READY_PRIORITY 和 portRESET_READY_PRIORITY
位置:在portmacro.h文件中定义;
a. portRECORD_READY_PRIORITY 完成功能:任务就绪就把uxReadyPriorities相应的位置1;得到能够反应就绪的32位的变量uxReadyPriorities;
b.portRESET_READY_PRIORITY 功能相反;
#if configUSE_PORT_OPTIMISED_TASK_SELECTION == 1/* 根据优先级设置/清除优先级位图中相应的位 */#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )/*-----------------------------------------------------------*/#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )#endif /* taskRECORD_READY_PRIORITY */
taskSELECT_HIGHEST_PRIORITY_TASK()
位置:在portmacro.h文件中定义;
就是利用前面提到的计算前导零的指令:CLZ 实现;
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
从而实现宏:taskSELECT_HIGHEST_PRIORITY_TASK。
#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() */
有关任务重要函数
修改:TaskHandle_t xTaskCreateStatic(...)
1)内部增加了将任务添加到就绪列表的功能:prvAddNewTaskToReadyList;
2)在原本的prvInitialiseNewTask中增加了设置优先级的功能;
prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters,uxPriority, &xReturn, pxNewTCB);
原来的函数,初始化与任务相关的列表,如就绪列表;
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
是一个带参宏,位置:在task.c文件中定义;
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{/* 进入临界段 */taskENTER_CRITICAL();{/* 全局任务计时器加一操作 */uxCurrentNumberOfTasks++;/* 如果pxCurrentTCB为空,则将pxCurrentTCB指向新创建的任务 */if( pxCurrentTCB == NULL ){pxCurrentTCB = pxNewTCB;/* 如果是第一次创建任务,则需要初始化任务相关的列表 */if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){/* 初始化任务相关的列表 */prvInitialiseTaskLists();}}else /* 如果pxCurrentTCB不为空,则根据任务的优先级将pxCurrentTCB指向最高优先级任务的TCB */{if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}}uxTaskNumber++;/* 将任务添加到就绪列表 */prvAddTaskToReadyList( pxNewTCB );}/* 退出临界段 */taskEXIT_CRITICAL();
}
完成如下功能:
a. 初始化任务链表(若是第一次):prvInitialiseTaskLists;
b. 把任务加到就绪列表:prvAddTaskToReadyList;
这个是通过前面的宏 (查找优先级)+ 列表插入函数实现(根据优先级将任务插入就绪列表pxReadyTasksLists[])
* 将任务添加到就绪列表 */
#define prvAddTaskToReadyList( pxTCB ) \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );
vTaskStartScheduler()维持不变
完成如下功能:
1)创建空闲任务;
2)启动任务调度器xPortStartScheduler;
修改:void vTaskDelay( const TickType_t xTicksToDelay )
位置:在task.c文件中定义;
void vTaskDelay( const TickType_t xTicksToDelay )
{TCB_t *pxTCB = NULL;/* 获取当前任务的TCB */pxTCB = pxCurrentTCB;/* 设置延时时间 */pxTCB->xTicksToDelay = xTicksToDelay;/* 将任务从就绪列表移除 *///uxListRemove( &( pxTCB->xStateListItem ) );taskRESET_READY_PRIORITY( pxTCB->uxPriority );/* 任务切换 */taskYIELD();
}
此处的修改注意,以前是从任务列表中移除:uxListRemove( &( pxTCB->xStateListItem ) );
现在是通过函数(宏):taskRESET_READY_PRIORITY( pxTCB->uxPriority ) ;实现复位变量uxReadyPriorities相对于的位;
然后是调用任务切换taskYIELD,触发中断:vTaskSwitchContext;
修改:void vTaskSwitchContext( void )
任务切换,即寻找优先级最高的就绪任务;
利用宏自动寻找优先级最高的就绪任务的TCB,然后更新到全局变量任务控制块指针pxCurrentTCB中;
/* 任务切换,即寻找优先级最高的就绪任务 */
void vTaskSwitchContext( void )
{/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */taskSELECT_HIGHEST_PRIORITY_TASK();
}
修改:void xTaskIncrementTick( void )
位置:在task.c文件中定义;
由定时中断xPortSysTickHandler调用;完成如下功能:
a. 实现全局变量xTickCount
计数操作;
此处有一个疑问:xTickCount
的加1为何这么麻烦,通过xConstTickCount常量来实现啊?
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
可能的原因 这种写法可能是出于以下原因之一: 1. 调试方便:使用局部变量 xConstTickCount 可以方便地在调试时观察 xTickCount 自增后的值。 2. 代码风格:某些开发者可能倾向于通过局部变量来分离计算和赋值逻辑,以提高代码的可读性。 3. 历史遗留:这段代码可能是从更复杂的逻辑简化而来的,原本 xConstTickCount 可能有其他用途,但后来简化成了当前的形式。
b. 扫描所有的就绪列表的时间是否到了,到了记录:taskRECORD_READY_PRIORITY( pxTCB->uxPriority );
c. 扫描结束进行任务切换portYIELD,触发中断;
void xTaskIncrementTick( void )
{TCB_t *pxTCB = NULL;BaseType_t i = 0;/* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 扫描就绪列表中所有线程的xTicksToDelay,如果不为0,则减1 */for(i=0; i<configMAX_PRIORITIES; i++){pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );if(pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay --;}}/* 任务切换 */portYIELD();
}