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