• FreeRTOS学习记录--任务创建函数详解


    开局一张图。一步一步分析就好。

     

     

     

     

     

     

    (一)什么是任务?

      在多任务系统中,我们按照功能不同,把整个系统分割成一个个独立的,且无法返回的函数,这个函数我们称为任务;任务包含几个属性:任务堆栈,任务函数、任务控制块、任务优先级;下面主要介绍一下任务控制块,其他都比较容易理解。

    (二)什么是任务控制块?

      任务控制块内包含了该任务的全部信息,任务的执行需要通过任务调度器来控制,那么任务调度器怎么“控制”任务实体的呢?就要抓住任务的小辫子---“任务控制块”,系统对任务的全部操作都可以通过任务控制块来实现!它是一种特别的数据结构。

      在任务创建函数xTaskCreat()创建任务的时候就会自动给每个任务分配一个任务控制块。

     

    复制代码
    复制代码
    typedef struct tskTaskControlBlock
    {
        volatile StackType_t    *pxTopOfStack;    /*任务堆栈栈顶指针*/
    
        #if ( portUSING_MPU_WRAPPERS == 1 )
            xMPU_SETTINGS    xMPUSettings;        /*MPU相关设置*/
        #endif
    
        ListItem_t            xStateListItem;        /*状态列表项,这是一个内置在TCB控制块中的一个链表节点,通过这个节点,将任务挂到其他链表中
    比如就绪列表,阻塞列表,挂起列表等*/
    ListItem_t xEventListItem; /*事件列表项,用于引用事件列表中的任务*/ UBaseType_t uxPriority; /*任务优先级*/ StackType_t *pxStack; /*任务堆栈起始地址,是一个栈底*/ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*任务名字*/ #if ( portSTACK_GROWTH > 0 ) StackType_t *pxEndOfStack; /*任务堆栈栈底*/ #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; /*临界区嵌套深度*/ #endif #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTCBNumber; /*debug的时候用到*/ UBaseType_t uxTaskNumber; /*trace的时候用到*/ #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; /*任务基础优先级,优先级反转时用到*/ UBaseType_t uxMutexesHeld; /*任务获取到的互斥信号量个数*/ #endif #if ( configUSE_APPLICATION_TASK_TAG == 1 ) TaskHookFunction_t pxTaskTag; #endif #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) //与本地存储有关 void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; #endif #if( configGENERATE_RUN_TIME_STATS == 1 ) uint32_t ulRunTimeCounter; /*用来记录任务运行总时间*/ #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) struct _reent xNewLib_reent; /*定义一个newlib结构体变量*/ #endif #if( configUSE_TASK_NOTIFICATIONS == 1 ) /*任务通知相关变量*/ volatile uint32_t ulNotifiedValue; /*任务通知值*/ volatile uint8_t ucNotifyState; /*任务通知状态*/ #endif /* 用来标记任务是动态创建还是静态创建*/ #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) uint8_t ucStaticallyAllocated; /*静态创建此变量为pdTURE;动态创建此变量为pdFALSE*/ #endif #if( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif } tskTCB;
    复制代码

    注:#if 开头的都是条件编译,咱们可以先不用理解。基本结构如下:

     

     

      

       指针pxStack指向堆栈的起始位置,任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。

       很多刚接触FreeRTOS的人会分不清指针pxTopOfStack和pxStack的区别,这里简单说一下:pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出。

    (三)任务是怎么创建出来的?

      任务有两种创建方式,动态创建静态创建,两者的区别就是: 静态创建时候任务控制块和任务堆栈的内存是由用户自己定义的,任务删除的时候,内存不能自动释放。动态创建,任务堆栈和任务控制块的内存是由系统自动创建的,自动释放的。

      动态创建任务的函数为 xTaskCreate();

    复制代码
    复制代码
    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;
                
        #define portSTACK_GROWTH    //-1表示满减栈
        #if( portSTACK_GROWTH > 0 ){
        }
        #else{ /* portSTACK_GROWTH<0  代表堆栈向下增长 */
            StackType_t *pxStack;
            /* 任务栈内存分配,stm32是向下增长的堆栈,获取到的pxStack 是一个栈底的指针*/
            pxStack = ( StackType_t *) pvPortMalloc(((( size_t) usStackDepth ) * sizeof( StackType_t))); 
            if( pxStack != NULL ){
                /* 任务控制块内存分配 */
                pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 
                if( pxNewTCB != NULL ){
                    /* 赋值栈地址 */
                    pxNewTCB->pxStack = pxStack;
                }
                else{
                    /* 释放栈空间 */
                    vPortFree( pxStack );
                }
            }
            else{
                /* 没有分配成功 */
                pxNewTCB = NULL;
            }
        }
        #endif /* portSTACK_GROWTH */
    
        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()来新建任务初始化。我们看看下面是如何定义的。

    复制代码
    复制代码
    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( portSTACK_GROWTH < 0 ){
            /* 把栈空间的高地址分配给栈顶 */
            pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
            /* 栈对齐----栈要8字节对齐 */
            pxTopOfStack = (StackType_t *)(((portPOINTER_SIZE_TYPE) pxTopOfStack) & (~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK))); 
            /* 检查是否有错误 */
            configASSERT((((portPOINTER_SIZE_TYPE) pxTopOfStack & (portPOINTER_SIZE_TYPE) portBYTE_ALIGNMENT_MASK) == 0UL));
        }
        #else /* portSTACK_GROWTH */
        {
        }
        #endif /* portSTACK_GROWTH */
    
        /* 存储任务名称 */
        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){
            pxNewTCB->pcTaskName[ x ] = pcName[ x ];
    
            if( pcName[ x ] == 0x00 ){
                break;
            }
            else{
                mtCOVERAGE_TEST_MARKER();
            }
        }
    
        /* \0补齐字符串 */
        pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
        /* 判断任务分配的优先级,是否大于最大值  如果超过最大值,赋值最大值 */
        if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){
            uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
        }
        else{
            mtCOVERAGE_TEST_MARKER();
        }
        /* 赋值任务优先级到任务控制块 */
        pxNewTCB->uxPriority = uxPriority;
        /* 任务状态表 事件表初始化 */
        vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
        vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
        /* 设置任务控制块中的状态列表项的成员变量ower ,是属于PxNewTCB(拥有该结点的内核对象) */
        listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
        /*更改事件列表项中的成员变量xItemValue的值,目的是列表在排列的时候,是按照优先级由大到小排列 */
        listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); 
    /*设置任务控制块中事件列表项的成员变量ower,同上*/ listSET_LIST_ITEM_OWNER(
    &( pxNewTCB->xEventListItem ), pxNewTCB ); #if( portUSING_MPU_WRAPPERS == 1 ){ } #else{ /* portUSING_MPU_WRAPPERS */ /* 初始化任务堆栈,之后返回任务栈顶 */ pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters ); } #endif /* portUSING_MPU_WRAPPERS */ if( ( void * ) pxCreatedTask != NULL ){ /* 任务句柄指向任务控制块 */ *pxCreatedTask = ( TaskHandle_t ) pxNewTCB; } else{ mtCOVERAGE_TEST_MARKER(); } }
    复制代码

       prvInitialiseNewTask()函数的形参,出来xTaskCreat()的形参之外,又多出来pxNewTCB和xRegions两个形参;

    复制代码
    后面又调用了 pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
    复制代码
    来初始化任务堆栈。
    复制代码
    复制代码
    StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters){
        pxTopOfStack--;        /* 入栈程序状态寄存器 */
        *pxTopOfStack = portINITIAL_XPSR;    /* xPSR */
        
        pxTopOfStack--;        /* 入栈PC指针 */
        *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;    /* PC */
        
        pxTopOfStack--;        /* 入栈LR链接寄存器 */
        *pxTopOfStack = ( StackType_t ) prvTaskExitError;    /* LR */
        
        pxTopOfStack -= 5;    /* 跳过R12, R3, R2 and R1这四个寄存器,不初始化 */
        *pxTopOfStack = ( StackType_t ) pvParameters;    /* R0作为传参入栈 */
        
        pxTopOfStack--;        /* 保存EXC_RETURN的值,用于退出SVC或PendSV中断时候,处理器处于什么状态*/
        *pxTopOfStack = portINITIAL_EXEC_RETURN;
        
        pxTopOfStack -= 8;    /* 跳过R11, R10, R9, R8, R7, R6, R5 and R4这8个寄存器,不初始化 */
        return pxTopOfStack;    /*最终返回栈顶*/
    复制代码
    复制代码
    
    

    初始化堆栈完成之后堆栈如下图:

     

     层层深入完毕,现在我们返回到xTaskCreat()函数后面,看看  prvAddNewTaskToReadyList( pxNewTCB ); 函数是怎么把任务添加到就绪列表中!

    复制代码
    复制代码
    static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
    {
       
        taskENTER_CRITICAL();
        {
            uxCurrentNumberOfTasks++;
            if( pxCurrentTCB == NULL )   //正在运行的任务块为NULL,没有任务运行;
            {
                pxCurrentTCB = pxNewTCB;  //将新任务控制块赋值给pxCurrentTCB
     
                if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) //为1说明正在创建的任务是第一个任务。
                {
         
                    prvInitialiseTaskLists();   //初始化列表,就绪列表、阻塞列表等等
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
       
                if( xSchedulerRunning == pdFALSE ) //判断任务调度器是否运行,pdfalse代表没有运行
                {
                    if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                    {
                        pxCurrentTCB = pxNewTCB;// 将新创建的任务控制块赋值给当前任务控制块
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
     
            uxTaskNumber++;  // 用于任务控制块编号
     
            #if ( configUSE_TRACE_FACILITY == 1 )
            {
                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();
        }
    }
    复制代码

       一定要耐心分析,别无他法,加油!不难。

  • 相关阅读:
    最新下载:MindMapper 17【软件附加安装教程】
    Spring事务之@Transactional注解详解
    python:切分多个串联但单个内部按大小排列数据列表
    pytorch的backward()的底层实现逻辑
    Linux必会100个命令(五十三)dmesg命令
    java 函数式接口与Lambda表达式
    java基于springboot+vue协同过滤算法的音乐推荐系统
    中断和异常理论详解,Linux操作系统原理与应用
    docker入门加实战—docker数据卷
    图聚类算法(Graph clustering)
  • 原文地址:https://www.cnblogs.com/maxwell-01/p/16100999.html