在 FreeRTOS 中,数字优先级越小,逻辑优先级也越小,这与RT-Thread 和 μC/OS刚好相反。
就绪列表 pxReadyTasksLists[ configMAX_PRIORITIES ]是一个数组,数组里面存的是就绪任务的 TCB(准确来说是 TCB 里面的 xStateListItem 节点),数组的下标对应任务的优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为 0 的链表。空闲任务自系统启动后会一直就绪,因为系统至少得保证有一个任务可以运行。任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置,相同优先级的任务插入到就绪列表里面的同一条链表中。
查找最高优先级就绪任务
pxCurrenTCB 是一个全局的 TCB 指针,用于指向优先级最高的就绪任务的 TCB,即当前正在运行的 TCB。那么我们要想让任务支持优先级,即只要解决在任务切换(taskYIELD)的时候,让 pxCurrenTCB 指向最高优先级的就绪任务的 TCB 就可以。那问题的关键就是:如果找到最高优先级的就绪任务的 TCB。FreeRTOS 提供了两套方法,一套是通用的,一套是根据特定的处理器优化过的,接下来我们重点讲解下这两个方法。
1 通用的方法:
#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 */
这个逻辑很清晰,就是优先级编号不断的递减,一直找直到找到一个不为空的列表,这就是当前就绪列表的最高优先级。然后把最高优先级的任务控制块更新到当前任务控制块pxCurrentTCB,并更新uxTopReadyPriority。
2 优化方法(用内核中的自带指令来操作):
利用这条指令,一步就可以得到最高优先级。
修改代码,支持多优先级
1 修改任务控制块,增加与优先级相关的成员:
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;
2 修改创建任务的xTaskCreateStatic()函数,要增加一个传入参数优先级,数值越大,优先级越高。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, /* 任务入口 */
const char * const pcName, /* 任务名称,字符串形式 */
const uint32_t ulStackDepth, /* 任务栈大小,单位为字 */
void * const pvParameters, /* 任务形参 */
// 新增加的优先级成员
UBaseType_t uxPriority, /* 任务优先级,数值越大,优先级越高 */
StackType_t * const puxStackBuffer, /* 任务栈起始地址 */
TCB_t * const pxTaskBuffer ) /* 任务控制块 */
{
TCB_t *pxNewTCB;
TaskHandle_t xReturn;
if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
{
pxNewTCB = ( TCB_t * ) pxTaskBuffer;
pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;
/* 创建新的任务 */
prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters,uxPriority, &xReturn, pxNewTCB);
/* 将任务添加到就绪列表 */
prvAddNewTaskToReadyList( pxNewTCB );
}
else
{
xReturn = NULL;
}
return xReturn;
}
3 修改初始化一个新任务的prvInitialiseNewTask()函数
截取的部分代码(增加了初始化优先级):
/* 任务名字的长度不能超过configMAX_TASK_NAME_LEN */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* 初始化TCB中的xStateListItem节点 */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
/* 设置xStateListItem节点的拥有者 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 初始化优先级 */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
pxNewTCB->uxPriority = uxPriority;
/* 初始化任务栈 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
4 新增把任务添加到就绪列表的prvAddNewTaskToReadyList()函数
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();
}
之前,我们是先创建任务,再手动将任务插入到就绪列表,需要两个语句。
现在就不需要了,在创建任务时,会根据优先级自动插入到就绪列表。
5 修改开始任务调度器的函数 vTaskStartScheduler()
截取的部分代码,新增了空闲任务的优先级(定义为0),然后删除了添加任务到就绪列表的语句(因为新的创建任务函数,会自动的根据优先级插入到就绪列表),删除了手动指定第一个运行任务的语句,因为现在回自动获取最高优先级的任务去执行。
xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */
(char *)"IDLE", /* 任务名称,字符串形式 */
(uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) tskIDLE_PRIORITY, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */
(TCB_t *)pxIdleTaskTCBBuffer ); /* 任务控制块 */
/* 将任务添加到就绪列表 */
//vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
/*======================================创建空闲任务end================================================*/
/* 手动指定第一个运行的任务 */
//pxCurrentTCB = &Task1TCB;
/* 启动调度器 */
if( xPortStartScheduler() != pdFALSE )
{
/* 调度器启动成功,则不会返回,即不会来到这里 */
}
6 修改vTaskDelay()函数
根据优先级将优先级位图表 uxTopReadyPriority 中对应的位清零,由函数 taskRESET_READY_PRIORITY()来实现。这样,在扫描最高优先级时,就可以避免扫描到这个函数。
void vTaskDelay( const TickType_t xTicksToDelay )
{
TCB_t *pxTCB = NULL;
/* 获取当前任务的TCB */
pxTCB = pxCurrentTCB;
/* 设置延时时间 */
pxTCB->xTicksToDelay = xTicksToDelay;
/* 把优先级对应的位清零 */
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
/* 任务切换 */
taskYIELD();
}
7 修改任务切换的函数 vTaskSwitchContext()函数
不再实行手动切换,而是寻找最高优先级的任务去执行。
/* 任务切换,即寻找优先级最高的就绪任务 */
void vTaskSwitchContext( void )
{
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */
taskSELECT_HIGHEST_PRIORITY_TASK();
}
8 修改更新系统时基的函数(增加了当任务延时时间到,将任务就绪的代码):
void xTaskIncrementTick( void )
{
TCB_t *pxTCB = NULL;
BaseType_t i = 0;
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
/* 扫描就绪列表中所有线程的remaining_tick,如果不为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 --;
/* 延时时间到,将任务就绪 */
if( pxTCB->xTicksToDelay ==0 )
{
taskRECORD_READY_PRIORITY( pxTCB->uxPriority );
}
}
}
/* 任务切换 */
portYIELD();
}