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

news2024/11/24 2:49:06

 

这篇文章将记录我学习实时操作系统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.coloradmin.cn/o/1893534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【代码随想录】【算法训练营】【第55天】 [42]接雨水 [84]柱状图中最大的矩形

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 55&#xff0c;又是一个周一&#xff0c;不能再坚持~ 题目详情 [42] 接雨水 题目描述 42 接雨水 解题思路 前提&#xff1a;雨水形成的情况是凹的, 需要前中后3个元素&#xff0c;计算该元…

Modbus协议转Profinet协议网关模块连智能仪表与PLC通讯

一、现场需求&#xff1a;PLC作为控制器&#xff0c;仪表设备做为执行设备&#xff0c;执行设备能够实时响应PLC传来的指令&#xff0c;并且向PLC回馈数据&#xff0c;从而达到PLC对仪表设备进行控制和监测&#xff0c;实现对生产过程的精准控制。 二、解决方案&#xff1a;通过…

机器学习与AI大数据的融合:开启智能新时代

在当今这个信息爆炸的时代&#xff0c;大数据和人工智能&#xff08;AI&#xff09;已经成为推动社会进步的强大引擎。作为AI核心技术之一的机器学习&#xff08;Machine Learning, ML&#xff09;&#xff0c;与大数据的深度融合正引领着一场前所未有的科技革命&#xff0c;不…

Java判断范围型的数据是否存在重叠(数值类型、日期类型)

为什么写这么一篇文章呢&#xff1f; 遇到了个问题&#xff0c;同一天可以输入多个时间段&#xff0c;但是每个时间段的时间不能出现重叠。 纳尼&#xff0c;这不就是判断数据返回是否有重叠的变种嘛~ 简单&#xff0c;开搞 数字范围是否重叠判断 这里以int类型为例了&…

高德地图轨迹回放并提示具体信息

先上效果图 到达某地点后显示提示语:比如:12:56分驶入康庄大道、左转驶入xx大道等 <!doctype html> <html> <head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta n…

基于Vue.js的电商前端模板:Vue-Dashboard-Template的设计与实现

摘要 随着电子商务的飞速发展&#xff0c;前端页面的设计和实现变得愈发重要。本文介绍了一个基于Vue.js的电商前端模板——Vue-Dashboard-Template&#xff0c;旨在提供一个高性能、易扩展的电商平台前端解决方案。该模板遵循响应式设计、模块化、组件化开发等设计原则&#…

预制菜工厂MES系统:具体功能与应用场景

在现代化食品工业中&#xff0c;预制菜&#xff08;Ready-to-Eat, RTE&#xff09;因其方便快捷、卫生安全及营养均衡的特点&#xff0c;迅速在餐饮行业中占据重要地位。为了进一步提升预制菜工厂的生产效率、保障产品质量并降低生产成本&#xff0c;制造执行系统&#xff08;M…

C# 快速排序算法的详细讲解

目录 一、前言 二、例子 三、快速排序算法图片讲解 四、快速排序算法代码 五、纯净代码 一、前言 用比较好懂的方式讲一下快速排序算法。 二、例子 如果我有一堆钱&#xff0c;想数清楚&#xff0c;最快的方案是什么&#xff1f; 图1 一堆钱 答&#xff1a;先分类&…

【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)

目录 项目演示&#xff1a; 1. 主界面 技术讲解&#xff1a; TCP连接 进程的并发 链表 SQLite3 IO对文件的读写 功能实现 实现逻辑 我遇到的问题&#xff1a; 服务器端代码思路解析 必要条件 步骤详解 客户端代码思路解析 步骤详解 服务器源码如下&#xff1a;…

GD32实战项目-app inventor-BLE低功耗DX-BT24蓝牙上位机制作-文末有关于生成的软件闪退或者卡死问题的解决

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 后续项目主要在下面该专栏中发布&#xff1a; 手把手教你嵌入式国产化_不及你的温柔的博客-CSDN博客 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转&#xff1a; 手把手教你嵌入式国产化-实战项目-无刷电机驱动&am…

什么开放式耳机好用?五大王牌开放式耳机种草!

随着科技的持续进步&#xff0c;开放式蓝牙耳机悄然兴起&#xff0c;逐步取代了经典的入耳式耳机。入耳式耳机以其卓越的隔音性能著称&#xff0c;然而&#xff0c;长时间的使用却容易引发耳道受压&#xff0c;伴随而来的不仅是疼痛与不适&#xff0c;更潜藏着耳膜受损的风险。…

C++面试宝典30题丨第一题:开灯

专栏导读 见得题目越多&#xff0c;考试时抽中的概率也越大。每一题都有详细的解答思路和独有的视频讲解。 本文收录于&#xff1a;C面试宝典&#xff08;送视频讲解&#xff09; ☆☆☆购买专栏后&#xff0c;请加微信会私发讲解视频&#xff01; 题目描述 一条名叫Mango的街…

简过网:一建和二建的含金量,哪个难度更大一些?

你知道&#xff0c;一建和二建有什么区别吗&#xff0c;考哪个更合适自己呢&#xff1f;正在备考一建、二建的小伙伴们&#xff0c;这篇文章千万不要错过哦&#xff01; 首先&#xff0c;先说一下大家比较关注的含金量问题&#xff0c;一建含金量明显比二建高&#xff0c;但是…

MySQL篇-SQL优化实战-减少子查询

回顾 上一篇了解了分析SQL使用的explain&#xff0c;可以点击查看MySQL篇-SQL优化实战了解我在写sql的注意事项还有explain的说明&#xff0c;这次拿一段生产使用的sql进行优化说明。从14s优化到2.6s 待优化的SQL SELECT DISTINCTswpe.tag_number,hca.ACCOUNT_NAME customer…

精准定位推广盲点?Xinstall数据监测让每一分投入都见成效!

在这个数字化时代&#xff0c;App的推广早已不再是简单的“上线即成功”。面对激烈的市场竞争和日益挑剔的用户&#xff0c;如何精准监测推广数据&#xff0c;优化营销策略&#xff0c;成为了每个开发者与营销人员不得不面对的挑战。而在这个关键时刻&#xff0c;Xinstall作为一…

AI 驱动的数据中心变革与前景

文章主要探讨了AI计算时代数据中心的转型&#xff0c;涉及计算技术的多样性、规格尺寸和加速器的发展、大型语言模型&#xff08;LLM&#xff09;的发展、功耗和冷却趋势、基准测试的重要性以及数据中心的发展等方面。为大家提供深入了解AI基础设施发展的视角。 计算技术的多样…

Python 程序打印图案“G”(Python Program to print the pattern ‘G’)

在本文中&#xff0c;我们将学习如何使用星号和空格打印图案 G。给定一个数字 n&#xff0c;我们将编写一个程序&#xff0c;在 n 行或列上打印图案 G。 例子&#xff1a; 输入&#xff1a;7 输出&#xff1a; *** * * * *** * * * * *** 输入&…

红酒的秘密花园:探索葡萄的种植艺术

在远离城市喧嚣的某个角落&#xff0c;隐藏着一座神秘的红酒秘密花园。这里&#xff0c;葡萄藤缠绵交织&#xff0c;绿叶间闪烁着晶莹的露珠&#xff0c;仿佛在诉说着关于红酒与葡萄种植艺术的古老传说。今天&#xff0c;就让我们一起走进这片神秘的花园&#xff0c;探寻葡萄种…

@amap/amap-jsapi-loader 实现高德地图中添加多边围栏,并可编辑,编辑后获得围栏各个点的经纬度

先上一张效果图 看看是不是大家想要的效果&#xff5e; ❤️ 希望其中的小点能帮助大家&#xff0c;主要看怎么绘制在地图上的代码即可 1.第一步要加入项目package.json中或者直接yarn install它都可以 想必大家应该都会 "amap/amap-jsapi-loader": "0.0.7&qu…

Oracle EBS PO采购订单预审批状态处理

系统版本 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.6 问题症状: 采购订单状态:预审批 采购订单流程报错如下: po.plsql.PO_DOCUMENT_ACTION_AUTH.approve:90:archive_po not successful - po.plsql.PO_DOCUMENT_ACTION_PVT.do_action:110:unexpected error in acti…