这篇文章将记录我学习实时操作系统FreeRTOS的队列上锁和解锁的知识,在此分享给大家,希望我的分享能给你带来不一样的收获!
目录
一、简介
在FreeRTOS中,队列是一种用于在任务之间传递数据的通信机制。它可以实现生产者任务将数据发送到队列中,然后消费者任务从队列中接收数据。队列的上锁和解锁操作是用来保护队列数据的完整性和一致性的。
当一个任务要向队列发送数据时,首先需要对队列进行上锁操作。这是为了防止其他任务同时访问队列,从而导致数据的错误读写。在上锁期间,其他任务无法访问队列,直到上锁任务完成发送操作并解锁队列。
类似地,当一个任务要从队列接收数据时,也需要对队列进行上锁操作。这是为了保证在接收数据的过程中,队列中的数据不会被其他任务修改。在上锁期间,其他任务无法修改队列中的数据,直到上锁任务完成接收操作并解锁队列。
通过对队列进行上锁和解锁操作,可以确保在多任务环境下,队列的数据操作是安全和可靠的。
二、队列上锁函数prvLockQueue()
1、函数初探
/*-----------------------------------------------------------*/
/*
* Macro to mark a queue as locked. Locking a queue prevents an ISR from
* accessing the queue event lists.
*/
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL()
/*-----------------------------------------------------------*/
就是将队列中的成员变量cRxLock和cTxlock设置为queueLOCKED_UNMODIFIED就行
2、应用示例
在FreeRTOS中,队列并不需要显式的上锁(或者说信号量),因为队列的操作已经在内部进行了同步和互斥处理。这是因为FreeRTOS的队列实现是线程安全的,它在底层使用了信号量和互斥量来确保多任务环境下的安全访问。
下面是一个简单的示例,演示了如何在FreeRTOS中创建、发送和接收队列消息:
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#define QUEUE_LENGTH 5
#define ITEM_SIZE sizeof(int)
QueueHandle_t xQueue;
void SenderTask(void *pvParameters) {
int count = 0;
while (1) {
// 将消息发送到队列
xQueueSend(xQueue, &count, portMAX_DELAY);
count++;
// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void ReceiverTask(void *pvParameters) {
int received_value;
while (1) {
// 从队列接收消息
if (xQueueReceive(xQueue, &received_value, portMAX_DELAY) == pdPASS) {
// 处理接收到的消息
printf("Received: %d\n", received_value);
}
}
}
int main(void) {
// 创建队列
xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
// 创建发送任务
xTaskCreate(SenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
// 创建接收任务
xTaskCreate(ReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败,进入错误处理
for (;;);
return 0;
}
(1)、队列创建:在 main
函数中创建了一个队列,它可以容纳 QUEUE_LENGTH
个 int
类型的元素。
xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
(2)、 发送任务 (SenderTask
):SenderTask
函数中通过 xQueueSend
将 count
的值发送到队列中,然后 count
递增,并延时1秒钟。
void SenderTask(void *pvParameters) {
int count = 0;while (1) {
xQueueSend(xQueue, &count, portMAX_DELAY);
count++;
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
(3)、 接收任务 (ReceiverTask
):ReceiverTask
函数中通过 xQueueReceive
从队列中接收消息,并将接收到的消息打印出来。
void ReceiverTask(void *pvParameters) {
int received_value;while (1) {
if (xQueueReceive(xQueue, &received_value, portMAX_DELAY) == pdPASS) {
printf("Received: %d\n", received_value);
}
}
}
(4)、主函数 (main
):
- 创建了队列
xQueue
。- 创建了发送任务
SenderTask
和接收任务ReceiverTask
。- 启动了 FreeRTOS 的任务调度器
vTaskStartScheduler()
。
三、队列解锁函数prvUnLockQueue()
1、函数初探及详细注释
/*-----------------------------------------------------------*/
static void prvUnlockQueue( Queue_t * const pxQueue )
{
/* 必须在调度器挂起的情况下调用此函数 */
/* 锁计数器包含在队列被锁定期间添加或移除的额外数据项数量。
当队列被锁定时,可以添加或移除项目,但不能更新事件列表。 */
taskENTER_CRITICAL();
{
int8_t cTxLock = pxQueue->cTxLock;
/* 检查在队列被锁定期间是否有数据被添加 */
while( cTxLock > queueLOCKED_UNMODIFIED )
{
/* 当队列被锁定时有数据被发送。有任务因为数据可用而阻塞吗? */
#if ( configUSE_QUEUE_SETS == 1 )
{
if( pxQueue->pxQueueSetContainer != NULL )
{
if( prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != pdFALSE )
{
/* 队列是队列集的成员,并且向队列集发送数据导致更高优先级的任务解除阻塞。
需要进行上下文切换。 */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 从事件列表中移除的任务将被添加到等待准备列表中,因为调度器仍处于挂起状态。 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 等待的任务优先级更高,所以需要记录需要进行上下文切换。 */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
}
#else /* configUSE_QUEUE_SETS */
{
/* 从事件列表中移除的任务将被添加到等待准备列表中,因为调度器仍处于挂起状态。 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 等待的任务优先级更高,所以需要记录需要进行上下文切换。 */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
#endif /* configUSE_QUEUE_SETS */
--cTxLock;
}
pxQueue->cTxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
/* 处理接收锁(cRxLock)的解锁 */
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
while( cRxLock > queueLOCKED_UNMODIFIED )
{
/* 检查在队列被锁定期间是否有数据被移除 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cRxLock;
}
else
{
break;
}
}
pxQueue->cRxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
}
/*-----------------------------------------------------------*/
详细注释解释:
任务关键性操作管理:
- 使用
taskENTER_CRITICAL()
进入临界区,确保后续操作的原子性和避免抢占。- 使用
taskEXIT_CRITICAL()
退出临界区,保证对队列锁定状态的修改是原子的。解锁发送 (
cTxLock
):
- 检查在队列被锁定期间是否有数据被添加 (
cTxLock >queueLOCKED_UNMODIFIED
)。- 根据是否使用队列集 (
configUSE_QUEUE_SETS
),通知相关的队列集容器或者从等待接收数据的任务事件列表 (xTasksWaitingToReceive
) 中移除任务。- 如果由于数据可用导致更高优先级任务解除阻塞,调用
vTaskMissedYield()
进行上下文切换。解锁接收 (
cRxLock
):
- 检查在队列被锁定期间是否有数据被移除 (
cRxLock >queueLOCKED_UNMODIFIED
)。- 从等待发送数据的任务事件列表 (
xTasksWaitingToSend
) 中移除任务,如果任务因为数据发送而变得可运行,调用vTaskMissedYield()
进行上下文切换。测试覆盖率:
- 使用
mtCOVERAGE_TEST_MARKER()
调用来确保测试所有代码路径。结论:
这段代码在实时操作系统(RTOS)环境中非常重要,用于管理多任务对共享数据结构(如队列)的同步访问。通过管理锁计数和任务准备就绪状态,确保在队列解锁后,等待访问队列的任务可以及时执行,以提高系统效率和响应能力。
2、函数使用示例
假设我们有一个队列
xQueue
,在实际应用中可能是用于任务之间传递数据的队列。以下是一个示例代码:
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/* 定义一个队列 */
#define QUEUE_LENGTH 5
#define ITEM_SIZE sizeof(int)
QueueHandle_t xQueue;
void vSenderTask(void *pvParameters)
{
int i = 0;
BaseType_t xStatus;
while (1)
{
/* 发送数据到队列 */
xStatus = xQueueSend(xQueue, &i, portMAX_DELAY);
if (xStatus != pdPASS)
{
printf("Failed to send to queue!\n");
}
else
{
printf("Sent: %d\n", i);
}
i++;
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vReceiverTask(void *pvParameters)
{
int rxItem;
BaseType_t xStatus;
while (1)
{
/* 接收数据 */
xStatus = xQueueReceive(xQueue, &rxItem, portMAX_DELAY);
if (xStatus == pdPASS)
{
printf("Received: %d\n", rxItem);
}
else
{
printf("Failed to receive from queue!\n");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void)
{
/* 创建队列 */
xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
/* 创建发送任务 */
xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
/* 创建接收任务 */
xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 永远不应该运行到这里 */
return 0;
}
vSenderTask
和vReceiverTask
分别是发送和接收任务,用于往队列发送数据和从队列接收数据。xQueueSend
和xQueueReceive
函数用于操作队列,这些函数会在内部调用队列的管理函数,其中包括了类似prvUnlockQueue
的操作,确保在正确的时机解锁队列以便其他任务可以访问它。这段示例代码展示了如何使用队列在 FreeRTOS 环境中进行任务间通信,并在发送和接收数据时自动处理队列的锁定和解锁,确保数据的正确传递和任务的及时响应。
四、结语
关于FreeRTOS的队列上锁和解锁的知识就分享至此,希望我的分享对你有所帮助!