【FreeRTOS】【应用篇】消息队列【下篇】

前言

  • 本篇文章主要对 FreeRTOS 中消息队列的概念和相关函数进行了详解
  • 消息队列【下篇】详细剖析了消息队列中发送、接收时队列消息控制块中各种指针的行为,以及几个发送消息和接收消息的函数的运作流程
  • 笔者有关于 【FreeRTOS】【应用篇】消息队列【上篇】——队列基本概念、创建和删除,讲解了消息队列的基本概念(作用、存储结构、出入队列逻辑、阻塞机制)以及相关函数(队列创建和删除)
  • 一部分代码和图片参考野火 FreeRTOS 教程

    文章目录

    • 前言
    • 一、发送、接收时队列消息控制块中各种指针的行为
      • 1. 初始化后
      • 2. 发送消息到队列时
        • ① 发送到队尾(普通消息)
        • ② 发送到队头(紧急消息)
      • 3. 从队列中读取消息时
    • 二、消息发送 API
      • 1. 概要
        • ① 适用于任务中的消息发送函数
        • ② 适用于中断中的消息发送函数
      • 2. xQueueSend()与 xQueueSendToBack()
        • ① xQueueSend() 代码
        • ② xQueueSend() 使用示例
        • ③ xQueueSendToBack() 代码
      • 3. xQueueSendFromISR()与 xQueueSendToBackFromISR()
        • ① 代码
        • ② 使用示例
      • 4. xQueueSendToFront()
        • ① 代码
      • 5. xQueueSendToFrontFromISR()
        • ① 代码
    • 三、通用的任务中发送消息函数 xQueueGenericSend()
      • 1. xQueueGenericSend() 函数定义
      • 2. xQueueGenericSend() 函数流程图
      • 3. prvCopyDataToQueue() 函数说明
        • ① 如何被调用
        • ② 参数说明
        • ③ prvCopyDataToQueue() 函数完整代码
      • 4. 超时状态结构的配置 vTaskSetTimeOutState()
        • ① 超时状态结构如何配置
        • ② vTaskSetTimeOutState() 函数定义
      • 5. 挂起调度器和锁上队列的意义
        • ① 如何操作
        • ② 意义
        • ③ 队列上锁代码
      • 6. 检查延时是否到期函数 xTaskCheckForTimeOut()
        • ① 使用
        • ② 主要逻辑
        • ③ 源代码
      • 7. 将任务插入任务等待发送列表 vTaskPlaceOnEventList()
        • ① 使用
        • ② 函数定义
      • 8. 队列解锁函数 prvUnlockQueue()
        • ① 函数原型
        • ② 函数结构分析
        • ③ 计数锁的原理
    • 四、用于中断中的 通用的消息发送函数 xQueueGenericSendFromISR()
      • 1. 函数概要
      • 2. 函数代码
    • 五、消息读取 API
      • 1. 简介
      • 2. xQueueReceive()
        • ① 原型及作用
        • ② 宏展开
        • ③ 应用
      • 3. xQueuePeek()
        • ① 作用
        • ② 宏展开
      • 4. xQueueReceiveFromISR() 与 xQueuePeekFromISR()
        • ① xQueueReceiveFromISR() 使用示例
    • 六、通用读取消息函数 xQueueGenericReceive()
      • 1. 函数结构分析
      • 2. 函数定义
    • 四、性能提示
    • 后记

一、发送、接收时队列消息控制块中各种指针的行为

1. 初始化后

  • 消息空间从第一个单元到最后一个单元是从低地址增长到高地址
  • pcHead 指向消息空间第一个消息单元,此后固定不动,是为了方便快速定位消息空间头部而设定的指针
  • pcTail 指向消息空间最后一个消息单元(这个消息单元不作存储消息使用,前一个单元才是被用作存储的最后一个单元),此后固定不动,是为了方便快速定位消息空间尾部而设定的指针
  • pcWriteTo 指向 pcHead,指向下一个插入到队尾的消息的存储单元
  • pcReadFrom 指向 pcTail 的前一个消息单元,指向队列中下一个要被读取的消息的前一个单元

2. 发送消息到队列时

① 发送到队尾(普通消息)

  • xQueueSend() 和 xQueueSendToBack()
  • 消息被拷贝到 pcWriteTo 指向的消息单元,然后 pcWriteTo 自增一个消息单元
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->pcWriteTo += pxQueue->uxItemSize;
  • 当 pcWriteTo 自增到 pcTail 位置时,返回 pcHead
if( pxQueue->pcWriteTo >= pxQueue->pcTail ) {pxQueue->pcWriteTo = pxQueue->pcHead;}

② 发送到队头(紧急消息)

  • xQueueSendToFront()
  • 消息被拷贝到 pcReadFrom 指向的消息单元,然后 pcReadFrom 自减一个消息单元
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;
  • pcReadFrom 指向 pcHead 的上一个消息单元时(此时越界),就重置 pcReadFrom 指向 pcTail 的前一个消息单元
if( pxQueue->u.pcReadFrom < pxQueue->pcHead ){pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );}

3. 从队列中读取消息时

  • 首先自增 pcReadFrom,使其指向要读取的消息单元(pcReadFrom 初始化后保持为指向要读取的消息单元的前一个单元)
pxQueue->u.pcReadFrom += pxQueue->uxItemSize;
  • 然后判断自增后的 pcReadFrom 是否越界,如果指向的位置等于或者大于 pcTail 指向的位置,那么使 pcReadFrom 重新指向 pcHead
if( pxQueue->u.pcReadFrom >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as use of the relational operator is the cleanest solutions. */{pxQueue->u.pcReadFrom = pxQueue->pcHead;}
  • 最后从队列中将 pcReadFrom 指向的要读取的消息拷贝出来
( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize ); 

二、消息发送 API

1. 概要

① 适用于任务中的消息发送函数

适用于任务中的消息发送函数有三个版本

  • xQueueSend() 等同于 xQueueSendToBack(),作用都是将消息插入队列尾部

    • 从尾部插入的消息是普通消息,会按照插入顺序的先后被先后读取
    • 注意不是指 pcTail 所指的位置,pcTail 只是一个为了方便快速定位消息空间尾部设定的指针,在队列创建后就固定不动了
    • 这里所说的插入到队尾是指插入到 pcWriteTo 所指的位置,插入完成后,pcWriteTo 自增
  • xQueueSendToFront() 作用是将消息插入队列头部

    • 从头部插入的消息是紧急消息,紧急消息会比普通消息先被读取
    • 这里所说的插入头部并不是指插入到 pcHead 所指的位置,和 pcTail 一样,pcHead 只是一个为了方便快速定位消息空间头部设定的指针,在队列创建后就固定不动了
    • 这里所说的插入到队头是指插入到 pcReadFrom 所指的位置,插入完成后,pcReadFrom 自减(保持指向下一个要读取的消息的前一个消息
  • 这三个适用于任务中的消息发送函数实际上都调用了 xQueueGenericSend(),只是传入的发送位置的参数不同
    在这里插入图片描述

② 适用于中断中的消息发送函数

适用于中断中的消息发送函数也有三个版本(只能用于中断中执行,是不带阻塞机制的):

  • xQueueSendToBackFromISR() 等同于 xQueueSendFromISR()
    • 作用是将消息插入队列尾部,与上文的 xQueueSend() 说明一致
  • xQueueSendToFrontFromISR() 将消息插入队列头部,与上文的 xQueueSendToFront() 说明一致
    在这里插入图片描述

2. xQueueSend()与 xQueueSendToBack()

① xQueueSend() 代码

  • 原型
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
  • 参数说明

    • xQueue:队列句柄
    • pvItemToQueue:指针,指向要发送到队列尾部的队列消息
    • xTicksToWait:队列满时,等待队列空闲的最大超时时间。如果队列满并且 xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务挂起(没有超时)
  • 宏定义

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

② xQueueSend() 使用示例

该宏是为了向后兼容没有包含 xQueueSendToFront() 和 xQueueSendToBack() 这两个宏的 FreeRTOS 版本。

static void Send_Task(void* parameter)
{BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */uint32_t send_data1 = 1;uint32_t send_data2 = 2;while (1) {if (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON) {/* K1 被按下 */printf("发送消息 send_data1!\n");xReturn = xQueueSend(Test_Queue, /* 消息队列的句柄 */&send_data1, /* 发送的消息内容 */0); /* 等待时间 0 */if (pdPASS == xReturn)printf("消息 send_data1 发送成功!\n\n");}if (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON) {/* K2 被按下 */printf("发送消息 send_data2!\n");xReturn = xQueueSend(Test_Queue, /* 消息队列的句柄 */&send_data2, /* 发送的消息内容 */0); /* 等待时间 0 */if (pdPASS == xReturn)printf("消息 send_data2 发送成功!\n\n");}vTaskDelay(20); /* 延时 20 个 tick */}
}

③ xQueueSendToBack() 代码

  • 原型
    与 xQueueSend() 一样
  • 宏定义
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

3. xQueueSendFromISR()与 xQueueSendToBackFromISR()

xQueueSendFromISR() 与 xQueueSendToBackFromISR() 是等同的。

① 代码

  • 原型
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
  • 参数说明
    • xQueue:队列句柄
    • pvItemToQueue:指针,指向要发送到队列尾部的消息
    • pxHigherPriorityTaskWoken:如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将 *pxHigherPriorityTaskWoken设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换, 去执行被唤醒的优先级更高的任务 。
  • 宏定义
#define xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR((xQueue), (pvItemToQueue), (pxHigherPriorityTaskWoken), queueSEND_TO_FRONT)
#define xQueueSendToBackFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR((xQueue), (pvItemToQueue), (pxHigherPriorityTaskWoken), queueSEND_TO_BACK)

② 使用示例

使用 pxHigherPriorityTaskWoken 来确定是否需要上下文切换:

void vBufferISR(void)
{char cIn;BaseType_t xHigherPriorityTaskWoken;/* 在 ISR 开始的时候,我们并没有唤醒任务 */xHigherPriorityTaskWoken = pdFALSE;/* 直到缓冲区为空 */do {/* 从缓冲区获取一个字节的数据 */cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS);/* 发送这个数据 */xQueueSendFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken);} while (portINPUT_BYTE(BUFFER_COUNT));/* 这时候 buffer 已经为空,如果需要则进行上下文切换 */if (xHigherPriorityTaskWoken) {/* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */taskYIELD_FROM_ISR();}
}

4. xQueueSendToFront()

用于向队列队首发送一个消息,该消息会被优先读取。

① 代码

  • 原型
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );
  • 参数说明
    • xQueue 队列句柄
    • pvItemToQueue 指针,指向要发送到队首的消息
    • xTicksToWait 队列满时,等待队列空闲的最大超时时间。如果队列满并且 xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)
  • 宏定义
#define xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait) \xQueueGenericSend((xQueue), (pvItemToQueue), \(xTicksToWait), queueSEND_TO_FRONT)

5. xQueueSendToFrontFromISR()

用于在中断中向队首发送信息。

① 代码

  • 原型
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
  • 参数说明
    • xQueue 队列句柄
    • pvItemToQueue 指针,指向要发送到队首的消息
    • pxHigherPriorityTaskWoken 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将 *pxHigherPriorityTaskWoken 设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换,去执行被唤醒的优先级更高的任务 。
  • 宏定义
#define xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR((xQueue), (pvItemToQueue), \(pxHigherPriorityTaskWoken), queueSEND_TO_FRONT)

三、通用的任务中发送消息函数 xQueueGenericSend()

上述的任务中的消息发送函数 API 经过宏定义展开后实际上就是传入不同参数的 xQueueGenericSend(),下面将对这个函数进行详细的介绍。

1. xQueueGenericSend() 函数定义

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
{
BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif/* This function relaxes the coding standard somewhat to allow returnstatements within the function itself.  This is done in the interestof execution time efficiency. */for( ;; ){taskENTER_CRITICAL();{/* Is there room on the queue now?  The running task must be thehighest priority task wanting to access the queue.  If the head itemin the queue is to be overwritten then it does not matter if thequeue is full. *///消息空间未满或允许覆盖写入if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){traceQUEUE_SEND( pxQueue );xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );//此处省略队列集相关代码taskEXIT_CRITICAL();return pdPASS;}else//消息空间已满且不允许覆盖写入{if( xTicksToWait == ( TickType_t ) 0 ){/* The queue was full and no block time is specified (orthe block time has expired) so leave now. *///消息空间已满且不允许覆盖写入,且阻塞时间设置为0 或 阻塞时间已过taskEXIT_CRITICAL();/* Return to the original privilege level before exitingthe function. */traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}else if( xEntryTimeSet == pdFALSE ){/* The queue was full and a block time was specified soconfigure the timeout structure. *///消息空间已满且不允许覆盖写入,并且阻塞时间大于0所以需要配置超时结构vTaskSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. *///已经设置了进入时间mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();/* Interrupts and other tasks can send to and receive from the queuenow the critical section has been exited. *///挂起调度器 并 锁定队列的等待发送任务列表和等待接收任务列表://这两个操作都是为了防止其它任务或者中断操作 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表而引起优先级翻转//问:为什么可能会优先级翻转?//答:设想当前要进入阻塞的任务B 的优先级比已经在阻塞中的任务A 的优先级高。//正常情况下,当任务B 也进入阻塞后,此时两个任务都在阻塞中(比如都在阻塞等待入队),//那么当可以入队时应该是优先级高的B 线解除阻塞并入队;//但是可能会发生这样一种情况,在设置任务B 进入阻塞的过程中,//其它任务或者中断操作了 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表导致可以入队了,//正确的逻辑应该是解除优先级较高的任务B 的阻塞而任务A 继续阻塞等待入队,//但是此时由于任务B 还在设置进入阻塞的过程中,所以阻塞列表中只有任务A,导致只能解除//任务A 的阻塞。挂起调度器来禁止其它任务对两个列表的操作 和 锁定中断对两个列表的操作可以确保//在任务B 成功进入阻塞后在通过优先级高低来解除任务的阻塞,防止优先级较低的任务A 先于任务B 解除阻塞。vTaskSuspendAll();			//暂停任务切换,防止在运行其它任务时该任务修改队列的等待接收和等待发送列表prvLockQueue( pxQueue );	//锁定队列,防止此时有中断触发修改队列的等待接收和等待发送列表/* Update the timeout state to see if it has expired yet. */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )//延时未到期且还未插进去{if( prvIsQueueFull( pxQueue ) != pdFALSE )//如果队列还是满,就把未到期且未成功插入(等待插入)的任务放入 xTasksWaitingToSend 列表中{traceBLOCKING_ON_QUEUE_SEND( pxQueue );vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );//等待发送的任务放进等待发送列表中/* Unlocking the queue means queue events can effect theevent list.  It is possible	that interrupts occurring nowremove this task from the event	list again - but as thescheduler is suspended the task will go onto the pendingready last instead of the actual ready list. */prvUnlockQueue( pxQueue );//任务插入完成,解锁中断对两个列表的操作,但此时调度器还是挂起的状态/* Resuming the scheduler will move tasks from the pendingready list into the ready list - so it is feasible that thistask is already in a ready list before it yields - in whichcase the yield will not cause a context switch unless thereis also a higher priority task in the pending ready list. *///恢复任务调度器,根据返回值确定是否要进行任务切换//如果挂起的就绪列表中有优先级较高的任务,恢复后调度器后才需要进行任务切换if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}}else//延时未到期但是队列未满时,解除对队列列表的锁定从而可以再次尝试插入队列{/* Try again. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else//延时到期了还没有插进去,返回由于队列满插入失败{/* The timeout has expired. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}}
}
/*-----------------------------------------------------------*/

2. xQueueGenericSend() 函数流程图

下面将对 xQueueGenericSend() 中调用到的函数进行详细的说明:

  1. prvCopyDataToQueue() 消息拷贝到队列函数
  2. vTaskSetTimeOutState() 超时结构配置函数
  3. 挂起调度器函数 vTaskSuspendAll() 和 队列上锁函数 prvLockQueue() 的作用
  4. 检查延时是否到期函数 xTaskCheckForTimeOut()
  5. 将任务插入任务等待发送列表 vTaskPlaceOnEventList()
  6. 队列解锁函数 prvUnlockQueue()
    请添加图片描述

3. prvCopyDataToQueue() 函数说明

① 如何被调用

  • 在 xQueueGenericSend() 中被调用
  • 队列未满或者允许覆盖写入时,使用 prvCopyDataToQueue() 将消息拷贝到队列中:
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){traceQUEUE_SEND( pxQueue );xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );}

② 参数说明

  • prvCopyDataToQueue() 需要三个参数:
    • 指向操作的队列指针
    • 指向要拷贝到队列中的消息指针
    • 说明拷贝位置的参数(队头、队尾、覆盖)

  • 插入队尾时(xPosition == queueSEND_TO_BACK),为普通消息的发送,将消息拷贝到 pcWriteTo 所指向的消息单元后 pcWriteTo 自增,pcWriteTo 自增越界后返回 pcHead
else if( xPosition == queueSEND_TO_BACK ){( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );pxQueue->pcWriteTo += pxQueue->uxItemSize;if( pxQueue->pcWriteTo >= pxQueue->pcTail ){pxQueue->pcWriteTo = pxQueue->pcHead;}}
  • 插入队头时,为紧急消息的发送,将消息拷贝到 pcReadFrom 指向的消息单元,然后 pcReadFrom 自减一个单元。当 pcReadFrom 自减越界时,重置为指向 pcTail 的前一个单元
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) {pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );}
  • 覆盖写入时( xPosition == queueOVERWRITE ),即使队列已满也可以写入队列,这个功能旨在用于长度为 1 的队列(来自 FreeRTOS 官方),插入的位置和插入队头的位置是一样的
    在这里插入图片描述

③ prvCopyDataToQueue() 函数完整代码

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;/* This function is called from a critical section. */uxMessagesWaiting = pxQueue->uxMessagesWaiting;if( pxQueue->uxItemSize == ( UBaseType_t ) 0 ){#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){/* The mutex is no longer being held. */xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );pxQueue->pxMutexHolder = NULL;}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_MUTEXES */}else if( xPosition == queueSEND_TO_BACK ){( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e418 MISRA exception as the casts are only redundant for some ports, plus previous logic ensures a null pointer can only be passed to memcpy() if the copy size is 0. */pxQueue->pcWriteTo += pxQueue->uxItemSize;if( pxQueue->pcWriteTo >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */{pxQueue->pcWriteTo = pxQueue->pcHead;}else{mtCOVERAGE_TEST_MARKER();}}else{( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */{pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );}else{mtCOVERAGE_TEST_MARKER();}if( xPosition == queueOVERWRITE ){if( uxMessagesWaiting > ( UBaseType_t ) 0 ){/* An item is not being added but overwritten, so subtractone from the recorded number of items in the queue so whenone is added again below the number of recorded items remainscorrect. */--uxMessagesWaiting;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;return xReturn;
}
/*-----------------------------------------------------------*/

4. 超时状态结构的配置 vTaskSetTimeOutState()

① 超时状态结构如何配置

  • 在 xQueueGenericSend() 中被调用
  • 消息空间已满且不允许覆盖写入,并且阻塞时间大于 0 且未配置超时结构时需要配置超时状态结构:
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
  • 超时结构的配置主要是 溢出次数进入阻塞时间的配置:

② vTaskSetTimeOutState() 函数定义

void vTaskSetTimeOutState( TimeOut_t * const pxTimeOut )
{configASSERT( pxTimeOut );pxTimeOut->xOverflowCount = xNumOfOverflows;	//溢出次数pxTimeOut->xTimeOnEntering = xTickCount;		//进入时间 == 当前时间
}

5. 挂起调度器和锁上队列的意义

① 如何操作

//挂起调度器 并 锁定队列的等待发送任务列表和等待接收任务列表:
//这两个操作都是为了防止其它任务或者中断操作 xTasksWaitingToReceive 列表 xTasksWaitingToSend 列表而引起优先级翻转
vTaskSuspendAll();			//暂停任务切换,防止在运行其它任务时该任务修改队列的等待接收和等待发送列表prvLockQueue( pxQueue );	//锁定队列,防止此时有中断触发修改队列的等待接收和等待发送列表

② 意义

对于挂起调度器和锁上队列的意义,笔者思考了很久,如下:
挂起调度器 并 锁定队列的等待发送任务列表和等待接收任务列表:这两个操作都是为了防止其它任务或者中断操作 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表而引起优先级翻转。

  1. 当要使任务进入阻塞时,假设此任务(任务B)的优先级比已经在阻塞中的任务(任务A)优先级高。

  2. 正常情况下,两个任务都应该在阻塞列表中等待操作,当可以进行队列操作时,应该优先解除优先级高的任务B的阻塞并进行操作。

  3. 但是在某种情况下,可能发生在设置任务B进入阻塞的过程中,有其他任务或者中断对等待接收和等待发送列表进行了操作,使得可以进行队列操作。

    • 正确的行为应该是先解除优先级高的任务B的阻塞,任务A继续保持阻塞状态等待入队。
    • 但是由于任务B还在设置进入阻塞的过程中,阻塞列表中只有任务A,导致只能解除任务A的阻塞。这就导致了优先级较低的任务A先于任务B解除阻塞,出现了优先级翻转。
  4. 为了防止这种情况发生,代码中使用了两个控制操作:

    • vTaskSuspendAll() 函数暂停任务切换,即暂停调度器,防止在运行其他任务时当前任务修改队列的等待接收和等待发送列表。
    • prvLockQueue(pxQueue) 函数锁定队列,防止此时发生中断并触发了对等待接收和等待发送列表的修改。

通过挂起调度器和锁定队列,可以确保在任务B成功进入阻塞状态后,根据优先级高低来解除任务阻塞,防止优先级较低的任务A先于任务B解除阻塞,从而避免了优先级翻转的问题。

这样的保护措施可以确保在任务切换及队列操作过程中,保持任务的正确优先级顺序和避免优先级翻转对系统的影响。

③ 队列上锁代码

挂起调度器的代码在前面的文章中我们已经介绍过,这里不再赘述。

【学习日记】【FreeRTOS】调度器函数实现详解

此处介绍队列上锁代码:

  • 进入临界段
  • 标记队列控制块中的发送锁和接收锁
/** 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()
/*-----------------------------------------------------------*/

6. 检查延时是否到期函数 xTaskCheckForTimeOut()

① 使用

传入超时状态结构和设置的超时时长进行检查:

if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){//...}

② 主要逻辑

  1. 当使能了任务挂起且设定延时时间为 portMAX_DELAY 时,任务将永远被阻塞不会超时
  2. 如果现在的时间比进入延时的时间大 且 现在的时间计时溢出过,这意味着时基计数已经跑过一轮了,那么延时肯定到期了
  3. 或者是当前时间减进入时间 < 需要等待的时间,意味着延时还未到期
    • 现在需要等待的时间 = 原来需要等待的时间 - 已经等待的时间

③ 源代码

BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait )
{
BaseType_t xReturn;configASSERT( pxTimeOut );configASSERT( pxTicksToWait );taskENTER_CRITICAL();{/* Minor optimisation.  The tick count cannot change in this block. */const TickType_t xConstTickCount = xTickCount;#if( INCLUDE_xTaskAbortDelay == 1 )if( pxCurrentTCB->ucDelayAborted != pdFALSE ){/* The delay was aborted, which is not the same as a time out,but has the same result. */pxCurrentTCB->ucDelayAborted = pdFALSE;xReturn = pdTRUE;}else#endif#if ( INCLUDE_vTaskSuspend == 1 )if( *pxTicksToWait == portMAX_DELAY ){/* If INCLUDE_vTaskSuspend is set to 1 and the block timespecified is the maximum block time then the task should blockindefinitely, and therefore never time out. */xReturn = pdFALSE;}else#endifif( ( xNumOfOverflows != pxTimeOut->xOverflowCount ) && ( xConstTickCount >= pxTimeOut->xTimeOnEntering ) ) /*lint !e525 Indentation preferred as is to make code within pre-processor directives clearer. */{/* The tick count is greater than the time at whichvTaskSetTimeout() was called, but has also overflowed sincevTaskSetTimeOut() was called.  It must have wrapped all the wayaround and gone past again. This passed since vTaskSetTimeout()was called. *///现在的时间比进入延时的时间大 且 现在的时间计时溢出过//意味着时基计数已经跑过一轮了,延时肯定到期了xReturn = pdTRUE;	//延时肯定到期了}else if( ( ( TickType_t ) ( xConstTickCount - pxTimeOut->xTimeOnEntering ) ) < *pxTicksToWait ) /*lint !e961 Explicit casting is only redundant with some compilers, whereas others require it to prevent integer conversion errors. */{//当前时间减进入时间 < 需要等待的时间,意味着延时还未到期/* Not a genuine timeout. Adjust parameters for time remaining. *///现在需要等待的时间 = 原来需要等待的时间 - 已经等待的时间 *pxTicksToWait -= ( xConstTickCount - pxTimeOut->xTimeOnEntering );vTaskSetTimeOutState( pxTimeOut );xReturn = pdFALSE;	//延时没有到期}else{xReturn = pdTRUE;}}taskEXIT_CRITICAL();return xReturn;
}

7. 将任务插入任务等待发送列表 vTaskPlaceOnEventList()

① 使用

用于将延时未到期且暂时无法入队的任务插入任务等待列表:

  • 传入指向任务等待发送列表的指针
  • 传入要延时的时长
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );//等待发送的任务放进等待发送列表中

② 函数定义

  • 将当前 TCB 的事件项目插入 xTasksWaitingToSend 标识该任务正在等待发送消息
  • 将当前 TCB 插入延时列表中进行阻塞延时
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )
{configASSERT( pxEventList );/* THIS FUNCTION MUST BE CALLED WITH EITHER INTERRUPTS DISABLED OR THESCHEDULER SUSPENDED AND THE QUEUE BEING ACCESSED LOCKED. *//* Place the event list item of the TCB in the appropriate event list.This is placed in the list in priority order so the highest priority taskis the first to be woken by the event.  The queue that contains the eventlist is locked, preventing simultaneous access from interrupts. */vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
}
/*-----------------------------------------------------------*/

8. 队列解锁函数 prvUnlockQueue()

① 函数原型

/* Constants used with the cRxLock and cTxLock structure members. */
#define queueUNLOCKED					( ( int8_t ) -1 )
#define queueLOCKED_UNMODIFIED			( ( int8_t ) 0 )static void prvUnlockQueue( Queue_t * const pxQueue )
{/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. *//* The lock counts contains the number of extra data items placed orremoved from the queue while the queue was locked.  When a queue islocked items can be added or removed, but the event lists cannot beupdated. */taskENTER_CRITICAL();{int8_t cTxLock = pxQueue->cTxLock;/* See if data was added to the queue while it was locked. */while( cTxLock > queueLOCKED_UNMODIFIED ){/* Data was posted while the queue was locked.  Are any tasksblocked waiting for data to become available? */#if ( configUSE_QUEUE_SETS == 1 ){if( pxQueue->pxQueueSetContainer != NULL ){if( prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != pdFALSE ){/* The queue is a member of a queue set, and posting tothe queue set caused a higher priority task to unblock.A context switch is required. */vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}}else{/* Tasks that are removed from the event list will getadded to the pending ready list as the scheduler is stillsuspended. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record that acontext	switch is required. */vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}}else{break;}}}#else /* configUSE_QUEUE_SETS */{/* Tasks that are removed from the event list will get added tothe pending ready list as the scheduler is still suspended. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record thata context switch is required. */vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}}else{break;}}#endif /* configUSE_QUEUE_SETS */--cTxLock;}pxQueue->cTxLock = queueUNLOCKED;}taskEXIT_CRITICAL();/* Do the same for the Rx lock. */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();
}
/*-----------------------------------------------------------*/

② 函数结构分析

这段代码是 FreeRTOS 中的 prvUnlockQueue 函数的实现。以下是函数执行的主要步骤:

  1. 进入临界区。
  2. 检查发送锁的计数器 cTxLock,如果大于 queueLOCKED_UNMODIFIED(表示有锁被添加或删除),则执行以下步骤:
    a. 如果队列是队列集的成员,且添加数据导致更高优先级的任务解除阻塞,则记录需要进行任务切换。
    b. 否则,从等待接收任务列表中移除任务,并记录需要进行任务切换。
    c. 递减 cTxLock 计数器。
  3. 将发送锁设置为解锁状态。
  4. 再次进入临界区。
  5. 检查接收锁的计数器 cRxLock,如果大于 queueLOCKED_UNMODIFIED(表示有锁被添加或删除),则执行以下步骤:
    a. 从等待发送任务列表中移除任务,并记录需要进行任务切换。
    b. 递减 cRxLock 计数器。
  6. 将接收锁设置为解锁状态。
  7. 离开临界区。

可以看到,忽略锁的计数器操作(锁的计数器主要用于对队列的并发访问),3 和 6 说明只要调用这个函数,队列的等待发送任务列表和等待接收任务列表都会解锁。

③ 计数锁的原理

在FreeRTOS中,队列的cRxLock大于0的情况是在调用接收数据的API(比如xQueueReceive()函数)时。这是由于队列需要在多任务环境中保持线程安全性。当一个任务正在从队列接收数据时,会通过增加cRxLock的计数来锁定队列,防止其他任务同时进行接收操作。

具体来说,以下情况会使得cRxLock大于0:

  • 任务 A 调用了接收数据的 API,队列的 cRxLock 值增加到 1。
  • 在任务 A 还未完成接收操作之前,另一个任务 B 也调用了接收数据的 API,此时 cRxLock 值增加到 2。
  • 当任务 A 完成接收操作后,cRxLock 值减少到 1。
  • 当任务 B 完成接收操作后,cRxLock 值减少到 0。

这个锁定机制确保了队列在多任务环境中的正确操作,避免了竞态条件的发生。

四、用于中断中的 通用的消息发送函数 xQueueGenericSendFromISR()

上述的任务中的消息发送函数 API 经过宏定义展开后实际上就是传入不同参数的 xQueueGenericSendFromISR(),下面将对这个函数进行详细的介绍。

1. 函数概要

  • 这个函数用于在中断中向队列发送消息,由于是在中断中使用的,所以不带阻塞机制
  • 队列未满时或者可以覆盖写入时直接进行消息拷贝,否则直接返回队列已满错误
    • 因为是在中断中执行,需要对队列发送锁进行判断和操作
      • 如果队列发送锁是解锁状态,表示没有任务正在进行队列的发送操作,因此其他任务可以尝试从阻塞状态中恢复,并依据优先级高低进行上下文切换
      • 如果队列发送锁是上锁状态,每调用一次 xQueueGenericSendFromISR(),队列发送锁就自增 1,任务需要等待锁释放才能进行队列的发送操作,队列发送锁记录队列上锁时有多少数据尝试入队

2. 函数代码

BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );/* RTOS ports that support interrupt nesting have the concept of a maximumsystem call (or maximum API call) interrupt priority.  Interrupts that areabove the maximum system call priority are kept permanently enabled, evenwhen the RTOS kernel is in a critical section, but cannot make any calls toFreeRTOS API functions.  If configASSERT() is defined in FreeRTOSConfig.hthen portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertionfailure if a FreeRTOS API function is called from an interrupt that has beenassigned a priority above the configured maximum system call priority.Only FreeRTOS functions that end in FromISR can be called from interruptsthat have been assigned a priority at or (logically) below the maximumsystem call	interrupt priority.  FreeRTOS maintains a separate interruptsafe API to ensure interrupt entry is as fast and as simple as possible.More information (albeit Cortex-M specific) is provided on the followinglink: http://www.freertos.org/RTOS-Cortex-M3-M4.html */portASSERT_IF_INTERRUPT_PRIORITY_INVALID();/* Similar to xQueueGenericSend, except without blocking if there is no roomin the queue.  Also don't directly wake a task that was blocked on a queueread, instead return a flag to say whether a context switch is required ornot (i.e. has a task with a higher priority than us been woken by thispost). */uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){const int8_t cTxLock = pxQueue->cTxLock;traceQUEUE_SEND_FROM_ISR( pxQueue );/* Semaphores use xQueueGiveFromISR(), so pxQueue will not be asemaphore or mutex.  That means prvCopyDataToQueue() cannot resultin a task disinheriting a priority and prvCopyDataToQueue() can becalled here even though the disinherit function does not check ifthe scheduler is suspended before accessing the ready lists. */( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );/* The event list is not altered if the queue is locked.  This willbe done when the queue is unlocked later. */if( cTxLock == queueUNLOCKED ){#if ( configUSE_QUEUE_SETS == 1 ){if( pxQueue->pxQueueSetContainer != NULL ){if( prvNotifyQueueSetContainer( pxQueue, xCopyPosition ) != pdFALSE ){/* The queue is a member of a queue set, and postingto the queue set caused a higher priority task tounblock.  A context switch is required. */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority sorecord that a context switch is required. */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}}#else /* configUSE_QUEUE_SETS */{if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record that acontext	switch is required. */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_QUEUE_SETS */}else{/* Increment the lock count so the task that unlocks the queueknows that data was posted while it was locked. */pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}xReturn = pdPASS;}else{traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );xReturn = errQUEUE_FULL;}}portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}
/*-----------------------------------------------------------*/

五、消息读取 API

1. 简介

  • 任务中读取消息时,也可以指定阻塞超时时间,在队列中没有消息可供读取时对任务进行阻塞。队列中存入数据后自动转为就绪态进行数据读取或者等到超时后变回就绪态
  • 而中断中读取消息时,就不能指定阻塞机制,但同样可以输入一个提示是否进行上下文切换的参数来进行上下文的切换判断
  • 读取消息的函数分为读完将消息从队列中删除读完后将消息放回队列中原位的两种函数

2. xQueueReceive()

① 原型及作用

  • 用于从一个队列中接收消息并把消息从队列中删除
  • 接收的消息是以拷贝的形式进行的
BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);
  • xQueue 队列句柄
  • pvBuffer 指针,指向接收到要保存的数据
  • xTicksToWait 队列空时,阻塞超时的最大时间。如果该参数设置为 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(永远不会超时)

② 宏展开

 #define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) \xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \( xTicksToWait ), pdFALSE )

③ 应用

static void Receive_Task(void* parameter)
{BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为pdPASS */uint32_t r_queue; /* 定义一个接收消息的变量 */while (1) {xReturn = xQueueReceive(Test_Queue, /* 消息队列的句柄 */&r_queue, /* 接收的消息内容 */portMAX_DELAY); /* 等待时间 一直等 */if (pdTRUE == xReturn)printf("本次接收到的数据是:%d\n\n", r_queue);elseprintf("数据接收出错,错误代码: 0x%lx\n", xReturn);}
}

3. xQueuePeek()

① 作用

xQueuePeek()函数接收消息完毕不会删除消息队列中的消息。

② 宏展开

#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) \xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \( xTicksToWait ), pdTRUE )

4. xQueueReceiveFromISR() 与 xQueuePeekFromISR()

  • 类同于上文 API,但是只能在中断中使用
  • 队列项接收成功返回 pdTRUE,否则返回 pdFALSE

① xQueueReceiveFromISR() 使用示例

QueueHandle_t xQueue;/* 创建一个队列,并往队列里面发送一些数据 */
void vAFunction(void *pvParameters)
{char cValueToPost;const TickType_t xTicksToWait = (TickType_t)0xff;/* 创建一个可以容纳10个字符的队列 */xQueue = xQueueCreate(10, sizeof(char));if (xQueue == 0) {/* 队列创建失败 */}/* ... 任务其他代码 *//* 往队列里面发送两个字符如果队列满了则等待xTicksToWait个系统节拍周期*/cValueToPost = 'a';xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);cValueToPost = 'b';xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);/* 继续往队列里面发送字符当队列满的时候该任务将被阻塞 */cValueToPost = 'c';xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);
}/* 中断服务程序:输出所有从队列中接收到的字符 */
void vISR_Routine(void)
{BaseType_t xTaskWokenByReceive = pdFALSE;char cRxedChar;while (xQueueReceiveFromISR(xQueue,(void *)&cRxedChar,&xTaskWokenByReceive)) {/* 接收到一个字符,然后输出这个字符 */vOutputCharacter(cRxedChar);/* 如果从队列移除一个字符串后唤醒了向此队列投递字符的任务,那么参数xTaskWokenByReceive将会设置成pdTRUE,这个循环无论重复多少次,仅会有一个任务被唤醒 */}if (xTaskWokenByReceive != pdFALSE) {/* 我们应该进行一次上下文切换,当ISR返回的时候则执行另外一个任务 *//* 这是一个上下文切换的宏,不同的处理器,具体处理的方式不一样 */taskYIELD();}
}

六、通用读取消息函数 xQueueGenericReceive()

1. 函数结构分析

这段代码是FreeRTOS中的xQueueGenericReceive函数的实现。以下是函数执行的主要步骤:

  1. 检查输入参数的有效性。
  2. 进入临界区。
  3. 检查队列中是否有数据。
    • 如果有数据,则:
      • 如果只是查看数据(xJustPeeking为pdTRUE),则将数据复制到pvBuffer,并恢复读指针,然后从等待接收任务列表中移除任务。
      • 如果要删除数据(xJustPeeking为pdFALSE),则将数据复制到pvBuffer,并更新队列中的消息计数、处理互斥(如果队列类型是互斥),并从等待发送任务列表中移除任务。
      • 退出临界区,返回pdPASS表示成功。
    • 如果队列为空且等待时间为0,则退出临界区,返回errQUEUE_EMPTY表示队列为空。
    • 如果队列为空且需要等待一段时间,则设置超时结构体xTimeOut,并继续循环。
  4. 退出临界区。
  5. 挂起调度器,并锁定队列。
  6. 更新超时状态,并检查超时时间是否已过期。
    • 如果超时时间未过期且队列仍为空,则:
      • 如果队列是互斥队列类型,则提升互斥的持有任务的优先级。
      • 将任务添加到等待接收任务列表中,并解锁队列。
      • 恢复调度器状态,如果返回pdFALSE则进行任务切换。
    • 如果超时时间已过期,则返回errQUEUE_EMPTY表示队列为空。
    • 如果队列非空,则继续循环。
  7. 循环直到成功或超时。

总之,这个函数的主要功能是从队列中接收数据,并在数据不可用时进行阻塞等待,直到有数据可用或超时。它还处理了互斥队列和任务优先级的协调。

2. 函数定义

BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;configASSERT( pxQueue );configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif/* This function relaxes the coding standard somewhat to allow returnstatements within the function itself.  This is done in the interestof execution time efficiency. */for( ;; ){taskENTER_CRITICAL();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;/* Is there data in the queue now?  To be running the calling taskmust be the highest priority task wanting to access the queue. */if( uxMessagesWaiting > ( UBaseType_t ) 0 ){/* Remember the read position in case the queue is only beingpeeked. */pcOriginalReadPosition = pxQueue->u.pcReadFrom;prvCopyDataFromQueue( pxQueue, pvBuffer );if( xJustPeeking == pdFALSE ){traceQUEUE_RECEIVE( pxQueue );/* Actually removing data, not just peeking. */pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){/* Record the information required to implementpriority inheritance should it become necessary. */pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_MUTEXES */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{traceQUEUE_PEEK( pxQueue );/* The data is not being removed, so reset the readpointer. */pxQueue->u.pcReadFrom = pcOriginalReadPosition;/* The data is being left in the queue, so see if there areany other tasks waiting for the data. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority than this task. */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();return pdPASS;}else{if( xTicksToWait == ( TickType_t ) 0 ){/* The queue was empty and no block time is specified (orthe block time has expired) so leave now. */taskEXIT_CRITICAL();traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else if( xEntryTimeSet == pdFALSE ){/* The queue was empty and a block time was specified soconfigure the timeout structure. */vTaskSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. */mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();/* Interrupts and other tasks can send to and receive from the queuenow the critical section has been exited. */vTaskSuspendAll();prvLockQueue( pxQueue );/* Update the timeout state to see if it has expired yet. */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){taskENTER_CRITICAL();{vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );}taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}}#endifvTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );prvUnlockQueue( pxQueue );if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}else{/* Try again. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else{mtCOVERAGE_TEST_MARKER();}}}
}
/*-----------------------------------------------------------*/

四、性能提示

无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

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

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

相关文章

【链表OJ 11】复制带随机指针的链表

前言: &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&#x1f4a5; ✨✨刷题专栏:http://t.csdn.cn/UlvTc ⛳⛳本篇内容:力扣上链表OJ题目 目录 leetcode138. 复制带随机指针的链表 1. 问题描述 2.代码思路: 2.1拷贝节点插入到…

【文心一言大模型插件制作初体验】制作面试错题本大模型插件

文心一言插件开发初体验 效果图 注意&#xff1a;目前插件仅支持在本地运行&#xff0c;虽然只能自用&#xff0c;但仍然是一个不错的选择。&#xff08;什么&#xff1f;你说没有用&#xff1f;这不可能&#xff01;文心一言app可以支持语音&#xff0c;网页端结合手机端就可…

计算机网络第三节物理层

一&#xff0c;第二章 物理层&#xff08;数据通信有关&#xff09; 1.物理层引入的目的 屏蔽掉传输介质的多样性&#xff0c;导致数据传输方式的不同&#xff1b;物理层的引入使得高层看到的数据都是统一的0,1构成的比特流 2.物理层如何实现屏蔽 物理层靠定义的不同的通信…

智慧园区用水用电信息管理系统:实现高效节能的现代化园区管理

随着科技的不断发展&#xff0c;各类产业园区在我国经济社会发展中发挥着越来越重要的作用。为了提高园区的运营效率、降低能源消耗、实现绿色可持续发展&#xff0c;智慧园区用水用电信息管理系统应运而生。本文将从系统背景、功能特点、应用优势等方面进行详细介绍。 一、系统…

java八股文面试[数据库]——索引下推

什么是索引下推&#xff1f; 索引下推&#xff08;index condition pushdown &#xff09;简称ICP&#xff0c;在Mysql5.6的版本上推出&#xff0c;用于优化查询。 需求: 查询users表中 "名字第一个字是张&#xff0c;年龄为10岁的所有记录"。 SELECT * FROM users…

element+vue table表格全部数据和已选数据联动

1.组件TableChoose <template><div class"tableChooseBox"><div class"tableRow"><div class"tableCard"><div class"tableHeadTip">全部{{ labelTitle }}</div><slot name"body" …

Jupyter Notebook 好用在哪?

Jupyter Notebook 是一个 Web 应用程序&#xff0c;便于创建和共享文学化程序文档&#xff0c;支持实时代码、数学方程、可视化和 Markdown&#xff0c;其用途包括数据清理和转换、数值模拟、统计建模、机器学习等等。目前&#xff0c;数据挖掘领域中最热门的比赛 Kaggle 里的资…

MySQL用navicat工具对表进行筛选查找

这个操作其实很简单&#xff0c;但是对于没操作的人来说&#xff0c;就是不会呀。所以小编出这一个详细图解&#xff0c;希望能够帮助到大家。话不多说看图。 第一步&#xff1a; 点进一张表&#xff0c;点击筛选。 第二步&#xff1a; 点击添加 第三步&#xff1a; 选择要…

诗诺克科技引领数字资产智能交易革命

在当今全球金融市场中&#xff0c;数字资产的崛起正引发着一场前所未有的变革。随着区块链技术不断演进和数字资产广泛获得认可&#xff0c;智能交易系统正在迅速成为投资者和交易者的首选工具。这一趋势不仅在全球范围内显著&#xff0c;而且为金融领域的未来带来了令人瞩目的…

前端、后端面试集锦

诸位读者&#xff0c;我们在工作的过程中&#xff0c;经常会因跳槽而面试。 你开发能力很强&#xff0c;懂得技术也很多&#xff0c;若加上条理清晰的面试话术&#xff0c;可以让您的面试事半功倍。 个人博客阅读量破170万&#xff0c;为尔倾心打造的 面试专栏-前端、后端面试…

总结开发中一些数据处理方法的封装

摘要&#xff1a; 开发中经常会遇到一些组件需要的特定数据结构&#xff0c;后端不一定会返回你需要的数据结构的&#xff0c;所以还是要前端来处理的&#xff01;这里来总结一下平常开发中遇到的需要处理结构的方法&#xff0c;下次遇到直接拿来用就可以了&#xff01; 目录概…

Docker构建Springboot项目,并发布测试

把SpringBoot项目打包成Docker镜像有两种方案&#xff1a; 全自动化&#xff1a;先打好docker镜像仓库&#xff0c;然后在项目的maven配置中配置好仓库的地址&#xff0c;在项目里配置好Dockerfile文件&#xff0c;这样可以直接在idea中打包好后自动上传到镜像仓库&#xff0c…

抢跑预制菜,双汇发展转守为攻?

懒&#xff0c;懒出新风口&#xff0c;预制菜竟成了年轻人新时代的“田螺神话”&#xff1f; 《2022年中国预制菜产业发展白皮书》数据显示&#xff0c;2022年全国预制菜的市场规模是4196亿元人民币&#xff0c;到2026年可以突破万亿大关。 预制菜的火爆显而易见&#xff0c;…

使用docker容器内的anaconda虚拟环境启动python web项目

1、环境安装 1.1 基础镜像 这里以ubuntu18.04 cuda 11.8为基础镜像&#xff08;主机支持nvidia-gpu&#xff09; &#xff08;1&#xff09;拉取ubuntu18.4 cuda11.8镜像 docker pull nvidia/cuda:11.8.0-devel-ubuntu18.04 1.2 docker下anaconda安装 &#xff08;1&am…

Linux centos7 bash编程(循环与条件判断)

在编程训练中&#xff0c;循环结构与条件判断十分重要。 根据条件为真为假确定是否执行循环。 有时&#xff0c;根据条件的真假结果&#xff0c;决定执行哪些语句&#xff0c;这就是分支语句。 为了训练分支语句与循环语句&#xff0c;我们设计一个案例&#xff1a; 求一组…

【数据结构】顺序表详解

当我们写完通讯录后&#xff0c;顺序表肯定难不倒你&#xff0c;跟着小张一起来学习顺序表吧&#xff01; 线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#x…

EasyPOI处理excel、CSV导入导出

1 简介 使用POI在导出导出excel、导出csv、word时代码有点过于繁琐&#xff0c;好消息是近两年在开发市场上流行一种简化POI开发的类库&#xff1a;easyPOI。从名称上就能发现就是为了简化开发。 能干什么&#xff1f; Excel的快速导入导出,Excel模板导出,Word模板导出,可以…

k8s环境部署配置

目录 一.虚拟机准备 二.基础环境配置&#xff08;各个节点都做&#xff09; 1.IP和hosts解析 2.防火墙和selinux 3.安装基本软件 4.配置时间同步 5.禁用swap分区 6.修改内核参数并重载 7.配置ipvs 三.docker环境&#xff08;各个节点都做&#xff09; 1.配置软件源并…

【构造】CF Edu 12 D

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 这种题一定要从小数据入手&#xff0c;不然很有可能走歪思路 先考虑n 1的情况&#xff0c;直接输出即可 然后是n 2的情况&#xff0c;如果相加是质数&#xff0c;就输出2个&#xff0c;否则就输出一个 然后…

MATLAB 动态图GIF

MATLAB 动态图GIF 前言一、创建动态图&#xff08;动态曲线、动态曲面&#xff09;1. 创建动画曲线&#xff08;MATLAB animatedline函数&#xff09;2. 创建动画曲面 二. 保存动态图三、完整示例1. 动态曲线&#xff08; y s i n ( x ) ysin(x) ysin(x)&#xff09;2. 动态曲…