物联网操作系统任务通知

所谓"任务通知",你可以反过来读"通知任务"。
我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可
以明确指定:通知哪个任务。
使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构
体通信:
物联网操作系统任务通知_第1张图片

使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"
通知":
物联网操作系统任务通知_第2张图片

本章涉及如下内容:
⚫ 任务通知:通知状态、通知值
⚫ 任务通知的使用场合
⚫ 任务通知的优势
 

任务通知的特性
 

优势及限制


任务通知的优势:
⚫ 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队
列、信号量、事件组都有大的优势。
⚫ 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无
需额外创建结构体。
 

任务通知的限制:
 

⚫ 不能发送数据给 ISR:
ISR 并没有任务结构体,所以无法使用任务通知的功能给 ISR 发送数据。但是
ISR 可以使用任务通知的功能,发数据给任务。
⚫ 数据只能给该任务独享
使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务、 ISR 都可
以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这
些数据。
在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某
个任务,而不是把一个数据源的数据发给多个任务。
⚫ 无法缓冲数据
使用队列时,假设队列深度为 N,那么它可以保持 N 个数据。
使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。
⚫ 无法广播给多个任务
使用事件组可以同时给多个任务发送事件。
使用任务通知,只能发个一个任务。
⚫ 如果发送受阻,发送方无法进入阻塞状态等待
假设队列已经满了,使用 xQueueSendToBack()给队列发送数据时,任务可以进
入阻塞状态等待发送完成。
使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返
回错误。
 

通知状态和通知值


每个任务都有一个结构体: TCB(Task Control Block),里面有 2 个成员:
⚫ 一个是 uint8_t 类型,用来表示通知状态
⚫ 一个是 uint32_t 类型,用来表示通知值

typedef struct tskTaskControlBlock
{
......
/* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]
......
} tskTCB;

通知状态有3种取值:
⚫ taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
⚫ taskWAITING_NOTIFICATION:任务在等待通知
⚫ taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为 pending(有
数据了,待处理)

##define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /*
也是初始状态 */
##define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
##define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )

通知值可以有很多种类型:
⚫ 计数值
⚫ 位(类似事件组)
⚫ 任意数值
 

任务通知的使用
 

使用任务通知,可以实现轻量级的队列(长度为 1)、邮箱(覆盖的队列)、计数型信号量、
二进制信号量、事件组。

两类函数

任务通知有 2 套函数,简化版、专业版,列表如下:
⚫ 简化版函数的使用比较简单,它实际上也是使用专业版函数实现的
⚫ 专业版函数支持很多参数,可以实现很多功能
物联网操作系统任务通知_第3张图片

xTaskNotifyGive/ulTaskNotifyTake


在任务中使用 xTaskNotifyGive 函数,在 ISR 中使用 vTaskNotifyGiveFromISR 函数,都
是直接给其他任务发送通知:
⚫ 使得通知值加一
⚫ 并使得通知状态变为"pending",也就是
taskNOTIFICATION_RECEIVED,表示有数据了、待处理
可以使用ulTaskNotifyTake函数来取出通知值:
⚫ 如果通知值等于 0,则阻塞(可以指定超时时间)
⚫ 当通知值大于 0 时,任务从阻塞态进入就绪态
⚫ 在 ulTaskNotifyTake 返回之前,还可以做些清理工作:把通知值减一,或者
把通知值清零
使用 ulTaskNotifyTake 函数可以实现轻量级的、高效的二进制信号量、计数型信号量。
这几个函数的原型如下:
 

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTa
skWoken );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );

xTaskNotifyGive函数的参数说明如下:
物联网操作系统任务通知_第4张图片

vTaskNotifyGiveFromISR函数的参数说明如下:
物联网操作系统任务通知_第5张图片

ulTaskNotifyTake函数的参数说明如下:
物联网操作系统任务通知_第6张图片

 xTaskNotify/xTaskNotifyWait

xTaskNotify 函数功能更强大,可以使用不同参数实现各类功能,比如:
⚫ 让接收任务的通知值加一:这时 xTaskNotify()等同于 xTaskNotifyGive()
⚫ 设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的
事件组
⚫ 把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成
功。这就是轻量级的、长度为 1 的队列
⚫ 用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖
都成功。 类似 xQueueOverwrite()函数,这就是轻量级的邮箱。
xTaskNotify() 比 xTaskNotifyGive() 更 灵 活 、 强 大 , 使 用 上 也 就 更 复 杂 。
xTaskNotifyFromISR()是它对应的 ISR 版本。
这两个函数用来发出任务通知,使用哪个函数来取出任务通知呢?
使用xTaskNotifyWait()函数!它比ulTaskNotifyTake()更复杂:
⚫ 可以让任务等待(可以加上超时时间),等到任务状态为"pending"(也就是有数
据)
⚫ 还可以在函数进入、退出时,清除通知值的指定位


这几个函数的原型如下:

BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyActio
n eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );

xTaskNotify函数的参数说明如下:
物联网操作系统任务通知_第7张图片

物联网操作系统任务通知_第8张图片

eNotifyAction参数说明:
物联网操作系统任务通知_第9张图片

物联网操作系统任务通知_第10张图片

xTaskNotifyFromISR 函 数 跟 xTaskNotify 很 类 似 , 就 多 了 最 后 一 个 参 数
pxHigherPriorityTaskWoken。在很多ISR函数中,这个参数的作用都是类似的,使用场
景如下:
⚫ 被通知的任务,可能正处于阻塞状态
⚫ xTaskNotifyFromISR 函数发出通知后,会把接收任务从阻塞状态切换为就
绪态
⚫ 如果被唤醒的任务的优先级,高于当前任务的优先级,则
"*pxHigherPriorityTaskWoken"被设置为 pdTRUE,这表示在中断返回之前要进行
任务切换。


xTaskNotifyWait函数列表如下:
物联网操作系统任务通知_第11张图片

物联网操作系统任务通知_第12张图片

物联网操作系统任务通知_第13张图片

示例 :传输计数值
 

本程序创建2个任务:
⚫ 发送任务:把数据写入唤醒缓冲区,使用 xTaskNotifyGive()让通知值加一
⚫ 接收任务:使用 ulTaskNotifyTake()取出通知值,这表示字符数,打印字符
main 函数代码如下:
 

int main( void )
{
prvSetupHardware();
/* 创建 1 个任务用于发送任务通知
* 优先级为 2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建 1 个任务用于接收任务通知
* 优先级为 1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}

发送任务、接收任务的代码和执行流程如下:
⚫ A:发送任务优先级最高,先执行。连续存入 3 个字符、发出 3 次任务通
知:通知值累加为 3
⚫ B:发送任务阻塞,让接收任务能执行
⚫ C:接收任务读到通知值为 3,并把通知值清零
⚫ D:把 3 个字符依次读出、打印
⚫ E:再次读取任务通知,阻塞
物联网操作系统任务通知_第14张图片

本程序使用xTaskNotifyGive/ulTaskNotifyTake实现了轻量级的计数型信号量,代码
更简单:
⚫ 无需创建信号量
⚫ 消耗内存更少
⚫ 效率更高
信号量是个公开的资源,任何任务、 ISR 都可以使用它:可以释放、获取信号量。
而本节程序中,发送任务只能给指定的任务发送通知,目标明确;接收任务只能从自己
的通知值中得到数据,来源明确。
 

示例 : 传输任意值
 

在上述例子中使用任务通知来传输计数值、传输通知。
本节程序使用任务通知来传输任意数据,它创建2个任务:
⚫ 发送任务:把数据通过 xTaskNotify()发送给其他任务
⚫ 接收任务:使用 xTaskNotifyWait 取出通知值,这表示字符,并打印出来
main 函数代码如下:
 

int main( void )
{
prvSetupHardware();
/* 创建 1 个任务用于发送任务通知
* 优先级为 2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建 1 个任务用于接收任务通知
* 优先级为 1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}

发送任务、接收任务的代码和执行流程如下:
⚫ A:发送任务优先级最高,先执行。连续给对方任务发送 3 个字符,只成功
了 1 次
⚫ B:发送任务阻塞,让接收任务能执行
⚫ C:接收任务读取通知值
⚫ D:把读到的通知值作为字符打印出来
⚫ E:再次读取任务通知,阻塞
物联网操作系统任务通知_第15张图片

本程序使用xTaskNotify/xTaskNotifyWait实现了轻量级的队列(该队列长度只有1),代
码更简单:
⚫ 无需创建队列
⚫ 消耗内存更少
⚫ 效率更高
队列是个公开的资源,任何任务、 ISR 都可以使用它:可以存入数据、取出数据。
而本节程序中,发送任务只能给指定的任务发送通知,目标明确;接收任务只能从自己
的通知值中得到数据,来源明确。
注意:任务通知值只有一个,数据可能丢失,设计程序时要考虑这点
 

你可能感兴趣的:(FreeRTOS组件详解,物联网,stm32,嵌入式硬件,单片机,计算机外设)