Queue队列是最基本的数据结构,在FreeRTOS v10.0后提供了另外两种高级数据结构为Streambuffer和MessageBuffer,称为流式缓冲区和消息缓冲区。了解它们的区别能够更好的在工作中选用合适的结构类型。本文通过引用官方文档和论坛中权威的回帖展示三者的区别,不做具体的使用介绍。
1 Queue队列
队列是任务间通信的最基础形式,也是最灵活的方式。操作系统中队列是以项(item)为基本单元。
1 | QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, |
从其创建函数中传参可以看出,对列需要一个固定的长度,并且每个项的大小也是固定的。从发送和接收函数中可以看出,其默认传递的方式为拷贝,将指针指向的内容拷贝到自己的内存中。所以发送完成后可以修改原来的数据存储区,同样接收完成后,数据会从队列存储区中删除。当然,可以通过传递指针的指针解决大数据量拷贝慢的问题,同样也会存在其它问题(例如接收任务接收到数据之前,该区域内容不得更改等),这里不再详述。
2 StreamBuffer流缓冲区
流式缓冲区是在队列的基础上,针对单一生产者和消费者场景,优化的一种更适合的数据结构。 流缓冲区允许将字节流从中断服务例程传递到任务,或从一个任务传递到另一个任务。字节流可以是任意长度,并且不一定具有开头或结尾。可以一次写入任意数量的字节,并且可以一次读取任意数量的字节。数据通过复制传递 – 数据由发送方复制到缓冲区中,并通过读取从缓冲区中复制出来。
1 | StreamBufferHandle_t xStreamBufferCreate( size_t xBufferSizeBytes, |
上面简单列举了创建,发送和接收的函数,不难看出,流缓冲区可以任意长度读写数据,并且支持阻塞式访问。同时提供了一种辅助的触发方式(可以设定在多少字节时触发)。相比较队列而言在串口等数据调试和传输环境中具有更佳的可用性。
与大多数其他FreeRTOS API不同的是,流缓冲器针对单个读取器单写入器场景进行了优化,例如将数据从中断服务例程传递到任务,或者从双核CPU上的一个微控制器核心传递到另一个。在多任务读写的环境中,需要将该函数相关的调用置于关键区域内(taskENTER_CRITICAL和taskEXIT_CRITICAL),也可以使用互斥信号量来解决。我感觉关键区域的方式是最简单的。
NOTICE:
这里有个小问题,手册中在taskENTER_CRITICAL中明确说明了关键区域内不允许调用FreeRTOS API函数,那么和这一数据结构的使用有些冲突。下面是我对这一问题在官方论坛上询问和解答。
whzh – 23 hours ago
I see the following content in the manual, are
these two paragraphs contradictory?Manual P365 If there are to be multiple different writers then the
application writer must place each call to a writing API function
(such as xStreamBufferSend()) inside a critical section and use a send
block time of 0.Manual P59 FreeRTOS API functions must not be called from within a
critical section.Richard Damon – 21 hours ago
Both those statements are in my mind a bit simplified. First,
Streambuffers don’t need a critical section, but do need some form of
protection that you NEVER have multiple tasks trying to read or write
the buffer at a given time. A critical section is one simple way to do
this, having a Mutex protect the access should also work (and that
gets around the need for 0 block time.Second, the limitiation isn’t so much that they can’t be called from a
critical section, but that no FreeRTOS API call should block or
attempt to change the running task inside a critical section. (some
ports actually don’t have a problem with it, but some do).
3 MessageBuffer 消息缓冲区
消息缓冲区是在流式缓冲区的基础上实现的,其进一步针对“消息”进行设计改进。MessageBuffer每一条消息的写入增加了一个字节用来表示该条消息的长度。读取时需要一次性读出至少一条消息,否则会返回 0.
1 | MessageBufferHandle_t xMessageBufferCreate( size_t xBufferSizeBytes ); |
综上,对比了三种数据结构的区别,每一种数据结构都是在上一中简单结构中的增强和针对性的改进。确定的场景中选用合适的数据结构将事半功倍。