FreeRTOS任务切换,现场保护(M3,M4内核)

一.寄存器和栈

FreeRTOS在任务切换或中断发生时需要保存当前任务的上下文(寄存器状态、程序计数器等),确保任务恢复时能继续执行。上下文保存分为被动保存和主动保存两种场景。

保存现场

下图为三种切换现场的情况(现场就是寄存器存储值):

FreeRTOS任务切换,现场保护(M3,M4内核)_第1张图片

(1).经典的main函数中调用fun1函数,现场的保存:

FreeRTOS任务切换,现场保护(M3,M4内核)_第2张图片

绿色部分为切换到fun1函数时保存的main函数的现场,SP和FP被保存说明fun1中用到了栈,LR和PC被保存,说明fun1有其他函数的调用。

此图中除了pc,lr,sp,fp之外,r4-r11并不用被保存,所以接下来保存fun1的形参(str r0,[xxxxxx])。

(2).函数被硬件中断时现场的保存

上下文保存 硬件自动把R0-R3, R12, LR, PC, xPSR寄存器压栈。(约12个周期),剩下部分若有必要需要软件保存.

(3).FreeRTOS任务切换现场的保存

1.所有寄存器都需要保存,任务之间寄存器使用没有关联,编译器无法做出优化。

二.FreeRTOS任务创建/删除API源码分析

xTaskCreate()

动态方式创建任务(最常用)

xTaskCreateStatic()

静态方式创建任务

vTaskDelete()

删除任务

(1).任务控制块(TCB)结构

任务创建时需初始化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;

(2)动态任务创建流程
 

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 )

(3)任务删除流程
  1. 获取所要删除任务的控制块通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身。
  2. 将被删除任务,移除所在列表将该任务在所在列表中移除,包括:就绪、阻塞、挂起、事件等列表。
  3. 判断所需要删除的任务如果删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行;如果删除其他任务,直接释放内存,任务数量--                                                      注:1.任务中delete(null)删除自己,后面还有代码,依旧要被执行,所以是添加到等待列表                   2.删除别的任务不想让其再执行哪怕一条代码,所以直接删除,释放内更新下个任务的阻塞时间。
  4. 更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务。
    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--;
    }
}

(4)修改任务优先级流程

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();
}

三.FreeRTOS任务调度API源码分析

任务调度的核心逻辑在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
}

你可能感兴趣的:(FreeRTOS,stm32)