大連做網(wǎng)站的公司有哪些網(wǎng)上教育培訓機構(gòu)排名
目錄
- 1.特性
- 1.1 優(yōu)勢以及限制
- 1.2 TCB結(jié)構(gòu)體中的存儲
- 2.函數(shù)
- 2.1 兩類函數(shù)
- 2.2 簡化版
- 2.2.1 Give
- 2.2.2 Take
- 2.3 專業(yè)版
- 2.3.1 Give
- 2.3.2 Take
- 3.實現(xiàn)輕量級信號量
- 4.實現(xiàn)輕量級隊列
- 5.實現(xiàn)輕量級事件組
- 6.源碼分析
- 6.1 等待通知
- 6.2 發(fā)送通知
所謂"任務通知",你可以反過來讀"通知任務"。發(fā)送者和接收者是多對1的關(guān)系
我們使用隊列、信號量、事件組等等方法時,并不知道對方是誰。使用任務通知時,可以明確指定:通知哪個任務。
使用隊列、信號量、事件組時,我們都要事先創(chuàng)建對應的結(jié)構(gòu)體,雙方通過中間的結(jié)構(gòu)體通信:
使用任務通知時,任務結(jié)構(gòu)體TCB中就包含了內(nèi)部對象,可以直接接收別人發(fā)過來的"通知":
1.特性
任務通知(Task Notification)是 FreeRTOS 為任務間通信和同步提供的一種輕量級機制,其主要特點在于直接嵌入在任務的控制塊(TCB)中,不需要額外創(chuàng)建隊列、信號量或事件組結(jié)構(gòu),因此具有高效率和低內(nèi)存占用的優(yōu)勢。
1.1 優(yōu)勢以及限制
優(yōu)勢:
- 任務通知操作直接更新目標任務的TCB中的通知值和狀態(tài),省去了復雜的數(shù)據(jù)拷貝和對象管理過程。
- 相比于隊列、信號量或事件組,任務通知所需的CPU周期更少,適用于需要頻繁發(fā)送通知的場景。
- 任務通知的數(shù)據(jù)(通常為一個32位整數(shù))嵌入在任務的TCB中,不需要額外分配單獨的結(jié)構(gòu)體內(nèi)存。
- 這對于嵌入式系統(tǒng)或資源受限的環(huán)境尤為重要,可以減少系統(tǒng)整體內(nèi)存開銷。
限制:
- 不能發(fā)送數(shù)據(jù)給ISR:
-
- 由于 ISR 沒有自己的 TCB,因此無法直接使用任務通知機制發(fā)送數(shù)據(jù)給 ISR。
- 不過 ISR 可以通過任務通知的 API(例如 xTaskNotifyFromISR())將數(shù)據(jù)發(fā)送給任務,從而實現(xiàn)中斷到任務的通信。
- 數(shù)據(jù)只能給目標任務獨享:
-
- 通知的數(shù)據(jù)存放在目標任務的 TCB 中,只有該任務能讀取和操作。
- 如果需要廣播或共享數(shù)據(jù),使用隊列、信號量或事件組更合適。
- 無法緩沖多個數(shù)據(jù):
-
- 每個任務的通知存儲區(qū)域僅能保存一個32位通知值,相當于深度為1的緩沖區(qū)。
- 如果通知還未被讀取而又發(fā)送了新的通知,之前的通知數(shù)據(jù)會被覆蓋(視具體 API 行為而定)。
- 無法廣播給多個任務:
-
- 任務通知只能針對單個任務進行,不能像事件組那樣同時喚醒多個任務。
- 這意味著當需要將同一通知發(fā)送給多個任務時,任務通知就不適用了。
- 發(fā)送方無法阻塞等待:
-
- 如果目標任務的通知區(qū)域正被使用,發(fā)送任務不能像隊列那樣進入阻塞狀態(tài)等待目標任務清空通知值。
- 發(fā)送操作會立即返回錯誤,發(fā)送方需要自行處理這種情況。
1.2 TCB結(jié)構(gòu)體中的存儲
在TCB結(jié)構(gòu)體中:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{//省略#if ( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];#endif//省略
} tskTCB;
ulNotifiedValue,通知值:
ulNotifiedValue
是一個volatile uint32_t
數(shù)組。每個數(shù)組元素存儲一個任務通知值( 存儲實際的通知數(shù)據(jù),可以表示一個計數(shù)、一個位域(類似于事件組)或任意32位數(shù)值 )。volatile
關(guān)鍵字確保編譯器不會對這些變量進行優(yōu)化,因為它們可能在任務切換或中斷中被修改。- 通知值由發(fā)送任務或 ISR 寫入,接收任務可以讀取這個值以獲取額外信息。
- 數(shù)組大小由宏
configTASK_NOTIFICATION_ARRAY_ENTRIES
決定。默認情況下這個值通常為 1,但也可以配置為大于1,從而讓任務擁有多個通知槽(數(shù)組中的每個槽都可以獨立使用)。
ucNotifyState,通知狀態(tài):
- taskNOT_WAITING_NOTIFICATION (0): 表示任務當前沒有在等待通知。
- taskWAITING_NOTIFICATION (1): 表示任務正在等待通知。
- taskNOTIFICATION_RECEIVED (2): 表示任務已接收到通知(處于 pending 狀態(tài))。
2.函數(shù)
2.1 兩類函數(shù)
任務通知在 FreeRTOS 中提供了一種輕量級的任務間通信與同步機制。為了滿足不同場景的需求,任務通知提供了兩套 API:簡化版和專業(yè)版。
2.2 簡化版
2.2.1 Give
xTaskNotifyGive 與 vTaskNotifyGiveFromISR
這兩個函數(shù)用于向目標任務發(fā)送通知,主要特點是:
- 將目標任務的通知值增加 1(類似于計數(shù)器),
- 更新通知狀態(tài)為 “pending”,表明有通知等待處理。
xTaskNotifyGive:
- 在任務上下文中調(diào)用,用于將通知值加 1,表示向目標任務發(fā)送一個簡單的通知或事件。
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);// xTaskToNotify:目標任務的句柄(通常在創(chuàng)建任務時獲得)。它指明了通知應該發(fā)給哪個任務。
// 返回值:此函數(shù)必定返回 pdPASS,表示通知已成功發(fā)送。
- 當發(fā)送者任務想通知目標任務“有新事件發(fā)生”或“計數(shù)加1”時,調(diào)用該函數(shù)非常高效。
- 不需要傳遞復雜數(shù)據(jù),只是簡單地增加計數(shù)并改變通知狀態(tài)。
vTaskNotifyGiveFromISR:
- 在中斷服務例程(ISR)中調(diào)用,功能與 xTaskNotifyGive 類似,用于在中斷中向任務發(fā)送通知。
void vTaskNotifyGiveFromISR(TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken);/*
xTaskHandle:目標任務的句柄。
pxHigherPriorityTaskWoken:一個指向 BaseType_t 的指針,供 ISR 使用,若通知導致某個等待任務進入就緒態(tài)且其優(yōu)先級高于當前任務,則將該變量設置為 pdTRUE,指示在中斷結(jié)束后需要進行上下文切換。
返回值:此函數(shù)不返回值,主要通過 pxHigherPriorityTaskWoken 參數(shù)反饋信息。
*/
- 當中斷發(fā)生時,需要快速通知任務(例如傳感器數(shù)據(jù)就緒、外設事件等),使用該函數(shù)可以迅速將通知發(fā)送給任務,并確保高優(yōu)先級任務能在中斷退出前被調(diào)度。
2.2.2 Take
該函數(shù)用于在任務中等待并取出通知,是任務獲取通知的主要接口之一,類似于“獲取”操作。
- 如果通知值為 0,則任務會阻塞等待,直到在指定超時時間內(nèi)有通知值增加。
- 當通知值大于0時,任務從阻塞狀態(tài)恢復,并在返回之前,根據(jù)參數(shù)選擇將通知值清零或僅減一。
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
參數(shù):
-
xClearCountOnExit:
-
- pdTRUE:在函數(shù)返回前,將目標任務的通知值清零。
這種方式適用于二進制信號量風格,即接收到通知后立即清除。 - pdFALSE:在返回前,不會完全清零,而是將通知值減 1。
這適用于計數(shù)型信號量的場景,允許連續(xù)接收多個通知。
- pdTRUE:在函數(shù)返回前,將目標任務的通知值清零。
-
xTicksToWait:
-
- 指定任務進入阻塞等待的時間(以 Tick 為單位)。
- 如果設為 0,則函數(shù)立即返回當前通知值,不進行阻塞。
- 如果設為 portMAX_DELAY,則任務將無限等待直到收到通知。
- 其他值則表示等待的 Tick 數(shù),可以使用 pdMS_TO_TICKS() 將毫秒轉(zhuǎn)換為 Tick 數(shù)。
返回值:
-
返回在清零或減一之前的通知值:
-
- 如果在等待期間有通知到來,則返回大于 0 的值(表示接收到的通知數(shù)量);
- 如果一直沒有通知到來而超時,則返回 0。
當一個任務需要等待某個事件或計數(shù)器增加時,調(diào)用 ulTaskNotifyTake() 可使任務阻塞,直到事件發(fā)生。
例如,生產(chǎn)者任務使用 xTaskNotifyGive() 發(fā)出通知,消費者任務則調(diào)用 ulTaskNotifyTake() 來等待通知并“消費”該通知。
2.3 專業(yè)版
2.3.1 Give
任務上下文:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction );
ISR:
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,BaseType_t *pxHigherPriorityTaskWoken );
-
xTaskToNotify / 任務句柄:指定要發(fā)送通知的目標任務。這個句柄在任務創(chuàng)建時獲得。
-
ulValue:32 位數(shù)值,其具體作用由 eAction 決定:
-
- 當使用 eNoAction 時,不會對通知值做任何修改,只是更新通知狀態(tài)為“pending”(即有通知)。
- 當使用 eSetBits 時,通知值將按位“或”(OR)運算,即:
新通知值 = 原通知值 | ulValue
。
這種方式適合將各個事件以位的形式累積,類似輕量級事件組。 - 當使用 eIncrement 時,忽略 ulValue,通知值將遞增 1。
這種方式實現(xiàn)了計數(shù)功能,與 xTaskNotifyGive() 的行為一致。 - 當使用 eSetValueWithoutOverwrite 時,只有當目標任務的通知狀態(tài)不是“pending”(表示沒有未讀通知)時,才將通知值設為 ulValue;如果當前已有未處理通知,則此次調(diào)用不會更新通知值,并返回 pdFAIL。
這種方式適用于需要確保新通知不會覆蓋舊通知的場合。 - 當使用 eSetValueWithOverwrite 時,無論當前通知狀態(tài)如何,通知值都將被設為 ulValue,即直接覆蓋之前的通知值。
這種方式類似于輕量級郵箱,可以確保寫入新值,不受舊通知的影響。
-
eAction (eNotifyAction)
指定如何操作通知值,上述說明中列出的各個取值分別實現(xiàn)不同的通知行為。 -
pxHigherPriorityTaskWoken (僅用于 ISR 版本)
是一個指針,指向一個 BaseType_t 變量。 -
- 當 ISR 發(fā)送通知后,如果被通知的任務正處于阻塞狀態(tài)且其優(yōu)先級高于當前任務,則該變量將被設置為 pdTRUE,提示中斷服務例程在退出時進行上下文切換。
-
返回值:
-
- 對于大多數(shù)調(diào)用,返回值為 pdPASS 表示成功。
- 唯一可能返回 pdFAIL 的情況是在使用 eSetValueWithoutOverwrite 時,如果目標任務的通知狀態(tài)已經(jīng)處于“pending”,說明之前的通知還未被讀取,此時調(diào)用將失敗,不更新通知值。
使用場景:
- 作為事件組:
如果希望將不同事件以位的形式累加到同一通知值中,可以使用 eSetBits。多個通知可以同時累加,不會相互覆蓋。 - 作為計數(shù)型信號量:
使用 eIncrement 使通知值遞增 1,每次通知代表一個事件或資源釋放。 - 作為郵箱或單深度隊列:
使用 eSetValueWithOverwrite 將新的數(shù)據(jù)寫入通知值,無論是否有未讀通知;或使用 eSetValueWithoutOverwrite 防止覆蓋未處理的數(shù)據(jù)(如果需要確保數(shù)據(jù)不被覆蓋)。 - 在 ISR 中發(fā)送通知:
使用 xTaskNotifyFromISR,可以在中斷上下文中快速發(fā)送通知給任務,同時利用 pxHigherPriorityTaskWoken 實現(xiàn)實時響應。
2.3.2 Take
xTaskNotifyWait 用于在任務中等待通知到來,并提供在進入等待和退出等待時清除通知值特定位的功能。這使得任務在等待期間可以“清理”舊的通知數(shù)據(jù),確保只處理新到來的通知。
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );
-
ulBitsToClearOnEntry
-
- 在進入等待前,清除通知值的哪些位(僅在通知狀態(tài)不是“pending”的情況下執(zhí)行)。
- 例如,傳入 0x01 表示在進入等待之前先清除通知值中的 bit0。
- 如果傳入 ULONG_MAX,則表示清除所有位(即將通知值清零)。
-
ulBitsToClearOnExit
-
- 當?shù)却晒?#xff08;非超時返回)時,在函數(shù)退出前清除通知值的哪些位。
- 在清除之前,通知值會先被賦值給 *pulNotificationValue,以便任務獲取通知值。
- 例如,傳入 0x03 表示清除通知值的 bit0 和 bit1;傳入 ULONG_MAX 則表示清除所有位。
-
pulNotificationValue
-
- 用來取出通知值。
- 在函數(shù)退出時,使用ulBitsToClearOnExit清除之前,把通知值賦給"*pulNoti?cationValue"。
- 如果不需要取出通知值,可以設為NULL。
-
xTicksToWait
-
- 指定任務等待通知的最大時間(Tick 數(shù))。
- 0 表示不等待,立即返回當前通知值;
- portMAX_DELAY 表示無限等待,直到通知狀態(tài)變?yōu)椤皃ending”;
- 其他值表示等待指定的 Tick 數(shù)(可以通過 pdMS_TO_TICKS() 轉(zhuǎn)換為 Tick 數(shù))。
返回值:
- 返回 pdPASS 表示任務成功獲得通知,即等待期間通知狀態(tài)變?yōu)榱恕皃ending”,任務解除阻塞。
- 返回 pdFAIL 表示任務在指定時間內(nèi)沒有收到通知而超時返回。
一般工作流程:
-
進入等待之前
-
- 調(diào)用 xTaskNotifyWait 時,首先根據(jù) ulBitsToClearOnEntry 清除通知值中指定的位(如果當前通知狀態(tài)不是 pending),從而丟棄舊通知。
-
阻塞等待
-
- 任務進入阻塞狀態(tài),直到目標任務的通知狀態(tài)變?yōu)椤皃ending”(即有新通知到來),或等待時間超時。
-
退出等待前
-
- 在成功獲得通知后,函數(shù)先將當前通知值保存到 *pulNotificationValue(如果提供),然后根據(jù) ulBitsToClearOnExit 清除通知值中相應的位。
這種機制允許任務在等待通知時同時對通知值進行預處理和后處理,以確保任務接收到的是新鮮的數(shù)據(jù)或正確的事件標志。
3.實現(xiàn)輕量級信號量
創(chuàng)建信號量時,可以指定最大值、初始值;使用任務通知實現(xiàn)輕量級的信號量時呢
- 不能設置最大值
- 初始值為0,不能指定初始值
- 最小值是0,跟信號量是一樣的
static int sum = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;static SemaphoreHandle_t xSemCalc;
static SemaphoreHandle_t xSemUART;static TaskHandle_t xHandleTask2;void Task1Function(void * param)
{volatile int i = 0;while (1){for (i = 0; i < 10000; i++)sum++;//printf("1");for (i = 0; i < 10; i++){// xSemaphoreGive(xSemCalc);xTaskNotifyGive(xHandleTask2);}vTaskDelete(NULL);}
}void Task2Function(void * param)
{int i = 0;int val;while (1){//if (flagCalcEnd)flagCalcEnd = 0;//xSemaphoreTake(xSemCalc, portMAX_DELAY);val = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); //pdFALSE:每取出一次通知值減1flagCalcEnd = 1;printf("sum = %d, NotifyVal = %d, i = %d\r\n", sum, val, i++);}
}/*-----------------------------------------------------------*/int main( void )
{TaskHandle_t xHandleTask1;#ifdef DEBUGdebug();
#endifprvSetupHardware();printf("Hello, world!\r\n");xSemCalc = xSemaphoreCreateCounting(10, 0);xSemUART = xSemaphoreCreateBinary();xSemaphoreGive(xSemUART);xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);xTaskCreate(Task2Function, "Task2", 100, NULL, 1, &xHandleTask2);/* Start the scheduler. */vTaskStartScheduler();/* Will only get here if there was not enough heap space to create theidle task. */return 0;
}
4.實現(xiàn)輕量級隊列
隊列、使用任務通知實現(xiàn)的輕量級隊列,有什么異同?
- 隊列:可以容納多個數(shù)據(jù),數(shù)據(jù)大小可以指定
- 任務通知:只有1個數(shù)據(jù),數(shù)據(jù)時32位的
- 隊列:寫隊列時,可以阻塞
- 任務通知:寫隊列時,不可以阻塞
- 隊列:如果隊列長度是1,可以選擇覆蓋隊列
- 任務通知:可以覆蓋,也可不覆蓋
static int sum = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueCalcHandle;
static QueueHandle_t xQueueUARTcHandle;static TaskHandle_t xHandleTask2;int InitUARTLock(void)
{ int val;xQueueUARTcHandle = xQueueCreate(1, sizeof(int));if (xQueueUARTcHandle == NULL){printf("can not create queue\r\n");return -1;}xQueueSend(xQueueUARTcHandle, &val, portMAX_DELAY);return 0;
}void GetUARTLock(void)
{ int val;xQueueReceive(xQueueUARTcHandle, &val, portMAX_DELAY);
}void PutUARTLock(void)
{ int val;xQueueSend(xQueueUARTcHandle, &val, portMAX_DELAY);
}void Task1Function(void * param)
{volatile int i = 0;while (1){for (i = 0; i < 10000; i++)sum++;//printf("1");//flagCalcEnd = 1;//vTaskDelete(NULL);for (i = 0; i < 10; i++){//xQueueSend(xQueueCalcHandle, &sum, portMAX_DELAY);//xTaskNotify(xHandleTask2, sum, eSetValueWithoutOverwrite); // 不覆蓋xTaskNotify(xHandleTask2, sum, eSetValueWithOverwrite); // 覆蓋valuesum++;}vTaskDelete(NULL);//sum = 1;}
}void Task2Function(void * param)
{int val;int i = 0;while (1){//if (flagCalcEnd)flagCalcEnd = 0;//xQueueReceive(xQueueCalcHandle, &val, portMAX_DELAY);xTaskNotifyWait(0, 0, &val, portMAX_DELAY);flagCalcEnd = 1;printf("sum = %d, i = %d\r\n", val, i++);}
}/*-----------------------------------------------------------*/int main( void )
{TaskHandle_t xHandleTask1;#ifdef DEBUGdebug();
#endifprvSetupHardware();printf("Hello, world!\r\n");xQueueCalcHandle = xQueueCreate(10, sizeof(int));if (xQueueCalcHandle == NULL){printf("can not create queue\r\n");}InitUARTLock();xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);xTaskCreate(Task2Function, "Task2", 100, NULL, 1, &xHandleTask2);/* Start the scheduler. */vTaskStartScheduler();/* Will only get here if there was not enough heap space to create theidle task. */return 0;
}
5.實現(xiàn)輕量級事件組
任務通知并不能實現(xiàn)真正的事件組,為什么?
- 它不能等待指定的事件
- 它不能等待若干個事件中的任意一個
- 一旦有事件,總會喚醒任務
發(fā)送方可以設置事件,但是接收方不能挑選事件,即使不是它關(guān)心的事件,它也會被喚醒
- TCB結(jié)構(gòu)體中的uint_32的通知值,不管修改哪一位表示事件的發(fā)生,正在等待的接收方都會被通知
static int sum = 0;
static int dec = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueCalcHandle;static EventGroupHandle_t xEventGroupCalc;
static TaskHandle_t xHandleTask3;void Task1Function(void * param)
{volatile int i = 0;while (1){for (i = 0; i < 100000; i++)sum++;xQueueSend(xQueueCalcHandle, &sum, 0);/* 設置事件0 *///xEventGroupSetBits(xEventGroupCalc, (1<<0));xTaskNotify(xHandleTask3, (1<<0), eSetBits);printf("Task 1 set bit 0\r\n");vTaskDelete(NULL);}
}void Task2Function(void * param)
{volatile int i = 0;while (1){for (i = 0; i < 1000000; i++)dec--;xQueueSend(xQueueCalcHandle, &dec, 0);/* 設置事件1 *///xEventGroupSetBits(xEventGroupCalc, (1<<1));xTaskNotify(xHandleTask3, (1<<1), eSetBits);printf("Task 2 set bit 1\r\n");vTaskDelete(NULL);}
}void Task3Function(void * param)
{int val1, val2;int bits;while (1){/*等待事件 *///xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);xTaskNotifyWait(0, 0, &bits, portMAX_DELAY);if ((bits & 0x3) == 0x3){vTaskDelay(20);xQueueReceive(xQueueCalcHandle, &val1, 0);xQueueReceive(xQueueCalcHandle, &val2, 0);printf("val1 = %d, val2 = %d\r\n", val1, val2);}else{vTaskDelay(20);printf("have not get all bits, get only 0x%x\r\n", bits);}}
}/*-----------------------------------------------------------*/int main( void )
{TaskHandle_t xHandleTask1;#ifdef DEBUGdebug();
#endifprvSetupHardware();printf("Hello, world!\r\n");/* 創(chuàng)建事件組 */xEventGroupCalc = xEventGroupCreate();xQueueCalcHandle = xQueueCreate(2, sizeof(int));if (xQueueCalcHandle == NULL){printf("can not create queue\r\n");}xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);xTaskCreate(Task3Function, "Task3", 100, NULL, 1, &xHandleTask3);/* Start the scheduler. */vTaskStartScheduler();/* Will only get here if there was not enough heap space to create theidle task. */return 0;
}
6.源碼分析
上面就提到過一個任務的"通知狀態(tài)"有三種:
- taskNOT_WAITING_NOTIFICATION:任務沒有在等待通知
- taskWAITING_NOTIFICATION:任務在等待通知
- taskNOTIFICATION_RECEIVED:任務接收到了通知,也被稱為 pending(有數(shù)據(jù)了,待處理)
一個任務想等待別人發(fā)來通知,可以調(diào)用ulTaskNotifyTake
或xTaskNotifyWait
:
- 可能別人早就發(fā)來通知:"通知狀態(tài)"為taskNOTIFICATION_RECEIVED,那么函數(shù)立刻返回
- 可能別人還沒發(fā)來通知:這些函數(shù)把"通知狀態(tài)"從taskNOT_WAITING_NOTIFICATION改為taskWAITING_NOTIFICATION,然后休眠
別的任務可以使用xTaskNotifyGive
或xTaskNotify
給某個任務發(fā)通知:
- 會馬上喚醒對方
- 無條件喚醒對方,不管對方期待什么數(shù)據(jù)
6.1 等待通知
這里以專業(yè)版的 xTaskGenericNotifyWait
進行訴說:
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWait,uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t * pulNotificationValue,TickType_t xTicksToWait )
{BaseType_t xReturn;/* 確保傳入的索引有效,索引必須小于任務通知數(shù)組的總條目數(shù) */configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES );/* 進入臨界區(qū),確保接下來的操作原子執(zhí)行,不被中斷 */taskENTER_CRITICAL();{/* 如果當前任務的通知狀態(tài)未標記為“已接收通知”,則說明當前沒有通知等待結(jié)果* 只有當通知狀態(tài)不是 taskNOTIFICATION_RECEIVED 時,才允許任務進入等待狀態(tài) */if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED ){/* 在進入等待之前,根據(jù) ulBitsToClearOnEntry 參數(shù)清除任務通知值中的指定位* 這樣做可以在等待開始前清除舊的通知位(例如將通知值清零) */pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnEntry;/* 將任務的通知狀態(tài)標記為“等待通知” */pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;/* 如果指定的等待時間大于0,則任務將進入阻塞狀態(tài)等待通知 */if( xTicksToWait > ( TickType_t ) 0 ){/* 將當前任務添加到延時列表中,等待 xTicksToWait 個時鐘節(jié)拍后超時 */prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );/* 跟蹤記錄任務進入等待通知阻塞狀態(tài) */traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWait );/* 盡管處于臨界區(qū)內(nèi),內(nèi)核各移植層允許在 API 中進行任務切換(yield)* 這可能會導致立即進行上下文切換,但應用代碼一般不會直接調(diào)用此 yield */portYIELD_WITHIN_API();}else{/* 如果 xTicksToWait 為0,則不進入延時列表 */mtCOVERAGE_TEST_MARKER();}}else{/* 如果任務的通知狀態(tài)已經(jīng)是“已接收通知”,則無需改變狀態(tài) */mtCOVERAGE_TEST_MARKER();}}/* 退出第一個臨界區(qū) */taskEXIT_CRITICAL();/* 第二個臨界區(qū)用于處理解除阻塞后的通知值讀取和狀態(tài)恢復 */taskENTER_CRITICAL();{/* 跟蹤記錄任務通知等待完成 */traceTASK_NOTIFY_WAIT( uxIndexToWait );/* 如果調(diào)用者提供了 pulNotificationValue 指針,則將當前任務的通知值寫入該指針中* 這個通知值可能在等待期間被更新 */if( pulNotificationValue != NULL ){*pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];}/* 判斷通知狀態(tài):* 如果通知狀態(tài)不是 taskNOTIFICATION_RECEIVED,則說明任務未因通知解除阻塞,而是因超時解除 */if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED ){/* 沒有收到通知,返回 pdFALSE */xReturn = pdFALSE;}else{/* 如果通知狀態(tài)為 taskNOTIFICATION_RECEIVED,則任務在等待期間收到了通知* 根據(jù) ulBitsToClearOnExit 參數(shù)清除通知值中的相應位 */pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit;/* 設置返回值為 pdTRUE,表示成功接收到通知 */xReturn = pdTRUE;}/* 無論通知是否接收,將任務的通知狀態(tài)重置為“未等待通知” */pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;}/* 退出第二個臨界區(qū) */taskEXIT_CRITICAL();/* 返回任務是否成功接收到通知 */return xReturn;
}
根據(jù)當前任務的TCB結(jié)構(gòu)體pxCurrentTCB
中的狀態(tài)標志來決定需不需要等待
- 如果是
taskNOTIFICATION_RECEIVED
,已經(jīng)收到通知但是被pending了,不需要等待 - 如果是其它狀態(tài),將狀態(tài)設置為
taskWAITING_NOTIFICATION
,等待通知,然后調(diào)用prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
將該任務放入到任務等待鏈表當中,使其等待通知
下半部分則是用于處理解除阻塞后的通知值讀取和狀態(tài)恢復(第二個臨界區(qū)),注釋很清楚了,這里不贅述
6.2 發(fā)送通知
還是以專業(yè)版的xTaskNotify
為例子:
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,UBaseType_t uxIndexToNotify,uint32_t ulValue,eNotifyAction eAction,uint32_t * pulPreviousNotificationValue )
{/* 定義指向目標任務控制塊(TCB)的指針 */TCB_t * pxTCB;/* 默認返回值設置為 pdPASS,表示通知成功 */BaseType_t xReturn = pdPASS;/* 用于保存原始通知狀態(tài)的變量 */uint8_t ucOriginalNotifyState;/* 斷言檢查:確保傳入的通知索引在任務通知數(shù)組范圍內(nèi) */configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );/* 斷言檢查:確保要通知的任務不為空 */configASSERT( xTaskToNotify );/* 將任務句柄轉(zhuǎn)換為 TCB 指針,便于訪問任務的通知數(shù)據(jù) */pxTCB = xTaskToNotify;/* 進入臨界區(qū),確保以下操作是原子性的,防止中斷或任務切換干擾 */taskENTER_CRITICAL();{/* 如果調(diào)用者要求獲取通知前的值,則將目標任務對應索引的通知值保存到 pulPreviousNotificationValue */if( pulPreviousNotificationValue != NULL ){*pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];}/* 保存當前任務的通知狀態(tài),以便后續(xù)判斷任務是否處于等待通知狀態(tài) */ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];/* 將任務的通知狀態(tài)設置為“已接收通知”,表示本次調(diào)用已觸發(fā)通知 */pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;/* 根據(jù)通知操作類型(eAction)來更新任務的通知值 */switch( eAction ){case eSetBits:/* eSetBits:將 ulValue 指定位與當前通知值進行按位或運算 */pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;break;case eIncrement:/* eIncrement:將當前通知值遞增 */( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;break;case eSetValueWithOverwrite:/* eSetValueWithOverwrite:無條件覆蓋當前通知值,設置為 ulValue */pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;break;case eSetValueWithoutOverwrite:/* eSetValueWithoutOverwrite:僅當通知狀態(tài)之前未標記為“已接收通知”時才寫入新值 */if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ){pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;}else{/* 如果目標任務已處于“已接收通知”狀態(tài),則不允許覆蓋,返回失敗 */xReturn = pdFAIL;}break;case eNoAction:/* eNoAction:不改變通知值,僅更新通知狀態(tài),用于僅喚醒任務 */break;default:/* 默認情況:如果傳入了未處理的操作類型,則強制斷言失敗 */configASSERT( xTickCount == ( TickType_t ) 0 );break;}/* 記錄通知事件,用于調(diào)試和跟蹤 */traceTASK_NOTIFY( uxIndexToNotify );/* 檢查目標任務是否正處于等待通知的阻塞狀態(tài) */if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ){/* 如果是,則將該任務從阻塞列表中移除 */listREMOVE_ITEM( &( pxTCB->xStateListItem ) );/* 并將任務添加到就緒列表中,以便它可以被調(diào)度執(zhí)行 */prvAddTaskToReadyList( pxTCB );/* 確保該任務不在任何事件列表中 */configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );#if ( configUSE_TICKLESS_IDLE != 0 ){/* 如果使用 Tickless Idle 模式,可能需要重置下一個任務解除阻塞時間,* 以便更快進入睡眠模式 */prvResetNextTaskUnblockTime();}#endif/* 如果被通知的任務優(yōu)先級高于當前任務,* 則立即進行任務切換,使得高優(yōu)先級任務盡快運行 */if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ){taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{/* 如果目標任務原來并不處于等待通知狀態(tài),則不需要將其從阻塞列表中移除 */mtCOVERAGE_TEST_MARKER();}}/* 退出臨界區(qū) */taskEXIT_CRITICAL();/* 返回操作結(jié)果,pdPASS 表示通知成功,pdFAIL 表示未能寫入通知值(如 eSetValueWithoutOverwrite 情況) */return xReturn;
}
通知操作類型(如設置位、遞增、覆蓋等)更新目標任務的通知值,并在必要時喚醒等待通知的任務 。注釋很清楚了,這里就不再額外講了。看過之前關(guān)于隊列、信號量、互斥量的相關(guān)內(nèi)部機制講解,其實看這個也是很簡單了