# 任务通知的特性

通过任务通知,无需单独的通信对象,任务就可以与其他任务进行交互,以及与 ISR 同步。通过使用任务通知,任务或 ISR 可以直接向接收任务发送事件。

任务通知可以通过以下几种方式更新接收任务的通知值:

  • 直接设置而不用覆写接收任务的通知值。
  • 覆写接收任务的通知值。
  • 设置接收任务通知值的一个或多个 bit 位。
  • 增加接收任务的通知值。

相对于使用中介对象发送到任务(这样的例子对象是队列,信号量,互斥对象和事件组)如下图:

img

任务通知是一种将事件直接发送到任务的方法,而无需这样做中介对象:

img

任务通知功能是可选的,要包含任务通知功能,请在 FreeRTOSConfig.h 中将 configUSE_TASK_NOTIFICATIONS 设置为 1

configUSE_TASK_NOTIFICATIONS 设置为 1 时,每个任务都有一个通知状态(待处理或未在等待处理)和一个通知值(一个 32 位无符号整数)。当任务收到通知时,其通知状态将设置为待处理。当任务读取其通知值时,其通知状态将设置为未在等待处理。如果出于节省空间的考虑(每个任务可以节省 4 个字节),可以设置 configUSE_TASK_NOTIFICATIONS0 来禁用。
任务可在 “被阻止” 状态中等待其通知状态变为待处理,还可以选择指定超时。

# 存在的限制

在以下情形中不能使用任务通知:

  • 向 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:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果 xTicksToWait0 ,则 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 的按位取反值按位与操作。设置参数 ulBitsToClearOnEntry0xFFFFFFFF(ULONG_MAX) ,表示清零任务通知值。
  • ulBitsToClearOnExit:在退出函数之前,将任务的通知值与参数 ulBitsToClearOnExit 的按位取反值按位与操作。设置参数 ulBitsToClearOnExit0xFFFFFFFF(ULONG_MAX) ,表示清零任务通知值。
  • pulNotificationValue:用于向外回传任务的通知值。这个通知值在参数 ulBitsToClearOnExit 起作用前将通知值拷贝到 *pulNotificationValue 中。可选参数,如果不需要,可将其设置为 NULL。
  • xTicksToWait:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果 xTicksToWait0 ,则 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 任务通知只能用来发送数据到一个任务中,相比用队列实现,有一些限制:

  1. 只能发送 32-bit 数据。
  2. 保存的是接收任务的值,同一个时刻只能存在一个接收任务。

因此,使用 “轻量邮箱” 这个短语代替 “轻量队列”。任务的通知值就是邮箱值

  • 发送处理:使用 xTaskNotify() xTaskNotifyFromISR() 发送至任务,可以用来替换 xQueueOverwtite() xQueueOverwtiteFromISR()
  • 接收处理:使用 xTaskNotifyWait() 来获取通知值,可以用来代替 xQueueReceive() xQueuePeek()
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝