在嵌入式实时操作系统FreeRTOS的开发过程中,任务优先级管理是核心功能之一。作为一名长期从事STM32开发的工程师,我最近在移植FreeRTOS到STM32F103平台时,遇到了需要扩展多优先级支持的需求。本文将详细记录从单优先级系统升级到支持多优先级任务管理的完整过程。
FreeRTOS默认支持多优先级任务调度,但在某些轻量级移植版本或教学示例中,可能会简化实现为单优先级系统。当我们需要在项目中引入真正的任务优先级机制时,就需要对任务控制块、任务创建、调度器等核心模块进行系统性的改造。
任务控制块(TCB)是FreeRTOS中描述任务的核心数据结构。为了支持多优先级,首先需要在TCB中增加优先级相关的成员:
c复制typedef struct tskTaskControlBlock {
// 原有成员...
volatile StackType_t *pxTopOfStack; // 任务栈顶指针
ListItem_t xGenericListItem; // 通用链表项
ListItem_t xEventListItem; // 事件链表项
UBaseType_t uxPriority; // 新增:任务优先级
// 其他成员...
} tskTCB;
这里增加的uxPriority成员用于存储任务的优先级数值。在FreeRTOS中,数值越大表示优先级越高,这与许多其他RTOS的设计一致。
注意:优先级数值的有效范围取决于配置参数
configMAX_PRIORITIES,通常建议不要设置过大(一般不超过32),以避免不必要的内存开销和调度延迟。
在任务创建时,必须对传入的优先级参数进行有效性检查:
c复制// 在prvInitialiseNewTask函数中添加
if(uxPriority >= configMAX_PRIORITIES) {
uxPriority = configMAX_PRIORITIES - 1; // 限制到最大有效优先级
}
这种防御性编程可以防止传入非法优先级导致系统异常。在实际项目中,我建议在调试阶段加入断言检查,正式发布版本中再改为安全限制。
静态任务创建函数需要增加优先级参数,并传递给TCB初始化函数:
c复制TaskHandle_t xTaskCreateStatic(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority, // 新增优先级参数
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
{
// ...参数检查
// 调用TCB初始化函数
prvInitialiseNewTask(
pxTaskCode,
pcName,
ulStackDepth,
pvParameters,
uxPriority, // 传递优先级
&xHandle,
pxTaskBuffer,
puxStackBuffer );
// ...后续处理
}
prvInitialiseNewTask函数需要处理优先级相关的初始化:
c复制static void prvInitialiseNewTask(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
StaticTask_t * const pxNewTCB,
StackType_t * const puxStackBuffer )
{
// ...栈初始化等原有代码
// 初始化优先级
pxNewTCB->uxPriority = uxPriority;
// 初始化链表项
vListInitialiseItem( &( pxNewTCB->xGenericListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
// 设置链表项的所有者为该TCB
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xGenericListItem ), pxNewTCB );
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
// 设置链表项的值(优先级)
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xGenericListItem ),
( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
// ...其他初始化
}
这里特别需要注意的是链表项值的设置方式。FreeRTOS使用升序链表管理就绪任务,因此高优先级任务(数值大)对应的链表项值反而小,这样能确保高优先级任务排在链表前面。
FreeRTOS使用一个就绪列表数组来管理各优先级的就绪任务:
c复制PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
在系统启动时,需要通过prvInitialiseTaskLists函数初始化这些列表:
c复制static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
for( uxPriority = 0; uxPriority < configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
// 初始化其他列表...
}
为了快速查找最高优先级就绪任务,FreeRTOS使用了优先级位图机制:
c复制PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority;
每个bit代表一个优先级是否有就绪任务。当任务状态变化时,需要同步更新位图:
c复制#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
} \
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
/* 需要检查该优先级是否还有其他就绪任务 */ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == 0 ) \
{ \
/* 没有其他任务,清除位图对应位 */ \
uxTopReadyPriority = 0; \
/* 需要重新搜索最高优先级 */ \
} \
}
在实际项目中,我发现位图操作是影响调度性能的关键点。在STM32等Cortex-M系列MCU上,可以使用CLZ(Count Leading Zeros)指令来加速最高优先级查找。
在支持多优先级后,任务切换函数vTaskSwitchContext需要修改为自动选择最高优先级就绪任务:
c复制void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* 调度器挂起,不切换任务 */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
/* 寻找最高优先级就绪任务 */
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();
}
}
其中taskSELECT_HIGHEST_PRIORITY_TASK是一个宏,实现了优先级查找算法:
c复制#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
/* 从位图中找到最高优先级 */ \
while( ( pxReadyTasksLists[ uxTopPriority ] ).uxNumberOfItems == 0 ) \
{ \
--uxTopPriority; \
} \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
uxTopReadyPriority = uxTopPriority; \
}
在系统时钟节拍中断中,xTaskIncrementTick函数需要处理多优先级下的延时任务唤醒:
c复制BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
BaseType_t xSwitchRequired = pdFALSE;
if( uxSchedulerSuspended == pdFALSE )
{
const UBaseType_t uxPendedCounts = uxPendedTicks;
if( uxPendedCounts > 0 )
{
/* 处理挂起的tick */
uxPendedTicks = 0;
xSwitchRequired = xTaskCatchUpTicks( uxPendedCounts );
}
/* 常规tick处理 */
++xTickCount;
if( xTickCount == 0 )
{
/* 处理时间溢出 */
taskSWITCH_DELAYED_LISTS();
}
/* 检查延时任务 */
if( listCURRENT_LIST_LENGTH( pxDelayedTaskList ) > 0 )
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
if( xTickCount >= listGET_LIST_ITEM_VALUE( &( pxTCB->xGenericListItem ) ) )
{
/* 延时结束,移动到就绪列表 */
( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
/* 根据优先级添加到就绪列表 */
prvAddTaskToReadyList( pxTCB );
xSwitchRequired = pdTRUE;
}
}
/* 处理时间片调度 */
if( xSwitchRequired == pdFALSE )
{
#if ( configUSE_PREEMPTION == 1 )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > 1 )
{
xSwitchRequired = pdTRUE;
}
}
#endif
}
}
else
{
++uxPendedTicks;
}
return xSwitchRequired;
}
在多优先级系统中,优先级反转是一个常见问题。当高优先级任务等待低优先级任务持有的资源时,如果中间优先级任务抢占CPU,会导致高优先级任务被无限期延迟。
解决方案包括:
在FreeRTOS中,可以通过互斥量的优先级继承机制来缓解这个问题:
c复制xSemaphoreCreateMutex(); // 创建支持优先级继承的互斥量
在STM32等嵌入式系统中,需要特别注意中断优先级与任务优先级的关系:
c复制// 正确的中断服务程序示例
void USART1_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 处理USART中断
xSemaphoreGiveFromISR(xUSARTSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
多优先级系统会增加一定的内存开销,特别是在以下方面:
在资源受限的STM32平台上,可以通过以下方式优化:
在STM32F103C8T6开发板上,我设计了以下测试场景:
创建3个不同优先级的任务:
测试用例:
测试结果表明,改造后的多优先级系统工作正常,任务调度符合预期。通过逻辑分析仪捕获的任务切换时序显示,高优先级任务能够在预期时间内获得CPU控制权。
在移植过程中,我发现FreeRTOS的优先级机制设计非常高效,即使在72MHz的STM32F103上,任务切换时间也能控制在几个微秒以内。这对于大多数实时应用已经足够。