FreeRTOS 任务通知
直达任务通知最大的优势是仅占用5个字节的RAM,与信号量相比RAM占用空间更小,因而执行速度更快。向任务发送“直达任务通知”会将目标任务通知设为“挂起”状态,而调用xTaskNotifyWait() 读取通知值会将该通知的状态清除为“非挂起”(注意:发送任务是将目标任务通知设为挂起状态,即,发送方不能再次发送通知,而不是指目标任务被设置为挂起状态,目标任务挂起状态是在take或wait时设置为挂起
目录
直达任务通知(direct to task notifications)
直达任务通知(direct to task notifications)
直达任务通知最大的优势是仅占用5个字节的RAM,与信号量相比RAM占用空间更小,因而执行速度更快。
- 每个任务都有一组通知,每个通知包含一个32位值和一个布尔状态,总共只消耗5个字节的RAM。通知不仅可以传达事件,还可以通过多种方式传达数据。
- 大多数任务间通信方法借助中间对象,如队列,信号量或事件组。发送任务写入通信对象,而接收任务从通信对象中读取, 而直接任务通知则发送任务直接向接收任务发送通知,无需借助中间对象。
- 在大多数用例中,FreeRTOS 直接任务通知比信号量更小且速度快了高达45%,而且FreeRTOS消息缓冲区和流缓冲区提供的队列替代方案更小且速度更快。架构良好的FreeRTOS应用程序很少需要使用信号量。
- 信号量API的实现为一组调用队列API的宏,这种方式明显的优点是添加信号量功能而不增加code 大小(内存很小时,这一点极其重要),但这种方式使信号量成为异常重的对象,因为它继承了队列的所有综合功能(如,线程和优先级感知)。
- 一些信号量受益于这种综合功能,但最常见的信号量用例则不需要,因而,创建了直接任务通知来服务这些最常见的不需要综合功能的信号量用例,以精益事件机制。
- 信号量API的实现为一组调用队列API的宏,这种方式明显的优点是添加信号量功能而不增加code 大小(内存很小时,这一点极其重要),但这种方式使信号量成为异常重的对象,因为它继承了队列的所有综合功能(如,线程和优先级感知)。
向任务发送“直达任务通知”会将目标任务通知设为“挂起”状态,而调用xTaskNotifyWait() 读取通知值会将该通知的状态清除为“非挂起”(注意:发送任务是将目标任务通知设为挂起状态,即,发送方不能再次发送通知,而不是指目标任务被设置为挂起状态,目标任务挂起状态是在take或wait时设置为挂起状态的)。
任务通知接口丰富,可通过不同接口分别代替二进制信号量,计数信号量,事件组,邮箱等;
任务通知的使用限制:
- RTOS 任务通知仅可在只有一个任务 可以接收事件时使用(只能一对一,若要多对一,需要使用 uxIndexToNotify 进行区分)
- 只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下,都不会因为发送失败而进入阻塞态。
code示例
相关接口
/* 直达任务通知,可以通过共享变量来扩展通知数据,以传递更多信息,但要注意临界资源保护 */
/* xTaskToNotify: 接收通知的任务的句柄 */
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
/* uxIndexToNotify: 目标任务的通知值数组中要想其发送通知的索引 */
/* 该接口可用于多个不同事件通知同一个任务的情况,避免三个事件相互覆盖或冲突 */
BaseType_t xTaskNotifyGiveIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify );
/*ISR 中使用的notify */
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
void vTaskNotifyGiveIndexedFromISR( TaskHandle_t xTaskHandle,
UBaseType_t uxIndexToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
/* xClearCountOnExit:
如果收到任务通知,且xClearCountOnExit 设置为pdFALSE,
那么通知值将在ulTaskNotifyTake 退出前递减(与技术信号量的原理相同),
若设置为true,则通知值将在退出前重置为0 */
/* 返回值: 累计通知次数 */
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn,
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
/* 返回调用该接口的task handler*/
TaskHandle_t xTaskGetCurrentTaskHandle();
向任务发送“直达任务通知”可以使用以下任一方法更新目标通知的值:(xTaskNotify中参数eNotifyAction)
- eNoAction: 目标任务接受事件,但其通知值不会更新,即不会使用uIValue
- eSetValueWithOverwrite: 覆盖原值,无论接收任务是否读取被覆盖的值
- eSetValueWithoutOrwrite: 覆盖原值,但前提是接收任务已读取被覆盖的值,
- 若目标任务已有挂起的通知,则其通知值不会更新,以免之前的值在使用时被覆盖,在这种情况下调用xTaskNotify()会失败;
- eSetBits:在值中设置一个或多个位
- 目标任务的通知值将与uIValue 进行按位“或”操作, 如,uIvalue设置为0x01, 则目标任务通知值将与0x1进行 按位或的操作;
- eIncrement:对目标任务的通知值进行增量加1,则不会使用uIvalue
/* uIValue: 用于更新目标任务的通知值(该值将更新在wait中的)pulNotificationValue */
/* eAction: 通知值更新的方式,可设置为 eNoAction, eSetBits, eIncrement, eIncrement,
eSetValueWithOverwrite, eSetValueWithoutOrwrite, 具体含义如上所示*/
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xTaskNotifyIndexedFromISR( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
/* pulNotificationValue: 用于接收传出任务的通知值(即ulValue传入的值)*/
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
/* pulPreviousNotifyValue: 返回目标任务之前的通知值 */
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );
BaseType_t xTaskNotifyAndQueryIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );
code 实例
/* 等待通知的阻塞任务 */
static void vNotifiedTask( void *pvParameters )
{
for( ;; )
{
/* Wait to receive a notification sent directly to this task.
The first parameter is set to pdFALSE, which makes the call
replicate the behavior of a counting semaphore. Set the
parameter to pdTRUE to replicate the behavior of a binary
semaphore. The second parameter is set to portMAX_DELAY,
which makes the task block indefinitely to wait for the
notification. That is done to simplify the example – real
applications should not block indefinitely as that prevents
the task recovering from error conditions. */
if( ulTaskNotifyTake( pdFALSE, portMAX_DELAY ) != 0 )
{
/* The task received a notification – do whatever is
necessary to process the received event. */
DoSomething();
}
}
}
/* ISR 中 通知阻塞任务(xHandlerTask)*/
static uint32_t vNotifyingISR( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* The xHigherPriorityTaskWoken parameter must be initialized
to pdFALSE as it will get set to pdTRUE inside the interrupt
safe API function if calling the API function unblocks a task
that has a higher priority than the task in the running state
(the task this ISR interrupted). */
xHigherPriorityTaskWoken = pdFALSE;
/* Send a notification directly to the task that will perform
any processing necessitated by this interrupt. */
/* 执行完之后,xHandlerTask 中的 ulTaskNotifyTake 解除当前阻塞 */
vTaskNotifyGiveFromISR( /* The handle of the task to which
the notification is being sent. */
xHandlerTask,
&xHigherPriorityTaskWoken );
/* If xHigherPriorityTaskWoken is now pdTRUE then calling
portYIELD_FROM_ISR() will result in a context switch, and
this interrupt will return directly to the unblocked task.
The FAQ "why is there a separate API for use in interrupts"
describes why it is done this way. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
流缓冲区
流缓冲区指的是RTOS 任务到任务以及中断到任务的通信原语,主要针对单个读取者单个写入者场景进行了优化,数据通过复制的方式传递:数据由发送者复制到缓冲区中,然后由读取者复制出缓冲区。流缓冲区是基于直达任务通知实现的。
- 注意:流缓冲区只适用于这种一对一场景,不可用于多个写入者或读取者这种情况。
通过流缓冲区,可将连续字节流从中断服务程序传递到任务,也可将其从一项任务传递到另一项任务。字节流可以是任意长度,不一定有开头或结尾,由于数据是以复制的方式复制到缓冲区中,所以可以一次写入或读取任意长度的字节。
FreeRTOS/Demo/Common/Minimal/StreamBufferInterrupt.c 源文件提供了大量注释的示例,说明如何使用流缓冲区将数据从中断服务传递到任务。
code接口
xStreamBufferReceive()允许指定阻塞时间,若流缓冲区中没有数据或数据不够指定数量,则该任务进入阻塞状态,直到流缓冲区中有指定数量的数据可用,或阻塞时间到期。阻塞时间到期后,任务会接收实际可用的字节数。
- 该指定数量称为流缓冲区的触发级别,如,级别为10流缓冲区,则在流缓冲区至少包含10个字节时,才会解除任务阻塞。(注意:级别不可为0,也不可大于流缓冲区大小的值)
xStreamBufferSend()在写入恰好已满的流缓冲区时,若指定了阻塞时间,则该任务将进入阻塞状态,直到可写或阻塞时间到期。(会写入最多的数据直到流缓冲区满,没有类似接收的触发级别概念)
/* xBufferSizeBytes: 流缓冲区在任何时候能够容纳的总字节数 */
/* xTriggerLevelBytes: 缓冲区的触发等级 */
/* 创建成功则返回流缓冲区的句柄,否则返回NULL */
StreamBufferHandle_t xStreamBufferCreate( size_t xBufferSizeBytes,
size_t xTriggerLevelBytes );
/* 用于多核场景 */
/* pxSendCompletedCallback: 发送完成后调用的回调函数 */
/* pxReceiveCompletedCallback: 读取数据完成后调用的回调函数 */
StreamBufferHandle_t xStreamBufferCreateWithCallback(
size_t xBufferSizeBytes,
size_t xTriggerLevelBytes
StreamBufferCallbackFunction_t pxSendCompletedCallback,
StreamBufferCallbackFunction_t pxReceiveCompletedCallback );
void vSendCallbackFunction( StreamBufferHandle_t xStreamBuffer,
BaseType_t xIsInsideISR,
BaseType_t * const pxHigherPriorityTaskWoken );
void vReceiveCallbackFunction( StreamBufferHandle_t xStreamBuffer,
BaseType_t xIsInsideISR,
BaseType_t * const pxHigherPriorityTaskWoken );
/* pvTxData: 要发送到缓冲区的数据指针 */
/* xDataLengthBytes: 要发送到缓冲区的数据长度 */
/* xTicksToWait: 阻塞时间 */
/* 返回实际写入流缓冲区的字节数 */
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait );
size_t xStreamBufferSendFromISR( StreamBufferHandle_t xStreamBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken );
/* pvRxData: 接收到的数据存放的指针 */
/* xBufferLengthBytes: pvRxData 长度,或要接收的长度 */
/* 返回实际接收的数据长度 */
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
TickType_t xTicksToWait );
size_t xStreamBufferReceiveFromISR( StreamBufferHandle_t xStreamBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken );
/* 删除创建的流缓冲区 */
void vStreamBufferDelete( StreamBufferHandle_t xStreamBuffer );
/* 查询流缓冲区可读数据长度 */
size_t xStreamBufferBytesAvailable( StreamBufferHandle_t xStreamBuffer );
/*查询流缓冲区可写入的数据长度*/
size_t xStreamBufferSpacesAvailable( StreamBufferHandle_t xStreamBuffer );
/* 设置流缓冲区的触发级别 */
BaseType_t xStreamBufferSetTriggerLevel( StreamBufferHandle_t xStreamBuffer,
size_t xTriggerLevel );
/* 当前流缓冲区是否为空 or full*/
BaseType_t xStreamBufferIsEmpty( StreamBufferHandle_t xStreamBuffer );
BaseType_t xStreamBufferIsFull( StreamBufferHandle_t xStreamBuffer );
消息缓冲区
消息缓冲区使用流缓冲区进行数据传递,但流缓冲区传递连续的字节流,而消息缓冲区传递大小可变但离散的消息(长度为10个字节的消息只能作为10个字节的消息接收,而不能以单独的字节读取)。
- 要使消息缓冲区能够处理可变大小的消息,在将消息写入消息缓冲区之前,需将每条消息的长度 写入消息缓冲区
FreeRTOS/Demo/Common/Minimal/MessageBufferAMP.c 源文件 提供了经过大量注释的示例,说明如何使用消息缓冲区将可变长度数据 从多核 MCU 的一个 MCU 核心传递到另一个 MCU 核心。
code接口
向消息缓冲区发送消息时,会写入额外的sizeof(size_t)字节以存储消息的长度,因此,大部分32位架构上将xDataLengthBytes设置为20,这样一共是使用24字节空间(20字节消息数据和4字节保存消息长度)
/* 消息缓冲区创建 */
MessageBufferHandle_t xMessageBufferCreate( size_t xBufferSizeBytes );
MessageBufferHandle_t xMessageBufferCreateWithCallback(
size_t xBufferSizeBytes,
StreamBufferCallbackFunction_t pxSendCompletedCallback,
StreamBufferCallbackFunction_t pxReceiveCompletedCallback );
/* 消息发送 */
size_t xMessageBufferSend( MessageBufferHandle_t xMessageBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait );
size_t xMessageBufferSendFromISR( MessageBufferHandle_t xMessageBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken );
/* 消息接收 */
size_t xMessageBufferReceive( MessageBufferHandle_t xMessageBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
TickType_t xTicksToWait );
size_t xMessageBufferReceiveFromISR( MessageBufferHandle_t xMessageBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
BaseType_t *pxHigherPriorityTaskWoken );
/* 删除之前创建的消息缓冲区 */
void vMessageBufferDelete( MessageBufferHandle_t xMessageBuffer );
/* 查询消息缓冲区当前还有多少空闲空间可用 */
size_t xMessageBufferSpacesAvailable( MessageBufferHandle_t xMessageBuffer );
/* 查询消息缓冲区是否空 or full*/
BaseType_t xMessageBufferIsEmpty( MessageBufferHandle_t xMessageBuffer );
BaseType_t xMessageBufferIsFull( MessageBufferHandle_t xMessageBuffer );
参考
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)