简介
所谓"
任务通知
"
,你可以反过来读
"
通知任务
"
。
我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。
使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信。
使用任务通知时,任务结构体 TCB
中就包含了内部对象,可以直接接收别人发过来的
"通知
"
:
任务通知的特性
优势及限制
任务通知的优势:
效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有大的优势。
更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的限制:
不能发送数据给 ISR
:ISR 并没有任务结构体,所以无法使用任务通知的功能给
ISR
发送数据。但是
ISR可以使用任务通知的功能,发数据给任务。
数据只能给该任务独享使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务和ISR
都可以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这些数据。
在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。
无法缓冲数据:使用队列时,假设队列深度为 N
,那么它可以保持
N
个数据。使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。
无法广播给多个任务:使用事件组可以同时给多个任务发送事件。使用任务通知,只能发个一个任务。
如果发送受阻,发送方无法进入阻塞状态等待假设队列已经满了,使用
xQueueSendToBack()
给队列发送数据时,任务可以进入阻塞状态等待发送完成。使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返回错误。
通知状态和通知值
每个任务都有一个结构体:TCB(Task Control Block),里面有 2 个成员:
一个是 uint8_t 类型,用来表示通知状态。
一个是 uint32_t 类型,用来表示通知值。
![](https://i-blog.csdnimg.cn/direct/eb5edc6f9c1f44c98e42fdff8139b498.png)
通知状态有 3 种取值:
taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
taskWAITING_NOTIFICATION
:任务在等待通知
taskNOTIFICATION_RECEIVED
:任务接收到了通知,也被称为
pending(
有数据了,待处理)
通知值可以有很多种类型:
计数值
位
(
类似事件组
)
任意数值
任务通知的使用
使用任务通知,可以实现轻量级的队列(
长度为
1)
、邮箱
(
覆盖的队列
)
、计数型信号量、二进制信号量、事件组。
两类函数
任务通知有 2 套函数,简化版、专业版,列表如下:
简化版函数的使用比较简单,它实际上也是使用专业版函数实现的。
专业版函数支持很多参数,可以实现很多功能。
xTaskNotifyGive/ulTaskNotifyTake
在任务中使用 xTaskNotifyGive 函数,在
ISR
中使用
vTaskNotifyGiveFromISR
函数,都是直接给其他任务发送通知:
使得通知值加一。
并使得通知状态变为"pending"
,也就是
taskNOTIFICATION_RECEIVED
,表示有数据了、待处理。
可以使用 ulTaskNotifyTake
函数来取出通知值:
如果通知值等于 0
,则阻塞
(
可以指定超时时间
)当通知值大于 0 时,任务从阻塞态进入就绪态 在 ulTaskNotifyTake 返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零使用 ulTaskNotifyTake
函数可以实现轻量级的、高效的二进制信号量、计数型信号量。
这几个函数的原型如下:
xTaskNotifyGive 函数的参数说明如下:
![](https://i-blog.csdnimg.cn/direct/49ee2328b57d4173accc0fa76390b2ba.png)
vTaskNotifyGiveFromISR 函数的参数说明如下:
ulTaskNotifyTake 函数的参数说明如下:
xTaskNotify/xTaskNotifyWait
xTaskNotify
函数功能更强大,可以使用不同参数实现各类功能,比如:
1、让接收任务的通知值加一:这时
xTaskNotify()
等同于
xTaskNotifyGive()
2、
设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的事件组。
3、把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成功。这就是轻量级的、长度为 1
的队列。
4、用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖都成功。类似
xQueueOverwrite()
函数,这就是轻量级的邮箱。
xTaskNotify() 比 xTaskNotifyGive() 更灵活、强大,使用上也就更复杂。
xTaskNotifyFromISR()是它对应的 ISR 版本
这两个函数用来发出任务通知,使用哪个函数来取出任务通知呢?
使用
xTaskNotifyWait()
函数!它比
ulTaskNotifyTake()
更复杂:
可以让任务等待(可以加上超时时间
)
,等到任务状态为
"pending"(
也就是有数据)。
还可以在函数进入、退出时,清除通知值的指定位。
这几个函数的原型如下:
xTaskNotifyWait 函数列表如下:![](https://i-blog.csdnimg.cn/direct/5c830ea47fe94e468cb70f4397792249.png)
任务通知内部机制
FreeRTOS的任务通知(task notification)提供了一种任务间轻量级的同步和通信机制,允许任务相互通知和等待事件。其内部机制如下:
1.32位的通知值(Notification Value):
2.每个任务通知都是一个32位的无符号整数,可以被发送和接收。
3.任务可以发送通知给其他任务,也可以等待来自其他任务的通知。
4.发送通知:
5.任务可以使用 xTaskNotifyGive() 函数向其他任务发送通知。
6.这个函数会将目标任务的通知值递增。如果目标任务在等待通知,它将会被唤醒。
7.接收通知:
8.任务可以使用 ulTaskNotifyTake() 函数等待通知。
9.如果任务的通知值为0,它将阻塞在这个函数调用上,直到接收到非零的通知值。
10.如果任务的通知值已经是非零的,它会立即返回,并减少通知值。
11.通知值的处理:
12.通知值可以被用来传递简单的状态信息或者事件。
13.接收任务可以根据通知值的具体内容执行不同的操作或者决策。
14.任务通知的清零和超时处理:
15.可以使用 ulTaskNotifyTake() 函数的第二个参数来设置超时时间,超时后函数会返回一个特殊的值以指示超时。
16.可以使用 ulTaskNotifyValueClear() 函数来清除任务的通知值。
我们其实可以把任务通知的一系列操作看成之前讲的队列,只不过任务通知无需创建队列结构体,因为任务通知的对象是任务,在创建任务的时候,就已经在TCB结构体里面定义了任务通知值和任务状态,我们只需要对每个任务的这两个值进行操作就可以了。
至于说的那些阻塞、唤醒、等待,思路和前面的一模一样,都是利用关闭调度、链表的方法来实现的,如果有不明白的,可以去看我之前的一系列的讲解,会有详细讲解如何把任务放进去阻塞链表和唤醒阻塞链表里的任务。