首页 > 其他分享 >FreeRTOS之队列上锁和解锁(详解)

FreeRTOS之队列上锁和解锁(详解)

时间:2024-07-04 23:29:32浏览次数:21  
标签:FreeRTOS 队列 解锁 上锁 任务 void xQueue pxQueue

 

这篇文章将记录我学习实时操作系统FreeRTOS的队列上锁和解锁的知识,在此分享给大家,希望我的分享能给你带来不一样的收获!

目录

一、简介

 二、队列上锁函数prvLockQueue()

1、函数初探

2、应用示例 

 三、队列解锁函数prvUnLockQueue() 

1、函数初探及详细注释

详细注释解释:

结论:

2、函数使用示例


一、简介

在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_LENGTHint 类型的元素。

xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);

(2)、 发送任务 (SenderTask):SenderTask 函数中通过 xQueueSendcount 的值发送到队列中,然后 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();
}
/*-----------------------------------------------------------*/

详细注释解释:

  1. 任务关键性操作管理:

    • 使用 taskENTER_CRITICAL() 进入临界区,确保后续操作的原子性和避免抢占。
    • 使用 taskEXIT_CRITICAL() 退出临界区,保证对队列锁定状态的修改是原子的。
  2. 解锁发送 (cTxLock):

    • 检查在队列被锁定期间是否有数据被添加 (cTxLock >queueLOCKED_UNMODIFIED)。
    • 根据是否使用队列集 (configUSE_QUEUE_SETS),通知相关的队列集容器或者从等待接收数据的任务事件列表 (xTasksWaitingToReceive) 中移除任务。
    • 如果由于数据可用导致更高优先级任务解除阻塞,调用 vTaskMissedYield() 进行上下文切换。
  3. 解锁接收 (cRxLock):

    • 检查在队列被锁定期间是否有数据被移除 (cRxLock >queueLOCKED_UNMODIFIED)。
    • 从等待发送数据的任务事件列表 (xTasksWaitingToSend) 中移除任务,如果任务因为数据发送而变得可运行,调用 vTaskMissedYield() 进行上下文切换。
  4. 测试覆盖率:

    • 使用 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的队列上锁和解锁的知识就分享至此,希望我的分享对你有所帮助!

 

标签:FreeRTOS,队列,解锁,上锁,任务,void,xQueue,pxQueue
From: https://blog.csdn.net/m0_73931287/article/details/140173294

相关文章

  • 报名参课 | 解锁 Serverless+AI 新模式,拥有专属AIGC环境
    如今,Serverless被越来越多的企业所接受,并应用于业务实践中。科技的每一次进步都在更新着我们的工作模式,除了互联网企业最早“尝鲜”之外,传统企业也在探索大规模使用Serverless。越来越多人迈过了对Serverless技术的初级认知阶段,走向了落地实践。Serverless和AI大模型都是......
  • 解锁Diffusion Model: 初识Stable Diffusion、DALL-E、Imagen!
    前言扩散模型在生成高质量图像、视频、声音等方面表现突出。它们与物理学中的自然扩散过程相似而得名,自然扩散过程描述了分子如何从高浓度区域移动到低浓度区域。在机器学习的背景下,扩散模型通过逆转扩散过程来生成新数据。主要的思想是向数据添加随机噪声,然后反过来从噪声......
  • AI 绘画变现全攻略:解锁财富新密码
    AIGC,这可是个大家都得了解的词,指的是利用AI工具生成创作内容。这里给您列举了近年全球五十个超热门的AI工具访问量,ChatGPT那可是一骑绝尘,以140亿次的访问量稳坐AI界头把交椅。在众多工具中,除了语言类,占比最多的要数AI绘画工具了。这里面还没算上开源免费能在本地......
  • FreeRTOS移植到STM32
    本内容主要是讲解关于如果把FreeRTOS移植到STM32中去的操作。明白各部分的作用以及打通思路,具体操作按照下列进行相应的操作。第一:早一个STM32的裸机程序我们这里用的是STM32F103的芯片为例。 二、去官网上下载FreeRTOSV9.0.0源码在移植之前,我们首先要获取到......
  • AI绘画Stable Diffusion 3.0大模型解锁AIGC写实摄影:摄影构图与视角关键提示,SD3模型最
    大家好,我是设计师阿威在现实摄影领域中,创作出优秀的摄影图像会涉及很多关键技术要素,如:光影效果、摄影构图(摄影机位置:相机与主体的距离)和摄影角度(相机相对于主体的位置)等的选择。这些核心要素对于AIGC绘图(StableDiffusion1.5/XL、Playground、Midjourney)创作也极为重要......
  • 揭秘!FFmpeg+Whisper双剑合璧:解锁视频到文本的二阶段奇迹
    解锁视频到文本的二阶段奇迹一、引言二、视频音频提取与处理视频音频提取与处理2.1环境搭建2.2视频音频提取2.3音频预处理示例代码:三、语音识别与翻译3.1加载Whisper模型3.2语音识别3.3语言检测与翻译四、结果处理与验证4.1结果整理4.2视频与文本同步验证五......
  • 解锁Memcached的Key长度极限:深入探索与实践
    ......
  • FreeRTOS静态创建任务分析
    #defineconfigSUPPORT_STATIC_ALLOCATION        1  设置了静态创建任务需要重新定义这2个函数,由程序员进行分配任务空间(空闲任务)(定时任务)在调度器里被使用这2个函数空闲任务定时任务定义空闲分配空间函数和定时分配空间函数静态创建任务内部......
  • 【算法探秘】无重复字符的最长子串:解锁字符串中的独特风景
    【算法探秘】无重复字符的最长子串:解锁字符串中的独特风景一、引言:在字符的海洋中航行二、技术概述:独步字符森林技术定义核心特性代码示例:初尝甜蜜果实三、技术细节:拨开迷雾,洞悉本质原理解析难点剖析四、实战应用:字节跳跃,解密信息应用场景案例展示五、优化与改进:精益......
  • 【Linux】解锁权限的神秘面纱,让你的系统更安全、更高效!
    XShell原理+权限1.Shell命令以及运行原理*1.1Shell外壳1.2shell周边知识2.Linux权限的概念*2.1用户2.2用户切换2.3sudo3.Linux权限管理*3.1文件访问者的分类3.2文件类型3.3file指令3.4文件访问权限3.5文件权限值的表示方法4.文件访问权限的设......