(四)FreeRTOS队列

本文深入探讨FreeRTOS操作系统中的队列机制,包括队列创建、数据存储、发送与接收API,以及队列如何帮助RTOS内核有效管理任务,解决中断服务程序与任务间的消息传递问题。

       在没有使用队列之前,任务之间的通信是通过共享全局变量或者传递指针参数来进行消息传递,但是全局变量一旦使用多了就会占用很大的资源,在操作系统中,这就会涉及到资源管理的问题。操作系统需要管理有限的资源,进而产生了队列,解决了任务与任务、中断与中断、任务与中断的通信问题,任务与任务、任务与中断之间要交流的数据保存在队列中,这就叫做队列项目。而队列中能存储的数据是有限的,每个数据项目大小是固定的。

操作系统使用队列有如下好处:

  1. 使用消息队列可以让RTOS内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现。
  2.  使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
  3.  使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
  4. FIFO机制更有利于数据的处理。

数据存储

       FreeRTOS有两种数据缓存机制,将数据发送到队列中,第一种是先进先出的的FIFO缓存,第二种为先进后出的类似栈的缓存机制。

       数据缓存势必涉及到数据拷贝,将要发送的数据拷贝至队列中,FreeRTOS中采用的是值传递方式,在拷贝的是将整个数据拷贝到队列中,虽然这会导致时间上的浪费,但是数据一旦被拷贝至队列后,原始数据就可以重复读写。如果采用引用传递(即指针方式)将地址发送至队列,效率会比较高效,但是原始数据必须一致保持可见性,期间不能更改。很明显,在处理大量数据时,采用引用传递方式比较靠谱。

队列创建

       队列创建API为xQueueCreate(x,y),x为创建队列长度,y为每个队列项的长度,通过宏定义可以知道,实际完成队列创建工作的xQueueGenericCreate()函数。

队列创建过程追踪:

队列创建完成

队列发送相关API

入队方式

API接口

实际执行函数

从队列尾部入队

xQueueSend()

 

 

xQueueGenericSend()

xQueueSendToBack()

xQueueSendToFront()

从队列首部入队

xQueueSendToFront()

从队列尾部入队

(带中断保护)

xQueueSendFromISR()

 

 

xQueueGenericSendFromISR()

xQueueSendToBackFromISR()

xQueueOverWriteFromISR()

从队列首部入队

(带中断保护)

xQueueSendToFront()

        普通任务中发送数据到队列中最终是经过xQueueGenericSend()函数进行处理,带中断保护的发送函数最终是调用xQueueGenericSendFromISR()函数,不同的函数调用相同的函数进行处理,所以需要借用标识符在函数内部进行不同的处理。

       在xQueueGenericSend()函数中主要完成的事情分两种情况:

        第一种情况是队列未满的情况或者采用覆写的方式入队,这种情况处理比较简单,将数据拷贝至队列(FreeRTOS传递数据是通过传值方式进行),检查出对队列中是否有阻塞的任务,如果有则解除阻塞状态,如果该任务比当前运行的任务优先级高,则进行一次上下文切换,返回入队成功标志。

      第二种情况是对列已满的情况,需要将调度器挂起,并且将队列上锁,因为希望任务在由于条件不满足的时候被阻塞挂在阻塞队列期间不希望被其他任务或者中断操作队列而导致原来阻塞的任务解除了,这是不符合的。接着再判断阻塞时间是否到达,如果已到达恢复调度器解锁队列,返回队列已满的信息,如果阻塞时间还未到而队列依旧是满,则只能将发送队列项挂起。

        在入队函数中使用到了数据拷贝到队列的函数prvCopyDataToQueue(),在该函数中主要进行的工作是判断入队类型,如果是队列后面入队,就将数据拷贝至pcWriteTo指向的地方,如果是队列前入队,将数据拷贝至u.xQueue.pcReadFrom指向的地方,这里需注意如果传入的队列项长度为0,代表的是互斥列队类型,此时如果不使用互斥队列不能将pcWriteTo指针指向NULL,因为FreeRTOS中规定如果指向NULL代表互斥队列。

        带中断保护的入队函数xQueueGenericSendFromISR()由于在中断中不能执行阻塞或者挂起操作,所以带中断保护的入队函数处理入队时比较简单,队列未满则数据拷贝进队列,队列已满则返回队列满的信息。

出队函数:

出队原理与入队差不多。

### 使用方法 在基于STM32F407 HAL库和FreeRTOS的开发中,队列是重要的任务间通信机制,允许任务间安全传递数据。在使用时,任务或者中断服务程序都能给消息队列发送消息。发送消息时,若队列未满或者允许覆盖入队,FreeRTOS会将消息拷贝到消息队列队尾;否则,会根据用户指定的阻塞超时时间进行阻塞。若队列一直不允许入队,该任务保持阻塞状态等待队列允许入队。当其他任务从其等待的队列中读取数据(队列未满),该任务自动由阻塞态转为就绪态。若任务等待时间超过指定阻塞时间,即使队列还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到错误码errQUEUE_FULL。发送紧急消息时,位置是消息队列队头而非队尾,以便接收者优先接收并及时处理。消息队列发送函数有多个,通过宏定义展开,有些只能在任务调用,有些只能在中断中调用 [^2][^3]。 ### 原理 队列FreeRTOS中为任务间的数据传递提供安全、可靠的机制。它的实现基于特定的数据结构和算法,保证数据在任务间有序传递。当发送消息时,系统会对队列状态进行检查,根据队列是否已满等情况进行不同处理,实现任务的阻塞和唤醒机制,确保数据的正确传递和任务的同步 [^1]。 ### 示例 以下是一个简单的伪代码示例,展示基本的FreeRTOS队列使用: ```c #include "FreeRTOS.h" #include "queue.h" // 创建队列句柄 QueueHandle_t xQueue; void vTaskSender( void *pvParameters ) { int valueToSend = 10; while(1) { // 发送数据到队列 if( xQueueSend( xQueue, &valueToSend, portMAX_DELAY ) == pdPASS ) { // 发送成功处理 } vTaskDelay( pdMS_TO_TICKS( 1000 ) ); } } void vTaskReceiver( void *pvParameters ) { int receivedValue; while(1) { // 从队列接收数据 if( xQueueReceive( xQueue, &receivedValue, portMAX_DELAY ) == pdPASS ) { // 处理接收到的数据 } } } void main( void ) { // 创建队列队列长度为10,每个元素大小为sizeof(int) xQueue = xQueueCreate( 10, sizeof( int ) ); if( xQueue != NULL ) { // 创建发送和接收任务 xTaskCreate( vTaskSender, "Sender", configMINIMAL_STACK_SIZE, NULL, 1, NULL ); xTaskCreate( vTaskReceiver, "Receiver", configMINIMAL_STACK_SIZE, NULL, 1, NULL ); // 启动调度器 vTaskStartScheduler(); } for( ;; ); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值