精华内容
下载资源
问答
  • FreeRTOS任务创建

    千次阅读 2017-09-10 18:43:17
    FreeRTOS 任务创建

    本文是《ALIENTEK STM32F429 FreeRTOS 开发教程》第八章学习笔记-1
    第一章笔记–FreeRTOS简介与源码下载
    第二章笔记–FreeRTOS在STM32F4上移植
    第三章笔记-FreeRTOS系统配置
    第四章笔记-FreeRTOS中断分析
    第四章笔记补充-FreeRTOS临界段代码
    第五章笔记-FreeRTOS任务基础
    第六章笔记-FreeRTOS任务API函数的使用
    第七章笔记-FreeRTOS列表和列表项

    任务创建

    1. 任务创建函数

    FreeRTOS使用函数xTaskCreate()和xTaskCreateStatic()分别动态和静态创建任务

    xTaskCreate()的源代码在tasks.c中:

    #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
        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;
            #if( portSTACK_GROWTH > 0 )
            {
                pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
                if( pxNewTCB != NULL )
                {
                    pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 
                    if( pxNewTCB->pxStack == NULL )
                    {
                        vPortFree( pxNewTCB );
                        pxNewTCB = NULL;
    
                    }
                        }
            }
            #else /* portSTACK_GROWTH */
            {
                StackType_t *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 )
            {
                #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
                {
                    pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
                }
                #endif /* configSUPPORT_STATIC_ALLOCATION */
                prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
                prvAddNewTaskToReadyList( pxNewTCB );
                xReturn = pdPASS;
            }
            else
            {
                xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
            }
            return xReturn;
        }
    
    #endif /* configSUPPORT_DYNAMIC_ALLOCATION */

    if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ):可以看出使用此函数进行任务创建时必须将支持动态内存宏定为1

    if( portSTACK_GROWTH > 0 ):如果宏定义堆栈向上增长则执行接下来的代码,portSTACK_GROWTH在portmacro.h里有宏定义

    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
    if( pxNewTCB != NULL )
                {
                    pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 
                    if( pxNewTCB->pxStack == NULL )
                    {
                        vPortFree( pxNewTCB );
                        pxNewTCB = NULL;
    
                    }
                        }

    先使用函数pvPortMalloc()给任务控制块申请内存,如果成功的话,再给人物的任务堆栈申请内存,如果申请失败释放任务控制块内存

    #else /* portSTACK_GROWTH */
            {
                StackType_t *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 */

    这段代码表示若向下增长堆栈时,先申请任务堆栈内存,再申请任务控制块内存

    pxNewTCB->ucStaticallyAllocated=tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB:标记任务堆栈和任务控制块是使用动态内存分配方法得到的

    prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ):使用函数prvInitialiseNewTask()初始化任务,完成对任务控制块中各个字段的初始化工作

    prvAddNewTaskToReadyList( pxNewTCB ):将新创建的任务加入到就绪表中

    2. 任务初始化函数

    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 )

    任务初始化代码较多,只列出了函数定义,之后重点分析当中代码,源代码在tasks.c中

    参数:pxTaskCode:任务函数;pcName:任务名称;ulStackDepth:任务堆栈大小,从任务创建函数中强制转化成uint32_t格式传入任务初始化函数;pvParameters:传递给任务函数的参数,一直是NULL没看出作用;uxPriority:任务优先级;pxCreatedTask:任务句柄;pxNewTCB:任务控制块;xRegions:使用xTaskCreateRestricted()函数建立微处理器保护任务时传递的内存地址参数,普通人物建立时此参数为NULL

    #if( portUSING_MPU_WRAPPERS == 1 )
            /* Should the task be created in privileged mode? */
            BaseType_t xRunPrivileged;
            if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
            {
                xRunPrivileged = pdTRUE;
            }
            else
            {
                xRunPrivileged = pdFALSE;
            }
            uxPriority &= ~portPRIVILEGE_BIT;
        #endif /* portUSING_MPU_WRAPPERS == 1 */

    如果宏定义portUSING_MPU_WRAPPERS为1,判断该任务是否在特权模式下创建,给xRunPrivileged赋值,并赋值优先级大小,由于我们将portUSING_MPU_WRAPPERS宏定义为0,所以不编译这段代码

        #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
        {
            /* Fill the stack with a known value to assist debugging. */
            ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
        }
    #endif

    如果使能了堆栈溢出检测功能或者追踪可视化就使用一个定值tskSTACK_FILL_BYTE来填充任务堆栈,值为0xa5U

        #if( portSTACK_GROWTH < 0 )
        {
            pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
            pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type. */
    
            /* Check the alignment of the calculated top of stack is correct. */
            configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
        }
        #else /* portSTACK_GROWTH */
        {
            pxTopOfStack = pxNewTCB->pxStack;
    
            /* Check the alignment of the stack buffer is correct. */
            configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
    
            /* The other extreme of the stack space is required if stack checking is
            performed. */
            pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
        }
        #endif /* portSTACK_GROWTH */

    使用条件编译,在向上堆栈和向下堆栈不同时计算堆栈栈顶pxTopOfStack

        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {
            pxNewTCB->pcTaskName[ x ] = pcName[ x ];
    
            /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
            configMAX_TASK_NAME_LEN characters just in case the memory after the
            string is not accessible (extremely unlikely). */
            if( pcName[ x ] == 0x00 )
            {
                break;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

    保存任务的任务名

    pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = ‘\0’:在任务名数组添加字符串结束符’\0’

        if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
        {
            uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

    判断任务优先级是否大于预先定义的最大优先级configMAX_PRIORITIES宏,若大于则将任务优先级设置为configMAX_PRIORITIES-1

    pxNewTCB->uxPriority = uxPriority:将得到的uxPriority值赋给任务控制块的优先级(即初始化任务控制块的优先级)

        #if ( configUSE_MUTEXES == 1 )
        {
            pxNewTCB->uxBasePriority = uxPriority;
            pxNewTCB->uxMutexesHeld = 0;
        }
        #endif /* configUSE_MUTEXES */

    如果宏定义configUSE_MUTEXES为1,即使用了互斥信号量,这里初始化相应变量

    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

    这里初始化列表项xStateListItem和xEventListItem,任务控制块结构体中有两个列表项,对其做初始化操作

    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
    listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
    
    #define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )  ( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
    #define listSET_LIST_ITEM_VALUE( pxListItem, xValue )   ( ( pxListItem )->xItemValue = ( xValue ) )

    这里设置了列表项xStateListItem和xEventListItem属于当前任务控制块,即设置两个列表项的成员变量pvOwner为新创建的任务的任务控制块;并且设置了列表项xEventListItem的变量xItemValue为configMAX_PRIORITIES-uxPriority,意味着xItemValue的值越大优先级越小(列表插入按照xItemValue的值升序排列)

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
    {
        pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
    }
    #endif /* portCRITICAL_NESTING_IN_TCB */
    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    {
        pxNewTCB->pxTaskTag = NULL;
    }
    #endif /* configUSE_APPLICATION_TASK_TAG */
    #if ( configGENERATE_RUN_TIME_STATS == 1 )
    {
        pxNewTCB->ulRunTimeCounter = 0UL;
    }
    #endif /* configGENERATE_RUN_TIME_STATS */

    分别条件编译,portCRITICAL_NESTING_IN_TCB:临界区嵌套宏定义;configUSE_APPLICATION_TASK_TAG:任务标签功能;configGENERATE_RUN_TIME_STATS:时间统计功能;若使能这些功能宏定义,则给任务控制块的相关变量初始化赋值

    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
        {
        for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
        {
            pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
        }
        }
    #endif

    如果使能了宏configNUM_THREAD_LOCAL_STORAGE_POINTERS为1,初始化线程本地存储指针

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        {
            pxNewTCB->ulNotifiedValue = 0;
            pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
        }
    #endif
    
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            /* Initialise this task's Newlib reent structure. */
            _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
        }
    #endif
    
    #if( INCLUDE_xTaskAbortDelay == 1 )
        {
            pxNewTCB->ucDelayAborted = pdFALSE;
        }
    #endif

    条件编译,configUSE_TASK_NOTIFICATIONS:使能任务通知功能(默认开启);configUSE_NEWLIB_REENTRANT:使能NEWLIB;INCLUDE_xTaskAbortDelay:使能函数INCLUDE_xTaskAbortDelay();如果使能了相应功能,则给任务控制块的相关变量初始化赋值

    #if( portUSING_MPU_WRAPPERS == 1 )
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
        }
    #else /* portUSING_MPU_WRAPPERS */
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
        }
    #endif /* portUSING_MPU_WRAPPERS */

    调用了函数pxPortInitialiseStack()初始化任务堆栈

    if( ( void * ) pxCreatedTask != NULL )
    {
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    生成任务句柄,返回给参数pxCreatedTask,任务句柄其实就是任务控制块

    3. 任务堆栈初始化函数

    堆栈用来进行上下文切换时候保存现场,新创建好一个堆栈后会对其进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。初值保存在任务堆栈中,保存顺序为:xPSR,R15(PC),R14(LR),R12,R3\~R0,R11~R14

    函数pxPortInitialiseStack()即为堆栈初始化函数,函数源码:

    StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
    {
        /* Simulate the stack frame as it would be created by a context switch
        interrupt. */
    
        /* Offset added to account for the way the MCU uses the stack on entry/exit
        of interrupts, and to ensure alignment. */
        pxTopOfStack--;
    
        *pxTopOfStack = portINITIAL_XPSR;   /* xPSR */
        pxTopOfStack--;
        *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;    /* PC */
        pxTopOfStack--;
        *pxTopOfStack = ( StackType_t ) prvTaskExitError;   /* LR */
    
        /* Save code space by skipping register initialisation. */
        pxTopOfStack -= 5;  /* R12, R3, R2 and R1. */
        *pxTopOfStack = ( StackType_t ) pvParameters;   /* R0 */
    
        /* A save method is being used that requires each task to maintain its
        own exec return value. */
        pxTopOfStack--;
        *pxTopOfStack = portINITIAL_EXEC_RETURN;
    
        pxTopOfStack -= 8;  /* R11, R10, R9, R8, R7, R6, R5 and R4. */
    
        return pxTopOfStack;
    }

    参数:pxTopOfStack:堆栈栈顶;pxCode:任务函数;pvParameters:传递参数(null)

    *pxTopOfStack = portINITIAL_XPSR: 寄存器xPSR值为portINITIAL_XPSR(0x01000000),此时表示xPSR寄存器的bit24为1,即处于Thumb状态(Cortex-m系列没有ARM状态)

    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK:寄存器PC初始化为任务函数pxCode

    *pxTopOfStack = ( StackType_t ) prvTaskExitError:寄存器LR初始化为函数prvTaskExitError()

    pxTopOfStack -= 5;  /* R12, R3, R2 and R1. */
    *pxTopOfStack = ( StackType_t ) pvParameters

    跳过4个寄存器R12,R3,R2和R1(这四个寄存器不初始化);并把寄存器R0初始化为pvParameters

    *pxTopOfStack = portINITIAL_EXEC_RETURN:保存EXC_RETURN值,用于退出SVC和PendSV中断的时候处理器该处于什么状态。当处理器进入异常或中断服务程序时,链接寄存器R14(LR)的数值更新为EXC_RETURN,这里宏定义为0xfffffffd

    pxTopOfStack -= 8:跳过8个寄存器,R11,R10,R8,R7,R6,R5,R4

    初始化之后,堆栈结果为:
    image

    4. 添加任务到就绪表

    任务创建后被添加到就绪列表中,FreeRTOS使用不同的列表表示任务的不同状态,tasks.c中定义了多个列表完成不同功能:

    PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
    PRIVILEGED_DATA static List_t xDelayedTaskList1;                /*< Delayed tasks. */
    PRIVILEGED_DATA static List_t xDelayedTaskList2;                /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
    PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;     /*< Points to the delayed task list currently being used. */
    PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
    PRIVILEGED_DATA static List_t xPendingReadyList;                /*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */
    

    其中pxReadyTasksLists[]即是任务就绪列表,数组大小为configMAX_PRIORITIES即可使用的最大优先级,所以一个优先级一个列表,相同优先级的任务则使用一个列表。

    函数prvAddNewTaskToReadyList()完成将一个新创建的任务添加到就绪列表中

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

    taskENTER_CRITICAL():首先先进入临界区,关闭中断,使接下来函数里代码完整运行不被打断

    uxCurrentNumberOfTasks++:全局变量uxCurrentNumberOfTasks加1,统计任务数量

    if( pxCurrentTCB == NULL ){pxCurrentTCB = pxNewTCB:如果正在运行任务控制块为NULL,即为没有任务运行,则将新任务的任务控制块赋值给pxCurrentTCB,新创建的任务便是第一个任务

    if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){prvInitialiseTaskLists();}:如果任务数量为1,则说明创建的任务是第一个任务,此时通过函数prvInitialiseTaskLists()来初始化相应的列表(函数prvInitialiseTaskLists()即调用列表初始化函数vListInitialise()来初始化几个列表)

    if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}:如果调度器尚未运行,新任务的优先级比正在运行的任务优先级高,则修改pxCurrentTCB为新建任务的任务控制块

    uxTaskNumber++:uxTaskNumber加1,用作任务控制块编号

    if ( configUSE_TRACE_FACILITY == 1 ){pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif:条件编译如果启动了可视化跟踪调试,则将任务控制块编号赋值给新任务的任务控制块的uxTCBNumber成员变量

    prvAddTaskToReadyList( pxNewTCB ):调用函数吧任务添加到就绪表中,这个函数其实是个宏:

    #define prvAddTaskToReadyList( pxTCB )                                                              \
        traceMOVED_TASK_TO_READY_STATE( pxTCB );    \
        taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );                         \
        vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
        tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )

    traceMOVED_TASK_TO_READY_STATE( pxTCB )和tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )都没什么用;taskRECORD_READY_PRIORITY用来记录处于就绪态任务,接下来使用函数vListInsertEnd()将任务添加到就绪列表末尾

    taskEXIT_CRITICAL():之后退出临界区

    if( xSchedulerRunning != pdFALSE ){if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){
    taskYIELD_IF_USING_PREEMPTION();}:如果调度器已经开始运行,并且新任务的优先级最高,则调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换

    展开全文
  • FreeRTOS 任务调度 任务创建

    千次阅读 2016-10-13 00:30:16
    任务创建 静态创建任务 动态创建任务 初始化任务控制块 栈初始化举例 插入就绪链表 参考 FreeRtos 简述FreeRTOS 的任务调度在 Source/include/task.c 中实现,包含了任务的创建、切换、挂起、延时和删除等所有功能...

    @(嵌入式)

    Freertos
    FreeRtos

    简述

    FreeRTOS 的任务调度在 Source/include/task.c 中实现,包含了任务的创建、切换、挂起、延时和删除等所有功能。涉及到的链表组织见文章 FreeRTOS 任务调度 List 组织 。任务切换实现代码量比较大,因此关于任务调度这一块会分几个文章来描述,这一篇主要分析任务的创建的调用与实现。

    分析的源码版本是 v9.0.0
    (为了方便查看,github 上保留了一份源码Source目录下的拷贝)

    任务状态

    TaskState

    系统运行过程,任务可能处于以下各种状态,各个状态之间切换的关系如上图所示。
    * Running
    运行状态, 当前正在执行,占有处理器的任务
    * Ready
    就绪状态,准备被运行的任务,没有被挂起和阻塞,但不是当前正在执行的任务,等待更高优先级任务或者同等级任务时间片结束释放处理器
    * Blocked
    阻塞状态,任务在等待一个事件而进入阻塞状态,比如延时、获取信号量等
    * Suspended
    挂起状态,任务由于调用 vTaskSuspend() 而被挂起不能被执行, 直到调用 xTaskResume() 重新恢复

    使用示例

    FreeRTOS 中创建任务并开始调度的基本框架如下 :

    void vATaskFunction( void *pvParameters )
    {
        for( ;; )
        {
        // -- 任务代码 --
        }
        // 任务不能有任何 返回
        // 对自行结束的任务,退出前需要自行清理
        vTaskDelete( NULL );
    }
    
    void main(void)
    {
        static unsigned char ucParameterToPass;  
        xTaskHandle xHandle;  
        xTaskCreate( vATaskFunction, /*任务实现函数*/
                    "TASK_NAME", /*任务名,方便调试*/
                    STACK_SIZE,  /*任务堆栈大小 *StackType_t*/
                    &ucParameterToPass, /*任务运行时的参数*/ 
                    tskIDLE_PRIORITY, /*任务优先级*/
                    &xHandle );  /*回传任务句柄,供其他地方引用任务*/
        // 其他任务和拉拉杂杂的初始化
        // 启动任务调度器 loop ....
    }

    任务创建函数中, 设置的栈大小单位由使用平台的 StackType_t 决定,不同平台栈指针对齐有自己的要求。
    回传的句柄(指向TCB的指针)一般用于在其他任务中发送消息通知给任务,或者删除任务时引用。
    任务成功创建后返回 pdPASS, 否则失败回传错误码。

    另外,删除任务,可以通过其他任务中调用 voidvTaskDelete进行删除,此时该任务会从各种链表中移除,并且内存会被马上回收; 但是如果是任务自己调用删除,则其内存回收需要由空闲任务来完成(毕竟当前正在使用这些资源)。
    使用 voidvTaskDelete 的前提是在 FreeRTOSConfig.h 设置 INCLUDE_vTaskDelete 为1(Tips !! API 在使用前最后需要看看是否需要设置对应的宏定义)。


    叙述完上层的调用,后续介绍背后具体是如何实现的。

    数据结构

    TCB

    任务调度离不开任务控制块(TCB), 用于存储任务的状态信息、运行时环境等。源代码见 tskTaskControlBlock, 以下具体介绍下这个数据结构。

    typedef struct tskTaskControlBlock
    {
        // 任务栈顶指针
        volatile StackType_t *pxTopOfStack;
        // 启用MPU 的情况下设置 
        #if ( portUSING_MPU_WRAPPERS == 1 )
            // 设置任务访问内存的权限
            xMPU_SETTINGS xMPUSettings;
        #endif
    
        // 状态链表项(Ready, Blocked, Suspended)
        // 任务处于不同状态 该项会被插入到对应的链表, 供链表引用任务
        ListItem_t xStateListItem;
        // 事件链表项
        // 比如任务延时挂起等,被插入到延时链表中,到时间或事件发生,链表引用唤醒任务
        ListItem_t xEventListItem;
        // 任务优先级 0 最低
        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; 
            // 调试 供用户设置特定数值 
            UBaseType_t uxTaskNumber;
         #endif
    
        #if ( configUSE_MUTEXES == 1 )
            // 涉及互斥锁下的优先级继承(避免优先级反转), queue 那边介绍
            // 当优先级被临时提高(继承了拿锁被堵的高优先级任务)时,这个变量保存任务实际的优先级
            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 )
            //为任务分配一个Newlibreent结构体变量
            // Newlib是一个C库函数,非FreeRTOS维护
            // 个人没用过,不清楚 
            struct  _reent xNewLib_reent;
        #endif
    
        #if( configUSE_TASK_NOTIFICATIONS == 1 )
            // 任务通知
            volatile uint32_t ulNotifiedValue;
            volatile uint8_t ucNotifyState;
        #endif
    
        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
            // 表明任务栈和TCB占用的是 heap 还是 stack
            // 供任务删除回收时判断
            uint8_t ucStaticallyAllocated; 
        #endif
    
        #if( INCLUDE_xTaskAbortDelay == 1 )
            uint8_t ucDelayAborted;
        #endif
    } tskTCB;
    typedef tskTCB TCB_t;

    任务控制块中有两个链表项 xStateListItemxEventListItem, 在前面文章提到链表项中有一个指针指向所属的TCB。当任务状态变化或者等待事件的时候,将任务所属的这个链表项插入到对应的链表中,系统调度器就是通过这个方式追踪每个任务, 当符合条件的情况下,系统会通过该链表项引用任务,实现任务切换等操作。

    链表

    如上所述, 系统中包含的链表定义如下。

    // 就绪任务链表 每个优先级对应一个链表
    PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
    // 延时任务链表
    PRIVILEGED_DATA static List_t xDelayedTaskList1;                        
    PRIVILEGED_DATA static List_t xDelayedTaskList2;                        
    PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
    PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
    // 就绪任务链表,当任务调度器被挂起时,状态变换为就绪的任务先保存在此, 
    // 恢复后移到 pxReadyTasksLists 中
    PRIVILEGED_DATA static List_t xPendingReadyList;                
    // 任务删除后,等待空闲任务释放内存
    #if( INCLUDE_vTaskDelete == 1 )
        PRIVILEGED_DATA static List_t xTasksWaitingTermination;
        PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = 
            ( UBaseType_t ) 0U;
    #endif
    // 被挂起的任务链表
    #if ( INCLUDE_vTaskSuspend == 1 )
        PRIVILEGED_DATA static List_t xSuspendedTaskList;                   
    #endif

    任务创建

    FreeRTOS V9.0.0 版本提供三个函数用于创建任务
    * xTaskCreateStatic
    通过传递的静态内存创建任务
    * xTaskCreate
    通过动态申请的内存创建任务
    * xTaskCreateRestricted
    创建任务参数通过TaskParameters_t传递给函数,用户自己申请栈的内存,创建函数只负责申请 TCB 所需内存空间

    项目中接触版本 V8.0.0, 发现有一些改动, 旧版中实际创建任务的函数实际是 xTaskGenericCreate, 参数比较多, 可以实现从 heap 动态申请内存或通过静态内存创建任务, 而一般用到的xTaskCreate 实际是一个宏,调用了 xTaskGenericCreate, 默认采用动态申请内存的方式。

    以下主要介绍 xTaskCreateStaticxTaskCreate 这两个函数的实现。

    静态创建任务

    源代码 xTaskCreateStatic
    静态的方式创建任务,需要用户先申请任务控制模块和任务栈需要的内存(一般使用静态内存),然后把内存地址传递给函数,函数负责其他初始化。
    函数按顺序完成:
    * 根据用户传递内存,初始化任务 TCB
    * 初始化任务堆栈
    * 将新建任务加入到就绪链表中
    * 如果调度器运行,新任务优先级更高,触发系统切换

    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_t *pxNewTCB;
        TaskHandle_t xReturn;
        configASSERT( puxStackBuffer != NULL );
        configASSERT( pxTaskBuffer != NULL );
    
        if ((pxTaskBuffer != NULL) && (puxStackBuffer != NULL)) 
        {
            // 设置用户传递进来的任务控制块和栈的内存地址到对应指针变量
            pxNewTCB = (TCB_t *)pxTaskBuffer; 
            pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;
    
            #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
            {
                // 标识这个任务控制块和栈内存时静态的
                // 删除任务的时候, 系统不会做内存回收处理
                pxNewTCB->ucStaticallyAllocated = 
                    tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
            }
            #endif
            // 初始化任务控制块 下文介绍
            prvInitialiseNewTask( pxTaskCode, pcName,
                ulStackDepth, pvParameters, uxPriority, 
                &xReturn, pxNewTCB, NULL );
    
            // 把新任务插入就绪链表 下文介绍
            prvAddNewTaskToReadyList( pxNewTCB );
        }
        else 
        {
            xReturn = NULL;
        }
        return xReturn;
    }

    动态创建任务

    源代码 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;
    
        // 如果是向下增长的栈, 先申请栈内存再申请任务控制块内存
        // 可以避免栈溢出覆盖了自己任务控制块
        // 对应向上增长的则相反
    
        // 在旧版本 V8.0.0 中没有这么处理,统一先 TCB 后 Stack
        // 项目上碰到平台栈向下增长, 栈溢出错时候覆盖了自己的 TCB 
        // 导致调试的时候无法获取出错任务信息(比如任务名)
        #if( portSTACK_GROWTH > 0 )
        {
            // 申请任务控制块内存
            pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
            if( pxNewTCB != NULL )
            {
                // 申请栈内存, 返回地址设置任务中的栈指针
                pxNewTCB->pxStack = (StackType_t *)pvPortMalloc(
                    (((size_t)usStackDepth) * sizeof(StackType_t)));
    
                if( pxNewTCB->pxStack == NULL )
                {
                    // 栈内存申请失败, 释放前面申请的任务控制块内存
                    vPortFree( pxNewTCB );
                    pxNewTCB = NULL;
                }
            }
        }
        #else /*栈向下增长*/
        {
            StackType_t *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
    
    
        if( pxNewTCB != NULL )
        {
            // 成功申请所需内存 执行任务初始化操作
    
            #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
            {
                // 标志任务控制块和栈是动态申请
                // 删除任务系统会自动回收内存
                pxNewTCB->ucStaticallyAllocated = 
                    tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
            }
            #endif /* configSUPPORT_STATIC_ALLOCATION */
    
            // 初始任务控制块
            prvInitialiseNewTask(pxTaskCode, pcName,
                (uint32_t)usStackDepth, pvParameters, 
                uxPriority, pxCreatedTask, pxNewTCB, NULL );
    
            // 将新任务插入到就绪链表  
            prvAddNewTaskToReadyList( pxNewTCB );
            xReturn = pdPASS;
        }
        else
        {
            // 创建任务失败,返回错误码
            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
        }
        return xReturn;
    }

    初始化任务控制块

    在创建任务的函数中, 如果成功获得新任务所需要的内存空间, 则会调用以下函数对任务控制块 TCB 的成员变量进行初始化。

    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;
    
        // 如果开启了 MPU, 判断任务是否运行在特权模式
        #if( portUSING_MPU_WRAPPERS == 1 )
            BaseType_t xRunPrivileged;
            if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
            {
                // 优先级特权模式掩码置位
                // 任务运行在特权模式
                xRunPrivileged = pdTRUE;
            }
            else
            {
                xRunPrivileged = pdFALSE;
            }
            uxPriority &= ~portPRIVILEGE_BIT;
        #endif /* portUSING_MPU_WRAPPERS == 1 */
    
        #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) 
            || ( configUSE_TRACE_FACILITY == 1 ) 
            || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
        {
            // 调试 栈初始化填充指定数据(默认 0x5a)
            (void)memset(pxNewTCB->pxStack, 
                    (int)tskSTACK_FILL_BYTE, 
                    (size_t)ulStackDepth * sizeof(StackType_t));
        }
        #endif
    
        #if( portSTACK_GROWTH < 0 )
        {
            // 向下增长栈, 初始化栈顶在内存高位
            pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t )1);
            // 字节对齐处理
            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
        {
            // 向上增长栈, 初始化栈顶在内存低位
            pxTopOfStack = pxNewTCB->pxStack;
            // 字节对齐断言
            configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack & 
                (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
            // 设置上边界
            pxNewTCB->pxEndOfStack = 
                pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
        }
        #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();
            }
        }
        // 确保任务名有正确字符串结尾
        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;
        #if ( configUSE_MUTEXES == 1 )
        {
            pxNewTCB->uxBasePriority = uxPriority;
            pxNewTCB->uxMutexesHeld = 0;
        }
        #endif /* configUSE_MUTEXES */
    
        // 初始化包含的两个链表项
        vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
        vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
    
        // 设置状态链表项的 pvOwner 指向所属 TCB
        // 如此,系统可以通过该项引用到任务
        // 比如任务状态切换到就绪时,则这个链表项会被插入到 就绪链表
        // 系统从就绪链表取出这一项进而获得 TCB(ListItem->pvOwner),切换到运行状态 
        listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
    
        // 写入优先级 用于在对应事件链表中排序
        // 链表中是按从小到达排序,因此为了实现优先级高的在前
        // 两者相反,所以写入优先级的 “补数”
        // 保证优先级高的任务,插入时在链表靠前
        listSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem), 
            (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority);
        // 设置所属 TCB, 同上  
        listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
    
        // 初始化嵌套 0
        #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        {
            pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
        }
        #endif /* portCRITICAL_NESTING_IN_TCB */
    
        #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        {
            pxNewTCB->pxTaskTag = NULL;
        }
        #endif /* configUSE_APPLICATION_TASK_TAG */
    
        #if ( configGENERATE_RUN_TIME_STATS == 1 )
        {
            pxNewTCB->ulRunTimeCounter = 0UL;
        }
        #endif /* configGENERATE_RUN_TIME_STATS */
    
        #if ( portUSING_MPU_WRAPPERS == 1 )
        {
            // 设置 MPU,任务内存访问权限设置
            vPortStoreTaskMPUSettings(&(pxNewTCB->xMPUSettings), 
                xRegions, pxNewTCB->pxStack, ulStackDepth );
        }
        #else
        {
            // 避免编译报 warning 没有使用变量
            ( void ) xRegions;
        }
        #endif
    
        // 初始化任务局部数据指针
        #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
        {
            for( x = 0; x < (UBaseType_t) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
            {
                pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
            }
        }
        #endif
    
        // 初始化任务消息通知变量
        #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        {
            pxNewTCB->ulNotifiedValue = 0;
            pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
        }
        #endif
    
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
        }
        #endif
    
        #if( INCLUDE_xTaskAbortDelay == 1 )
        {
            pxNewTCB->ucDelayAborted = pdFALSE;
        }
        #endif
    
        // 初始化栈 使其像任务已经运行了,但是被调度器中断切换,入栈做了现场保护
        // 当任务被调度器取出后, 可以直接执行出栈恢复现场,运行任务
        // 而不需要调度器额外特殊处理第一次运行的任务
        // 栈初始化涉及系统底层, 由对应平台移植层提供
        // 见下举例栈初始化
        #if( portUSING_MPU_WRAPPERS == 1 )
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, 
                pxTaskCode, pvParameters, xRunPrivileged);
        }
        #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();
        }
    }

    栈初始化举例

    新任务初始化任务后,使得当前新建任务像已经运行,但是被调度器中断,栈中保存该任务被中断时的现场,但轮到该任务执行的时候,系统可以直接执行现场恢复,运行任务。
    不同平台实现任务切换时的现场保护可能不一样,所以该函数由平台移植层提供
    列举 Cotex-M3 没有MPU下的栈初始化函数, 向下增长栈。

    StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
    {
        // 模拟任务被切换前的现场保护
        // 调度切换回来可以统一执行恢复操作
        pxTopOfStack--; 
        *pxTopOfStack = portINITIAL_XPSR;   /* xPSR */
        pxTopOfStack--;
        // 指向任务函数
        *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
        pxTopOfStack--;
        *pxTopOfStack = ( StackType_t ) prvTaskExitError;   /* LR */
        pxTopOfStack -= 5;  /* R12, R3, R2 and R1. */
        // 传递参数
        *pxTopOfStack = ( StackType_t ) pvParameters;   /* R0 */
        pxTopOfStack -= 8;  /* R11, R10, R9, R8, R7, R6, R5 and R4. */
    
        return pxTopOfStack;
    }

    如上初始化后栈如下所示

    – 低位地址

    pxStack->..
    ....
    pxTopOfStack->R4
    ..
    R11
    R0
    R1
    R2
    R3
    R12
    LR : prvTaskExitError
    PC : pxCode
    XPSR :portINITIAL_XPSR

    – 高位地址

    初始化后,当任务第一次真正被运行,当前环境设置,使其从对应的函数入口开始执行。
    其中LR 寄存器设置的地址是系统的出错处理函数,如果任务错误返回,就会调用该函数。
    根据 约定, R0~R3保存调用时传递的参数。

    插入就绪链表

    任务创建初始化后,需要将任务插入到就绪链表中,通过调度器切换到运行状态。
    该函数主要实现将新任务加入就绪链表,第一次调用该函数会进行系统必要的初始化,同时,判断是否需要马上执行任务切换,保证更高优先级的就绪任务可以及时获得CPU 的使用权限。

    注意,这里提到的把任务插入到链表,是指将任务所含的链表项插入到合适的链表中,而但需要重新取回任务,则通过该链表项中指向所属任务的指针实现。

    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 )
            {
                // 调试追踪用
                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();
        }
    }

    之前的文章分析过 FreeRtos 的链表,同样,当第一次调用将新任务插入就绪链表这个函数,会对系统涉及的几个链表进行初始化。

    调度器会在每次任务切换中,依据优先级顺序从链表中选出合适的任务,相同优先级任务在同一个就绪链表中,系统按照时间片轮序调度(如果使能),

    参考

    展开全文
  • FreeRTOS 任务管理之任务创建

    千次阅读 2016-06-28 21:07:23
    FreeRTOS 任务管理之任务创建...任务创建在FreeRTOS中,任务创建是由任务创建函数来执行,任务创建函数原型如下:#define xTaskCreate( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask )

    FreeRTOS 任务管理之任务创建

    任务概念

    任务:个人感觉就是将相关的一系列操作放在一个任务函数里来,跟线程差不多一个概念。

    任务创建

    在FreeRTOS中,任务创建是由任务创建函数来执行,任务创建函数原型如下:

    #define xTaskCreate( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask ) xTaskGenericCreate( ( pvTaskCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask ), ( NULL ), ( NULL ) )
    BaseType_t xTaskGenericCreate( TaskFunction_t pxTaskCode, 
                                const char * const pcName, 
                                const uint16_t usStackDepth, 
                                void * const pvParameters, 
                                UBaseType_t uxPriority, 
                                TaskHandle_t * const pxCreatedTask,
                                StackType_t * const puxStackBuffer, 
                                const MemoryRegion_t * const xRegions );
    

    我们平时调用的是xTaskCreate()这个API,而其实这个API是个参数宏,其实际函数xTaskBenericCreate();在这里就置分析xTaskCreate()的参数;

    • pvTaskCode,这个是一个指向任务函数的指针,类型为:TaskFunction_t–void (TaskFunction_t)( void ),也就是说这是一个指向无返回值人口参数为指针 函数的函数指针。从这里就引出来了 任务函数一说,任务函数原型为:
    void (*TaskFunction_t)( void * );

    举个例子,一个闪灯的任务函数如下:

    static void vLEDTask( void *pvParameters )  
        {  
            while(1)
            {
                LED_Toggle(GREEN_LED_Toggle);
                printf("LED Toggle\n");
                Delay(1000);
            }
        }
    • pcName,这个是任务的描述性名字,这个并没有多大的作用,只是方便用于调试程序,该字段的长度由FreeRTOSConfig.h中configMAX_TASK_NAME_LEN来决定。
    • usStackDepth,当创建任务时,内核会为每个任务分配自己的堆栈空间,该参数告诉内核该分配多少堆栈给该任务;当MCU为STM32时,该参数的内存单位不是字节,而是uint32_t为单位,四个字节为单位,代码中如下:
    #define portSTACK_TYPE  uint32_t
    • pvParameters, 任务函数接受一个指向 void 的指针(void*)。pvParameters 的值即
      是传递到任务中的值。
    • uxPriority,任务执行的优先级;优先级从0到(configMAX_PRIORITIES-1),0为最低优先级,数字越大优先级越高;
    • pxCreatedTask, pxCreatedTask 用于传出任务的句柄。这个句柄将在 API 调用中对
      该创建出来的任务进行引用,比如改变任务优先级,或者删除任务。
      如果应用程序中不会用到这个任务的句柄,则 pxCreatedTask 可以
      被设为 NULL。

    最后,我们来创建上面闪灯的任务:

    xTaskCreate(vLEDTask, 
                "vLEDTASK", 
                512, 
                NULL, 
                1, 
                NULL);
    展开全文
  • FreeRTOS高级篇2---FreeRTOS任务创建分析

    万次阅读 多人点赞 2016-05-03 13:31:08
    在FreeRTOS基础系列《FreeRTOS系列第10篇---FreeRTOS任务创建和删除》中介绍了任务创建API函数xTaskCreate(),我们这里先回顾一下这个函数的声明: BaseType_t xTaskCreate( TaskFunction_tp vTaskCode,

          在FreeRTOS基础系列《FreeRTOS系列第10篇---FreeRTOS任务创建和删除》中介绍了任务创建API函数xTaskCreate(),我们这里先回顾一下这个函数的声明:

     

            BaseType_t xTaskCreate(
                                TaskFunction_tp vTaskCode,
                                const char * constpcName,
                                unsigned short usStackDepth,
                                void *pvParameters,
                                UBaseType_t uxPriority,
                                TaskHandle_t *pvCreatedTask
                              );

     

          这个API函数的作用是创建新的任务并将它加入到任务就绪列表,函数参数含义为:

     

    • pvTaskCode:函数指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型TaskFunction_t定义在文件projdefs.h中,定义为:typedef void(*TaskFunction_t)( void * ),即参数为空指针类型并返回空类型。
    • pcName:任务描述。主要用于调试。字符串的最大长度(包括字符串结束字符)由宏configMAX_TASK_NAME_LEN指定,该宏位于FreeRTOSConfig.h文件中。
    • usStackDepth:指定任务堆栈大小,能够支持的堆栈变量数量(堆栈深度),而不是字节数。比如,在16位宽度的堆栈下,usStackDepth定义为100,则实际使用200字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t类型所能表示的最大值。比如,size_t为16位,则可以表示堆栈的最大值是65535字节。这是因为堆栈在申请时是以字节为单位的,申请的字节数就是堆栈宽度乘以深度,如果这个乘积超出size_t所表示的范围,就会溢出,分配的堆栈空间也不是我们想要的。
    • pvParameters:指针,当任务创建时,作为一个参数传递给任务。
    • uxPriority:任务的优先级。具有MPU支持的系统,可以通过置位优先级参数的portPRIVILEGE_BIT位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为2的特权任务,参数uxPriority可以设置为 ( 2 | portPRIVILEGE_BIT )。
    • pvCreatedTask:用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。

     

          虽然xTaskCreate()看上去很像函数,但其实是一个宏,真正被调用的函数是xTaskGenericCreate(),xTaskCreate()宏定义如下所示:

     

    #define xTaskCreate( pvTaskCode, pcName, usStackDepth,pvParameters, uxPriority, pxCreatedTask )    \
          xTaskGenericCreate( ( pvTaskCode ),( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask), ( NULL ), ( NULL ), ( NULL ) )

     

          可以看到,xTaskCreate比xTaskGenericCreate少了三个参数,在宏定义中,这三个参数被设置为NULL。这三个参数用于使用静态变量的方法分配堆栈、任务TCB空间以及设置MPU相关的参数。一般情况下,这三个参数是不使用的,所以任务创建宏xTaskCreate定义的时候,将这三个参数对用户隐藏了。接下来的章节中,为了方便,我们还是称xTaskCreate()为函数,虽然它是一个宏定义。

          上面我们提到了任务TCB(任务控制块),这是一个需要重点介绍的关键点。它用于存储任务的状态信息,包括任务运行时的环境。每个任务都有自己的任务TCB。任务TCB是一个相对比较大的数据结构,这也是情理之中的,因为与任务相关的代码占到整个FreeRTOS代码量的一半左右,这些代码大都与任务TCB相关,我们先来介绍一下任务TCB数据结构的定义:

     

    typedef struct tskTaskControlBlock
    {
        volatile StackType_t    *pxTopOfStack; /*当前堆栈的栈顶,必须位于结构体的第一项*/
     
        #if ( portUSING_MPU_WRAPPERS == 1 )
            xMPU_SETTINGS   xMPUSettings;      /*MPU设置,必须位于结构体的第二项*/
        #endif
     
        ListItem_t          xStateListItem; /*任务的状态列表项,以引用的方式表示任务的状态*/
        ListItem_t          xEventListItem;    /*事件列表项,用于将任务以引用的方式挂接到事件列表*/
        UBaseType_t         uxPriority;        /*保存任务优先级,0表示最低优先级*/
        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;       /*保存一个数值,每个任务都有唯一的值*/
            UBaseType_t     uxTaskNumber;      /*存储一个特定数值*/
        #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 )
            /* 为任务分配一个Newlibreent结构体变量。Newlib是一个C库函数,并非FreeRTOS维护,FreeRTOS也不对使用结果负责。如果用户使用Newlib,必须熟知Newlib的细节*/
            struct _reent xNewLib_reent;
        #endif
     
        #if( configUSE_TASK_NOTIFICATIONS == 1 )
            volatile uint32_t ulNotifiedValue; /*与任务通知相关*/
            volatile uint8_t ucNotifyState;
        #endif
     
        #if( configSUPPORT_STATIC_ALLOCATION == 1 )
            uint8_t ucStaticAllocationFlags; /* 如果堆栈由静态数组分配,则设置为pdTRUE,如果堆栈是动态分配的,则设置为pdFALSE*/
        #endif
     
        #if( INCLUDE_xTaskAbortDelay == 1 )
            uint8_t ucDelayAborted;
        #endif
     
    } tskTCB;
     
    typedef tskTCB TCB_t;

     

          下面我们详细的介绍这个数据结构的主要成员:

          指针pxTopOfStack必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack总是指向最后一个入栈的项目。

          如果使用MPU,xMPUSettings必须位于结构体的第二项,用于MPU设置。

          接下来是状态列表项xStateListItem和事件列表项xEventListItem,我们在上一章介绍列表和列表项的文章中提到过:列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。调度器就是通过把任务TCB中的状态列表项xStateListItem和事件列表项xEventListItem挂接到不同的列表中来实现上述过程的。在task.c中,定义了一些静态列表变量,其中有就绪、阻塞、挂起列表,例如当某个任务处于就绪态时,调度器就将这个任务TCB的xStateListItem列表项挂接到就绪列表。事件列表项也与之类似,当队列满的情况下,任务因入队操作而阻塞时,就会将事件列表项挂接到队列的等待入队列表上。

          uxPriority用于保存任务的优先级,0为最低优先级。任务创建时,指定的任务优先级就被保存到该变量中。

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

          字符数组pcTaskName用于保存任务的描述或名字,在任务创建时,由参数指定。名字的长度由宏configMAX_TASK_NAME_LEN(位于FreeRTOSConfig.h中)指定,包含字符串结束标志。

          如果堆栈向上生长(portSTACK_GROWTH > 0),指针pxEndOfStack指向堆栈尾部,用于检验堆栈是否溢出。

          变量uxCriticalNesting用于保存临界区嵌套深度,初始值为0。

          接下来两个变量用于可视化追踪,仅当宏configUSE_TRACE_FACILITY(位于FreeRTOSConfig.h中)为1时有效。变量uxTCBNumber存储一个数值,在创建任务时由内核自动分配数值(通常每创建一个任务,值增加1),每个任务的uxTCBNumber值都不同,主要用于调试。变量uxTaskNumber用于存储一个特定值,与变量uxTCBNumber不同,uxTaskNumber的数值不是由内核分配的,而是通过API函数vTaskSetTaskNumber()来设置的,数值由函数参数指定。

          如果使用互斥量(configUSE_MUTEXES == 1),任务优先级被临时提高时,变量uxBasePriority用来保存任务原来的优先级。

          变量ucStaticAllocationFlags也需要说明一下,我们前面说过任务创建API函数xTaskCreate()只能使用动态内存分配的方式创建任务堆栈和任务TCB,如果要使用静态变量实现任务堆栈和任务TCB就需要使用函数xTaskGenericCreate()来实现。如果任务堆栈或任务TCB由静态数组和静态变量实现,则将该变量设置为pdTRUE(任务堆栈空间由静态数组变量实现时为0x01,任务TCB由静态变量实现时为0x02,任务堆栈和任务TCB都由静态变量实现时为0x03),如果堆栈是动态分配的,则将该变量设置为pdFALSE。

          到这里任务TCB的数据结构就讲完了,下面我们用一个例子来讲述任务创建的过程,为方便起见,假设被创建的任务叫“任务A”,任务函数为vTask_A():

     

        TaskHandle_t xHandle;
        xTaskCreate(vTask_A,”Task A”,120,NULL,1,&xHandle);

     

          这里创建了一个任务,任务优先级为1,由于硬件平台是32为架构,所以指定了120*4=480字节的任务堆栈,向任务函数vTask_A()传递的参数为空(NULL),任务句柄由变量xHandle保存。当这个语句执行后,任务A被创建并加入就绪任务列表,我们这章的主要目的,就是看看这个语句在执行过程中,发生了什么事情。

    1.创建任务堆栈和任务TCB

          调用函数prvAllocateTCBAndStack()创建任务堆栈和任务TCB。有两种方式创建任务堆栈和任务TCB,一种是使用动态内存分配方法,这样当任务删除时,任务堆栈和任务控制块空间会被释放,可用于其它任务;另一种是使用静态变量来实现,在创建任务前定义好全局或者静态堆栈数组和任务控制块变量,在调用创建任务API函数时,将这两个变量以参数的形式传递给任务创建函数xTaskGenericCreate()。如果使用默认的xTaskCreate()创建任务函数,则使用动态内存分配,因为与静态内存分配有关的参数不可见(在本文一开始我们说过xTaskCreate()其实是一个带参数的宏定义,真正被执行的函数是xTaskGenericCreate(),参考宏xTaskCreate()的定义可以知道,xTaskCreate()对外隐藏了使用静态内存分配的参数,在调用xTaskGenericCreate()时,这些参数被设置为NULL)。

          任务堆栈成功分配后,经过对齐的堆栈起始地址被保存到任务TCB的pxStack字段。如果使能堆栈溢出检查或者使用可视化追踪功能,则使用固定值tskSTACK_FILL_BYTE(0xa5)填充堆栈。

          函数prvAllocateTCBAndStack()的源码去除断言和不常用的条件编译后如下所示:

     

    static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer, TCB_t * const pxTaskBuffer )
    {
    TCB_t *pxNewTCB;
    StackType_t *pxStack;
     
        /* 分配堆栈空间*/
        pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer );
        if( pxStack != NULL )
        {
            /* 分配TCB空间 */
            pxNewTCB = ( TCB_t * ) pvPortMallocAligned( sizeof( TCB_t ), pxTaskBuffer );
     
            if( pxNewTCB != NULL )
            {
                /* 将堆栈起始位置存入TCB*/
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                /* 如果TCB分配失败,释放之前申请的堆栈空间 */
                if( puxStackBuffer == NULL )
                {
                    vPortFree( pxStack );
                }
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
     
        if( pxNewTCB != NULL )
        {
            /* 如果需要,使用固定值填充堆栈 */
            #if( ( configCHECK_FOR_STACK_OVERFLOW> 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark== 1 ) )
            {
                /* 仅用于调试 */
                ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) usStackDepth * sizeof( StackType_t ) );
            }
            #endif
        }
     
        return pxNewTCB;
    }

     

    2.初始化任务TCB必要的字段

          调用函数prvInitialiseTCBVariables()初始化任务TCB必要的字段。在调用创建任务API函数xTaskCreate()时,参数pcName(任务描述)、uxPriority(任务优先级)都会被写入任务TCB相应的字段,TCB字段中的xStateListItem和xEventListItem列表项也会被初始化,初始化后的列表项如图2-1所示。在图2-1中,列表项xEventListItem的成员列表项值xItemValue被初始为4,这是因为我在应用中设置的最大优先级数目(configMAX_PRIORITIES)为5,而xEventListItem. xItemValue等于configMAX_PRIORITIES减去任务A的优先级(为1),即5-1=4。这一点很重要,在这里xItemValue不是直接保存任务优先级,而是保存优先级的补数,这意味着xItemValue的值越大,对应的任务优先级越小。FreeRTOS内核使用vListInsert函数(详细见高级篇第一章)将事件列表项插入到一个列表,这个函数根据xItemValue的值的大小顺序来进行插入操作。使用宏listGET_OWNER_OF_HEAD_ENTRY获得列表中的第一个列表项的xItemValue值总是最小,也就是优先级最高的任务!

     

    图2-1:初始化状态和事件列表项

          此外,TCB其它的一些字段也被初始化,比如临界区嵌套次数、运行时间计数器、任务通知值、任务通知状态等,函数prvInitialiseTCBVariables()的源码如下所示:

     

    static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority,   \
                                  const MemoryRegion_t * const xRegions, const uint16_t usStackDepth )
    {
    UBaseType_t x;
     
        /* 将任务描述存入TCB */
        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {
            pxTCB->pcTaskName[ x ] = pcName[ x ];
            if( pcName[ x ] == 0x00 )
            {
                break;
            }
        }
        /* 确保字符串有结束 */
        pxTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
     
        /* 调整优先级,宏configMAX_PRIORITIES的值在FreeRTOSConfig.h中设置 */
        if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
        {
            uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
        }
     
        pxTCB->uxPriority = uxPriority;
        #if ( configUSE_MUTEXES == 1 )              /*使用互斥量*/
        {  
            pxTCB->uxBasePriority = uxPriority;
            pxTCB->uxMutexesHeld = 0;
        }
        #endif /* configUSE_MUTEXES */
       
        /*初始化列表项*/
        vListInitialiseItem( &( pxTCB->xStateListItem ) );
        vListInitialiseItem( &( pxTCB->xEventListItem ) );
     
        /* 设置列表项xStateListItem的成员pvOwner指向当前任务控制块 */
        listSET_LIST_ITEM_OWNER( &( pxTCB->xStateListItem ), pxTCB );
     
        /* 设置列表项xEventListItem的成员xItemValue*/
        listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
        /* 设置列表项xEventListItem的成员pvOwner指向当前任务控制块 */
        listSET_LIST_ITEM_OWNER( &( pxTCB->xEventListItem ), pxTCB );
     
        #if ( portCRITICAL_NESTING_IN_TCB ==1 )    /*使能临界区嵌套功能*/
        {  
            pxTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
        }
        #endif /* portCRITICAL_NESTING_IN_TCB */
     
        #if ( configUSE_APPLICATION_TASK_TAG == 1 ) /*使能任务标签功能*/
        {  
            pxTCB->pxTaskTag = NULL;
        }
        #endif /* configUSE_APPLICATION_TASK_TAG */
     
        #if ( configGENERATE_RUN_TIME_STATS == 1 )  /*使能事件统计功能*/
        {
            pxTCB->ulRunTimeCounter = 0UL;
        }
        #endif /* configGENERATE_RUN_TIME_STATS */
     
        #if ( portUSING_MPU_WRAPPERS == 1 )         /*使用MPU功能*/
        {
            vPortStoreTaskMPUSettings( &( pxTCB->xMPUSettings ), xRegions, pxTCB->pxStack, usStackDepth );
        }
        #else /* portUSING_MPU_WRAPPERS */
        {
            ( void ) xRegions;
            ( void ) usStackDepth;
        }
        #endif /* portUSING_MPU_WRAPPERS */
     
        #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )/*使能线程本地存储指针*/
        {
            for( x = 0; x < ( UBaseType_t )configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
            {
                pxTCB->pvThreadLocalStoragePointers[ x ] = NULL;
            }
        }
        #endif
     
        #if ( configUSE_TASK_NOTIFICATIONS == 1 )   /*使能任务通知功能*/
        {
            pxTCB->ulNotifiedValue = 0;
            pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
        }
        #endif
     
        #if ( configUSE_NEWLIB_REENTRANT == 1 )     /*使用Newlib*/
        {
            _REENT_INIT_PTR( ( &( pxTCB->xNewLib_reent ) ) );
        }
        #endif
     
        #if( INCLUDE_xTaskAbortDelay == 1 )
        {
            pxTCB->ucDelayAborted = pdFALSE;
        }
        #endif
    }

     

    3.初始化任务堆栈

          调用函数pxPortInitialiseStack()初始化任务堆栈,并将最新的栈顶指针赋值给任务TCB的pxTopOfStack字段。

          调用函数pxPortInitialiseStack()后,相当于执行了一次系统节拍时钟中断:将一些重要寄存器入栈。虽然任务还没开始执行,也并没有中断发生,但看上去就像寄存器已经被入栈了,并且部分堆栈值被修改成了我们需要的已知值。对于不同的硬件架构,入栈的寄存器也不相同,所以我们看到这个函数是由移植层提供的。对于Cortex-M3架构,需要依次入栈xPSR、PC、LR、R12、R3~R0、R11~R4,假设堆栈是向下生长的,初始化后的堆栈如图3-1所示。

          在图3-1中我们看到寄存器xPSR被初始为0x01000000,其中bit24被置1,表示使用Thumb指令;寄存器PC被初始化为任务函数指针vTask_A,这样当某次任务切换后,任务A获得CPU控制权,任务函数vTask_A被出栈到PC寄存器,之后会执行任务A的代码;LR寄存器初始化为函数指针prvTaskExitError,这是由移植层提供的一个出错处理函数。当中断发生时,LR被设置成中断要返回的地址,但是每个任务都是一个死循环,正常情况下不应该退出任务函数,所以一旦从任务函数退出,说明那里出错了,这个时候会调用寄存器LR指向的函数来处理这个错误,即prvTaskExitError;根据ATPCS(ARM-Thumb过程调用标准),我们知道子函数调用通过寄存器R0~R3传递参数,在文章的最开始讲xTaskCreate()函数时,提到这个函数有一个空指针类型的参数pvParameters,当任务创建时,它作为一个参数传递给任务,所以这个参数被保存到R0中,用来向任务传递参数。

          任务TCB结构体成员pxTopOfStack表示当前堆栈的栈顶,它指向最后一个入栈的项目,所以在图中它指向R4,TCB结构体另外一个成员pxStack表示堆栈的起始位置,所以在图中它指向堆栈的最开始处。

     

    图3-1:初始化任务堆栈

    4.进入临界区

          调用taskENTER_CRITICAL()进入临界区,这是一个宏定义,最终进入临界区的代码由移植层提供。

    5.当前任务数量增加1

          在tasks.c中 ,定义了一些静态私有变量,用来跟踪任务的数量或者状态等等,其中变量uxCurrentNumberOfTasks表示当前任务的总数量,每创建一个任务,这个变量都会增加1。

    6.为第一次运行做必要的初始化

          如果这是第一个任务(uxCurrentNumberOfTasks等于1),则调用函数prvInitialiseTaskLists()初始化任务列表。FreeRTOS使用列表来跟踪任务,在tasks.c中,定义了静态类型的列表变量:

     

    PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*按照优先级排序的就绪态任务*/
    PRIVILEGED_DATAstatic List_t xDelayedTaskList1;                        /*延时的任务 */
    PRIVILEGED_DATAstatic List_t xDelayedTaskList2;                        /*延时的任务 */
    PRIVILEGED_DATAstatic List_t xPendingReadyList;                        /*任务已就绪,但调度器被挂起 */
     
    #if (INCLUDE_vTaskDelete == 1 )
        PRIVILEGED_DATA static List_t xTasksWaitingTermination;             /*任务已经被删除,但内存尚未释放*/
    #endif
     
    #if (INCLUDE_vTaskSuspend == 1 )
        PRIVILEGED_DATA static List_t xSuspendedTaskList;                   /*当前挂起的任务*/
    #endif

     

          现在这些列表都要进行初始化,会调用API函数vListInitialise()初始化列表,这个函数在《FreeRTOS高级篇1---FreeRTOS列表和列表项》中讲过,每个列表的初始化方式都是相同的,以就绪态列表pxReadyTasksLists[0]为例,初始化后如图6-1所示:

     

    图6-1:初始化后的列表

          函数prvInitialiseTaskLists()的源代码如下所示:

     

    static void prvInitialiseTaskLists( void )
    {
    UBaseType_tuxPriority;
     
        for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
        {
            vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
        }
     
        vListInitialise( &xDelayedTaskList1 );
        vListInitialise( &xDelayedTaskList2 );
        vListInitialise( &xPendingReadyList );
     
        #if ( INCLUDE_vTaskDelete == 1 )
        {
            vListInitialise( &xTasksWaitingTermination );
        }
        #endif /* INCLUDE_vTaskDelete */
     
        #if ( INCLUDE_vTaskSuspend == 1 )
        {
            vListInitialise( &xSuspendedTaskList );
        }
        #endif /* INCLUDE_vTaskSuspend */
     
        /* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskListusing list2. */
        pxDelayedTaskList = &xDelayedTaskList1;
        pxOverflowDelayedTaskList = &xDelayedTaskList2;
    }

     

    7.更新当前正在运行的任务TCB指针

          tasks.c中定义了一个任务TCB指针型变量:

          PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB= NULL;

          这是一个全局变量,在tasks.c中只定义了这一个全局变量。这个变量用来指向当前正在运行的任务TCB,我们需要多了解一下这个变量。FreeRTOS的核心是确保处于优先级最高的就绪任务获得CPU运行权。在下一章讲述任务切换时会知道,任务切换就是找到优先级最高的就绪任务,而找出的这个最高优先级任务的TCB,就被赋给变量pxCurrentTCB。

          如果调度器还没有准备好(程序刚开始运行时,可能会先创建几个任务,之后才会启动调度器),并且新创建的任务优先级大于变量pxCurrentTCB指向的任务优先级,则设置pxCurrentTCB指向当前新创建的任务TCB(确保pxCurrentTCB指向优先级最高的就绪任务)。

     

    if( xSchedulerRunning == pdFALSE )
    {
        if( pxCurrentTCB->uxPriority <= uxPriority )
        {
            pxCurrentTCB = pxNewTCB;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

     

    8.将新创建的任务加入就绪列表数组

          调用prvAddTaskToReadyList(pxNewTCB)将创建的任务TCB加入到就绪列表数组中,任务的优先级确定了加入到就绪列表数组的哪个下标。比如我们新创建的任务优先级为1,则这个任务被加入到列表pxReadyTasksLists[1]中。

           prvAddTaskToReadyList()其实是一个宏,由一系列语句组成,去除其中的跟踪宏外,这个宏定义如下所示:

     

    #defineprvAddTaskToReadyList( pxTCB )                        \
        taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority );       \
        vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

     

          宏taskRECORD_READY_PRIORITY()用来更新变量uxTopReadyPriority,这个变量在tasks.c中定义为静态变量,记录处于就绪态的最高任务优先级。这个变量参与了FreeRTOS的最核心代码:确保处于优先级最高的就绪任务获得CPU运行权。它在这里参与如何最快的找到优先级最高的就绪任务。为了最快,不同的架构会各显神通,一些架构还有特殊指令可用,所以这个宏由移植层提供。我们会在下一章介绍任务切换时,以Cortex-M3架构为例,详细介绍如何最快的找到优先级最高的就绪任务。

          函数vListInsertEnd()将列表项插入到列表末端,在《FreeRTOS高级篇1---FreeRTOS列表和列表项》中已经提到过,这里会结合着例子再看一下这个函数。从前面我们直到,在调用函数vListInsertEnd()之前,就绪列表pxReadyTasksLists[1]和任务TCB的状态列表项xStateListItem都已经初始化好了,见图6-1和图2-1,为了方便查看,我们将这两幅图合成一副,见图8-1。

     

    图8-1:初始化后的列表和列表项

             调用vListInsertEnd(a,b)会将列表项b,插入到列表a的后面,函数执行完毕后,列表和列表项的关系如图8-2所示。

     

    图8-2:插入一个列表项后的列表

          在此基础上,假设又创建了任务B,任务A和任务B优先级相同,都为1。和任务A一样,任务B也有它自己的任务TCB,其中的状态列表项字段xStateListItem也要插入到列表pxReadyTasksLists[1]中,新的列表和列表项如图8-3所示。

     

    图8-3:相同优先级就绪列表挂接两个列表项

    9.退出临界区

          调用taskEXIT_CRITICAL()退出临界区,这是一个宏定义,最终退出临界区的代码由移植层提供。

    10.执行上下文切换

        如果上面的步骤都正确执行,并且调度器也开始工作,则判断当前任务的优先级是否大于新创建的任务优先级。如果新创建的任务优先级更高,则调用taskYIELD_IF_USING_PREEMPTION()强制进行一次上下文切换,切换后,新创建的任务将获得CPU控制权,精简后的代码如下所示。

     

     if( xReturn == pdPASS )
        {
            if( xSchedulerRunning != pdFALSE )
            {
                /* 如果新创建的任务优先级大于当前任务优先级,则新创建的任务应该被立即执行。*/
                if(pxCurrentTCB->uxPriority < uxPriority )
                {
                    taskYIELD_IF_USING_PREEMPTION();
                }
            }
        }

     

     

     

    展开全文
  • 任务创建函数OSTaskCreate解析

    万次阅读 2012-12-31 11:31:23
    任务创建函数有两种,一种是基本的创建函数OSTaskCreate,另一种是扩展的任务创建函数OSTaskCreateExt。两个函数都实现了任务的创建,但是OSTaskCreateExt的功能更强,带有很多附加的功能,如果不需要附加功能,...
  • 1、流程任务创建:  (1).在项目中创建diagram文件夹,并创建Activiti Diagram文件MyProcess.bpmn: 2、创建MyProcess.bpmn 流程,详情如下: 整体结构示意图: 右击diagram文件夹,新建一个Activiti ...
  • 第一部分:任务创建与删除。重点在于理解任务创建的各个参数即可。 第二部分:任务挂起和恢复。函数比较简单,看看即可。 第三部分:时间片调度 重点在于第三部分代码讲解 任务创建与删除创建 步骤1:先调用...
  • 如何判断一个进程是由用户创建 还是由服务创建,计划任务创建的? 如果使用C#来判断一个进程(非当前进程)是由谁(服务,计划任务,用户)创建的。并加以区别?
  • Windows7 计划任务创建说明

    千次阅读 2015-12-28 21:22:15
    通过上一篇博客《Windows Server 2003 R2 计划任务创建说明》给大家介绍了如何在Windows Server 2003 R2系统上创建一个计划任务,今天继续给大家介绍一下在Windows7系统上如何创建一个计划任务 1.打开控制面板 ...
  • FreeRTOS(三)——任务创建与删除

    千次阅读 2018-07-02 11:26:51
    1 任务创建和删除的API函数 函数 描述 xTaskCreate() 使用动态方法创建一个任务 xTaskCreateStaitic() 使用静态方法创建一个任务 xTaskCreateRestricted() 创建一个使用MPU...
  • FreeRTOS系列第10篇---FreeRTOS任务创建和删除

    万次阅读 多人点赞 2015-12-21 14:43:04
    在FreeRTOS移植到Cortex-M3硬件平台的文章中,我们已经见过任务创建API,但那篇文章的重点在于如何移植FreeRTOS,本文将重点放在任务的创建和删除API函数上面。 任务创建和删除API函数位于文件task.c中,需要包含...
  • azkaban详解之简单任务创建

    千次阅读 2018-08-01 21:06:31
    azkaban详解之简单并行任务创建 1.需求 建立一个具有开始-&amp;amp;gt;并行运行-&amp;amp;gt;结束的azkaban job。 2.代码如下 start.job type=noop first.job type=command command=&amp;...
  • PLSQL定时任务创建(Oracle数据库dbms_job使用)

    万次阅读 多人点赞 2018-08-20 21:11:57
    PLSQL定时任务创建(Oracle数据库中dbms_job使用) 通过本文将学习到 如何用PLSQL在中创建一个JOB 查看JOB 操作JOB 时间的判断 1、前言  2018/8/20,今天我们邮件系统出现了问题 ,无法正常的将待发邮件发出...
  • STM32CubeMX学习笔记——FreeRTOS_任务创建与删除Github简介任务创建可视化创建方式代码创建方式任务删除 Github ...简介 在STM32CubeMX上配置FreeRTOS非...
  • ucos 任务创建

    千次阅读 2017-01-10 14:26:08
    任务是什么?有一句话,CPU不同的时刻总是在执行着优先级最高的任务,所以CPU总是在不同的任务间切换,任务好比linux中近程 在ucos世界里,任务就是一个大箩筐,框中一个或多个函数,CPU像个美女,总是从一个大箩筐...
  • 6.1 任务创建和删除API 函数FreeRTOS 最基本的功能就是任务管理,而任务管理最基本的操作就是创建和删除任务,FreeRTOS 的任务创建和删除API 函数如表6.1.1.1 所示:1、函数xTaxkCreate()此函数用来创建一个任务,...
  • 6.3 任务创建和删除实验(静态方法)6.3.1 实验程序设计1、实验目的上一小节我们讲了使用函数xTaskCreate()来创建任务,本节在上一小节的基础上做简单的修改,使用函数xTaskCreateStatic()来创建任务,也就是静态方法...
  • SAP EWM 仓库任务创建和确认的相关函数 在EWM项目中经常会用到自定义程序来批量创建、确认、修改和取消仓库任务(Warehouse Task)。它们对应的函数都包含在函数组/SCWM/L03B中。以下是对于最常见的创建和确认...
  • Laravel Console 任务创建和调度

    千次阅读 2019-07-01 17:59:21
    一。开启: //linux中执行 //每分钟调用Laravel命令 /home/wwwroot/laravel/-项目目录 * * * * * root ... 任务调度定义在app/Console/Kernel.php文件的schedule方法中。...创建...
  • 对于VxWorks 的任务创建可以通过调用 taskSpawn()或是taskInit()/taskActivate()两种方式,以下就对这两种方式进行说明。  taskSpawn() 实现任务的创建分为两步:1、分配任务栈的空间,初始化WIND_TCB; 2、激活...
  • 要使用uC/OS的任务必须先声明任务控制块和创建任务,调用OSTaskCreate ()函数可以创建一个任务。OSTaskCreate ()函数的信息如下表所示。
  • Windows Server 2003 计划任务创建说明

    万次阅读 2015-12-28 21:09:21
    什么是计划任务?  在日常的工作中,我们都有一些固定的或临时性的工作,而每次在爱机前一坐,就不愿再起身,你是否也曾因沉迷于游戏而忘了事先的计划或约会呢?在Windows中,系统有一项重要的“计划...怎么样创建
  • LiteOS学习第四篇——任务创建

    千次阅读 2017-04-19 17:22:42
    1、创建任务首先看一下创建任务需要的数据,结构体如下 typedef struct tagTskInitParam {  TSK_ENTRY_FUNC pfnTaskEntry; /**  UINT16 usTaskPrio; /**  UINT32

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,426,147
精华内容 570,458
关键字:

任务创建