目录
- 支持多优先级的方法
- 通用方法
- 优化方法
- 1、修改任务控制块
- 2、修改xTaskCerateStactic()
- 修改 prvInitialiseNewTask() 函数
- prvAddTaskToReadyList()
- 初始化任务列表
- prvAddTaskToReadyList()
- vTaskStartScheduler()
- vTaskDelay()
- vTaskSwitchContext()
- xTaskIncrementTick()
- 实验
- 实验现象
在RTOS中,数字优先级越小,逻辑优先级也越小。
支持多优先级的方法
就绪列表是一个数组,数组里面存的是对应就绪任务的TCB的xStateListItem节点,数组下标对应任务的优先级,优先级越低,对应数组下标的数字越小。空闲任务对应的数组下标为0,空闲任务自系统启动就会一直处于就绪状态。
在创建任务时,会根据任务的优先级将任务插入就绪列表不同的位置。相同优先级的任务插入就绪列表中的同一条链表中。
那么如何找到当前优先级最高的任务是关键。
FreeRTOS中是利用pxCurrentTCB这个全局指针来指向优先级最高的就绪任务的TCB(zu即当前任务)。
FreeRTOS提供了两种方法,一种是通用的,一种是根据特定的处理器优化过的。
在portmaccro.h中,该宏的定义为0表示通用方法,为1表示根据处理器优化的方法。
#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#endif
寻找优先级的就绪任务相关代码在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 */
通用方法
taskRECORD_READY_PRIORITY() 是用于更新uxTopReadyPriority 的值。该值是在task.c
中定义的静态变量,用于表示创建的任务的最高优先级,默认初始化为0,即空闲任务的优先级。
taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
}
uxTopReadyPriority的定义:
/* 空闲任务优先级宏定义,在task.h中定义*/
#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )
/*初始化为空闲任务的优先级,在task.c中定义*/
static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;
taskSELECT_HIGHEST_PRIORITY_TASK() 是用于寻找优先级最高的就绪任务,实质是更新uxTopReadyPriority 和 pxCurrentTCB。
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 */
从最高优先级对应的就绪列表数组下标开始寻找当前链表下是否有任务存在,如果没有,则uxTopPriority减一操作,继续寻找下一个优先级对应的链表中是否有任务存在,如果有则跳出while循环,表示找到了最高优先级的就绪任务。
优化方法
该方法是利用Cortex-M内核的一个计算前导零的指令CLZ。
ps:前导零指令就是计算一个变量(Cortex-M内核单片机的变量为32位)从高位开始第一次出现1的位的前面的0的个数。例子,如图所示,该变量的前导零的个数为6。
该指令是由Cortex-M内核来实现,执行速度会更快。
taskRECORD_READY_PRIORITY( ) 用于根据传进行的参数将uxTopReadyPriority的某一位置1。
该方法与通用方法中用来表示创建任务的最高优先级不一样;在优化方法中,该变量的每个位对应任务的优先级,即如果任务就绪,则将其对应位置1,反之清0。因此,,只需要计算uxTopReadyPriority的前导零数就找到看就绪任务列表中的当前最高优先级的任务。
在portmaccro.h中定义:
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
taskRESET_READY_PRIORITY( ) 与 taskRECORD_READY_PRIORITY( ) 作用相反,是用于传进来的形参将uxTopReadyPriority的某一位清零。
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
taskSELECT_HIGHEST_PRIORITY_TASK() 是用于寻找优先级最高的就绪任务,即uxTopReadyPriority 和 pxCurrentTCB。
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )
uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
更新代码引入多优先级
1、修改任务控制块
在任务控制块中添加与优先级相关的成员变量
在FreeRTOS.h中
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、修改xTaskCerateStactic()
在创建任务时增加优先级,参数值越大,优先级越高
在task.c中
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
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;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
修改 prvInitialiseNewTask() 函数
增加优先级形参和优先级初始化相关的代码
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, /* 任务入口 */
const char * const pcName, /* 任务名称,字符串形式 */
const uint32_t ulStackDepth, /* 任务栈大小,单位为字 */
void * const pvParameters, /* 任务形参 */
UBaseType_t uxPriority, /* 任务优先级,数值越大,优先级越高 */
TaskHandle_t * const pxCreatedTask, /* 任务句柄 */
TCB_t *pxNewTCB ) /* 任务控制块 */
{
StackType_t *pxTopOfStack;
UBaseType_t x;
/* 获取栈顶地址 */
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
//pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* 向下做8字节对齐 */
pxTopOfStack = ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) & ( ~( ( uint32_t ) 0x0007 ) ) );
/* 将任务的名字存储在TCB中 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == 0x00 )
{
break;
}
}
/* 任务名字的长度不能超过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 );
/* 让任务句柄指向任务控制块 */
if( ( void * ) pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
}
prvAddTaskToReadyList()
/* 将任务添加到就绪列表 */
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();
}
初始化任务列表
初始化任务相关的列表,此处只有就绪列表
/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
}
prvAddTaskToReadyList()
将任务添加到就绪列表
/* 将任务添加到就绪列表 */
#define prvAddTaskToReadyList( pxTCB ) \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
vTaskStartScheduler()
修改开启任务调度函数。
void vTaskStartScheduler( void )
{
/*======================================创建空闲任务start==============================================*/
TCB_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
/* 获取空闲任务的内存:任务栈和任务TCB */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
&pxIdleTaskStackBuffer,
&ulIdleTaskStackSize );
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 )
{
/* 调度器启动成功,则不会返回,即不会来到这里 */
}
}
vTaskDelay()
添加任务从就绪列表中移出的操作
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();
}
vTaskSwitchContext()
直接调用函数taskSELECT_HIGHEST_PRIORITY_TASK()寻找到优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB。
/* 任务切换,即寻找优先级最高的就绪任务 */
void vTaskSwitchContext( void )
{
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */
taskSELECT_HIGHEST_PRIORITY_TASK();
}
xTaskIncrementTick()
在原来的基础上增加:当任务延时时间到,将任务就绪的代码,根据优先级将优先级位图表uxTopReadyPriority中对应的位置位。
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();
}
实验
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* 创建任务 */
Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */
(char *)"Task1", /* 任务名称,字符串形式 */
(uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) 1, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)Task1Stack, /* 任务栈起始地址 */
(TCB_t *)&Task1TCB ); /* 任务控制块 */
/* 将任务添加到就绪列表 */
//vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
Task2_Handle = xTaskCreateStatic( (TaskFunction_t)Task2_Entry, /* 任务入口 */
(char *)"Task2", /* 任务名称,字符串形式 */
(uint32_t)TASK2_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) 2, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)Task2Stack, /* 任务栈起始地址 */
(TCB_t *)&Task2TCB ); /* 任务控制块 */
/* 将任务添加到就绪列表 */
//vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
/* 启动调度器,开始多任务调度,启动成功则不返回 */
vTaskStartScheduler();
for(;;)
{
/* 系统启动成功不会到达这里 */
}
}
实验现象
设置优先级后与之前手动设置任务的先后一致。