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 強制觸發一次任務切換,讓最高優先級的任務得到調度;

總體來說,創建一個任務的流程如下:

 

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