队列 (Queue)是最核心的、最常用的任务间通信(IPC)机制之一。它提供了任务之间、任务和中断服务程序(ISR)之间安全传递数据的通道。
1. 队列的本质与特点
数据结构:本质是一个先进先出(FIFO)的缓刑缓冲区。数据项按写入的顺序存储,按写入的顺序读出。
数据传递:通过拷贝实现数据传递。发送方将数据复制到队列的存储区,接收方从队列的 存储区复制数据到自己的内存空间。这意味着:
(1)发送方和接收方不想需要共享内存地址(增强了隔离性)
(2)传递的是数据的副本,原始数据在发送后可以进行安全修改。
(3)适用于传递小型、固定大小的数据项(结构体、基本类型、小数组)。传递大型数据(如图像)应使用指针(需小心挂你内存生命周期)。
线程安全:FREERTOS中的队列是线程安全的。多个任务或ISR可以安全的同时访问同一个队列(通过接收或发送的API),内核内部会处理同步和互斥的问题。
阻塞机制:队列操作(发送和接收)支持阻塞。当任务尝试从空队列中读取到数据时,它可以阻塞等待直到有数据可用;当任务向满队列写入数据时,他可以阻塞等待直到队列有空间。阻塞时间可以指定(portTICK_PERIOD_MS或 pdMS_TO_TICKS转换)。
多任务访问:可以被任意数量的任务或ISR访问。
长度与项大小:在创建队列时指定:
uxQueueLength:队列能容纳的最大数据项数量。
uxItemSize:每个数据项的大小(字节数)
2. 队列的核心操作 API (任务上下文)
创建队列:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数:
uxQueueLength:队列能容纳的最大数据项数量。
uxItemSize:每个数据项的大小(字节数)
返回值:成功返回句柄,失败返回NULL
发送数据到队尾(入队-Enqueue):
BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );
BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ); // 同上
参数:
xQueue(输入参数)
-
类型:
QueueHandle
pvItemToQueue(输入参数)
-
类型:
const void *(指向常量 void 的指针) -
含义: 指向要发送到队列的数据项的指针。
xTicksToWait(输入参数)
-
类型:
TickType_t -
含义: 任务在队列满时,愿意等待队列腾出空间的最大时间(以系统节拍 ticks 为单位)。
-
作用: 控制当队列满时,调用任务的阻塞行为。
-
0:立即返回errQUEUE_FULL。 -
portMAX_DELAY:无限期阻塞(需configUSE_MUTEXES配置)。 -
N:阻塞最多 N 个系统节拍 (ticks)。
发送数据到队头(插队):
BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );
//新数据项成为队列头,下次接收操作将首先得到它。其他行为同 xQueueSendToBack
从队列接收数据(出队 -Dequeue):
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait );
-
如果队列非空,立即接收成功 (
pdPASS)。 -
从队头移除一个数据项 并复制到pvBuffer
-
如果队列空,根据
xTicksToWait阻塞:-
0:立即返回errQUEUE_EMPTY。 -
portMAX_DELAY:无限期阻塞。 -
N:阻塞最多 N 个系统节拍。
-
窥视数据(Peek):
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait );
从队列头部复制一个数据项到 pvBuffer,但不移除该数据项。队列内容不变。阻塞行为同 xQueueReceive。
覆盖写入 (用于长度=1的队列):
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );
-
专为单数据项队列 (
uxQueueLength = 1) 设计。 -
无论队列空或满,总是写入成功 (
pdPASS)。 -
如果队列满,会覆盖已有的数据项(即唯一的数据项)。
-
保证队列中始终是最新的数据。
-
无阻塞(总是立即成功)。
删除队列:
void vQueueDelete( QueueHandle_t xQueue );
释放队列占用的内存。删除后不能再使用该队列句柄。
3.中断安全API(ISR上下文)
在中断服务程序 (ISR) 中操作队列必须使用以 FromISR结尾的 API。这些 API:
-
不会阻塞(ISR 中不允许阻塞)。
-
使用更轻量级的同步机制(如暂时屏蔽中断)。
-
需要一个额外的
pxHigherPriorityTaskWoken参数。
BaseType_t xQueueSendFromISR( QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
//向队列尾部发送数据
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
//向队列尾部发送数据
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
//向队列头部发送数据
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void * pvBuffer, BaseType_t *pxHigherPriorityTaskWoken );
//从队列接收并移除数据
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue, void * pvBuffer );
// 注意:PeekFromISR 没有 pxHigherPriorityTaskWoken 参数! 因为只查看不做任何处理。
//查看队列头部数据(不移除)
BaseType_t xQueueOverwriteFromISR( QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
//强制覆盖写入队列(即使队列满) 仅用于长度为1的队列
pxHigherPriorityTaskWoken参数:
-
这是一个指向
BaseType_t变量的指针(通常初始化为pdFALSE)。 -
如果队列操作(发送或接收)导致一个等待该队列的任务解除阻塞,并且这个被解除阻塞的任务的优先级高于当前被中断的任务,那么该 API 会将
*pxHigherPriorityTaskWoken设置为pdTRUE。 -
如何使用: 在 ISR 退出前,检查
pxHigherPriorityTaskWoken指向的值。如果是pdTRUE,必须调用portYIELD_FROM_ISR()(或移植相关的宏如taskYIELD()) 来请求一次上下文切换,让更高优先级的任务在中断退出后立即运行。
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
//初始化为 pdFALSE
// ... 在 ISR 中调用队列 API ...
if (xHigherPriorityTaskWoken == pdTRUE)
//程序结尾检查是否为pdTRUE
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 如为pdTRUE进行一次任务切换
}
4. 队列的常见用途
-
任务间数据传输: 最基本的功能,一个任务生产数据,另一个任务消费数据。
-
任务间同步:
-
事件通知: 发送一个“空”消息(如
0或一个枚举值)表示事件发生。接收任务阻塞在队列上等待事件。 -
二值信号量 (Binary Semaphore): 创建一个长度为 1,项大小为 0 (
uxItemSize = 0) 的队列。xQueueGive()(发送) 相当于释放信号量,xQueueTake()(接收) 相当于获取信号量。队列为空表示信号量不可用,队列满(有一个“令牌”)表示信号量可用。 -
计数信号量 (Counting Semaphore): 创建一个长度大于 1,项大小为 0 的队列。队列中的数据项数量代表信号量的计数值。
xQueueGive()增加计数,xQueueTake()减少计数。 -
互斥锁 (Mutex): FreeRTOS 的互斥锁 (
xSemaphoreCreateMutex()) 内部通常也是基于队列(长度=1,项大小=0)实现的,并增加了优先级继承机制。
-
-
中断到任务通信: ISR 使用
FromISRAPI 将数据或事件通知发送给任务。任务阻塞在队列上等待。
3776

被折叠的 条评论
为什么被折叠?



