章節概述:
如何使用任務通知實現輕量級同步。
概述
每個RTOS任務都有一個32位的通知值,任務創建時,這個值被初始化為0。RTOS任務通知相當於直接向任務發送一個事件,接收到通知的任務可以解除因等待通知而引起的阻塞狀態。
相比於使用信號量解除任務阻塞,使用任務通知可以快45%、使用更少的RAM。
發送通知的同時,也可以可選的改變接收任務的通知值。
可以通過下列方法向接收任務更新通知:
- 不覆蓋接收任務的通知值
- 覆蓋接收任務的通知值
- 設置接收任務通知值的某些位
- 增加接收任務的通知值
雖然RTOS任務通知速度更快並且占用內存更少,但它也有一些限制:
- 只能有一個任務接收通知事件。
- 接收通知的任務可以因為等待通知而進入阻塞狀態,但是發送通知的任務即便不能立即完成通知發送也不能進入阻塞狀態。
相對於用前必須分別創建隊列、二進制信號量、計數信號量或事件組的情況,使用任務通知顯然更靈活。更好的是,
RTOS任務通知功能默認是使能的,可以通過在文件FreeRTOSConfig.h中設置宏configUSE_TASK_NOTIFICATIONS為0來禁止這個功能,禁止后每個任務節省8字節內存。
發送通知
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction);
BaseType_t xTaskNotifyGive( xTaskToNotify );
// 中斷保護等價函數
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
pxHigherPriorityTaskWoken );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
描述:使用API發送通知,在接收RTOS任務調用API函數xTaskNotifyWait()或ulTaskNotifyTake()之前,這個通知都被保持着。
xTaskNotify
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction);
描述:向指定任務發送指定的通知值。如果打算使用RTOS任務通知實現輕量級的二進制或計數信號量,推薦使用API函數xTaskNotifyGive()來代替本函數。
參數解析:
- xTaskToNotify:被通知的任務句柄。
- ulValue:通知更新值
- eAction:枚舉類型,指明更新通知值的方法
枚舉值 | 描述 |
---|---|
eNoAction | 發送通知,但不更新值(參數ulValue未使用) |
eSetBits | 被通知任務的通知值按位或ulValue。(某些場景下可代替事件組,效率更高) |
eIncrement | 被通知任務的通知值增加1(參數ulValue未使用),相當於xTaskNotifyGive |
eSetValueWithOverWrite | 被通知任務的通知值設置為 ulValue。(某些場景下可代替xQueueOverwrite ,效率更高) |
eSetValueWithoutOverwrite | 如果被通知的任務當前沒有通知,則被通知的任務的通知值設為ulValue。 如果被通知任務沒有取走上一個通知,又接收到了一個通知,則這次通知值丟棄,在這種情況下視為調用失敗並返回 pdFALSE (某些場景下可代替 xQueueSend ,效率更高) |
返回值:參數eAction為eSetValueWithoutOverwrite時,如果被通知任務還沒取走上一個通知,又接收到了一個通知,則這次通知值未能更新並返回pdFALSE,否則返回pdPASS。
注意:此函數不可以在中斷服務例程中調用,中斷保護等價函數為xTaskNotifyFromISR()
。
xTaskNotifyGive
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify );
描述:本質上相當於xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement )
。
其實這是一個宏,使用該API函數代替二進制或計數信號量,但速度更快。
參數解析:
xTaskToNotify:被通知的任務句柄。
注意:
應該使用API函數ulTaskNotifyTake()來等待通知,而不應該使用API函數xTaskNotifyWait()。
此函數不可以在中斷服務例程中調用,中斷保護等價函數為vTaskNotifyGiveFromISR()
。
例子:
staticvoid prvTask1( void *pvParameters );
staticvoid prvTask2( void *pvParameters );
/* 保存任務句柄 */
staticTaskHandle_t xTask1 = NULL, xTask2 = NULL;
/* 創建兩個任務,它們之間反復發送通知,啟動RTOS調度器*/
voidmain( void )
{
xTaskCreate( prvTask1, "Task1",200, NULL, tskIDLE_PRIORITY, &xTask1 );
xTaskCreate( prvTask2, "Task2",200, NULL, tskIDLE_PRIORITY, &xTask2 );
vTaskStartScheduler();
}
/*-----------------------------------------------------------*/
staticvoid prvTask1( void *pvParameters )
{
for( ;; )
{
/*向prvTask2(),發送通知,使其解除阻塞狀態 */
xTaskNotifyGive( xTask2 );
/* 等待prvTask2()的通知,進入阻塞 */
ulTaskNotifyTake( pdTRUE, portMAX_DELAY);
}
}
/*-----------------------------------------------------------*/
staticvoid prvTask2( void *pvParameters )
{
for( ;; )
{
/* 等待prvTask1()的通知,進入阻塞 */
ulTaskNotifyTake( pdTRUE, portMAX_DELAY);
/*向prvTask1(),發送通知,使其解除阻塞狀態 */
xTaskNotifyGive( xTask1 );
}
}
獲取通知
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
描述:專門為使用更輕量級更快的方法來代替二進制或計數信號量而量身打造的。
如果RTOS任務的通知值為0,使用xTaskNotifyTake()可以指定任務進入阻塞狀態的時間(可為0),直到該任務的通知值不為0。
參數解析:
- xClearCountOnExit:如果該參數設置為pdFALSE,則API函數xTaskNotifyTake()退出前,將任務的通知值減1;如果該參數設置為pdTRUE,則API函數xTaskNotifyTake()退出前,將任務通知值清零。
兩種方法處理任務的通知值:
- 一種方法是在函數退出時將通知值清零,這種方法適用於實現二進制信號量;
- 另外一種方法是在函數退出時將通知值減1,這種方法適用於實現計數信號量。
- xTicksToWait:因等待通知而進入阻塞狀態的最大時間。時間單位為系統節拍周期。可用
pdMS_TO_TICKS
將指定的毫秒時間轉化為相應的系統節拍數。
返回值:返回任務的當前通知值,為0或者為調用API函數xTaskNotifyTake()之前的通知值減1。
可以用來代替FreeRTOS獲取信號量的API函數xSemaphoreTake()。
當一個任務使用通知值來實現二進制或計數信號量時,其它任務或者中斷要使用API函數xTaskNotifyGive()或者使用參數eAction為eIncrement的API函數xTaskNotify()。推薦使用xTaskNotifyGive()函數(其實是個宏,我們這里把它看作一個API函數)。另外需要注意的是,如果在中斷中使用,要使用它們的中斷保護等價函數:vTaskNotifyGiveFromISR()和xTaskNotifyFromISR()。
例子:
/* 中斷處理程序。*/
voidvANInterruptHandler( void )
{
BaseType_txHigherPriorityTaskWoken;
prvClearInterruptSource();
/* xHigherPriorityTaskWoken必須被初始化為pdFALSE。如果調用vTaskNotifyGiveFromISR()會解除vHandlingTask任務的阻塞狀態,並且vHandlingTask任務的優先級高於當前處於運行狀態的任務,則xHigherPriorityTaskWoken將會自動被設置為pdTRUE。*/
xHigherPriorityTaskWoken = pdFALSE;
/*向一個任務發送通知,xHandlingTask是該任務的句柄。*/
vTaskNotifyGiveFromISR( xHandlingTask,&xHigherPriorityTaskWoken );
/* 如果xHigherPriorityTaskWoken為pdTRUE,則強制上下文切換。這個宏的實現取決於移植層,可能會調用portEND_SWITCHING_ISR */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken );
}
/*---------------------------------------------------------------------------------------------------*/
/* 一個因為等待通知而阻塞的任務。*/
voidvHandlingTask( void *pvParameters )
{
BaseType_txEvent;
for( ;; )
{
/*等待通知,無限期阻塞。參數pdTRUE表示函數退出前會清零通知值。這顯然是用於替代二進制信號量的用法。需要注意的是,真實的程序一般不會無限期阻塞。*/
ulTaskNotifyTake( pdTRUE, portMAX_DELAY);
/* 當處理完所有事件后,仍然等待下一個通知*/
do
{
xEvent = xQueryPeripheral();
if( xEvent != NO_MORE_EVENTS )
{
vProcessPeripheralEvent( xEvent);
}
} while( xEvent != NO_MORE_EVENTS );
}
}
等待通知
如果打算使用RTOS任務通知實現輕量級的二進制或計數信號量,推薦使用API函數ulTaskNotifyTake()來代替本函數。
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t*pulNotificationValue,
TickType_t xTicksToWait );
描述:進入阻塞狀態,等待通知;當接收到通知后,任務解除阻塞並清除通知。
參數解析:
- ulBitsToClearOnEntry:在使用通知之前,先將任務的通知值與參數ulBitsToClearOnEntry的按位取反值按位與操作。設置參數ulBitsToClearOnEntry為0xFFFFFFFF(ULONG_MAX),表示清零任務通知值。
- ulBitsToClearOnExit:在函數xTaskNotifyWait()退出前,將任務的通知值與參數ulBitsToClearOnExit的按位取反值按位與操作。設置參數ulBitsToClearOnExit為0xFFFFFFFF(ULONG_MAX),表示清零任務通知值。
- pulNotificationValue:用於向外回傳任務的通知值。這個通知值在參數ulBitsToClearOnExit起作用前將通知值拷貝到*pulNotificationValue中。如果不需要返回任務的通知值,這里設置成NULL。
- xTicksToWait:因等待通知而進入阻塞狀態的最大時間。時間單位為系統節拍周期。可用
pdMS_TO_TICKS
將指定的毫秒時間轉化為相應的系統節拍數。
返回值:如果接收到通知,返回pdTRUE,如果API函數xTaskNotifyWait()等待超時,返回pdFALSE。
例子:
/*這個任務使用任務通知值的位來傳遞不同的事件,這在某些情況下可以代替事件組。*/
voidvAnEventProcessingTask( void *pvParameters )
{
uint32_tulNotifiedValue;
for( ;; )
{
/*等待通知,無限期阻塞(沒有超時,所以不用檢查函數返回值)。其它任務或者中斷設置的通知值中的不同位表示不同的事件。參數0x00表示使用通知前不清除任務的通知值位,參數ULONG_MAX 表示函數xTaskNotifyWait()退出前將任務通知值設置為0*/
xTaskNotifyWait( 0x00, ULONG_MAX,&ulNotifiedValue, portMAX_DELAY );
/*根據通知值處理事件*/
if( ( ulNotifiedValue & 0x01 ) != 0)
{
prvProcessBit0Event();
}
if( ( ulNotifiedValue & 0x02 ) != 0)
{
prvProcessBit1Event();
}
if( ( ulNotifiedValue & 0x04 ) != 0)
{
prvProcessBit2Event();
}
/* ……*/
}
}
任務通知並查詢
BaseType_t xTaskNotifyAndQuery(TaskHandle_t xTaskToNotify,
uint32_tulValue,
eNotifyActioneAction,
uint32_t*pulPreviousNotifyValue );
描述:此函數與任務通知API函數xTaskNotify()非常像,只不過此函數具有一個附加參數,用來回傳任務當前的通知值,然后根據參數ulValue和eAction更新任務的通知值。
參數解析:
- xTaskToNotify:被通知的任務句柄。
- ulValue:通知更新值
- eAction:枚舉類型,指明更新通知值的方法,枚舉變量成員以及作用見xTaskNotify()一節。
- pulPreviousNotifyValue:回傳未被更新的任務通知值。如果不需要回傳未被更新的任務通知值,這里設置為NULL,這樣就等價於調用xTaskNotify()函數。
返回值:參數eAction為eSetValueWithoutOverwrite時,如果被通知任務還沒取走上一個通知,又接收到了一個通知,則這次通知值未能更新並返回pdFALSE,否則返回pdPASS。
注意:此函數不能在中斷服務例程中使用,在中斷服務例程中使用xTaskNotifyAndQueryFromISR()函數。