登录后台

页面导航

本文编写于 256 天前,最后修改于 256 天前,其中某些信息可能已经过时。

队列管理

队列特征

数据存储

队列可以存储固定大小的数据项目。队列最大可存储数据项条目叫"length"。"length"和每条数据项尺寸都是队列创建时设置的。
队列是最常见的FIFO缓存——先进先出缓存,数据是写入到队尾,删除是从队列头开始。图31展示了数据的读写过程,它就像FIFO一样使用。也可以从队头开始写,超写数据就是在队列头。

# 队列数据读,写顺实例。图31
----------     -----------------------   ----------
|Task A  |     |Queue                |   |Task B  |
|int x;  |     | ___ ___ ___ ___ ___ |   |int y;  |
|        |     -----------------------   |        |
----------                               ----------
创建一个队列,允许任务A任务B进行交流。队列最多可以保存5个整数。创建后队列是空的不包含任何数据。

# 下一步
----------     -----------------------   ----------
|Task A  |     |Queue                |   |Task B  |
|int x;  |     | ___ ___ ___ ___ _10_|   |int y;  |
|x=10;   |     -----------------------   |        |
---------- x发送到队列                   ----------
任务A写一个本地变量值到队列。由于队列之前是空的,所以只有一个数据项,这个值的位置即是队列头也是队列尾。

# 下一步
----------     -----------------------   ----------
|Task A  |     |Queue                |   |Task B  |
|int x;  |     | ___ ___ ___ 20_ _10_|   |int y;  |
|x=20;   |     -----------------------   |        |
---------- x发送到队列                   ----------
任务A再次写入队列前改变本地变量值,然后写入队列。目前队列中就包含两个值的副本。第一个值保存在队列头,新值插入到队列尾。队列还剩3个空位。
# 下一步
----------     -----------------------   ----------
|Task A  |     |Queue                |   |Task B  |
|int x;  |     | ___ ___ ___ 20_ _10_|   |int y;  |
|x=20;   |     -----------------------   |y等于10 |
----------              y从队列接收数据  ----------
任务B从队列中读(接收)一个数据。接收的数据是队列头的数据,就是任务A第一次写入的数据——10。
# 下一步
----------     -----------------------   ----------
|Task A  |     |Queue                |   |Task B  |
|int x;  |     | ___ ___ ___ ___ _20_|   |int y;  |
|x=20;   |     -----------------------   |y等于10 |
----------                               ----------
任务B删除一个项,只留下任务A写的第二个数据——20。下次任务B读会接收到的数据。目前队列还有4个空位。

实现队列的方式有两种:

  • 复制队列:复制方式意味着发送给队列的数据,会被复制一份保存于队列之中。
  • 引用队列:引用队列意味着发送给队列的数据,队列只保存数据指针,而不保存数据本身。

FreeRTOS使用复制的方式。复制队列相比于引用队列更加强大和简单:

  • 栈变量可以写入队列,即使在函数退出,变量已经释放之后,复制的变量也会在队列之中。
  • 发送给队列的数据可以不用首先分配缓存保存数据,可以后面复制数据到缓存中。
  • 写入数据的任务可以立即重复使用这个变量或缓存,而不会影响已经发送给队列的值。
  • 发送任务和接收任务完全是双向的。程序设计者不用考虑数据的所有者,哪个任务应该负责释放数据。
  • 复制队列不会阻碍队列用于引用队列。例如,当数据太大不适合直接放到队列中时,数据的指针确可以复制到队列中。
  • RTOS全权负责内存分配用于存储数据。
  • 内存保护系统中,任务可以访问的内存会被限制。因此只能使用读写任务都可以访问的有数据的RAM引用队列。复制队列不受这种限制。内核总是拥有所有权限,允许队列通过受保护内存传递数据。

多任务访问

队列是拥有自身权限的实体,知道它存在的任务或中断处理程序都能访问。任何任务都可以写同一个队列,任何任务也可以读队列。实际上,一个队列有多个写任务非常常见,相比而言一个队列多个读任务就要少一些了。

阻塞在读队列

当试图读一个队列,可以随意指定一个阻塞时间。队列为空时,可以指定等待队列数据可用在阻塞态保持的时间。一个等待可用数据,处于阻塞态的任务会自动转变为就绪态,当其他任务或中断写入数据到队列时。当期待等待数据的时间到期等待数据的任务也会自动转为就绪态。
队列可以有多个读任务,所以可能有多个任务在等一个队列数据,而处于阻塞。这时候,只会有一个任务变成可用的。解除阻塞状态的任务应该是优先级最高的那一个。如果阻塞的任务们优先级相同,等待最久的那个任务就会被解除阻塞。

阻塞在写队列

和读队列一样。可以随意指定写队列阻塞时间。因此,当队列已经满时,可以指定等待可用空间时最大阻塞时间。
队列可以有多个写入者,所以多个任务等待一个写入队列操作而阻塞的情况是有可能发生的。这时候只有一个任务会解除阻塞。解除阻塞的任务总是优先级最高的任务。如果优先级相同的任务阻塞在写同一个队列,阻塞时间最长的任务会解除阻塞。

队列的使用

xQueueCreate()接口

队列可能会在使用前创建。队列使用句柄引用,是一个QueueHandle_t类型引用的队列。xQueueCreate()接口创建一个队列,返回一个QueueHandle_t类型引用的队列。

FreeRTOS V9.0.0也包含了xQueueCreateStatic()函数,它会静态的分配队列空间。FreeRTOS在队列创建时从堆中分配队列空间。RAM空间既包括队列数据结构也包括队列包含的项目。xQueueCreate()函数在没有足够的堆空间分配队列时会返回NULL指针。

// xQueueCreate()原型
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

参数:
uxQueueLength: 队列一次可以保存的项目最大数量
uxItemSize:    队列可以保存的每个项目的字节大小
返回值:        返回NULL指针,队列不能被创建。没有足够的堆空间分配给队列数据结构
                返回非NULL表示成功创建队列。这个返回值保存了分配的队列数据结构句柄

xQueueSendToBack()&&xQueueSendToFront()接口

xQueueSendToBack()是将写入数据存储到队列最后,xQueueSendToFront()是将写入数据存储到队列头。
xQueueSend()和xQueueSendToBack()是相同的。
注意不要在中断处理程序中使用xQueueSendToBack()和xQueueSendToFront(),可以使用中断安全版本xQueueSendToBackFromISR()和xQueueSendToFrontFromISR()。

// 写入队列函数原型
BaseType_t xQueueSendToBack(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
BaseType_t xQueueSendToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);

// 参数
// xQueue: 数据要写入的队列句柄。这个队列句柄是调用xQueueCreate()函数创建队列的返回值。
// pvItemToQueue: 指向要复制进队列的数据指针。队列每个项目的大小是在队列创建的时候设置的,会将pvItemToQueue指向数据的这些为复制到队列中,就是写入操作。
// xTicksToWait: 如果队列已经写满,队列会进入阻塞态等待空间可用(有空闲空间保存数据)的最大等待tick数量。如果这个参数是0 xQueueSendToBack()和xQueueSendToFront()都会立即返回。这里的最大阻塞时间使用tick数量指定的。而非绝对的时钟时间,tick数量和绝对时钟时间之间比例取决于系统频率。pdMS_TO_TICKS()宏可以将时钟时间转换成tick数量,用于这个函数的第三个参数,注意这里的时钟时间是ms作为单位。将xTicksToWait设置成portMAX_DELAY会使任务一直等待下去(没有超时时间),FreeRTOS.h中的`INCLUE_vTaskSuspend`需要设置为1才支持这个特性。
// 返回值: 有两个可能返回值。
           pdPASS:写入成功。如果超时时间不是0,函数返回前任务可能进入阻塞态,等待队列空间空闲。但数据在超时之前写入成功,就返回写入成功。
           errQUEUE_FULL:因为队列已满数据不能写入到队列。如果超时值不为0,任务等待其他任务或中断释放队列空间而进入阻塞态,设置的超时时间在队列空间被释放前,就返回这个错误。

xQueueReceive()接口

xQueueReceive()用于从一个队列上读取数据。读取的项会从队列中删除。
注意不要在中断处理函数中使用xQueueReceive()函数,中断处理函数中应该使用它的中断版本xQueueReceiveFromISR()

// xQueueReceive()原型
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);

// 参数
// xQueue: 要读取数据队列句柄。这个句柄是由xQueueCreate()函数创建返回的。
// pvBuffer: 接收数据复件的内存指针地址。队列存储数据项大小是在队列创建时设定的。pvBuffer指针指向内存必须足够保存队列项。
// xTicksToWait: 队列为空时,任务等待有数据可以读取而进入阻塞状态,维持这样的阻塞状态的最大tick数量。如果xTicksToWait为0,xQueueReceive()会立即返回。阻塞时间是用tick数量表示的,而非时钟时间,tick数量的多少和系统频率有关。pdMS_TO_TICKS可以用来将时钟时间转换成tick数量,刚好总在这里。设置xTicksToWait为portMAX_DELAY会引起xQueueReceive()一直阻塞(没有超时时间)——直到有数据可以读取。但需要FreeRTOS.h文件中的INCLUE_vTaskSuspend设置为1。
// 返回值: 有两种可能返回值
           pdPASS: 读取成功。如果xTicksToWait不为0,调用任务可能进入阻塞态等待队列数据可用。但在超时tick数量到来之前读取操作成功,就返回这个值。
           errQUEUE_EMPTY: 因为队列为空,不能从队列读取到数据。如果xTicksToWait不为0,调用任务可能进入阻塞态等待其他任务或中断处理程序向队列写入数据。但写入数据在超时后到来,就返回这个errQUEUE_EMPTY。

uxQueueMessageWaiting()接口

uxQueueMessagesWaiting()用于获取队列中保存的数据数量。
注意uxQueueMessagesWaiting不能用于中断处理程序,可以使用uxQueueMessagesWaitingFromISR()中断版本。

// uxQueueMessagesWaiting()原型
UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue);
// 参数
// xQueue: 待计算队列句柄。这个队列句柄是使用xQueueCreate()创建队列时返回的句柄。
// 返回值; 当前队列中保存的项目数量。如果返回0表示对队列为空。
博主已关闭本页面的评论功能