韦东山freeRTOS系列教程之【第九章】任务通知(Task Notifications)
需要获取更好阅读体验的同学,请访问我专门设立的站点查看,地址:http://rtos.100ask.net/
系列教程总目录
本教程连载中,篇章会比较多,为方便同学们阅读,点击这里可以查看文章的 目录列表,目录列表页面地址:https://blog.csdn.net/thisway_diy/article/details/121399484
概述
所谓"任务通知",你可以反过来读"通知任务"。
我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。
使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信:
使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"通知":
本章涉及如下内容:
任务通知:通知状态、通知值
任务通知的使用场合
任务通知的优势
9.1 任务通知的特性
9.1.1 优势及限制
任务通知的优势:
- 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有大的优势。
- 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的限制:
- 不能发送数据给ISR:
ISR并没有任务结构体,所以无法使用任务通知的功能给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。 - 数据只能给该任务独享
使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务、ISR都可以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这些数据。
在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。 - 无法缓冲数据
使用队列时,假设队列深度为N,那么它可以保持N个数据。
使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。 - 无法广播给多个任务
使用事件组可以同时给多个任务发送事件。
使用任务通知,只能发个一个任务。 - 如果发送受阻,发送方无法进入阻塞状态等待
假设队列已经满了,使用xQueueSendToBack()
给队列发送数据时,任务可以进入阻塞状态等待发送完成。
使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返回错误。
9.1.2 通知状态和通知值
每个任务都有一个结构体: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 )
通知值可以有很多种类型:
- 计数值
- 位(类似事件组)
- 任意数值
9.2 任务通知的使用
使用任务通知,可以实现轻量级的队列(长度为1)、邮箱(覆盖的队列)、计数型信号量、二进制信号量、事件组。
9.2.1 两类函数
任务通知有2套函数,简化版、专业版,列表如下:
- 简化版函数的使用比较简单,它实际上也是使用专业版函数实现的
- 专业版函数支持很多参数,可以实现很多功能
简化版 | 专业版 | |
---|---|---|
发出通知 | xTaskNotifyGive vTaskNotifyGiveFromISR |
xTaskNotify xTaskNotifyFromISR |
取出通知 | ulTaskNotifyTake | xTaskNotifyWait |
9.2.2 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 *pxHigherPriorityTaskWoken );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
xTaskNotifyGive函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskToNotify | 任务句柄(创建任务时得到),给哪个任务发通知 |
返回值 | 必定返回pdPASS |
vTaskNotifyGiveFromISR函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskHandle | 任务句柄(创建任务时得到),给哪个任务发通知 |
pxHigherPriorityTaskWoken | 被通知的任务,可能正处于阻塞状态。 此函数发出通知后,会把它从阻塞状态切换为就绪态。 如果被唤醒的任务的优先级,高于当前任务的优先级, 则"*pxHigherPriorityTaskWoken"被设置为pdTRUE, 这表示在中断返回之前要进行任务切换。 |
ulTaskNotifyTake函数的参数说明如下:
参数 | 说明 |
---|---|
xClearCountOnExit | 函数返回前是否清零: pdTRUE:把通知值清零 pdFALSE:如果通知值大于0,则把通知值减一 |
xTicksToWait | 任务进入阻塞态的超时时间,它在等待通知值大于0。 0:不等待,即刻返回; portMAX_DELAY:一直等待,直到通知值大于0; 其他值:Tick Count,可以用 pdMS_TO_TICKS() 把ms转换为Tick Count |
返回值 | 函数返回之前,在清零或减一之前的通知值。 如果xTicksToWait非0,则返回值有2种情况: 1. 大于0:在超时前,通知值被增加了 2. 等于0:一直没有其他任务增加通知值,最后超时返回0 |
9.2.3 xTaskNotify/xTaskNotifyWait
xTaskNotify
函数功能更强大,可以使用不同参数实现各类功能,比如:
- 让接收任务的通知值加一:这时
xTaskNotify()
等同于xTaskNotifyGive()
- 设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的事件组
- 把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成功。这就是轻量级的、长度为1的队列
- 用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖都成功。类似
xQueueOverwrite()
函数,这就是轻量级的邮箱。
xTaskNotify()
比xTaskNotifyGive()
更灵活、强大,使用上也就更复杂。xTaskNotifyFromISR()
是它对应的ISR版本。
这两个函数用来发出任务通知,使用哪个函数来取出任务通知呢?
使用xTaskNotifyWait()
函数!它比ulTaskNotifyTake()
更复杂:
- 可以让任务等待(可以加上超时时间),等到任务状态为"pending"(也就是有数据)
- 还可以在函数进入、退出时,清除通知值的指定位
这几个函数的原型如下:
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 );
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
xTaskNotify函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskToNotify | 任务句柄(创建任务时得到),给哪个任务发通知 |
ulValue | 怎么使用ulValue,由eAction参数决定 |
eAction | 见下表 |
返回值 | pdPASS:成功,大部分调用都会成功 pdFAIL:只有一种情况会失败,当eAction为eSetValueWithoutOverwrite, 并且通知状态为"pending"(表示有新数据未读),这时就会失败。 |
eNotifyAction参数说明:
eNotifyAction取值 | 说明 |
---|---|
eNoAction | 仅仅是更新通知状态为"pending",未使用ulValue。 这个选项相当于轻量级的、更高效的二进制信号量。 |
eSetBits | 通知值 = 原来的通知值 | ulValue,按位或。 相当于轻量级的、更高效的事件组。 |
eIncrement | 通知值 = 原来的通知值 + 1,未使用ulValue。 相当于轻量级的、更高效的二进制信号量、计数型信号量。 相当于 xTaskNotifyGive() 函数。 |
eSetValueWithoutOverwrite | 不覆盖。 如果通知状态为"pending"(表示有数据未读), 则此次调用xTaskNotify不做任何事,返回pdFAIL。 如果通知状态不是"pending"(表示没有新数据), 则:通知值 = ulValue。 |
eSetValueWithOverwrite | 覆盖。 无论如何,不管通知状态是否为"pendng", 通知值 = ulValue。 |
xTaskNotifyFromISR函数跟xTaskNotify很类似,就多了最后一个参数pxHigherPriorityTaskWoken
。在很多ISR函数中,这个参数的作用都是类似的,使用场景如下:
- 被通知的任务,可能正处于阻塞状态
xTaskNotifyFromISR
函数发出通知后,会把接收任务从阻塞状态切换为就绪态- 如果被唤醒的任务的优先级,高于当前任务的优先级,则"*pxHigherPriorityTaskWoken"被设置为pdTRUE,这表示在中断返回之前要进行任务切换。
xTaskNotifyWait函数列表如下:
参数 | 说明 |
---|---|
ulBitsToClearOnEntry | 在xTaskNotifyWait入口处,要清除通知值的哪些位? 通知状态不是"pending"的情况下,才会清除。 它的本意是:我想等待某些事件发生,所以先把"旧数据"的某些位清零。 能清零的话:通知值 = 通知值 & ~(ulBitsToClearOnEntry)。 比如传入0x01,表示清除通知值的bit0; 传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0 |
ulBitsToClearOnExit | 在xTaskNotifyWait出口处,如果不是因为超时推出,而是因为得到了数据而退出时: 通知值 = 通知值 & ~(ulBitsToClearOnExit)。 在清除某些位之前,通知值先被赋给"*pulNotificationValue"。 比如入0x03,表示清除通知值的bit0、bit1; 传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0 |
pulNotificationValue | 用来取出通知值。 在函数退出时,使用ulBitsToClearOnExit清除之前,把通知值赋给"*pulNotificationValue"。 如果不需要取出通知值,可以设为NULL。 |
xTicksToWait | 任务进入阻塞态的超时时间,它在等待通知状态变为"pending"。 0:不等待,即刻返回; portMAX_DELAY:一直等待,直到通知状态变为"pending"; 其他值:Tick Count,可以用 pdMS_TO_TICKS() 把ms转换为Tick Count |
返回值 | 1. pdPASS:成功 这表示xTaskNotifyWait成功获得了通知: 可能是调用函数之前,通知状态就是"pending"; 也可能是在阻塞期间,通知状态变为了"pending"。 2. pdFAIL:没有得到通知。 |
9.3 示例22: 传输计数值
本节源码是FreeRTOS_22_tasknotify_tansfer_count
,基于FreeRTOS_13_semaphore_circle_buffer
修改。
本程序创建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:再次读取任务通知,阻塞
运行结果如下图所示:
本程序使用xTaskNotifyGive/ulTaskNotifyTake
实现了轻量级的计数型信号量,代码更简单:
- 无需创建信号量
- 消耗内存更少
- 效率更高
信号量是个公开的资源,任何任务、ISR都可以使用它:可以释放、获取信号量。
而本节程序中,发送任务只能给指定的任务发送通知,目标明确;接收任务只能从自己的通知值中得到数据,来源明确。
9.4 示例23: 传输任意值
本节源码是FreeRTOS_23_tasknotify_tansfer_value
。
在上述例子中使用任务通知来传输计数值、传输通知。
本节程序使用任务通知来传输任意数据,它创建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:再次读取任务通知,阻塞
运行结果如下图所示:
本程序使用xTaskNotify/xTaskNotifyWait
实现了轻量级的队列(该队列长度只有1),代码更简单:
- 无需创建队列
- 消耗内存更少
- 效率更高
队列是个公开的资源,任何任务、ISR都可以使用它:可以存入数据、取出数据。
而本节程序中,发送任务只能给指定的任务发送通知,目标明确;接收任务只能从自己的通知值中得到数据,来源明确。
注意:任务通知值只有一个,数据可能丢失,设计程序时要考虑这点。
韦东山freeRTOS系列教程之【第九章】任务通知(Task Notifications)的更多相关文章
- 实战SpringCloud响应式微服务系列教程(第九章)使用Spring WebFlux构建响应式RESTful服务
本文为实战SpringCloud响应式微服务系列教程第九章,讲解使用Spring WebFlux构建响应式RESTful服务.建议没有之前基础的童鞋,先看之前的章节,章节目录放在文末. 从本节开始我们 ...
- 实战SpringCloud响应式微服务系列教程(第二章)
接上一篇:实战SpringCloud响应式微服务系列教程(第一章) 1.1.2背压 背压是响应式编程的核心概念,这一节也是我们了解响应式编程的重点. 1.背压的机制 在生产者/消费者模型中,我们意识到 ...
- Cobalt Strike系列教程第五章:截图与浏览器代理
Cobalt Strike系列教程分享如约而至,新关注的小伙伴可以先回顾一下前面的内容: Cobalt Strike系列教程第一章:简介与安装 Cobalt Strike系列教程第二章:Beacon详 ...
- Cobalt Strike系列教程第四章:文件/进程管理与键盘记录
Cobalt Strike系列教程分享如约而至,新关注的小伙伴可以先回顾一下前面的内容: Cobalt Strike系列教程第一章:简介与安装 Cobalt Strike系列教程第二章:Beacon详 ...
- D3.js的v5版本入门教程(第九章)——完整的柱状图
D3.js的v5版本入门教程(第九章) 一个完整的柱状图应该包括的元素有——矩形.文字.坐标轴,现在,我们就来一一绘制它们,这章是前面几章的综合,这一章只有少量新的知识点,它们是 d3.scaleBa ...
- Cobalt Strike系列教程第七章:提权与横向移动
Cobalt Strike系列教程分享如约而至,新关注的小伙伴可以先回顾一下前面的内容: Cobalt Strike系列教程第一章:简介与安装 Cobalt Strike系列教程第二章:Beacon详 ...
- Cobalt Strike系列教程第六章:安装扩展
Cobalt Strike系列教程分享如约而至,新关注的小伙伴可以先回顾一下前面的内容: Cobalt Strike系列教程第一章:简介与安装 Cobalt Strike系列教程第二章:Beacon详 ...
- Cobalt Strike系列教程第三章:菜单栏与视图
通过前两章的学习,我们掌握了Cobalt Strike教程的基础知识,及软件的安装使用. Cobalt Strike系列教程第一章:简介与安装 Cobalt Strike系列教程第二章:Beacon详 ...
- 【小梅哥FPGA进阶教程】第九章 基于串口猎人软件的串口示波器
九.基于串口猎人软件的串口示波器 1.实验介绍 本实验,为芯航线开发板的综合实验,该实验利用芯航线开发板上的ADC.独立按键.UART等外设,搭建了一个具备丰富功能的数据采集卡,芯航线开发板负责进行数 ...
- Node入门教程(11)第九章:Node 的网络模块
net网络模块 net模块是node对TCP或者IPC开发的封装,包括了客户端和服务器端相关API.对于阅读本文,请您有一定的网络编程的基础.您需要已经了解了: ip协议,会配置ip地址 了解dns解 ...
随机推荐
- laravel-cms学习笔记
学习地址: https://www.houdunren.com/edu/video/12045 laravel 文档地址: https://gitee.com/houdunren/code/blob/ ...
- postgresql建表空间、建库、建模式、建用户的规范
一.官方概念说明 1.表空间(表空间位置不应该位于数据目录内) 性能优化:您可以将高I/O的表和索引放在高速磁盘上,而将不经常访问的表放在普通的磁盘上. 管理磁盘空间:当数据库存储空间不足时,可以通过 ...
- 包含Symbol类型的对象遍历取key使用Reflect.ownKeys
Reflect.ownKeys(target)等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和
- [渗透测试] HTB_Surveillance WriteUp [上]
靶机:Surveillance (from Hack The Box) 工具:Kali Linux 目标:拿到user和root的一串32位hex字符串 ## 配置hosts 环境启动后,要设置 ...
- jsonp原理详解——终于弄明白了JSONP
什么是JSONP? 其实网上关于JSONP的讲解有很多,但却千篇一律,而且云里雾里,对于很多刚接触的人来讲理解起来有些困难,着用自己的方式来阐释一下这个问题,看看是否有帮助. 1.一个众所周知的问题, ...
- windows隐藏文件如何查看
1.组织 2.查看 3.显示隐藏文件
- webpack处理静态资源
像项目中字体资源是不需要进行打包处理的,可以直接的通过复制方式给打包到目标目录中 # 安装 npm i -D copy-webpack-plugin # 引入 const CopyPlugin = r ...
- dubbo~全局异常拦截器的使用与设计缺陷~续
上一次的介绍,主要围绕如何统一去捕获异常,以及为每一种异常添加自己的Mapper实现,并且我们知道,当在ExceptionMapper中返回非200的Response,不支持application/j ...
- php反序列化个人笔记
反序列化 什么是反序列化? 格式转换 序列化:对象转换为字符串或者数组等格式 反序列化:将数组或字符串转换成对象 为什么会出现安全漏洞? 魔术方法 如何利用漏洞? 通过构造pop链,找到代码的逻辑漏洞 ...
- 阿里云安全扫描漏洞修复fastjson,jackson,xstream,redis, 微信支付xml转对象 ForbiddenClassException
阿里云安全扫描漏洞修复fastjson,jackson,xstream,redis 1.fastjson漏洞fastjson爆发新的反序列化远程代码执行漏洞,黑客利用漏洞,可绕过autoType限制, ...