1. 程式人生 > 實用技巧 >FreeRTOS --(8)任務管理之建立任務

FreeRTOS --(8)任務管理之建立任務

轉載自https://blog.csdn.net/zhoutaopower/article/details/107034995

在《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, R5和R4;

此刻的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強制觸發一次任務切換,讓最高優先順序的任務得到排程;

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