FreeRTOS源码(二) 任务调度
任务创建
1、任务结构体
typedef struct tskTaskControlBlock
{volatile StackType_t *pxTopOfStack; /* Points to the location of the last item placed on the task's stack. */ListItem_t xStateListItem; /* The list that holds the task's state (Ready, Blocked, Suspended). */ListItem_t xEventListItem; /* Used to reference a task from an event list. */UBaseType_t uxPriority; /* The priority of the task (0 is the lowest). */StackType_t *pxStack; /* Points to the start of the stack. */char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* Name for the task, for debugging. */#if ( portSTACK_GROWTH > 0 )StackType_t *pxEndOfStack; /* Points to the end of the stack (for architectures with upward growing stacks). */#endif#if ( portCRITICAL_NESTING_IN_TCB == 1 )UBaseType_t uxCriticalNesting; /* Holds critical section nesting depth (for ports without a dedicated counter). */#endif#if ( configUSE_MUTEXES == 1 )UBaseType_t uxBasePriority; /* Last assigned priority (used in priority inheritance). */UBaseType_t uxMutexesHeld; /* The number of mutexes currently held by the task. */#endif#if ( configUSE_TcASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue; /* Holds the notification value. */volatile uint8_t ucNotifyState; /* Holds the state of task notifications (e.g., pdFALSE, pdTRUE). */#endif
} tskTCB;
分析:
1、任务堆栈相关:pxTopOfStack: 指向任务堆栈的顶部。pxStack: 指向任务堆栈的起始位置。pxEndOfStack: (如果堆栈向上增长)指向堆栈的末尾
2、任务状态和列表管理:xStateListItem: 用于管理任务的状态(就绪、阻塞、挂起等)xEventListItem: 用于任务与事件的关联
3、任务调度和优先级:uxPriority: 任务的优先级,0为最低优先级
4、任务名pcTaskName: 任务的名字,方便调试
5、任务通知(可选)ulNotifiedValue: 任务通知的值。ucNotifyState: 任务通知的状态(如是否已被通知)
6、互斥锁和优先级继承(可选)uxBasePriority: 用于优先级继承的基础优先级。uxMutexesHeld: 当前任务持有的互斥锁数量
2、创建任务
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const uint16_t usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask )
{TCB_t *pxNewTCB;BaseType_t xReturn;// 分配任务控制块 (TCB)pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){// 分配栈空间pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( usStackDepth * sizeof( StackType_t ) );if( pxNewTCB->pxStack == NULL ){// 如果栈分配失败,释放TCB并返回vPortFree( pxNewTCB );pxNewTCB = NULL;}}if( pxNewTCB != NULL ){// 标记任务为动态分配#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif// 初始化任务prvInitialiseNewTask( pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );// 将任务添加到就绪队列prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;
}
分析:
1、栈分配:在栈向上增长的情况下,首先分配任务控制块(TCB)内存。然后,分配栈内存,并将栈的指针存储在 pxStack 中
2、动态分配标记:使用宏 tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE 来判断任务是否是动态分配的,这对于一些支持静态和动态分配的环境很有用。如果不需要此功能,可以去掉相关代码
3、任务初始化和加入就绪队列prvInitialiseNewTask() 用于初始化任务,设置任务的代码、名称、栈深度、优先级等prvAddNewTaskToReadyList() 将任务添加到就绪队列,准备调度
4、内存分配失败处理如果栈分配失败,则释放已分配的任务控制块内存,并返回分配失败的错误代码
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,const MemoryRegion_t * const xRegions )
{StackType_t *pxTopOfStack;UBaseType_t x;/* 填充栈空间,用于调试 */#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )memset( pxNewTCB->pxStack, tskSTACK_FILL_BYTE, ulStackDepth * sizeof( StackType_t ) );#endif/* 计算栈顶地址:栈向上增长 */pxTopOfStack = pxNewTCB->pxStack;pxNewTCB->pxEndOfStack = pxTopOfStack + ( ulStackDepth - 1 );/* 复制任务名称 */for( x = 0; x < configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];if( pcName[ x ] == 0x00 ){break;}}pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';/* 设置任务优先级 */if( uxPriority >= configMAX_PRIORITIES ){uxPriority = configMAX_PRIORITIES - 1;}pxNewTCB->uxPriority = uxPriority;/* 初始化任务状态 */vListInitialiseItem( &( pxNewTCB->xStateListItem ) );vListInitialiseItem( &( pxNewTCB->xEventListItem ) );listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), configMAX_PRIORITIES - uxPriority );listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );/* 初始化堆栈 */pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );/* 如果需要,返回任务句柄 */if( pxCreatedTask != NULL ){*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}
}
分析:
任务名称会逐个字符复制到 TCB 中,确保任务名称字符串在 pcTaskName 中正确终止
任务的优先级会进行检查,如果超出最大优先级,则设置为最大优先级减一
使用 FreeRTOS 提供的 vListInitialiseItem() 和 listSET_LIST_ITEM_OWNER() 初始化任务的状态列表项
使用 pxPortInitialiseStack() 函数初始化任务的堆栈,准备好从指定的 pxTaskCode 开始执行
如果 pxCreatedTask 非空,则将新创建的任务句柄传回给调用者
任务调度
1、创建空闲任务
空闲任务是最低优先级的任务,在系统没有其他任务需要执行时,空闲任务会运行。空闲任务的目的是确保系统有一个任务持续运行
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{StaticTask_t *pxIdleTaskTCBBuffer = NULL;StackType_t *pxIdleTaskStackBuffer = NULL;uint32_t ulIdleTaskStackSize;vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );xIdleTaskHandle = xTaskCreateStatic( prvIdleTask, "IDLE", ulIdleTaskStackSize, (void *) NULL, (tskIDLE_PRIORITY | portPRIVILEGE_BIT), pxIdleTaskStackBuffer, pxIdleTaskTCBBuffer );if( xIdleTaskHandle != NULL ){xReturn = pdPASS;}else{xReturn = pdFAIL;}
}
#else
{xReturn = xTaskCreate( prvIdleTask, "IDLE", configMINIMAL_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle );
}
#endif
2、创建定时器任务
如果启用了 configUSE_TIMERS,则在成功创建空闲任务后,创建定时器任务。这是一个负责处理 FreeRTOS 定时器的任务
#if ( configUSE_TIMERS == 1 )
{if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask();}else{mtCOVERAGE_TEST_MARKER();}
}
#endifxTimerCreateTimerTask() 的主要作用是:检查定时器队列是否初始化。
根据配置选择静态或动态创建一个定时器任务,用于处理定时器事件。
设置定时器任务的优先级、堆栈大小,并初始化相关资源。
返回任务创建的结果
3、禁用中断
portDISABLE_INTERRUPTS();
4、启动调度器
通过调用 xPortStartScheduler()
启动调度器,这通常是硬件相关的函数,具体实现取决于所用的平台和移植的接口。调度器一旦启动,就会一直运行,直到调用 vTaskEndScheduler()
停止调度器
1、configASSERT( ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) );
确保 configMAX_SYSCALL_INTERRUPT_PRIORITY 被正确设置。该值定义了中断的优先级,不能设置为 02、portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
配置 PendSV 和 SysTick 中断优先级
设置 PendSV 和 SysTick 的优先级,使其与内核的优先级相同。这样,PendSV 中断和 SysTick 中断能够在任务切换时起到控制作用
PendSV:用于任务切换的系统中断。
SysTick:用于系统时钟的中断3、prvStartFirstTask();
启动第一个任务
调度过程
1、xPortSysTickHandler 系统的时钟中断中断时,会调用 xPortSysTickHandler()
函数。在此函数中,系统会处理定时器事件,并决定是否需要切换任务
void xPortSysTickHandler( void )
{#if( configUSE_PREEMPTION == 1 ){if( xTaskIncrementTick() != pdFALSE ){vTaskSwitchContext();}}#else{vTaskIncrementTick();}#endif
}
xTaskIncrementTick()
这个函数在每个时钟中断发生时被调用,它的主要职责是增加系统的时钟计数器(tick count),检查是否有任务因为超时而被解除阻塞,以及根据需要触发任务切换
检查调度器是否被挂起(uxSchedulerSuspended),如果是,则不增加时钟计数器,而是增加 uxPendedTicks 计数器,以保持时钟中断的处理连续性。
如果调度器没有被挂起,则增加时钟计数器(xTickCount),并检查是否需要切换延迟任务和溢出延迟任务列表。
然后检查时钟计数器是否已经超过下一个任务解除阻塞的时间(xNextTaskUnblockTime),如果是,则遍历延迟任务列表,找到第一个可以解除阻塞的任务,并将其从阻塞状态移除,加入到就绪任务列表中。
最后,检查是否有必要进行任务切换。如果启用了抢占式多任务(configUSE_PREEMPTION),则需要检查是否有更高优先级的任务等待执行,或者是否有多个具有相同优先级的任务等待时间片(configUSE_TIME_SLICING)
xTickCount
是系统的 Tick 计数。如果当前 Tick 计数大于或等于下一个任务解除阻塞的时间,就会标记需要任务切换
vTaskSwitchContext任务切换
void vTaskSwitchContext( void )
{if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ){/* The scheduler is currently suspended - do not allow a contextswitch. */xYieldPending = pdTRUE;}else{xYieldPending = pdFALSE;traceTASK_SWITCHED_OUT();#if ( configGENERATE_RUN_TIME_STATS == 1 ){#ifdef portALT_GET_RUN_TIME_COUNTER_VALUEportALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );#elseulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();#endif/* Add the amount of time the task has been running to theaccumulated time so far. The time the task started running wasstored in ulTaskSwitchedInTime. Note that there is no overflowprotection here so count values are only valid until the timeroverflows. The guard against negative values is to protectagainst suspect run time stat counter implementations - whichare provided by the application, not the kernel. */if( ulTotalRunTime > ulTaskSwitchedInTime ){pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );}else{mtCOVERAGE_TEST_MARKER();}ulTaskSwitchedInTime = ulTotalRunTime;}#endif /* configGENERATE_RUN_TIME_STATS *//* Check for stack overflow, if configured. */taskCHECK_FOR_STACK_OVERFLOW();/* Select a new task to run using either the generic C or portoptimised asm code. */taskSELECT_HIGHEST_PRIORITY_TASK();traceTASK_SWITCHED_IN();#if ( configUSE_NEWLIB_REENTRANT == 1 ){/* Switch Newlib's _impure_ptr variable to point to the _reentstructure specific to this task. */_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif /* configUSE_NEWLIB_REENTRANT */}
}
切换任务
函数首先检查调度器是否被挂起(uxSchedulerSuspended)。如果调度器被挂起,它设置 xYieldPending 为 pdTRUE,这会阻止任务切换,因为调度器被挂起时不允许进行任务切换。
如果调度器没有被挂起,它设置 xYieldPending 为 pdFALSE,并记录任务切换的跟踪信息(traceTASK_SWITCHED_OUT)。
如果启用了运行时间统计(configGENERATE_RUN_TIME_STATS),它会更新当前任务的运行时间统计,并存储当前运行时间计数器的值(ulTotalRunTime)为下一次任务切换时的起始时间(ulTaskSwitchedInTime)。
函数检查是否有栈溢出(taskCHECK_FOR_STACK_OVERFLOW),以确保任务堆栈的使用在安全范围内。
然后,函数选择一个新任务来运行,这通常是通过调用 taskSELECT_HIGHEST_PRIORITY_TASK 函数来实现的,该函数选择优先级最高的就绪任务
taskSELECT_HIGHEST_PRIORITY_TASK()
#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() */
-
查找所有优先级队列中,当前就绪队列中任务优先级最高的队列。
-
确保该队列中有任务准备好运行。
-
获取该优先级队列中的第一个任务,并将其设置为当前任务