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

 

这篇文章将记录我学习实时操作系统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的队列上锁和解锁的知识就分享至此,希望我的分享对你有所帮助!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/772162.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

转让北京文化传媒公司带营业性演出经纪许可证

影视文化传播倡导将健康的影视文化有效传播给观众&#xff0c;从而构建观众与电影制作者的良 性沟通与互动&#xff0c;是沟通电影制作者与电影受众的重要桥梁。影视文化泛指以电影&#xff0c;电视方式所进行的全部文化创造&#xff0c;即体现为电影&#xff0c;电视全部的存在…

找不到msvcp120.dll无法继续执行的原因分析及解决方法

在计算机使用中&#xff0c;经常会遇到msvcp120.dll文件丢失的情况&#xff0c;很多人对这个文件不是很熟悉&#xff0c;今天就来给大家讲解一下msvcp120.dll文件的丢失以及这个文件的重要性&#xff0c;让大家更好地了解计算机&#xff0c;同时也可以帮助我们更好地掌握这个文…

航模插头篇

一、常见的电池插头&#xff08;电调端 是公头 电池端 是母头&#xff09; 电池总是被插的 1.XT60头 过流大 安全系数高 难插拔 2.T插 插拔轻松 过流比较小 容易发烫 电调端 是公头 电池端 是母头 3.香蕉头插孔 过流够 插拔轻松 但 容易插反 爆炸 4.TX90(和XT60差…

2024 年 亚太赛 APMCM (A题)中文赛道国际大学生数学建模挑战赛 | 飞行器外形的优化 | 数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; 完整内容可以在文章末尾领取&#xff01; 第一个问…

C++内存管理(候捷)第一讲 笔记

内存分配的每一层面 applications可以调用STL&#xff0c;里面会有allocator进行内存分配&#xff1b;也可以使用C 基本工具primitives&#xff0c;比如new, new[], new(), ::operator new()&#xff1b;还可以使用更底层的malloc和free分配和释放内存。最底层的是系统调用&…

明星代言6个提升企业形象的杀手锏-华媒舍

在当今竞争激烈的商业世界中&#xff0c;企业形象的塑造对于品牌的发展至关重要。而明星代言作为一种常见的营销手段&#xff0c;被广泛使用来提升企业形象和产品销售。本文将介绍明星代言的六个杀手锏&#xff0c;帮助您了解如何通过明星代言来提升企业形象。 1. 拥有广泛的影…

十二、【源码】Spring整合AOP

源码地址&#xff1a;https://github.com/spring-projects/spring-framework 仓库地址&#xff1a;https://gitcode.net/qq_42665745/spring/-/tree/12-spring-aop Spring整合AOP 核心类&#xff1a; DefaultAdvisorAutoProxyCreator&#xff1a;用于在Spring框架中自动为符…

若依多数据源原理分析

首先&#xff0c;想明白不同的接口想要使用不同的数据源。 那么自然想到了AOP&#xff0c;自定义注解。 通过自定义注解标注当前方法到底使用的是哪个数据源。 上面是前置条件。 看下若依是怎么处理的&#xff1a; 1.定义自定义注解&#xff0c;以及对应的多数据源的枚举类…

天润融通分析AI技术助力客户服务,实现满意度三倍增长

如今&#xff0c;客户体验越来越成为影响客户决策的核心要素。 对于企业来讲&#xff0c;客户在不同触点的每一次互动体验&#xff0c;都成为塑造品牌声誉的“Aha时刻”。但同时&#xff0c;随着社会的发展的加速&#xff0c;客户的需求也在日新月异&#xff0c;给企业带来挑战…

Codeforces Round 955 (Div. 2, with prizes from NEAR!)(A~C题解)

这场比赛怎么说呢&#xff0c;一开始打的还算好&#xff0c;能进前1000&#xff0c;但是后面就被卡住了&#xff0c;这个确实没办法水平还是不够&#xff0c;学过的还是没想起来&#xff0c;后面继续练 A. Soccer 题解&#xff1a;水题一个&#xff0c;想要在过程中出现平局的…

web零碎知识

&nbsp 在html文件中 连续的空格会被认为是一个空格 所以我们需要使用&nbsp来代表空格 &#x3000 把这个当成tab键来使用 我们可以引入js文件&#xff0c;就可以减少html文件的长度。 首先创建一个js文件夹&#xff0c;然后在js文件夹中创建一个&#xff0c;后缀…

【第17章】MyBatis-Plus自动维护DDL

文章目录 前言一、功能概述二、注意事项三、代码示例四、实战1. 准备2. ddl配置类3. 程序启动4. 效果(数据库) 总结 前言 在MyBatis-Plus的3.5.3版本中&#xff0c;引入了一项强大的功能&#xff1a;数据库DDL&#xff08;数据定义语言&#xff09;表结构的自动维护。这一功能…

【电路笔记】-B类放大器

B类放大器 文章目录 B类放大器1、概述2、B类放大器介绍3、推挽式配置4、限制交叉失真5、B类放大器效率6、总结1、概述 我们在之前的文章中已经知道,A 类放大器的特点是导通角为 360,理论最大效率为 50%。 在本文中,我们将详细介绍另一类放大器,称为B类放大器,它是为解决A…

康姿百德磁性床垫好不好,效果怎么样靠谱吗

康姿百德典雅款床垫&#xff0c;打造舒适睡眠新体验 康姿百德床垫是打造舒适睡眠新体验的首选&#xff0c;其设计能够保护脊椎健康&#xff0c;舒展脊椎&#xff0c;让您享受一夜好眠。康姿百德床垫的面料选择也非常重要&#xff0c;其细腻亲肤的针织面料给您带来柔软舒适的触…

A*——AcWing 179. 八数码

A* 定义 A* 算法是一种在图形或地图中寻找最短路径的启发式搜索算法。它通过综合考虑起始节点到当前节点的实际代价和当前节点到目标节点的预估代价&#xff0c;来决定下一步的搜索方向。 运用情况 路径规划&#xff1a;如在地图导航中为车辆、行人规划最优路线。游戏开发&…

旅游系统(附管理端+前台)PHP源码

一. 前言 今天小编给大家带来了一款可学习&#xff0c;可商用的&#xff0c;旅游系统 源码&#xff0c;支持二开&#xff0c;无加密。支持景点管理&#xff0c;登录&#xff0c;景点预定&#xff0c;意见反馈&#xff0c;统计等功能。详细界面和功能见下面视频演示。 二. 视频…

深入挖掘海外快手kwai ads推广巴西slots手游广告独家优势

深入挖掘海外快手kwai ads推广巴西slots手游广告独家优势 在数字化时代&#xff0c;广告投放已成为各行各业不可或缺的一部分&#xff0c;特别是在游戏行业&#xff0c;如何有效地推广游戏产品&#xff0c;吸引玩家的眼球&#xff0c;成为了每一个游戏开发商和广告主所关注的焦…

DllImport进阶:参数配置与高级主题探究

深入讨论DllImport属性的作用和配置方法 在基础篇中&#xff0c;我们已经简单介绍了DllImport的一些属性。现在我们将深入探讨这些属性的实际应用。 1. EntryPoint EntryPoint属性用于指定要调用的非托管函数的名称。如果托管代码中的函数名与非托管代码中的函数名不同&#…

TreeSize Free - 硬盘空间管理工具

TreeSize FreeTreeSize Free 是一款免费的强大灵活的硬盘空间管理工具。可以帮你找出硬盘上最大的目录以及它占用的空间。支持空间大小显示、分配空间和占用空间、文件数、3D工具条和分配图、最近使用数据、文件作者、NTFS压缩率等信息&#xff0c;并支持搜索文件。该软件类似浏…

掌握亚马逊自养号:测评策略的核心要点与实战经验

在当今电商领域的激烈角逐中&#xff0c;亚马逊测评对于卖家而言&#xff0c;已从单纯的销量助推器与好评累积工具&#xff0c;进化为品牌塑造与市场洞察的关键环节。然而&#xff0c;许多卖家仍局限于传统认知&#xff0c;未能充分挖掘自养号测评的多元化价值与深远影响。本文…