# 任务通知的特性
通过任务通知,无需单独的通信对象,任务就可以与其他任务进行交互,以及与 ISR 同步。通过使用任务通知,任务或 ISR 可以直接向接收任务发送事件。
任务通知可以通过以下几种方式更新接收任务的通知值:
- 直接设置而不用覆写接收任务的通知值。
- 覆写接收任务的通知值。
- 设置接收任务通知值的一个或多个 bit 位。
- 增加接收任务的通知值。
相对于使用中介对象发送到任务(这样的例子对象是队列,信号量,互斥对象和事件组)如下图:
任务通知是一种将事件直接发送到任务的方法,而无需这样做中介对象:
任务通知功能是可选的,要包含任务通知功能,请在 FreeRTOSConfig.h
中将 configUSE_TASK_NOTIFICATIONS
设置为 1
。
当 configUSE_TASK_NOTIFICATIONS
设置为 1
时,每个任务都有一个通知状态(待处理或未在等待处理)和一个通知值(一个 32 位无符号整数)。当任务收到通知时,其通知状态将设置为待处理。当任务读取其通知值时,其通知状态将设置为未在等待处理。如果出于节省空间的考虑(每个任务可以节省 4 个字节),可以设置 configUSE_TASK_NOTIFICATIONS
为 0
来禁用。
任务可在 “被阻止” 状态中等待其通知状态变为待处理,还可以选择指定超时。
# 存在的限制
在以下情形中不能使用任务通知:
向 ISR 发送事件或数据。
通信对象可供 ISR 和任务相互发送事件和数据。
任务通知可用于从 ISR 向任务发送事件和数据,但不能用于从任务向 ISR 发送事件或数据。
启用多个接收任务。
通信对象可由任何知其句柄(可能是队列句柄、信号灯句柄或事件组句柄)的任务或 ISR 访问。任意数量的任务和 ISR 可处理发送到任何给定通信对象的事件或数据。
任务通知直接发送给接收任务,因此只能由接收通知的任务来处理。这在多数情况下并不是限制,因为尽管通常有多个任务和 ISR 向相同的通信对象发送事件或数据,但是很少有多个任务和 ISR 接收来自同一通信对象的事件或数据。
缓冲多个数据项。
队列是一次可保存多个数据项的通信对象。已发送到队列但尚未从队列中接收的数据将在队列对象中缓冲。
任务通知通过更新接收任务的通知值向任务发送数据。任务的通知值一次只能保存一个值。
广播到多个任务。
事件组是可用于一次向多个任务发送事件的通信对象。
任务通知直接发送给接收任务,因此只能由接收任务来处理。
在 “被阻止” 状态中等待发送完成。
如果通信对象暂时处于无法向其中写入更多数据或事件的状态(例如,当队列已满、无法向该队列发送更多数据时),尝试向该对象写入内容的任务可以选择进入 “被阻止” 状态,以等待其写入操作完成。
如果一个任务尝试向已有待处理通知的另一个任务发送任务通知,则发送任务无法在 “被阻止” 状态中等待接收任务重置其通知状态。在大多数使用任务通知的情况下,这极少成为限制。
# API 函数
任务通知使用 API 函数 xTaskNotify()
或 xTaskNotifyGive()
来发送,这个通知将一直挂起直到接收任务使用 API 函数 xTaskNotifyWait()
或 ulTaskNotifyTake()
来接收通知。如果接收任务在通知到来时已经被阻塞,则会从阻塞态恢复,同时通知被清除。
功能 | API 接口 | 实际执行函数 | 其它 |
---|---|---|---|
发送通知 | xTaskNotifyGive() | xTaskGenericNotify() | 没有通知值 (信号量类型) |
发送通知(用于中断中) | vTaskNotifyGiveFromISR() | ||
接收通知 | ulTaskNotifyTake() | 单独对应 xTaskNotifyGive () | |
发送通知 | xTaskNotify() | xTaskGenericNotify() | 带通知值 |
发送通知(用于中断中) | xTaskNotifyFromISR() | xTaskGenericNotifyFromISR() | |
发送通知 | xTaskNotifyAndQuery() | xTaskGenericNotify() | 带通知值,并且返回原通知值 |
发送通知(用于中断中) | xTaskNotifyAndQueryFromISR() | ||
接收通知 | xTaskNotifyWait() | ||
清除通知 | xTaskNotifyStateClear() |
在大多数情况下,并不需要 xTaskNotify()
和 xTaskNotifyWait()
API 函数提供的灵活性。更简单的函数就足够了。 xTaskNotifyGive()
API 函数可替代 xTaskNotify()
,它更简单、但灵活性稍差。 ulTaskNotifyTake()
API 函数可替代 xTaskNotifyWait()
,它更简单、但灵活性稍差。
1、xTaskNotifyGive () 和 vTaskNotifyGiveFromISR () API 函数
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); |
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, | |
BaseType_t *pxHigherPriorityTaskWoken ); |
传入参数:
- xTaskToNotify:需要通知的任务句柄。这个句柄即是调用
xTaskCreate()
创建任务时传递的句柄。 - pxHigherPriorityTaskWoken:如果调用
vTaskNotifyGiveFromISR()
发送通知导致被发送通知的任务离开阻塞状态,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么xSemaphoreGiveFromISR()
会在函数内部将*pxHigherPriorityTaskWoken
设为pdTRUE
。如果vTaskNotifyGiveFromISR()
将此值设为pdTRUE
,则在中断退出前应当进行一次上下文切换。
返回参数(始终返回 pdPASS):
- pdPASS:发送成功。
2、ulTaskNotifyTake () API 函数
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, | |
TickType_t xTicksToWait ); |
传入参数:
- xClearCountOnExit:当设置为
pdFALSE
,则在函数退出时任务的通知值递减,这样,通知值就像一个计数信号量;如果是pdTRUE
,那么当函数退出时,任务的通知值将被清除为零,这样,通知值就像一个二进制信号量。 - xTicksToWait:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果
xTicksToWait
为0
,则ulTaskNotifyTake()
在通知无效时会立即返回;如果把xTicksToWait
设置为portMAX_DELAY
,那么阻塞等待将没有超时限制。
返回参数:
- uint32_t:在任务的通知计数清空为零或递减之前的任务通知值。
3、xTaskNotify () 和 xTaskNotifyFromISR () API 函数
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, | |
uint32_t ulValue, | |
eNotifyAction eAction ); |
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, | |
uint32_t ulValue, | |
eNotifyAction eAction, | |
BaseType_t *pxHigherPriorityTaskWoken ); |
传入参数:
- xTaskToNotify:需要通知的任务句柄。这个句柄即是调用
xTaskCreate()
创建任务时传递的句柄。 - ulValue:与通知一起发送的数据,如何使用
ulValue
取决于eNotifyAction
值。 - eAction:枚举类型,用于指定如何更新接收任务的通知值。
枚举成员 | 描述 |
---|---|
eNoAction | 通知任务,而不更新其通知值;参数 ulValue 未使用。可用作更快速的二进制信号量轻型替代方案。始终返回 pdPASS。 |
eSetBits | 对被通知任务的通知值按位执行或运算。可用作更快速的事件组轻型替代方案。始终返回 pdPASS。 |
eIncrement | 接收任务的通知值将递增。可用作更快速的计数信号量轻型替代方案。始终返回 pdPASS。 |
eSetValueWithOverwrite | 即使任务尚未读取前一个值,也将任务的通知值设置为 ulValue 参数中传递的值。始终返回 pdPASS。 |
eSetValueWithoutOverwrite | 如果任务当前没有通知值,则设置任务的通知值并返回 pdPASS;如果任务没有取出前一个通知值,则不执行任何操作,将返回 pdFAIL。 |
- pxHigherPriorityTaskWoken:如果调用
xTaskNotifyFromISR()
发送通知导致被发送通知的任务离开阻塞状态,并且这个任务的优先级高于当前任务 (也就是被中断的任务),那么xTaskNotifyFromISR()
会在函数内部将*pxHigherPriorityTaskWoken
设为pdTRUE
。如果xTaskNotifyFromISR()
将此值设为pdTRUE
,则在中断退出前应当进行一次上下文切换。
返回参数:
- BaseType_t:取决于
eAction
的值。
4、xTaskNotifyWait () API 函数
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, | |
uint32_t ulBitsToClearOnExit, | |
uint32_t *pulNotificationValue, | |
TickType_t xTicksToWait ); |
传入参数:
- ulBitsToClearOnEntry:在使用通知之前,先将任务的通知值与参数
ulBitsToClearOnEntry
的按位取反值按位与操作。设置参数ulBitsToClearOnEntry
为0xFFFFFFFF(ULONG_MAX)
,表示清零任务通知值。 - ulBitsToClearOnExit:在退出函数之前,将任务的通知值与参数
ulBitsToClearOnExit
的按位取反值按位与操作。设置参数ulBitsToClearOnExit
为0xFFFFFFFF(ULONG_MAX)
,表示清零任务通知值。 - pulNotificationValue:用于向外回传任务的通知值。这个通知值在参数
ulBitsToClearOnExit
起作用前将通知值拷贝到*pulNotificationValue
中。可选参数,如果不需要,可将其设置为 NULL。 - xTicksToWait:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果
xTicksToWait
为0
,则xTaskNotifyWait()
在通知无效时会立即返回;如果把xTicksToWait
设置为portMAX_DELAY
,那么阻塞等待将没有超时限制。
返回参数:
- pdPASS:接收成功。
- pdFAIL:接收失败(超时)。
# 应用
1、替代二值信号量 (binary semaphore)
任务通知比二值信号量快 45% 并且内存占用更小
- 发送处理:
xTaskNotifyGive()
和vTaskNotifyGiveFromISR()
用来代替xSemaphoreGive()
和xSemaphoreGiveFromISR()
。 - 接收处理:
ulTaskNotifyTake()
用来替代xSemaphoreTake()
(ps:ulTaskNotifyTake()
的第一个参数xClearCountOnExit
应设置为pdTRUE
)。
2、替代计数信号量 (counting semaphore)
与替代二值信号量类似,当使用任务通知来代替计数信号量的时候,接收任务的通知值被用来代替计数信号量的计数值
- 发送处理:
xTaskNotifyGive()
和vTaskNotifyGiveFromISR()
用来代替xSemaphoreGive()
和xSemaphoreGiveFromISR()
。 - 接收处理:
ulTaskNotifyTake()
用来替代xSemaphoreTake()
(ps:ulTaskNotifyTake()
的第一个参数xClearCountOnExit
应设置为pdFALSE
)。
3、替代事件组 (event group)
当任务通知用来代替时间组的时候,通知值被用来代替事件组的任务值,通知值的每一位被用来代表某个标志
- 发送处理:
xTaskNotify()
和xTaskNotifyFromISR()
用来代替xEventGroupSetBits()
和xEventGroupSetBitsFromISR()
。 - 接收处理:
xTaskNotifyWait()
用来代替xEventGroupWaitBits()
。
4、替代邮箱队列 (mailbox)
RTOS 任务通知只能用来发送数据到一个任务中,相比用队列实现,有一些限制:
- 只能发送 32-bit 数据。
- 保存的是接收任务的值,同一个时刻只能存在一个接收任务。
因此,使用 “轻量邮箱” 这个短语代替 “轻量队列”。任务的通知值就是邮箱值
- 发送处理:使用
xTaskNotify()
和xTaskNotifyFromISR()
发送至任务,可以用来替换xQueueOverwtite()
和xQueueOverwtiteFromISR()
。 - 接收处理:使用
xTaskNotifyWait()
来获取通知值,可以用来代替xQueueReceive()
和xQueuePeek()
。