FreeRTOS在任务切换或中断发生时需要保存当前任务的上下文(寄存器状态、程序计数器等),确保任务恢复时能继续执行。上下文保存分为被动保存和主动保存两种场景。
下图为三种切换现场的情况(现场就是寄存器存储值):
绿色部分为切换到fun1函数时保存的main函数的现场,SP和FP被保存说明fun1中用到了栈,LR和PC被保存,说明fun1有其他函数的调用。
此图中除了pc,lr,sp,fp之外,r4-r11并不用被保存,所以接下来保存fun1的形参(str r0,[xxxxxx])。
上下文保存: 硬件自动把R0-R3, R12, LR, PC, xPSR寄存器压栈。(约12个周期),剩下部分若有必要需要软件保存.
1.所有寄存器都需要保存,任务之间寄存器使用没有关联,编译器无法做出优化。
xTaskCreate() |
动态方式创建任务(最常用) |
xTaskCreateStatic() |
静态方式创建任务 |
vTaskDelete() |
删除任务 |
任务创建时需初始化TCB(TaskControlBlock_t
),包含任务栈指针、优先级、状态链表项等关键字段。
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack; //栈顶指针
//任务状态链表项,在哪个链表下任务就是什么状态(ReadyList,DelayList,SuspendList)
ListItem_t xStateListItem;
//用于从事件链表中引用任务
ListItem_t xEventListItem;
UBaseType_t uxPriority; //任务优先级
StackType_t * pxStack; //栈底指针-4
char pcTaskName[ configMAX_TASK_NAME_LEN ]; //任务名,有长度限制
/*************跟踪任务信息**************************************************/
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber;
UBaseType_t uxTaskNumber;
#endif
/************是否开启互斥**************************************************/
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; //优先级继承获取的的优先级
UBaseType_t uxMutexesHeld;
#endif
/************任务通知是否开启**********************************************/
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
//通知值
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
//通知状态
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
} tskTCB;
xTaskCreate()
动态分配栈和TCB内存:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, /*指向任务函数*/
const char * const pcName, /*任务名,字符串或者单个字符 */
const configSTACK_DEPTH_TYPE usStackDepth, /*栈大小,单位word:4byte,最大128*/
void * const pvParameters, /*任务函数的形参,应为无符号指针类型*/
UBaseType_t uxPriority, /*优先级,很重要,越小优先级越低*/
TaskHandle_t * const pxCreatedTask ) /*TaskHandle_t 结构体指针:TCB*/
{
TCB_t * pxNewTCB;
BaseType_t xReturn;
/*************
1.分配栈空间
2.若栈空间分配成功,分配TCB所需空间
3.如果TCB分配成功,初始化TCB空间
4.栈底指针保存在TCB的pxStack中
*************/
StackType_t * pxStack;
pxStack = pvPortMallocStack(((( size_t ) usStackDepth ) * sizeof(StackType_t)) );
if( pxStack != NULL )
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
memset( ( void * ) pxNewTCB, 0x00, sizeof( TCB_t ) );
pxNewTCB->pxStack = pxStack;
}
else
{
vPortFreeStack( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
/**********************************************************************************/
/**************若是栈区申请成功,且TCB空间申请成功,初始化TCB,加入ReadyList,返回ture****/
if( pxNewTCB != NULL )
{
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
/*********************************************************************************/
}
/*******************************************************************************
一.prvInitialiseNewTask()函数完成TCB的设置:
1.计算并设置栈顶指针
2.设置任务名,超过最大长度-1的部分不会被写入,结尾为'\0'
3.优先级检查,设置(超过15按15算)
4.如果设置MUTEXES,uxBasePriority=uxPriority
5.将TCB指针放入链表项xStateListItem,方便任务调度
6.将TCB指针、优先级差值放入链表项xEventListItem
7.任务名,任务的形参入栈
二.prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
1.任务添加到就绪列表-ReadyList
/*****************************添加任务到就绪列表*******************************/
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
/***************************链表操作要关中断*********************************/
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++; //统计当前任务数量
/***************当前没有任务正在运行************************/
if( pxCurrentTCB == NULL )
{
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) //如果这是第一个任务
{
prvInitialiseTaskLists(); //调度器所需链表项初始化
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/**************当前有任务需要运行*************************/
else
{
/*********调度器没有运行**********/
/* 依据新建任务和当前任务的优先级来决定谁先运行 */
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/********调度器已经在运行*********/
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/******************任务跟踪相关操作****************************/
uxTaskNumber++;
#if ( configUSE_TRACE_FACILITY == 1 )
{
/* Add a counter into the TCB for tracing only. */
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
/************************************************************/
prvAddTaskToReadyList( pxNewTCB ); //任务添加到就绪链表
portSETUP_TCB( pxNewTCB );
}
taskEXIT_CRITICAL();
/**********************************开中断(恢复调度)*********************************/
if( xSchedulerRunning != pdFALSE ) //如果调度器没开始运行
{
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ) //新任务优先级高
{
taskYIELD_IF_USING_PREEMPTION(); //抢占调度
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else //如果调度器已经开始运行,不切换上下文
{
mtCOVERAGE_TEST_MARKER();
}
}
实际的TCB作为链表项插入ReadyList相应优先级的链表由prvAddTaskToReadyList() 完成
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );
/************尾插法,将xStateListItem链表项插入ReadyList相应优先级对应的链表*****************/
listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t * pxTCB;
/*************************关中断**********************************************/
taskENTER_CRITICAL();
{
//获取需要删除的任务的句柄
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
//uxListRemove()将链表项从链表中移除
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//是否在等待某个事件
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) ); //移除链表项
}
else
{
mtCOVERAGE_TEST_MARKER();
}
uxTaskNumber++; //增加链表值,相当一个list重新设置的信号
/******************如果要删除当前任务**********************/
*任务状态链表项放入终止链表
*uxDeletedTasksWaitingCleanUp增加,告诉idle任务,有任务需要删除,去终止链表找
*钩子函数相关操作
if( pxTCB == pxCurrentTCB )
{
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
++uxDeletedTasksWaitingCleanUp;
traceTASK_DELETE( pxTCB );
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
/*****************删除其他任务***************************/
else
{
--uxCurrentNumberOfTasks; //当前任务数量减一
traceTASK_DELETE( pxTCB );
prvResetNextTaskUnblockTime(); //调整下一个任务的阻塞时间
}
}
taskEXIT_CRITICAL();
/***********************************开不断*********************************************/
if( pxTCB != pxCurrentTCB )
{
prvDeleteTCB( pxTCB );
}
/* 调度器开启时,如果删除的是当前任务,强制重新开始调度 */
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
删除当前任务时,任务实际在idle函数中被删除,在DeleteTask()函数中实际完成的是链表从ReadyList移除,uxDeletedTasksWaitingCleanUp增加,告诉idle任务,有任务需要删除,idle任务在xTasksWaitingTermination链表中找到要删除的TCB,释放其申请的空间。
空闲任务调用prvCheckTasksWaitingTermination()
清理已删除任务的TCB和栈:
static void prvCheckTasksWaitingTermination(void) {
while (uxDeletedTasksWaitingCleanUp > 0) {
TCB_t *pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(xTasksWaitingTermination);
prvDeleteTCB(pxTCB);
uxDeletedTasksWaitingCleanUp--;
}
}
vTaskPrioritySet()
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority) {
TCB_t *pxTCB = (TCB_t *)xTask;
// 从就绪列表中移除任务
if (listIS_CONTAINED_WITHIN(&pxReadyTasksLists[pxTCB->uxPriority],
&(pxTCB->xStateListItem))) {
uxListRemove(&(pxTCB->xStateListItem));
}
// 更新优先级并重新加入就绪列表
pxTCB->uxPriority = uxNewPriority;
vListInsertEnd(&pxReadyTasksLists[uxNewPriority], &(pxTCB->xStateListItem));
// 触发调度检查
taskYIELD();
}
任务调度的核心逻辑在task.c
中的xTaskResumeAll()
和vTaskSuspendAll()
。
调度器启停
vTaskSuspendAll()
通过递增调度器挂起计数器实现:
void vTaskSuspendAll(void) {
++uxSchedulerSuspended; // 原子操作递增,不为零则调度器停止
}
上下文切换
xPortPendSVHandler()就是
PendSV_handler,执行实际的任务切换:
__asm void xPortPendSVHandler(void) {
// 保存当前任务上下文(寄存器值入栈)
mrs r0, psp
stmdb r0!, {r4-r11}
// 将当前栈指针保存到TCB
ldr r1, =pxCurrentTCB
ldr r1, [r1]
str r0, [r1]
// 加载下一个任务的TCB和栈指针
ldr r1, =pxCurrentTCB
ldr r0, [r1]
ldr r0, [r0]
// 恢复新任务的上下文
ldmia r0!, {r4-r11}
msr psp, r0
bx lr
}