【freertos】009-任務控制

李柱明發表於2022-05-29


前言

本節描述任務相關的控制。

主要講解使用,原始碼分析後面對應章節會有。

學習本節前,建議同學們往前回憶下任務控制塊的內容。

參考:

任務控制主要是對任務控制塊的處理。

比如任務延時、重置任務優先順序、任務掛起與恢復。

對於延時相關的程式碼細節,可以參考前面的【freertos】007-系統節拍和系統延時管理實現細節章節詳細分析。

9.1 相對延時

9.1.1 函式原型

void vTaskDelay( portTickTypexTicksToDelay );

9.1.2 函式說明

  • vTaskDelay()用於相對延時,是指每次延時都是從任務執行函式vTaskDelay()開始,延時指定的時間結束。
  • xTicksToDelay引數用於設定延遲的時鐘節拍個數。
  • 延時的最大值巨集在portmacro.h中有定義:#define portMAX_DELAY (TickType_t )0xffffffffUL

9.1.3 參考例子

static void lzmTestTask(void* parameter)
{
    /* task init */
    printf("start lzmTestTask\r\n");

    for(;;)
    {
        /* 任務主體 */
  
        /* 延時1000個tick再跑 */
        vTaskDelay(1000);
    }
}

9.2 絕對延時

該功能可用於週期性任務,保證執行頻率不變。

9.2.1 函式原型

BaseType_t vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement ); 

9.2.2 函式說明

  • vTaskDelayUntil()用於絕對延時,也叫週期性延時。想象下精度不高的定時器。
  • pxPreviousWakeTime引數是儲存任務上次處於非阻塞狀態時刻的變數地址。
  • xTimeIncrement引數用於設定週期性延時的時鐘節拍個數。
  • 返回:pdFALSE 說明延時失敗。
  • 使用此函式需要在FreeRTOSConfig.h配置檔案中開啟:#defineINCLUDE_vTaskDelayUntil 1
  • 需要保證週期性延時比任務主體執行時間長。
  • 相對延時的意思是延時配置的N個節拍後恢復當前任務為就緒態。
  • 絕對延時的意思是延時配置的N個節拍後該任務跑回到當前絕對延時函式。

9.2.3 參考例子

static void lzmTestTask(void* parameter)
{
    portTickType last_wake_time = 0;

    /* task init */
    printf("start lzmTestTask\r\n");
    /* 重置下該變數 */
    last_wake_time = xTaskGetTickCount();
    for(;;)
    {
        /* 再確保任務主體佔用CPU時長不會超過週期值(1000tick)的情況下,
            不管任務主體跑多長時間,1000tick後依然內跑回這裡。 */
        vTaskDelayUntil(&last_wake_time, 1000);

        /* 任務主體 */
    }
}

9.3 獲取任務優先順序

9.3.1 函式原型

UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask );

9.3.2 函式說明

  • 用於獲取任務的優先順序。
  • xTask引數為任務控制程式碼。傳入NULL,表示獲取當前呼叫該API的任務的優先順序。
  • 使能方法:在FreeRTOSConfig.h中配置INCLUDE_vTaskPriorityGet為1。
  • 返回:任務優先順序。

9.3.3 uxTaskPriorityGet()原始碼分析

  • 通過控制程式碼獲取任務控制塊。
  • 通過任務控制塊獲取任務優先順序並返回。
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )
{
    TCB_t const * pxTCB;
    UBaseType_t uxReturn;

    taskENTER_CRITICAL(); /* 加入臨界 */
    {
        /* 獲取任務控制塊 */
        pxTCB = prvGetTCBFromHandle( xTask );
        /* 通過任務控制塊獲取任務優先順序 */
        uxReturn = pxTCB->uxPriority;
    }
    taskEXIT_CRITICAL(); /* 退出臨界 */
    return uxReturn;
}

9.3.4 例子參考程式碼

 void vAFunction( void )
 {
     TaskHandle_t xHandle;
     /* 建立一個任務,儲存該控制程式碼 */
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
     // ...
     /* 使用控制程式碼獲取建立的任務的優先順序 */
     if( uxTaskPriorityGet( xHandle ) != tskIDLE_PRIORITY )
     {
         /* 任務可以改變自己的優先順序 */
     }
     // ...
     /* 當前任務優先順序比建立的任務優先順序高? */
     if( uxTaskPriorityGet( xHandle ) < uxTaskPriorityGet( NULL ) )
     {
         /* 當前優先順序較高 */
     }
 }

9.4 設定任務優先順序

任務優先順序除了在建立時設定外,也可以在系統啟動後重置,畢竟任務優先順序的本質也只是任務控制塊裡面的一直成員值。

但是修改優先順序時需要維護優先順序繼承機制。

9.4.1 函式原型

void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );

9.4.2 函式說明

作用:

  • 該函式用於重置任務優先順序。
  • 如果設定的優先順序高於當前正在執行的任務,則會在函式返回之前進行上下文切換。

引數:

  • xTask:需要修改任務優先順序的任務控制程式碼。NULL時,表示修改當前任務的任務優先順序。
  • uxNewPriority:新的任務優先順序。在[0,configMAX_PRIORITIES - 1]範圍內,否則會引起斷言。

使能方法:使用該功能需要在FreeRTOSConfig.h中配置INCLUDE_vTaskPrioritySet為1。

9.4.3 vTaskPrioritySet()原始碼分析

更改任務優先順序的實現,是更改任務控制塊裡面記錄的任務優先順序值,但是需要維護好優先順序繼承機制。

看到了原始碼,產生兩個疑問:

  • 如果重置的優先順序比優先順序繼承後的優先順序還高,這種情況下為什麼不更新該任務在用優先順序?
  • 重置優先順序後,好像沒有重置該任務在事件連結串列中的順序。所以重置任務優先順序不會更改任務在現有事件阻塞連結串列的順序。

重置優先順序簡要步驟:

  • 引數校驗&引數糾正。
  • 獲取任務優先順序。任務優先順序包括基優先順序和在用優先順序。
  • 任務排程需求檢測。
  • 遷移就緒連結串列。
  • 觸發任務排程。

9.4.3.1 引數校驗

傳入的優先順序必須小於限制值,否則會觸發斷言。

/* 斷言式引數校驗 */
configASSERT( uxNewPriority < configMAX_PRIORITIES );
/* 引數糾正 */
if( uxNewPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
    uxNewPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}

9.4.3.2 臨界處理

重置任務優先順序,涉及到就緒連結串列、事件連結串列的操作,而系統時鐘節拍這些中斷會設計到操作這些連結串列。

9.4.3.3 獲取任務優先順序

通過任務控制程式碼獲取任務控制塊,通過任務控制塊獲取任務優先順序。

如果使能了互斥量,及系統支援優先順序繼承機制時,需要區分基優先順序uxBasePriority和在用優先順序uxPriority

/* 獲取任務控制塊 */
pxTCB = prvGetTCBFromHandle( xTask );
#if ( configUSE_MUTEXES == 1 )
{
    /* 開啟了互斥量就獲取基優先順序,處理優先順序繼承使用 */
    uxCurrentBasePriority = pxTCB->uxBasePriority;
}
#else
{
    /* 沒有開啟互斥量功能就直接獲取優先順序 */
    uxCurrentBasePriority = pxTCB->uxPriority;
}
#endif

9.4.3.4 任務排程需求檢查

在修改任務優先順序前,先檢查修改後是否需要進行任務排程,以下情況都需要任務排程:

  1. 新的任務優先順序比當前在跑任務優先順序高,需要標記觸發任務排程。
  2. 把當前在跑任務優先順序調低,需要標記觸發任務排程。

實現程式碼如下:

/* 檢查是否需要標記任務排程 */
if( uxNewPriority > uxCurrentBasePriority ) /* 新的優先順序比基優先順序更高了 */
{
    if( pxTCB != pxCurrentTCB )
    {
        /* 如果需要修改的任務不是當前在跑任務,且新配置的優先順序大於當前在跑的任務優先順序,需要標記任務排程 */
        if( uxNewPriority >= pxCurrentTCB->uxPriority )
        {
            /* 標記任務排程 */
            xYieldRequired = pdTRUE;
        }
    }
    else
    {
        /* 如果被提高優先順序的任務已經在跑了,就不需要任務切換 */
    }
}
else if( pxTCB == pxCurrentTCB ) /* 把當前任務優先順序下調,也需要觸發任務排程 */
{
    /* 標記任務排程 */
    xYieldRequired = pdTRUE;
}
else
{
    /* 下調其它任務優先順序,不需要排程 */
}

9.4.3.5 更新任務優先順序

在更新任務優先順序前,需要儲存該任務在用優先順序,等等用於遷移就緒連結串列。

/* 獲取該任務當前使用的優先順序 */
uxPriorityUsedOnEntry = pxTCB->uxPriority;

如果開啟了互斥量功能,檢查該任務是否處於優先順序繼承狀態:

  • 如果是,則不更新該任務在用優先順序值。

    • 原始碼是這樣一個邏輯,但是本作者在這裡保留個疑問:如果重置的優先順序比優先順序繼承後的優先順序還高,這種情況下為什麼不更新該任務在用優先順序?
  • 如果不是,則需要更新該任務在用優先順序值。

#if ( configUSE_MUTEXES == 1 )
{
    /* 開啟了互斥量功能,但是當前沒有在優先順序繼承狀態,可以更新當前任務在用優先順序 */
    if( pxTCB->uxBasePriority == pxTCB->uxPriority )
    {
        pxTCB->uxPriority = uxNewPriority;
    }
    /* 更新基優先順序 */
    pxTCB->uxBasePriority = uxNewPriority;
}
#else /* if ( configUSE_MUTEXES == 1 ) */
{
    /* 沒有開啟互斥量功能就直接更新當前在用優先順序 */
    pxTCB->uxPriority = uxNewPriority;
}
#endif /* if ( configUSE_MUTEXES == 1 ) */

9.4.3.6 更新事件連結串列

按照作者的想法,任務優先順序會影響到該任務在事件連結串列中的位置,所以也需要對事件連結串列處理。

由於事件連結串列節點值按功能裝載不同的值:

  • 一般情況下裝載任務優先順序,用於在事件連結串列中排序,如訊息佇列阻塞。
  • 如果事件節點掛入了事件組,則裝載的是事件組資料。

所以修改該值前先判斷當前是否裝載任務優先順序。

/* 當前事件連結串列節點值是否被鎖定。參考freertos事件組元件 */
if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
    /* 時間連結串列節點值沒有被鎖定,則預設用於儲存任務優先順序,用於事件連結串列排序。可更新事件連結串列節點值。 */
    listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxNewPriority ) );
}

當前freertos官方提供的修改任務優先順序API內事件連結串列處理程式碼就這。

按照作者的想法,如果更新了任務優先順序到事件節點值。

也應該檢查下當前任務是否阻塞在有序事件連結串列中,如訊息佇列,這些都是按照優先順序插入事件連結串列的,解除阻塞是取應該排序在前的任務的。

9.4.3.7 遷移就緒連結串列

如果被修改任務優先順序的任務在就緒連結串列,需要遷移到新的優先順序就緒連結串列中。

該任務如果處於就緒態,會存在在用優先順序的就緒連結串列中,而不是基優先順序的就緒連結串列。

遷移就緒連結串列時需要注意,如果遷出就緒連結串列後,該連結串列沒有就緒任務了,需要對系統任務優先順序點陣圖值uxTopReadyPriority進行更新處理。

  • 開啟優先順序檢索優化功能後,uxTopReadyPriority該值是一個點陣圖值。
  • 關閉優先順序檢索優化功能後,uxTopReadyPriority該值就是系統就緒任務中最高優先順序的值。

所以實現程式碼如下:

/* 判斷被調節優先順序的任務是否處於就緒態,如果是,需要遷移到新的優先順序的就緒連結串列。 */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
    /* 解除任務所有狀態,即是遷出當前就緒連結串列。 */
    if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
    {
        /* 如果當前就緒連結串列沒有其它任務了,遷出就緒任務優先順序點陣圖值對應位。 */
        portRESET_READY_PRIORITY( uxPriorityUsedOnEntry, uxTopReadyPriority );
    }
    /* 根據新的優先順序重新插入就緒連結串列 */
    prvAddTaskToReadyList( pxTCB );
}

所有功能都實現後,觸發任務排程,退出臨界後,便可進入排程異常的回撥進行任務排程。

9.4.4 參考例子

void vAFunction( void )
{
    TaskHandle_t xHandle;
    /* 建立一個任務,儲存該控制程式碼 */
    xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
    // ...
    /* 使用控制程式碼重置建立任務的優先順序 */
    vTaskPrioritySet( xHandle, tskIDLE_PRIORITY + 1 );
    // ...
    /* 傳入null,重置當前任務優先順序 */
    vTaskPrioritySet( NULL, tskIDLE_PRIORITY + 1 );
}

9.5 掛起任務

9.5.1 函式原型

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

9.5.2 函式說明

引數:xTaskToSuspend:需要掛起的任務的任務控制程式碼。為NULL時,掛起當前任務。

使能方法:在FreeRTOSConfig.h中配置INCLUDE_vTaskSuspend為1。

作用:掛起一個任務。任務掛起後,插入到就掛起連結串列中,該任務不會被排程,也無權佔用CPU。

配對使用API:呼叫vTaskResume()恢復被掛起的任務到就緒連結串列。

9.5.3 vTaskSuspend()原始碼分析

9.5.3.1 進出臨界

掛起任務的處理設計到任務狀態連結串列和任務解除阻塞時間這些全域性資料,而這些資料在滴答時鐘或者其它中斷回撥中使用的字尾FromISR API中也可能用到。

所以為了維護這些資料的原子性,需要使用臨界級別來實現。

進出臨界使用的函式:

/* 進入臨界 */
taskENTER_CRITICAL()

/* 退出臨界 */
taskEXIT_CRITICAL()

9.5.3.2 獲取任務控制塊

/* 獲取需要掛起的任務控制程式碼。傳入NULL,即獲取當前任務的控制程式碼。 */
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );

9.5.3.3 任務轉為掛起態

切換任務狀態不是設定某個任務狀態值,而是把任務按規則放到各種狀態連結串列。

  1. 先解除任務所有狀態,即是把任務從對應狀態連結串列中遷出:
  • 移出後,如果返回0,說明當前連結串列沒有掛載其它任務了,需要重新更新下系統就緒任務點陣圖表。當然,雖然不知道該任務是不是掛起前是不是在就緒態,多做這步是沒錯的。另外,點陣圖表需要開啟優先順序優化才生效。
/* 解除任務所有狀態。即是把任務從狀態連結串列中遷出。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
    /* 移出後如果當前優先順序的就緒連結串列沒有其它任務了,就需要重置下點陣圖標。(開啟了優先順序優化功能才會生效) */
    taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
  1. 解除任務狀態後,也需要解除任務事件,從事件連結串列中移除當前任務:
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
    /* 如果存在事件,需要從事件中移除。 */
    ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
  1. 然後就可以把當前任務掛載到掛起任務連結串列:
/* 把任務插入到掛起連結串列 */
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
  1. 還有任務通知需要處理。如果任務處於等待任務通知狀態,如果收到任務通知,也可能從掛起連結串列中解除阻塞,所以必須解除任務通知狀態到沒有等待通知狀態:
#if ( configUSE_TASK_NOTIFICATIONS == 1 ) /* 任務通知功能 */
{
    BaseType_t x;

    for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
    {
        if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
        {
            /* 如果任務正在等待任務通知,則當任務被掛起時,需要清除這些任務通知。 */
            pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
        }
    }
}
#endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */

完成以上四小步才算是把任務從其它狀態切入到掛起態(是掛起任務的掛起態)。

9.5.3.4 重新整理系統解除阻塞任務時間

為了防止掛起的任務是下一個需要解除阻塞的任務而導致系統提前進入檢索解除阻塞任務的多餘操作,這裡可以重新整理下解除阻塞任務的時間。

  • 排程器啟動了才會任務去跑,才會有任務進入限時阻塞。
  • 維護系統解除阻塞任務時間的值需要在臨界區內。
if( xSchedulerRunning != pdFALSE ) /* 排程器已經啟動了 */
{
    taskENTER_CRITICAL();
    {
        /* 如果排程器已經開啟了,需要更新下一個需要解除任務阻塞的時間 */
        prvResetNextTaskUnblockTime();
    }
    taskEXIT_CRITICAL();
}

9.5.3.5 任務排程器處理

如果掛起的任務是當前任務,那需要更新下pxCurrentTCB值。

  1. 如果排程器已經啟動了,掛起當前任務後,需要強制觸發任務排程。

  2. 如果排程器還沒有啟動,掛起了當前任務,就需要更新pxCurrentTCB值即可。等待排程器啟動後先跑pxCurrentTCB

  • 如果全部任務都被掛起了,就設定pxCurrentTCB為空即可。下次建立任務或者恢復任務時會重置pxCurrentTCB。至少會在啟動排程器時會建立空閒任務,所以在啟動排程器前不必在乎pxCurrentTCB值是否為空。

  • 如果不是全部任務都被掛起,那就從就緒表中選出最合適的任務到pxCurrentTCB

    • 呼叫vTaskSwitchContext(),該任務的分析可以往前面的任務切換章節翻。
if( pxTCB == pxCurrentTCB ) /* 掛起當前任務 */
{
    if( xSchedulerRunning != pdFALSE )
    {
        /* 排程器正常執行,需要強制觸發任務排程,把任務切走 */
        configASSERT( uxSchedulerSuspended == 0 );
        portYIELD_WITHIN_API();
    }
    else
    {
        if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
        {
            /* 如果所有任務都被掛起了,就把pxCurrentTCB值標記為空 */
            pxCurrentTCB = NULL;
        }
        else
        {
            /* 找出新的pxCurrentTCB值 */
            vTaskSwitchContext();
        }
    }
}

9.6 恢復任務

9.6.1 函式原型

void vTaskResume( TaskHandle_t xTaskToResume );

9.6.2 函式說明

xTaskToResume:需要解除掛起的任務控制程式碼。

INCLUDE_vTaskSuspend必須定義為vTaskSuspend() 1,這個函式才生效。

該函式用於解除掛起的任務。

被一個或多個vTaskSuspend()呼叫掛起的任務將通過對vTaskResume()的單個呼叫重新可用。

9.6.3 實現分析

解除任務的掛起態的實現比較簡單,主要思路:

  • 通過任務控制程式碼找到任務控制塊。
  • 判斷該任務是否處於掛起態,就是判斷當前任務的狀態節點是否掛載在掛起連結串列。
  • 把當前任務從掛起連結串列遷到就緒連結串列。
  • 如果解除掛起態的任務優先順序更高或相等,就觸發一次任務排程。

9.6.4 完整程式碼實現

vTaskResume()

#if ( INCLUDE_vTaskSuspend == 1 ) /* 使能 */
void vTaskResume( TaskHandle_t xTaskToResume )
{
    TCB_t * const pxTCB = xTaskToResume;

    /* 任務控制程式碼不能為NULL */
    configASSERT( xTaskToResume );

    /* 正在跑的任務在執行態,不用處理。 */
    if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
    {
        taskENTER_CRITICAL(); /* 進入臨界。因為下面操作涉及任務狀態表等系統相關的全域性變數。 */
        {
            if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) /* 如果該任務處於掛起態 */
            {
                /* 從掛起連結串列遷出 */
                ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
                /* 重新插入到就緒連結串列 */
                prvAddTaskToReadyList( pxTCB );

                /* 如果恢復的任務的優先順序更高,就觸發任務排程。 */
                if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                {
                    /* 觸發任務排程 */                        taskYIELD_IF_USING_PREEMPTION();
                }
            }
        }
        taskEXIT_CRITICAL(); /* 退出臨界 */
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
#endif /* INCLUDE_vTaskSuspend */

prvTaskIsTaskSuspended()

#if ( INCLUDE_vTaskSuspend == 1 ) /* 使能 */
static BaseType_t prvTaskIsTaskSuspended( const TaskHandle_t xTask )
{
    BaseType_t xReturn = pdFALSE;
    const TCB_t * const pxTCB = xTask;

    /* 訪問xPendingReadyList,因此必須從臨界區呼叫。所以需要在呼叫本函式前進入。 */

    /* 檢查在跑任務是否掛起是沒有意義的 */
    configASSERT( xTask );

    /* 檢查該任務的狀態 */
    if( listIS_CONTAINED_WITHIN( &xSuspendedTaskList, &( pxTCB->xStateListItem ) ) != pdFALSE ) /* 該任務掛載在掛起連結串列 */
    {
        if( listIS_CONTAINED_WITHIN( &xPendingReadyList, &( pxTCB->xEventListItem ) ) == pdFALSE )/* 該任務不是因為排程器掛起而暫時放到掛起連結串列的 */

        {
            /* 再判斷該任務是否是因為等待事件而永久阻塞的,如果是,也不屬於掛起態。 */
            if( listIS_CONTAINED_WITHIN( NULL, &( pxTCB->xEventListItem ) ) != pdFALSE )
            {
                xReturn = pdTRUE;
            }
        }
    }

    return xReturn;
}
#endif /* INCLUDE_vTaskSuspend */

9.6.5 參考例子

void vAFunction( void )
{
    TaskHandle_t xHandle;
    /* 建立一個任務,儲存該控制程式碼 */
    xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
    // ...
    /* 掛起這個剛建立的任務 */
    vTaskSuspend( xHandle );
    // ...
    /* 掛起當前在跑任務 */
    vTaskSuspend( NULL );
  
    /* 在被其它任務恢復當前任務前,是不會跑到這裡的 */
}

附件

重置任務優先順序:vTaskPrioritySet()

void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
{
    TCB_t * pxTCB;
    UBaseType_t uxCurrentBasePriority, uxPriorityUsedOnEntry;
    BaseType_t xYieldRequired = pdFALSE;

    /* 斷言式引數校驗 */
    configASSERT( uxNewPriority < configMAX_PRIORITIES );

    /* 引數糾正 */
    if( uxNewPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxNewPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    /* 進入臨界處理 */
    taskENTER_CRITICAL();
    {
        /* 獲取任務控制塊 */
        pxTCB = prvGetTCBFromHandle( xTask );

        traceTASK_PRIORITY_SET( pxTCB, uxNewPriority );

        #if ( configUSE_MUTEXES == 1 )
            {
                /* 開啟了互斥量就獲取基優先順序,處理優先順序繼承使用 */
                uxCurrentBasePriority = pxTCB->uxBasePriority;
            }
        #else
            {
                /* 沒有開啟互斥量功能就直接獲取優先順序 */
                uxCurrentBasePriority = pxTCB->uxPriority;
            }
        #endif
    
        /* 新配置的優先順序和原有的優先順序不一樣才會處理 */
        if( uxCurrentBasePriority != uxNewPriority ) 
        {
            /* 檢查是否需要標記任務排程 */
            if( uxNewPriority > uxCurrentBasePriority ) /* 新的優先順序比基優先順序更高了 */
            {
                if( pxTCB != pxCurrentTCB )
                {
                    /* 如果需要修改的任務不是當前在跑任務,且新配置的優先順序大於當前在跑的任務優先順序,需要標記任務排程 */
                    if( uxNewPriority >= pxCurrentTCB->uxPriority )
                    {
                        /* 標記任務排程 */
                        xYieldRequired = pdTRUE;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    /* 如果被提高優先順序的任務已經在跑了,就不需要任務切換 */
                }
            }
            else if( pxTCB == pxCurrentTCB ) /* 把當前任務優先順序下調,也需要觸發任務排程 */
            {
                /* 標記任務排程 */
                xYieldRequired = pdTRUE;
            }
            else
            {
                /* 下調其它任務優先順序,不需要排程 */
            }

            /* 記錄該任務當前使用的優先順序 */
            uxPriorityUsedOnEntry = pxTCB->uxPriority;

            #if ( configUSE_MUTEXES == 1 )
                {
                    /* 開啟了互斥量功能,但是當前沒有在優先順序繼承狀態,可以更新當前任務在用優先順序 */
                    if( pxTCB->uxBasePriority == pxTCB->uxPriority )
                    {
                        pxTCB->uxPriority = uxNewPriority;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* 更新基優先順序 */
                    pxTCB->uxBasePriority = uxNewPriority;
                }
            #else /* if ( configUSE_MUTEXES == 1 ) */
                {
                    /* 沒有開啟互斥量功能就直接更新當前在用優先順序 */
                    pxTCB->uxPriority = uxNewPriority;
                }
            #endif /* if ( configUSE_MUTEXES == 1 ) */

            /* 當前事件連結串列節點值是否被鎖定。參考freertos事件組元件 */
            if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
            {
                /* 時間連結串列節點值沒有被鎖定,則預設用於儲存任務優先順序,用於事件連結串列排序。可更新事件連結串列節點值。 */
                listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxNewPriority ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            /* 判斷被調節優先順序的任務是否處於就緒態,如果是,需要遷移到新的優先順序的就緒連結串列。 */
            if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
            {
                /* 解除任務所有狀態,即是遷出當前就緒連結串列。 */
                if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
                {
                    /* 如果當前就緒連結串列沒有其它任務了,遷出就緒任務優先順序點陣圖值對應位。 */
                    portRESET_READY_PRIORITY( uxPriorityUsedOnEntry, uxTopReadyPriority );
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                /* 根據新的優先順序重新插入就緒連結串列 */
                prvAddTaskToReadyList( pxTCB );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            if( xYieldRequired != pdFALSE )
            {
                /* 觸發任務排程 */
                taskYIELD_IF_USING_PREEMPTION();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            /* 編譯警告處理 */
            ( void ) uxPriorityUsedOnEntry;
        }
    }
    /* 退出臨界 */
    taskEXIT_CRITICAL();
}

掛起任務:vTaskSuspend()

#if ( INCLUDE_vTaskSuspend == 1 )
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
    TCB_t * pxTCB;

    taskENTER_CRITICAL();
    {
        /* 獲取需要掛起的任務控制程式碼。傳入NULL,即獲取當前任務的控制程式碼。 */
        pxTCB = prvGetTCBFromHandle( xTaskToSuspend );

        traceTASK_SUSPEND( pxTCB );

        /* 解除任務所有狀態。即是把任務從狀態連結串列中遷出。 */
        if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
        {
            /* 移出後如果當前優先順序的就緒連結串列沒有其它任務了,就需要重置下點陣圖標。(開啟了優先順序優化功能才會生效) */
            taskRESET_READY_PRIORITY( pxTCB->uxPriority );
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
        {
            /* 如果存在事件,需要從事件中移除。 */
            ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* 把任務插入到掛起連結串列 */
        vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );

        #if ( configUSE_TASK_NOTIFICATIONS == 1 ) /* 任務通知功能 */
            {
                BaseType_t x;

                for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
                {
                    if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
                    {
                        /* 如果任務正在等待任務通知,則當任務被掛起時,需要清除這些任務通知。 */
                        pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
                    }
                }
            }
        #endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
    }
    taskEXIT_CRITICAL();

    if( xSchedulerRunning != pdFALSE )
    {
        taskENTER_CRITICAL();
        {
            /* 如果排程器已經開啟了,需要更新下一個需要解除任務阻塞的時間 */
            prvResetNextTaskUnblockTime();
        }
        taskEXIT_CRITICAL();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    if( pxTCB == pxCurrentTCB ) /* 掛起當前任務 */
    {
        if( xSchedulerRunning != pdFALSE )
        {
            /* 排程器正常執行,需要強制觸發任務排程,把任務切走 */
            configASSERT( uxSchedulerSuspended == 0 );
            portYIELD_WITHIN_API();
        }
        else
        {
            if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
            {
                /* 如果所有任務都被掛起了,就把pxCurrentTCB值標記為空 */
                pxCurrentTCB = NULL;
            }
            else
            {
                /* 找出新的pxCurrentTCB值 */
                vTaskSwitchContext();
            }
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
#endif /* INCLUDE_vTaskSuspend */

相關文章