FreeRTOS官方翻译文档——第二章 队列管理

2.1 概览
基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权
限的小程序。这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。
FreeRTOS 中所有的通信与同步机制都是基于队列实现的。
2.2队列的特性
数据存储
队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称
为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。
通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读
出。当然,由队列首写入也是可能的。
往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把
队列中的数据拷贝删除。 图 19 展现了队列的写入与读出过程,以及读写操作对队列中
数据的影响。

可被多任务存取
队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以
向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。

读队列时阻塞
当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如
果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程
往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超
过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞
状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除
阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级
相同,那么被解除阻塞的任务将是等待最久的任务。

写队列时阻塞
同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写
队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。

由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞
状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除
阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级
相同,那么被解除阻塞的任务将是等待最久的任务。

2.3 使用队列
xQueueCreate() API 函数
队列在使用前必须先被创建。
队列由声明为 xQueueHandle 的变量进行引用。 xQueueCreate()用于创建一个队
列,并返回一个 xQueueHandle 句柄以便于对其创建的队列进行引用。
当创建队列时, FreeRTOS 从堆空间中分配内存空间。分配的空间用于存储队列数
据结构本身以及队列中包含的数据单元。如果内存堆中没有足够的空间来创建队列,
xQueueCreate()将返回 NULL。

xQueueSendToBack() 与 xQueueSendToFront() API 函数
如同函数名字面意思所期望的一样, xQueueSendToBack()用于将数据发送到队列
尾;而 xQueueSendToFront()用于将数据发送到队列首。
xQueueSend()完全等同于 xQueueSendToBack()。
但 切 记 不 要 在 中 断 服 务 例 程 中 调 用 xQueueSendToFront() 或
xQueueSendToBack()。系统提供中断安全版本的 xQueueSendToFrontFromISR()与
xQueueSendToBackFromISR()用于在中断服务中实现相同的功能。

xQueueReceive()与 xQueuePeek() API 函数
xQueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。

xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到
的单元。 xQueuePeek()从队列首接收到数据后,不会修改队列中的数据,也不会改变
数据在队列中的存储序顺。
切记不要在中断服务例程中调用 xQueueRceive()和 xQueuePeek()。

uxQueueMessagesWaiting() API 函数
uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数。
切记不要在中断服务例程中调用 uxQueueMessagesWaiting()。应当在中断服务中
使用其中断安全版本 uxQueueMessagesWaitingFromISR()。

使用队列传递复合数据类型
一个任务从单个队列中接收来自多个发送源的数据是经常的事。通常接收方收到数
据后,需要知道数据的来源,并根据数据的来源决定下一步如何处理。一个简单的方式
就是利用队列传递结构体,结构体成员中就包含了数据信息和来源信息。 图 23 对这一方案进行了展现。

从图 23 中可以看出:
? 创建一个队列用于保存类型为 xData 的结构体数据单元。结构体成员包括了一个数
据值和表示数据含义的编码,两者合为一个消息可以一次性发送到队列。
? 中央控制任务用于完成主要的系统功能。 其必须对队列中传来的输入和其它系统状
态的改变作出响应。
? CAN 总线任务用于封装 CAN 总线的接口功能。当 CAN 总线任务收到并解码一个消
息后,其将把解码后的消息放到 xData 结构体中发往控制任务。结构体的 iMeaning
成员用于让中央控制任务知道这个数据是用来干什么的 — 从图中的描述可以看
出,这个数据表示电机速度。结构体的 iValue 成员可以让中央控制任务知道电机的
实际速度值。
? 人机接口(HMI)任务用于对所有的人机接口功能进行封装。设备操作员可能通过各种
方式进行命令输入和参数查询,人机接口任务需要对这些操作进行检测并解析。当
接收到一个新的命令后,人机接口任务通过 xData 结构将命令发送到中央控制任务。
结构体的 iMeaning 成员用于让中央控制任务知道这个数据是用来干什么的 — 从
图中的描述可以看出,这个数据表示一个新的参数设置。结构体的 iValue 成员可以
让中央控制任务知道具体的设置值。

工作于大型数据单元
如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对
数据本身在队列上一字节一字节地拷贝进或拷贝出。传递指针无论是在处理速度上还是
内存空间利用上都更有效。但是,当你利用队列传递指针时,一定要十分小心地做到以下两点:

1. 指针指向的内存空间的所有权必须明确
当任务间通过指针共享内存时,应该从根本上保证所不会有任意两个任务同时
修改共享内存中的数据,或是以其它行为方式使得共享内存数据无效或产生一致性
问题。原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;
共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
2. 指针指向的内存空间必须有效
如果指针指向的内存空间是动态分配的,只应该有一个任务负责对其进行内存
释放。当这段内存空间被释放之后,就不应该有任何一个任务再访问这段空间。
切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再有效。
 这个时候,再回过头去看看上一篇随笔,为什么官方demo使用结构体指针,因为这样效率更高。

时间: 2024-07-30 13:53:11

FreeRTOS官方翻译文档——第二章 队列管理的相关文章

Prism 文档 第二章 初始化Prism应用程序

                                                                       第二章 初始化Prism应用程序 本章将讨论为了使一个Prism应用程序的启动和运行哪些是必须的.Prism的应用程序在启动过程中需要注册和配置,这被称为引导应用程序. 什么是Bootstrapper? bootstrapper是一个类,通过Prism类库负责一个应用程序建立的初始化.通过使用bootstrapper,对于如何将Prism库组件连接到您的应

Welcome to Swift (苹果官方Swift文档初译与注解十七)---108~115页(第二章)

Range Operators (范围操作符) 在Swift中包含两种范围操作符,它们都是一个数值范围表达式的一种缩写方式. Closed Range Operator (闭区间范围操作符) 闭区间范围操作符(a...b)定义声明了一个从a到b之间的范围,并且包括a和b. 闭区间操作符通常用在迭代查询一个范围内所有的值,例如for-in循环: for index in 1...5 { println("\(index) times 5 is \(index * 5)") } // 1

Welcome to Swift (苹果官方Swift文档初译与注解十一)---70~76页(第二章)

Type Aliases (类型别名) 类型别名是个已经存在的类型定义另一个名字.定义类型别名的时候,使用关键字typealias. 当你想要使用名字来引用一个已经存在的类型时,类型别名将非常有效,并且更适合代码的上下文理解.例如,当要处理指定大小的一个数据时: typealias AudioSample = UInt16 一旦定义了一个类型别名,就可以在任何地方来代替原来的类型名: var maxAmplitudeFound = AudioSample.min // maxAmplitudeF

Welcome to Swift (苹果官方Swift文档初译与注解十)---63~69页(第二章)

如果你将整型与浮点型一起使用,结果将被认为是Double类型: et anotherPi = 3 + 0.14159 // anotherPi 的类型是Double 上述代码中,3的值是没有明确说明类型,因此,根据剩余部分的浮点类型可以确定返回值为Double. Numeric Literals (数值的进制表示) 整数类型可以进行如下表示: A decimal number, with no prefix         // 十进制数值,不需要前缀符号; A binary number, w

Welcome to Swift (苹果官方Swift文档初译与注解十二)---77~83页(第二章)

Optionals (可选类型) 当一个值可能有值,也可能没有值,这种情况你可以使用可选类型.可选类型的意思是: 有个一个值,它等于x,或者,根本没有任何值. 注意点: 可选类型的概念在C和OC中都是没有的.在OC中最相似的情况是,一个方法返回nil或者返回一个对象.OC中返回nil意思是没有合法的对象.然而,这只能针对对象,不能用在结构体,基本 数据类型,或者枚举值.对于这些类型,OC的方法只能返回固定的某个值(比如NSNotFound)来提示没有某个值.这要去方法的调用者清楚的记得返回的固定

Welcome to Swift (苹果官方Swift文档初译与注解二十)---133~139页(第二章..本节完)

Unicode (Unicode码) Unicode是一种国际标准的文本编码.它的标准表中几乎包含所有语言的任意字符,并且可以通过扩展文件或者网页读写这些字符. 在Swift中,String(字符串)类型和Character(字符)类型完全兼容Unicode,而且它们也支持非Unicode码. Unicode Terminology (Unicode 术语) 每个Unicode码都可以用一个或者多个Unicode标量表示.对于一个字符来说,一个Unicode标量都是一个唯一的21位的值(或名称)

Welcome to Swift (苹果官方Swift文档初译与注解十八)---116~122页(第二章)

Strings and Characters (字符串和字符) 字符串是一组字符组成的序列,例如“hello, world" 或 "albatross”.在Swift中,字符串使用String类型进行声明,它代表一组由Character(字符)类型组成的值的序列. String类型和Character类型提供了一种在代码中处理 (兼容Unicode) 文本的快速方式.字符串创建和管理的语法与C语言的相似,都属于轻量级并且可读性好.字符串的拼接只是简单的 使用加号(+)操作符,而且字符串

Welcome to Swift (苹果官方Swift文档初译与注解十九)---123~132页(第二章..本章节还剩6页)

Working with Characters (与字符相关) 在Swift中,String类型表示一组有序字符的值.每个字符都是一个Unicode符号.可以使用for-in循环来遍历字符串中的每个字符: for character in "Dog!??" {   println(character) } // D // o // g // ! // ?? 在Swift中也可以使用Character类型来显式的创建一个单字符的常量或者变量: let yenSign: Character

Welcome to Swift (苹果官方Swift文档初译与注解十四)---90~93页(第二章)

Debugging with Assertions (断言调试) 断言是一个假设逻辑条件为真的运行时检查机制.在执行任意代码之前,可以使用断言来确认一个基本的条件情况.如果条件判断为真,代码将继续执行,如果条件为假,代码将会结束,应 用程序也会退出. 如果在调试环境中,运行的代码触发了一个断言(比如你在Xcode中编译并运行一个应用),你可以明确的看到错误的状态发生在哪里,并且查询应用程序在这个时刻的状态.断言也可以让 你使用适合的调测显示信息作为断言信息. 可以写一个全局函数assert来触发