FreeRTOS --(8)任务管理之创建任务

目录

1、描述任务的结构

2、任务创建

2.1、xTaskCreate

2.2、prvInitialiseNewTask

2.3、pxPortInitialiseStack

2.4、prvAddNewTaskToReadyList


在《FreeRTOS --(7)任务管理之入门篇》文章基本分析了任务相关的轮廓后,我们知道使用什么接口来创建一个任务、怎么去开启调度器、以及根据宏配置,选择调度器的行为;接下来我们深入到 FreeRTOS 任务创建的源码来看看一个任务是怎么被创建的(某大神说过,Read The F**king Source Code ,能用代码解决的,尽量不 BB);

 

1、描述任务的结构

在 FreeRTOS 中,使用 TCB_t 来描述一个任务:

/*
 * Task control block.  A task control block (TCB) is allocated for each task,
 * and stores task state information, including a pointer to the task's context
 * (the task's run time environment, including register values)
 */
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;

1、pxTopOfStack 必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack 总是指向最后一个入栈的内容。此指针会移动

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

3、状态链表 xStateListItem ,用于将任务挂接到不同的状态链表中,比如任务处于 Ready 状态,那么就要将其挂到 Ready 链表;

4、事件链表 xEventListItem,类似;

5、uxPriority 用于保存任务的优先级,0 为最低优先级;

6、pxStack 指向堆栈的起始位置,申请堆栈内存函数返回的指针就被赋给该变量。pxTopOfStack 指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack 指向的位置是会变化的;pxStack 指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要 pxStack 变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量 pxEndOfStack 来辅助诊断是否堆栈溢出;

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

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

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

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

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

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

 

2、任务创建

2.1、xTaskCreate

创建一个任务,使用 xTaskCreate 接口,传入的参数在《FreeRTOS --(7)任务管理之入门篇》中有描述,这里不在多说,我们直接看看他的实现,在 task.c 中:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                        const char * const pcName,  
                        const configSTACK_DEPTH_TYPE usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t * const pxCreatedTask )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;

    /* If the stack grows down then allocate the stack then the TCB so the stack
    does not grow into the TCB.  Likewise if the stack grows up then allocate
    the TCB then the stack. */
    #if( portSTACK_GROWTH > 0 )
    {
        /* Allocate space for the TCB.  Where the memory comes from depends on
        the implementation of the port malloc function and whether or not static
        allocation is being used. */
        pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

        if( pxNewTCB != NULL )
        {
            /* Allocate space for the stack used by the task being created.
            The base of the stack memory stored in the TCB so the task can
            be deleted later if required. */
            pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

            if( pxNewTCB->pxStack == NULL )
            {
                /* Could not allocate the stack.  Delete the allocated TCB. */
                vPortFree( pxNewTCB );
                pxNewTCB = NULL;
            }
        }
    }
    #else /* portSTACK_GROWTH */
    {
    StackType_t *pxStack;

        /* Allocate space for the stack used by the task being created. */
        pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */

        if( pxStack != NULL )
        {
            /* Allocate space for the TCB. */
            pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */

            if( pxNewTCB != NULL )
            {
                /* Store the stack location in the TCB. */
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                /* The stack cannot be used as the TCB was not created.  Free
                it again. */
                vPortFree( pxStack );
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
    }
    #endif /* portSTACK_GROWTH */

    if( pxNewTCB != NULL )
    {
        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
        {
            /* Tasks can be created statically or dynamically, so note this
            task was created dynamically in case it is later deleted. */
            pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */

        prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
        prvAddNewTaskToReadyList( pxNewTCB );
        xReturn = pdPASS;
    }
    else
    {
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    }

    return xReturn;
}

首先是根据 portSTACK_GROWTH 宏来判断当前处理器体系结构堆栈的生长方向,portSTACK_GROWTH > 0 代表堆栈向上生长,portSTACK_GROWTH < 0 代表堆栈向下生长;堆栈的生长方向是和处理器的体系结构息息相关,这里拿 Cortex-M 系列的处理器做例子,它的堆栈是向下生长的,所以我们在往 Cortex-M 系列处理器上移植 FreeRTOS 的时候,一定记得这里将 portSTACK_GROWTH 定义得小于 0;

反过来,为啥要在创建任务的时候,判断堆栈的生长方向呢?根本原因是因为,在创建任务的时候,需要为任务的 TCB 和任务的 Stack 分配内存,分配内存需要通过 pvPortMalloc 接口来实现,而 pvPortMalloc 分配内存是从小地址开始分配的,所以:

如果堆栈是向上生长的,先调用 pvPortMalloc 分配任务的 TCB 结构,再去分配任务的 Stack,因为 TCB 大小是固定,但是堆栈要向上生长,这样就避免了堆栈踩到 TCB;

如果堆栈是向下生长的,先调用 pvPortMalloc 分配任务的 Stack,再去分配任务的 TCB 结构,这样 Stack 生长的时候,也可以避免踩到 TCB 结构;

分配 Stack 完成后,将 TCB 的 pxNewTCB->pxStack = pxStack; 此刻 pxStack 便初始化完毕

这里还要唠叨一句,分配任务栈的空间是:

( ( size_t ) usStackDepth ) * sizeof( StackType_t )

这里的 StackType_t 和 CPU 体系架构相关,32 bit 的 CPU 下,StackType_t 被定义为 uint32_t,也就是 4 字节;

如果为任务分配 TCB 结构和任务 Stack 都成功了,那么会调用到:prvInitialiseNewTask 和 prvAddNewTaskToReadyList;

prvInitialiseNewTask 主要是初始化任务的 TCB 相关的内容;

prvAddNewTaskToReadyList 将初始化好的任务添加到 Ready 链表,即允许投入执行;

如果创建任务成功,返回 pdPASS 否则返回 pdFLASE;

 

2.2、prvInitialiseNewTask

下面来看看 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( 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 */

    /* Avoid dependency on memset() if it is not required. */
    #if( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 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 /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */

    /* Calculate the top of stack address.  This depends on whether the stack
    grows from high memory to low (as per the 80x86) or vice versa.
    portSTACK_GROWTH is used to make the result positive or negative as required
    by the port. */
    #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 !e9033 !e9078 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type.  Checked by assert(). */

        /* Check the alignment of the calculated top of stack is correct. */
        configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

        #if( configRECORD_STACK_HIGH_ADDRESS == 1 )
        {
            /* Also record the stack's high address, which may assist
            debugging. */
            pxNewTCB->pxEndOfStack = pxTopOfStack;
        }
        #endif /* configRECORD_STACK_HIGH_ADDRESS */
    }
    #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 */

    /* Store the task name in the TCB. */
    if( pcName != NULL )
    {
        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 ] == ( char ) 0x00 )
            {
                break;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        /* Ensure the name string is terminated in the case that the string length
        was greater or equal to configMAX_TASK_NAME_LEN. */
        pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
    }
    else
    {
        /* The task has not been given a name, so just ensure there is a NULL
        terminator when it is read out. */
        pxNewTCB->pcTaskName[ 0 ] = 0x00;
    }

    /* This is used as an array index so must ensure it's not too large.  First
    remove the privilege bit if one is present. */
    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 ) );

    /* Set the pxNewTCB as a link back from the ListItem_t.  This is so we can get
    back to the containing TCB from a generic item in a list. */
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

    /* Event lists are always in priority order. */
    listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

    #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 )
    {
        vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
    }
    #else
    {
        /* Avoid compiler warning about unreferenced parameter. */
        ( void ) xRegions;
    }
    #endif

    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
    {
        memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
    }
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
    {
        memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
        memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
    }
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
    {
        /* Initialise this task's Newlib reent structure.
        See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
        for additional information. */
        _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
    }
    #endif

    #if( INCLUDE_xTaskAbortDelay == 1 )
    {
        pxNewTCB->ucDelayAborted = pdFALSE;
    }
    #endif

    /* Initialize the TCB stack to look as if the task was already running,
    but had been interrupted by the scheduler.  The return address is set
    to the start of the task function. Once the stack has been initialised
    the top of stack variable is updated. */
    #if( portUSING_MPU_WRAPPERS == 1 )
    {
        /* If the port has capability to detect stack overflow,
        pass the stack end address to the stack initialization
        function as well. */
        #if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
        {
            #if( portSTACK_GROWTH < 0 )
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
            }
            #else /* portSTACK_GROWTH */
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
            }
            #endif /* portSTACK_GROWTH */
        }
        #else /* portHAS_STACK_OVERFLOW_CHECKING */
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
        }
        #endif /* portHAS_STACK_OVERFLOW_CHECKING */
    }
    #else /* portUSING_MPU_WRAPPERS */
    {
        /* If the port has capability to detect stack overflow,
        pass the stack end address to the stack initialization
        function as well. */
        #if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
        {
            #if( portSTACK_GROWTH < 0 )
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
            }
            #else /* portSTACK_GROWTH */
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
            }
            #endif /* portSTACK_GROWTH */
        }
        #else /* portHAS_STACK_OVERFLOW_CHECKING */
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
        }
        #endif /* portHAS_STACK_OVERFLOW_CHECKING */
    }
    #endif /* portUSING_MPU_WRAPPERS */

    if( pxCreatedTask != NULL )
    {
        /* Pass the handle out in an anonymous way.  The handle can be used to
        change the created task's priority, delete the created task, etc.*/
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

内容不少,逐行分析;

配置 MPU 部分暂时不关注;

如果使用了 tskSET_NEW_STACKS_TO_KNOWN_VALUE 宏,代表需要将被初始化后任务的 Stack 使用一个固定的值(0xA5)进行填充;

然后判断堆栈的生长方向,为何这里又在判断堆栈的生长方向呢?之前是对初始化完成后的堆栈头指针 pxNewTCB->pxStack 进行了赋值,但是此刻我们需要对当任务跑起来后,实际上用到的堆栈指针(会移动的那个)进行赋初值;

如果堆栈向下生长的话,我们需要将栈顶指针 pxTopOfStack 放置到我们分配的堆栈的末尾,这样才能向下增长;

如果堆栈向上生长的话,我们直接将 pxNewTCB->pxStack 赋值给 pxTopOfStack 就好:

以 Cortex-M3 为例,堆栈是向下生长的 ,所以会走进 portSTACK_GROWTH < 0 的这个分支,将 pxTopOfStack 指向了被分配到堆栈深度的最后,并进行堆栈的 8 字节对齐操作(处理器架构要求);

注意,此刻的 pxTopOfStack 还只是本地的局部变量,并没有直接赋值给 TCB 的 pxTopOfStack;

接着给 TCB->pcTaskName 字段(任务名字)赋值,此字段是字符串,长度受限于 configMAX_TASK_NAME_LEN;

接着给 TCB->uxPriority 任务优先级字段赋值;

如果使用了 MUTEXES 的话,将 uxPriority 赋值给 BasePriority,并将当前 mutex hold 字段设置为 0;

初始化 TCB 的状态 Item,设置 Item 的 Owner 为当前初始化的 TCB;

初始化 TCB 的 Event Item,设置 Item 的 Owner 为当前初始化的 TCB,配置 Event Item 的值为最大的优先级减去当前优先级,这里的 Item 里面的 Value 不直接保存优先级,而是保存优先级的补数,意味着 xItemValue 的值越大,对应的任务优先级越小

初始化临界区的嵌套次数为 0;

任务通知 Value 初始化为 0;通知状态初始化等;

这里要注意一个地方,portHAS_STACK_OVERFLOW_CHECKING 这个宏在 CM3 中未定义,我想应该是处理器对应堆栈溢出的检测;

调用 pxPortInitialiseStack 传入之前设置的栈顶指针,Task 执行函数,以及要传递给 Task 的参数指针;将结果赋值给了 TCB->pxTopOfStack;

最后一切成功,将 TCB 的地址付给了创建 Task 成功后的句柄;

这里我们要看一下 pxPortInitialiseStack 函数:

 

2.3、pxPortInitialiseStack

带 Port,说明和处理器的体系架构相关,要看懂这个函数在干嘛,需要一点点体系架构方面的知识,这里依然以 Cortex-M3 为例;

在 Cortex-M3 处理器中,当发生异常/中断后,处理器会将关键的寄存器入栈,保护现场,然后跳转到 ISR 中执行,这个是纯硬件行为;当执行完 ISR 后,处理器会顺序出栈;

入栈的时候,再空间上顺序存储内容为:

xPSR、PC、LR、R12、R3、R2、R1、R0;

当之行为 ISR,处理器硬件上,就会到之前存储这些玩意的地方(SP)去弹栈,恢复现场;

而 OS 调度切换上下文就是在 ISR 中做;

所以呢,在初始化的时候(CM3 的线程模式),咱们就手动的模拟处理器入栈的行为,将这些玩意先给他放进去准备好,等 OS 调度的时候,只要告诉它堆栈指针,嘿,处理器就会去弹栈;

更多的关于 CM3 处理器的内容参考《Cortex-M3 处理器窥探

好了,解释完了后,可以看代码了:

/* Constants required to set up the initial stack. */
#define portINITIAL_XPSR                ( 0x01000000 )
/* For strict compliance with the Cortex-M spec the task start address should
have bit-0 clear, as it is loaded into the PC on exit from an ISR. */
#define portSTART_ADDRESS_MASK          ( ( StackType_t ) 0xfffffffeUL )

static void prvTaskExitError( void )
{
	/* A function that implements a task must not exit or attempt to return to
	its caller as there is nothing to return to.  If a task wants to exit it
	should instead call vTaskDelete( NULL ).
	Artificially force an assert() to be triggered if configASSERT() is
	defined, then stop here so application writers can catch the error. */
	configASSERT( uxCriticalNesting == ~0UL );
	portDISABLE_INTERRUPTS();
	for( ;; );
}

/*
 * See header file for description.
 */
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. */
    pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;   /* xPSR */
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;    /* PC */
    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;
}

按照 CM3 压栈方式,这个函数手动做了一次:

xPSR 的位置给了初值为:0x01000000,其中 bit24 被置 1,表示使用 Thumb 指令;

PC 的位置给了这个任务的入口,当 OS 调度选择好 SP 后,退出调度,这个就会被赋值给 CPU 的 PC 指针,也就是,任务函数被调用;

LR 的位置赋值了一个叫做 prvTaskExitError 的函数,因为我们的任务是永不返回的,所以如果任务返回了,说明出错了;

R12,R3,R2,R1 的位置预留;

R0 的位置保存了传递给任务的参数指针 pvParameters,根据汇编的调用规则,R0 是第一个传参;

剩余的位置给了 R11, R10, R9, R8, R7, R6, R5R4

此刻的 pxTopOfStack 便可用直接赋值给 TCB->pxTopOfStack

OK,此刻 TCB 的基本元素已经悉数初始化完毕;

我们在回到 xTaskCreate 调用,看最后,最后还调用了 prvAddNewTaskToReadyList,将当前已经初始化完毕的任务添加到 Ready 链表;

 

2.4、prvAddNewTaskToReadyList

prvAddNewTaskToReadyList,将任务添加到 Ready 链表,直接干代码:

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
    /* Ensure interrupts don't access the task lists while the lists are being
    updated. */
    taskENTER_CRITICAL();
    {
        uxCurrentNumberOfTasks++;
        if( pxCurrentTCB == NULL )
        {
            /* There are no other tasks, or all the other tasks are in
            the suspended state - make this the current task. */
            pxCurrentTCB = pxNewTCB;

            if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
            {
                /* This is the first task to be created so do the preliminary
                initialisation required.  We will not recover if this call
                fails, but we will report the failure. */
                prvInitialiseTaskLists();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            /* If the scheduler is not already running, make this task the
            current task if it is the highest priority task to be created
            so far. */
            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 the created task is of a higher priority than the current task
        then it should run now. */
        if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
        {
            taskYIELD_IF_USING_PREEMPTION();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

Ready 链表在多处访问,ISR 中也有访问,为了保证访问的有效性,这里先调用 taskENTER_CRITICAL() 进入临界区;

uxCurrentNumberOfTasks 记录了当前任务的个人,此刻任务新增了,所以这里自加;

pxCurrentTCB 是一个全局变量,在调度器还未工作的时候,指向的是 Ready 中优先级最高的任务;这里判断是否当前 Add 进来的任务是第一个任务,如果是第一个任务,那么调用 prvInitialiseTaskLists 来初始化任务链表;

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


static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;

    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 pxOverflowDelayedTaskList
    using list2. */
    pxDelayedTaskList = &xDelayedTaskList1;
    pxOverflowDelayedTaskList = &xDelayedTaskList2;
}

Ready 链表是一个链表数组的形式,将不同优先级的分开来放,典型的空间换时间;

这里基本上都是调用 vListInitialise 函数来初始化各个链表结构,具体的不深入聊,参考《FreeRTOS --(1)链表

相关链表已经初始化完毕,如果当前 pxCurrentTCB 不为 NULL,那么一定就不是第一次添加任务,此刻判断调度器是否已经开始工作了(创建任务可以在调度器开始工作之前,也可以在调度器工作之后);

pxCurrentTCB 是一个静态的全局变量,这个变量用来指向当前正在运行的任务 TCB

如果调度器还没有开始工作,则比较当前新增的任务的优先级是否大于上一个任务,如果是,那么更新 pxCurrentTCB 到这个最新的任务;

调用 prvAddTaskToReadyList 将当前的这个 TCB 添加到以这个 TCB 优先级为数组下标的那个 Ready 链表尾部;

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

taskRECORD_READY_PRIORITY() 用来更新静态全局变量 uxTopReadyPriority,它记录处于就绪态的最高任务优先级。这个变量参与了 FreeRTOS 的最核心代码:确保处于优先级最高的就绪任务获得 CPU 运行权。它在这里参与如何最快的找到优先级最高的就绪任务。

调用  taskEXIT_CRITICAL(); 退出临界区;

根据 xSchedulerRunning 标准,判断是否调度器已经跑起来了,如果没有跑起来,什么都不做,等着调度器起来,调度(因为前面已经将最高优先级的任务更新到了 pxCurrentTCB ),如果此刻调度器已经在运转,而新加入的这个任务优先级高于了当前运行任务的优先级,那么调用 taskYIELD_IF_USING_PRREEMPTION(),进而走到 portYIELD 强制触发一次任务切换,让最高优先级的任务得到调度;

总体来说,创建一个任务的流程如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章