FreeRTOS消息队列
队列又称消息队列,是一种常用于任务间通信的数据结构, 队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间 xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。 当队列中有新消息时, 被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。 消息队列是一种异步的通信方式。
通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO),但是也支持后进先出原则(LIFO)
FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:
消息支持先进先出方式排队,支持异步读写工作方式
读写队列均支持超时机制
消息支持后进先出方式排队, 往队首发送消息(LIFO)
可以允许不同长度(不超过队列节点最大值)的任意类型消息。
一个任务能够从任意一个消息队列接收和发送消息。
多个任务能够从同一个消息队列接收和发送消息
当队列使用结束后,可以通过删除队列函数进行删除。
创建消息队列时 FreeRTOS 会先给消息队列分配一块内存空间,这块内存的大小等于消息队列控制块大小加上(单个消息空间大小与消息队列长度的乘积) ,接着再初始化消息队列,此时消息队列为空。 FreeRTOS 的消息队列控制块由多个元素组成,当消息队列被创建时,系统会为控制块分配对应的内存空间,用于保存消息队列的一些信息如消息的存储位置,头指针 pcHead、尾指针 pcTail、消息大小 uxItemSize 以及队列长度 uxLength 等。同时每个消息队列都与消息空间在同一段连续的内存空间中,在创建成功的时候,这些内存就被占用了,只有删除了消息队列的时候,这段内存才会被释放掉,创建成功的时候就已经分配好每个消息空间与消息队列的容量,无法更改,每个消息空间可以存放不大于消息大小 uxItemSize 的任意类型的数据, 所有消息队列中的消息空间总数即是消息队列的长度,这个长度可在消息队列创建时指定。
任务或者中断服务程序都可以给消息队列发送消息, 当发送消息时, 如果队列未满或者允许覆盖入队, FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。当其它任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一
个错误码 errQUEUE_FULL。
发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时, 发送的位置是消息队列队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理
当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成,消息队列将被永久性的删除。
当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成,消息队列将被永久性的删除
消息队列的阻塞机制
很简单,因为 FreeRTOS 已经为我们做好了,我们直接使用就好了,每个对消息队列读写的函数,都有这种机制,我称之为阻塞机制。假设有一个任务 A 对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息,那么此时任务 A 有 3 个选择:第一个选择,任务 A 扭头就走,既然队列没有消息,那我也不等了,干其它事情去,这样子任务 A 不会进入阻塞态;第二个选择,任务 A 还是在这里等等吧,可能过一会队列就有消息,此时任务 A 会进入阻塞状态,在等待着消息的道来,而任务 A 的等待时间就由我们自己定义,比如设置 1000 个系统时钟节拍 tick 的等待,在这 1000 个 tick 到来之前任务 A 都是处于阻塞态,当阻塞的这段时间任务 A 等到了队列的消息,那么任务 A 就会从阻塞态变成就绪态,如果此时任务 A 比当前运行的任务优先级还高,那么,任务 A 就会得到消息并且运行;假如 1000 个 tick 都过去了,队列还没消息,那任务 A 就不等了,从阻塞态中唤醒,返回一个没等到消息的错误代码,然后继续执行任务 A 的其他代码;第三个选择,任务 A 死等,不等到消息就不走了,这样子任务 A 就会进入阻塞态,直到完成读取队列的消息。
而在发送消息操作的时候,为了保护数据,当且仅当队列允许入队的时候,发送者才能成功发送消息;队列中无可用消息空间时,说明消息队列已满,此时,系统会根据用户指定的阻塞超时时间将任务阻塞,在指定的超时时间内如果还不能完成入队操作,发送消息的任务或者中断服务程序会收到一个错误码 errQUEUE_FULL,然后解除阻塞状态;当然, 只有在任务中发送消息才允许进行阻塞状态,而在中断中发送消息不允许带有阻塞机制的,需要调用在中断中发送消息的 API 函数接口,因为发送消息的上下文环境是在中断中,不允许有阻塞的情况。
假如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权。
消息队列的应用场景
消息队列可以应用于发送不定长消息的场合,包括任务与任务间的消息交换,队列是FreeRTOS 主要的任务间通讯方式,可以在任务与任务间、中断和任务间传送信息,发送到队列的消息是通过拷贝方式实现的, 这意味着队列存储的数据是原数据,而不是原数据的引用。
消息队列常用函数
消息队列创建函数 xQueueCreate()
xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。 队列句柄其实就是一个指向队列数据结构类型的指针。队列就是一个数据结构, 用于任务间的数据的传递。 每创建一个新的队列都需要为其分配 RAM,一部分用于存储队列的状态, 剩下的作为队列消息的存储区域。使用
xQueueCreate()创建队列时, 使用的是动态内存分配,所以要想使用该函数必须 在FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1 来使能, 这是个用于使能动态内存分配的宏, 通常情况下,在 FreeRTOS 中, 凡是创建任务,队列,信号量和互斥量等内核对象都需要使用动态内存分配, 所以这个宏默认在 FreeRTOS.h 头文件中已经使能(即定义为 1) 。如果想使用静态内存,则可以使用 xQueueCreateStatic() 函数来创建一个队列。使用静态创建消息队列函数创建队列时需要的形参更多,需要的内存由编译的时候预先分配好, 一般很少使用这种方法。
消息队列静态创建函数 xQueueCreateStatic()
消息队列删除函数 vQueueDelete()
队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了,但是需要注意的是,如果某个消息队列没有被创建,那也是无法被删除的,
消息队列删除函数 vQueueDelete()的使用也是很简单的,只需传入要删除的消息队列的句柄即可,调用函数时,系统将删除这个消息队列。需要注意的是调用删除消息队列函数前,系统应存在 xQueueCreate()或 xQueueCreateStatic()函数创建的消息队列。 此外vQueueDelete()也可用于删除信号量。 如果删除消息队列时,有任务正在等待消息, 则不应该进行删除操作(官方说的是不允许进行删除操作,但是源码并没有禁止删除的操作,使用的时候注意一下就行了),
向消息队列发送消息函数
任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队, FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。当其它任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码 errQUEUE_FULL。
发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,发送的位置是消息队列队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。
xQueueSend()与 xQueueSendToBack()
xQueueSend()是一个宏, 宏展开是调用函数 xQueueGenericSend(),这个函数在后面会详 细 讲 解 其 实 现 过 程 。 该 宏 是 为 了 向 后 兼 容 没 有 包 含 xQueueSendToFront() 和xQueueSendToBack() 这 两 个 宏 的 FreeRTOS 版 本 。 xQueueSend() 等 同 于xQueueSendToBack()
xQueueSend() 用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以引用的形式。该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR() 来代替
xQueueSendFromISR()与 xQueueSendToBackFromISR()
xQueueSendFromISR()是一个宏, 宏展开是调用函数xQueueGenericSendFromISR()。 该宏是 xQueueSend()的中断保护版本,用于在中断服务程序中向队列尾部发送一个队列消息,等价于 xQueueSendToBackFromISR()。
xQueueSendToFront()
xQueueSendToFron() 是 一 个 宏 , 宏 展 开 也 是 调 用 函 数 xQueueGenericSend() 。xQueueSendToFront()用于向队列队首发送一个消息。消息以拷贝的形式入队,而不是以引用的形式。该函数绝不能在中断服务程序里面被调用, 而是必须使用带有中断保护功能的xQueueSendToFrontFromISR ()来代替。
xQueueSendToFrontFromISR()
xQueueSendToFrontFromISR() 是 一 个 宏 , 宏 展 开 是 调 用 函 数
xQueueGenericSendFromISR()。 该宏是 xQueueSendToFront()的中断保护版本,用于在中断服务程序中向消息队列队首发送一个消息。
通用消息队列发送函数 xQueueGenericSend()(任务)
发送消息的函数都是 xQueueGenericSend()展开的宏定义真正起作用的就是 xQueueGenericSend()函数
消息队列发送函数 xQueueGenericSendFromISR()(中断)
有任务中发送消息的函数,当然也需要有在中断中发送消息函数, 其实这个函数跟 xQueueGenericSend() 函 数 很 像 , 只 不 过 是 执 行 的 上 下 文 环 境 是 不 一 样 的 ,xQueueGenericSendFromISR()函数只能用于中断中执行,是不带阻塞机制的
从消息队列读取消息函数
当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
xQueueReceive()与 xQueuePeek()
xQueueReceive() 是 一 个 宏 , 宏 展 开 是 调 用 函 数 xQueueGenericReceive() 。xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。 接收的消息是以拷贝的形式进行的, 所以我们必须提供一个足够大空间的缓冲区。 具体能够拷贝多少数据到缓冲区,这个在队列创建的时候已经设定。该函数绝不能在中断服务程序里面被调用, 而是必须使用带有中断保护功能的 xQueueReceiveFromISR ()来代替。
只不过xQueuePeek()函数接收消息完毕不会删除消息队列中的消息而已
xQueueReceiveFromISR()与 xQueuePeekFromISR()
xQueueReceiveFromISR()是 xQueueReceive ()的中断版本,用于在中断服务程序中接收一个队列消息并把消息从队列中删除; xQueuePeekFromISR()是 xQueuePeek()的中断版本,用于在中断中从一个队列中接收消息, 但并不会把消息从队列中移除。
说白了这两个函数只能用于中断,是不带有阻塞机制的,并且是在中断中可以安全调用
从队列读取消息函数 xQueueGenericReceive()
由于在中断中接收消息的函数用的并不多,我们只讲解在任务中读取消息的函数——xQueueGenericReceive(),
消息队列使用注意事项
使用 xQueueSend()、 xQueueSendFromISR()、 xQueueReceive()等这些函数之前应先创建需消息队列,并根据队列句柄进行操作。
队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据。当然也 FreeRTOS 也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进队列的数据
在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数据区域大小不小于消息大小,否则,很可能引发地址非法的错误。无论是发送或者是接收消息都是以拷贝的方式进行, 如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。
队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读出倒是用的比较少
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)