send有优先级翻转

17.5 消息队列控制块

FreeRTOS 的消息队列控制块由多个元素组成,当消息队列被创建时,系统会为控制块分配对应的内存空间,用于保存消息队列的一些信息如消息的存储位置,头指针 pcHead、尾指针 pcTail、消息大小 uxItemSize 以及队列长度 uxLength, 以及当前队列消息个数uxMessagesWaiting 等。

  1. typedef struct QueueDefinition
  2. {
  3. int8_t *pcHead; // pcHead 指向队列消息存储区起始位置,即第一个消息空间。
  4. int8_t *pcTail; // pcTail 指向队列消息存储区结束位置地址。
  5. int8_t *pcWriteTo; // pcWriteTo 指向队列消息存储区下一个可用消息空间。
  6. union
  7. {
  8. // 从 pcReadFrom 指向的空间读取消息内容
  9. int8_t *pcReadFrom; //pcReadFrom 指向出队消息空间的最后一个
  10. // 当结构体用于互斥量时, uxRecursiveCallCount 用于计数,记录递归互斥量被“调用” 的次数
  11. UBaseType_t uxRecursiveCallCount;
  12. } u;
  13. // xTasksWaitingToSend 是一个发送消息阻塞列表, 用于保存阻塞在
  14. // 此队列的任务, 任务按照优先级进行排序, 由于队列已满,想要发送消息的任务无法发送
  15. // 消息。
  16. List_t xTasksWaitingToSend;
  17. // xTasksWaitingToReceive 是一个获取消息阻塞列表, 用于保存阻塞
  18. // 在此队列的任务, 任务按照优先级进行排序, 由于队列是空的,想要获取消息的任务无法
  19. // 获取到消息。
  20. List_t xTasksWaitingToReceive;
  21. // uxMessagesWaiting 用于记录当前消息队列的消息个数, 如果消息
  22. // 队列被用于信号量的时候, 这个值就表示有效信号量个数。
  23. volatile UBaseType_t uxMessagesWaiting;
  24. UBaseType_t uxLength; // uxLength 表示队列的长度,也就是能存放多少消息。
  25. UBaseType_t uxItemSize; // uxItemSize 表示单个消息的大小
  26. // 队列上锁后, 储存从队列收到的列表项数目, 也就是出队的数量,
  27. // 如果队列没有上锁,设置为 queueUNLOCKED
  28. volatile int8_t cRxLock;
  29. // 队列上锁后, 储存发送到队列的列表项数目, 也就是入队的数量,
  30. // 如果队列没有上锁,设置为 queueUNLOCKED
  31. volatile int8_t cTxLock;
  32. #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
  33. uint8_t ucStaticallyAllocated;
  34. #endif
  35. #if ( configUSE_QUEUE_SETS == 1 )
  36. struct QueueDefinition *pxQueueSetContainer;
  37. #endif
  38. #if ( configUSE_TRACE_FACILITY == 1 )
  39. UBaseType_t uxQueueNumber;
  40. uint8_t ucQueueType;
  41. #endif
  42. } xQUEUE;
  43. typedef xQUEUE Queue_t;

17.6 消息队列的常用函数

使用队列模块的典型流程如下:

  • 创建消息队列
  • 写队列操作
  • 读队列操作
  • 删除队列

    17.6.1 消息队列创建函数

    xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。 队列句柄其实就是一个指向队列数据结构类型的指针。

    • 队列就是一个数据结构,用于任务间的数据的传递。
    • 使用的是动态内存分配
    • 在FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1
    • 如果想使用静态内存,则可以使用 xQueueCreateStatic() 函数来创建一个队列。
      1. #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
      2. #define xQueueCreate( uxQueueLength, uxItemSize )
      3. xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
      4. #endif
      image.png
      创建队列真正使用的函数是 xQueueGenericCreate()。 ```c

      if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) { Queue_t pxNewQueue; size_t xQueueSizeInBytes; uint8_t pucQueueStorage;

  1. configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
  2. if( uxItemSize == ( UBaseType_t ) 0 )
  3. {
  4. /* 消息空间大小为 0*/
  5. xQueueSizeInBytes = ( size_t ) 0;
  6. }
  7. else
  8. {
  9. /* 分配足够消息存储空间,空间的大小为队列长度*单个消息大小 */
  10. xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
  11. }
  12. /* 向系统申请内存,内存大小为消息队列控制块大小+消息存储空间大小 */
  13. pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
  14. if( pxNewQueue != NULL )
  15. {
  16. /* 计算出消息存储空间的起始地址 */
  17. pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
  18. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
  19. {
  20. /* Queues can be created either statically or dynamically, so
  21. note this task was created dynamically in case it is later
  22. deleted. */
  23. pxNewQueue->ucStaticallyAllocated = pdFALSE;
  24. }
  25. #endif /* configSUPPORT_STATIC_ALLOCATION */
  26. // 调用 prvInitialiseNewQueue()函数将消息队列进行初始化。 其实
  27. // xQueueGenericCreate()主要是用于分配消息队列内存的
  28. prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
  29. }
  30. return pxNewQueue;

}

endif / configSUPPORT_STATIC_ALLOCATION /

/—————————————————————————————-/

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1572386/1648608300313-1e32b5d1-53b2-45ce-b6e8-47c4fd65146a.png#clientId=u00127591-a583-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=258&id=u0128217f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=298&originWidth=378&originalType=binary&ratio=1&rotation=0&showTitle=true&size=34760&status=done&style=none&taskId=u87f7c0b1-b03b-45ff-bfb9-c98c9d6e9e2&title=%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E7%9A%84%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4%E7%A4%BA%E6%84%8F%E5%9B%BE&width=327 "消息队列的内存空间示意图")
  2. <a name="Q3SXY"></a>
  3. #### 1. prvInitialiseNewQueue()
  4. 参数:<br />const UBaseType_t uxQueueLength:消息队列长度 <br />const UBaseType_t uxItemSize 单个消息大小 <br />uint8_t *pucQueueStorage 存储消息起始地址
  5. - const uint8_t ucQueueType 消息队列类型
  6. - queueQUEUE_TYPE_BASE:表示队列。
  7. - queueQUEUE_TYPE_SET:表示队列集合
  8. - queueQUEUE_TYPE_MUTEX:表示互斥量。
  9. - queueQUEUE_TYPE_COUNTING_SEMAPHORE:表示计数信号量。
  10. - queueQUEUE_TYPE_BINARY_SEMAPHORE:表示二进制信号量。
  11. - queueQUEUE_TYPE_RECURSIVE_MUTEX:表示递归互斥量。
  12. Queue_t *pxNewQueue 消息队列控制块
  13. ```c
  14. static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength,
  15. const UBaseType_t uxItemSize,
  16. uint8_t *pucQueueStorage,
  17. const uint8_t ucQueueType,
  18. Queue_t *pxNewQueue )
  19. {
  20. /* Remove compiler warnings about unused parameters should
  21. configUSE_TRACE_FACILITY not be set to 1. */
  22. ( void ) ucQueueType;
  23. if( uxItemSize == ( UBaseType_t ) 0 )
  24. {
  25. /*如果没有为消息队列分配存储消息的内存空间,而且 pcHead 指针
  26. 不能设置为 NULL,因为队列用作互斥量时, pcHead 要设置成 NULL,这里只能将 pcHead
  27. 向一个已知的区域,指向消息队列控制块 pxNewQueue。 */
  28. pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
  29. }
  30. else
  31. {
  32. /* 如果分配了存储消息的内存空间,则设置 pcHead 指向存储消息的
  33. 起始地址 pucQueueStorage */
  34. pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
  35. }
  36. /* 初始化消息队列控制块的其他成员,消息队列的长度与消息的大小。 */
  37. pxNewQueue->uxLength = uxQueueLength;
  38. pxNewQueue->uxItemSize = uxItemSize;
  39. /* 重置消息队列,在消息队列初始化的时候,需要重置一下相关参数 下面有*/
  40. ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
  41. #if ( configUSE_TRACE_FACILITY == 1 )
  42. {
  43. pxNewQueue->ucQueueType = ucQueueType;
  44. }
  45. #endif /* configUSE_TRACE_FACILITY */
  46. #if( configUSE_QUEUE_SETS == 1 )
  47. {
  48. pxNewQueue->pxQueueSetContainer = NULL;
  49. }
  50. #endif /* configUSE_QUEUE_SETS */
  51. traceQUEUE_CREATE( pxNewQueue );
  52. }
  53. /*-----------------------------------------------------------*/

2. xQueueGenericReset

  1. BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
  2. {
  3. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  4. configASSERT( pxQueue );
  5. // 进入临界区
  6. taskENTER_CRITICAL();
  7. {
  8. // 重置消息队列的成员变量, pcTail 指向存储消息内存空间的结束地址。
  9. pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
  10. // 当前消息队列中的消息个数 uxMessagesWaiting 为 0。
  11. pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
  12. // pcWriteTo 指向队列消息存储区下一个可用消息空间,因为是重置
  13. // 消息队列,就指向消息队列的第一个消息空间,也就是 pcHead 指向的空间。
  14. pxQueue->pcWriteTo = pxQueue->pcHead;
  15. // pcReadFrom 指向消息队列最后一个消息空间
  16. pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
  17. // 消息队列没有上锁,设置为 queueUNLOCKED。
  18. pxQueue->cRxLock = queueUNLOCKED;
  19. pxQueue->cTxLock = queueUNLOCKED;
  20. /*如果不是新建一个消息队列,那么之前的消息队列可能阻塞了一
  21. 些任务,需要将其解除阻塞。如果有发送消息任务被阻塞,那么需要将它恢复,而如果任
  22. 务是因为读取消息而阻塞,那么重置之后的消息队列也是空的,则无需被恢复。*/
  23. if( xNewQueue == pdFALSE )
  24. {
  25. // 消息队列不为空
  26. if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
  27. {
  28. // 移除
  29. if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
  30. {
  31. queueYIELD_IF_USING_PREEMPTION();
  32. }
  33. else
  34. {
  35. mtCOVERAGE_TEST_MARKER();
  36. }
  37. }
  38. else
  39. {
  40. mtCOVERAGE_TEST_MARKER();
  41. }
  42. }
  43. else
  44. {
  45. /* 这里是创建一个新的队列:初始化队列*/
  46. vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
  47. vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
  48. }
  49. }
  50. taskEXIT_CRITICAL(); // 退出临界区
  51. /* A value is returned for calling semantic consistency with previous
  52. versions. */
  53. return pdPASS;
  54. }

image.png
在创建消息队列的时候,是需要用户自己定义消息队列的句柄的,但是注意了,定义了队列的句柄并不等于创建了队列,创建队列必须是调用消息队列创建函数进行创建(可以是静态也可以是动态创建) ,否则,以后根据队列句柄使用消息队列的其它函数的时候会发生错误,创建完成会返回消息队列的句柄,用户通过句柄就可使用消息队列进行发送与读取消息队列的操作,如果返回的是 NULL 则表示创建失败,消息队列创建函数xQueueCreate() 。

使用示例:

  1. QueueHandle_t Test_Queue =NULL;
  2. #define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
  3. #define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
  4. BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
  5. taskENTER_CRITICAL(); // 进入临界区
  6. /* 创建 Test_Queue */
  7. Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
  8. (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
  9. if (NULL != Test_Queue)
  10. printf("创建 Test_Queue 消息队列成功!\r\n");
  11. taskEXIT_CRITICAL(); //退出临界区

17.6.2 消息队列静态创建函数xQueueCreateStatic()

xQueueCreateStatic()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。队列句柄其实就是一个指向队列数据结构类型的指针。
用xQueueCreateStatic()创建队列时,使用的是静态内存分配,所以要想使用该函数必须在FreeRTOSConfig.h 中把 configSUPPORT_STATIC_ALLOCATION 定义为 1 来使能。
使用方法和动态创建的类似。
image.png
使用示例:

  1. /* 创建一个可以最多可以存储 10 个 64 位变量的队列 */
  2. #define QUEUE_LENGTH 10
  3. #define ITEM_SIZE sizeof( uint64_t )
  4. /* 该变量用于存储队列的数据结构 */
  5. static StaticQueue_t xStaticQueue;
  6. /* 该数组作为队列的存储区域,大小至少有 uxQueueLength * uxItemSize 个字节 */
  7. uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];
  8. void vATask( void *pvParameters )
  9. {
  10. QueueHandle_t xQueue;
  11. /* 创建一个队列 */
  12. xQueue = xQueueCreateStatic( QUEUE_LENGTH, /* 队列深度 */
  13. ITEM_SIZE, /* 队列数据单元的单位 */
  14. ucQueueStorageArea,/* 队列的存储区域 */
  15. &xStaticQueue ); /* 队列的数据结构 */
  16. }

17.6.3 消息队列删除函数

队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。

  1. void vQueueDelete( QueueHandle_t xQueue )
  2. {
  3. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  4. // 对传入的消息队列句柄进行检查,如果消息队列是有效的才允许进行删除操作
  5. configASSERT( pxQueue );
  6. traceQUEUE_DELETE( pxQueue );
  7. #if ( configQUEUE_REGISTRY_SIZE > 0 )
  8. {
  9. /* 将消息队列从注册表中删除,我们目前没有添加到注册表中,暂时不用理会 */
  10. vQueueUnregisterQueue( pxQueue );
  11. }
  12. #endif
  13. #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
  14. {
  15. /* 因为用的消息队列是动态分配内存的,所以需要调用
  16. vPortFree 来释放消息队列的内存 */
  17. vPortFree( pxQueue );
  18. }
  19. #endif /* configSUPPORT_DYNAMIC_ALLOCATION */
  20. }
  21. /*-----------------------------------------------------------*/

需要注意的是调用删除消息队列函数前,系统应存在 xQueueCreate()或 xQueueCreateStatic()函数创建的消息队列。 此外vQueueDelete()也可用于删除信号量。 如果删除消息队列时,有任务正在等待消息, 则不应该进行删除操作。

使用示例:

  1. #define QUEUE_LENGTH 5
  2. #define QUEUE_ITEM_SIZE 4
  3. int main( void )
  4. {
  5. QueueHandle_t xQueue;
  6. /* 创建消息队列 */
  7. xQueue = xQueueCreate( QUEUE_LENGTH, QUEUE_ITEM_SIZE );
  8. if ( xQueue == NULL ) {
  9. /* 消息队列创建失败 */
  10. } else {
  11. /* 删除已创建的消息队列 */
  12. vQueueDelete( xQueue );
  13. }
  14. }

17.6.4 向消息队列发送消息函数

  1. 务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队
  2. FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队
  3. 当其它任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转为就绪态。
  4. 当任务等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码 errQUEUE_FULL | 功能 | 正常任务使用 | 中断使用 | | —- | —- | —- | | 队列后入 | xQueueSend | xQueueSendFromISR | | | xQueueSendToBack | xQueueSendToBackFromISR | | 队列前入 | xQueueSendToFront | xQueueSendToFrontFromISR | | 调用的API | xQueueGenericSend | xQueueGenericSendFromISR |

紧急消息:
当发送紧急消息时,发送的位置是消息队列队头而非队尾。

注意:
其实消息队列发送函数有好几个, 都是使用宏定义进行展开的, 有些只能在任务调用,有些只能在中断中调用。

1. xQueueSend()与 xQueueSendToBack()

  1. #define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \
  2. xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \
  3. ( xTicksToWait ), queueSEND_TO_BACK )
  1. #define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) \
  2. xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \
  3. ( xTicksToWait ), queueSEND_TO_BACK )

xQueueSend()用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以引用的形式。该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。
image.png
使用示例:

  1. static void Send_Task(void* parameter)
  2. {
  3. BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
  4. uint32_t send_data1 = 1;
  5. uint32_t send_data2 = 2;
  6. while (1) {
  7. if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
  8. /* K1 被按下 */
  9. printf("发送消息 send_data1! \n");
  10. xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
  11. &send_data1,/* 发送的消息内容 */
  12. 0 ); /* 等待时间 0 */
  13. if (pdPASS == xReturn)
  14. printf("消息 send_data1 发送成功!\n\n");
  15. }
  16. if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
  17. /* K2 被按下 */
  18. printf("发送消息 send_data2! \n");
  19. xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
  20. &send_data2,/* 发送的消息内容 */
  21. 0 ); /* 等待时间 0 */
  22. if (pdPASS == xReturn)
  23. printf("消息 send_data2 发送成功!\n\n");
  24. }
  25. vTaskDelay(20);/* 延时 20 个 tick */
  26. }
  27. }

2.xQueueSendFromISR()与 xQueueSendToBackFromISR()

  1. #define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) \
  2. xQueueGenericSendFromISR( ( xQueue ), \
  3. ( pvItemToQueue ), \
  4. ( pxHigherPriorityTaskWoken ), \
  5. queueSEND_TO_BACK )
  1. #define xQueueSendToBackFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \
  2. xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), \
  3. ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

xQueueSendFromISR()是一个宏, 宏展开是调用函数 xQueueGenericSendFromISR()。 该
宏是 xQueueSend()的中断保护版本。
image.png
代码示例:

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

3. xQueueSendToFront()

消息前入队

  1. #define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) \
  2. xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \
  3. ( xTicksToWait ), queueSEND_TO_FRONT )

image.png

4. xQueueSendToFrontFromISR()

image.png

5.通用消息队列发送函数xQueueGenericSend()

部分有删减 删除消息集合
消息集合:这个是简要的说明什么是消息集合,其实就是多种类型的消息,放在一个队列里面。
参数:
QueueHandle_t xQueue:消息队列句柄
const void * const pvItemToQueue:指针,指向要发送的消息
TickType_t xTicksToWait:指定阻塞超时时间
const BaseType_t xCopyPosition:发送数据到消息队列的位置

  • queueSEND_TO_BACK: 发送到队尾;
  • queueSEND_TO_FRONT:发送到队头;
  • queueOVERWRITE: 以覆盖的方式发送

注意:这个是个循环的函数

  1. BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
  2. const void * const pvItemToQueue,
  3. TickType_t xTicksToWait,
  4. const BaseType_t xCopyPosition )
  5. {
  6. BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
  7. TimeOut_t xTimeOut;
  8. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  9. /* 删除一些断言*/
  10. for( ;; )
  11. {
  12. taskENTER_CRITICAL();
  13. {
  14. /* 队列未满 */
  15. // uxMessagesWaiting表示有多少数量的
  16. if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
  17. || ( xCopyPosition == queueOVERWRITE ) )
  18. {
  19. traceQUEUE_SEND( pxQueue );
  20. // 如果队列没满,可以调用 prvCopyDataToQueue()函数将消息拷贝到消息队列中。
  21. xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
  22. /* 删除队列集合 */
  23. {
  24. /* 如果有任务在等待获取此消息队列 */
  25. if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
  26. {
  27. /* 将任务从阻塞中恢复 */
  28. if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
  29. {
  30. /* 如果恢复的任务优先级比当前运行任务优先级还高,
  31. 那么需要进行一次任务切换 */
  32. queueYIELD_IF_USING_PREEMPTION();
  33. }
  34. else
  35. {
  36. mtCOVERAGE_TEST_MARKER();
  37. }
  38. }
  39. else if( xYieldRequired != pdFALSE )
  40. {
  41. /* 如果没有等待的任务,拷贝成功也需要任务切换 */
  42. queueYIELD_IF_USING_PREEMPTION();
  43. }
  44. else
  45. {
  46. mtCOVERAGE_TEST_MARKER();
  47. }
  48. }
  49. taskEXIT_CRITICAL();
  50. return pdPASS;
  51. }
  52. /* 队列已满 */
  53. else
  54. {
  55. /* 如果用户不指定阻塞超时时间,退出 */
  56. if( xTicksToWait == ( TickType_t ) 0 )
  57. {
  58. /* The queue was full and no block time is specified (or
  59. the block time has expired) so leave now. */
  60. taskEXIT_CRITICAL();
  61. /* Return to the original privilege level before exiting
  62. the function. */
  63. traceQUEUE_SEND_FAILED( pxQueue );
  64. return errQUEUE_FULL;
  65. }
  66. else if( xEntryTimeSet == pdFALSE )
  67. {
  68. /* 初始化阻塞超时结构体变量,初始化进入
  69. 阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
  70. vTaskSetTimeOutState( &xTimeOut );
  71. xEntryTimeSet = pdTRUE;
  72. }
  73. else
  74. {
  75. /* Entry time was already set. */
  76. mtCOVERAGE_TEST_MARKER();
  77. }
  78. }
  79. }
  80. taskEXIT_CRITICAL();
  81. /* 挂起调度器 */
  82. vTaskSuspendAll();
  83. /* 队列上锁 */
  84. prvLockQueue( pxQueue );
  85. /* 检查超时时间是否已经过去了 */
  86. if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
  87. {
  88. /* 如果队列还是满的 */
  89. if( prvIsQueueFull( pxQueue ) != pdFALSE )
  90. {
  91. traceBLOCKING_ON_QUEUE_SEND( pxQueue );
  92. /* 将当前任务添加到队列的等待发送列表中
  93. 以及阻塞延时列表,延时时间为用户指定的超时时间 xTicksToWait */
  94. vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
  95. /* 队列解锁 */
  96. prvUnlockQueue( pxQueue );
  97. /* 恢复调度器 */
  98. if( xTaskResumeAll() == pdFALSE )
  99. {
  100. portYIELD_WITHIN_API();
  101. }
  102. }
  103. else
  104. {
  105. /* 队列有空闲消息空间,允许入队 */
  106. prvUnlockQueue( pxQueue );
  107. ( void ) xTaskResumeAll();
  108. }
  109. }
  110. else
  111. {
  112. /* 超时时间已过,退出 */
  113. prvUnlockQueue( pxQueue );
  114. ( void ) xTaskResumeAll();
  115. traceQUEUE_SEND_FAILED( pxQueue );
  116. return errQUEUE_FULL;
  117. }
  118. }
  119. }
  1. 因为接下来的操作系统不允许其他任务访问队列,简单粗暴挂起调度器就不会进行任务切换,但是挂起调度器并不会禁止中断的发生,所以还需给队列上锁,因为系统不希望突然有中断操作这个队列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表

总结:

  1. 阻塞时间不为0,则任务会因为等待而进入阻塞。
  2. 在将任务设置为阻塞的过程中, 系统不希望有其它任务和中断操作这个队列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,因为可能引起其它任务解除阻塞,这可能会发生优先级翻转。

任务A的优先级小于当前的任务,当前的任务因为队列满了,要进入阻塞队列。如果此时消费了一条消息,消息队列不满了,如果此时能操作队列,那么优先级小的任务A会解除阻塞,本来应该是要阻塞的当前任务先获得执行,然而此时却是低优先级的任务先执行了,那么就发生了优先级的翻转。本来优先级低的任务A先执行了。

优先级翻转:
解释:比如任务 A 的优先级低于当前任务,但是在当前任务进入阻塞的过程中(代码87~109),任务 A 却因为其它原因解除阻塞了,这显然是要绝对禁止的。因此FreeRTOS 使用挂起调度器禁止其它任务操作队列,因为挂起调度器意味着任务不能切换并且不准调用可能引起任务切换的 API 函数。但挂起调度器并不会禁止中断,中断服务函数仍然可以操作队列事件列表,可能会解除任务阻塞、可能会进行上下文切换,这也是不允许的。于是,解决办法是不但挂起调度器,还要给队列上锁,禁止任何中断来操作队列。

6.消息队列发送函数 xQueueGenericSendFromISR()(中断)

既然有任务中发送消息的函数,当然也需要有在中断中发送消息函数, 其实这个函数跟 xQueueGenericSend() 函 数 很 像 , 只不过是执行的上下文环境是不一样的 ,xQueueGenericSendFromISR()函数只能用于中断中执行,是不带阻塞机制的。
同样:删除了
参数:

  • QueueHandle_t xQueue:消息队列句柄。
  • const void * const pvItemToQueue:指针,指向要发送的消息。
  • BaseType_t const pxHigherPriorityTaskWoken:如果入队导致一个任务解锁,并且解锁的任务优先级高于当前运行的任务,则该函数将pxHigherPriorityTaskWoken设置成pdTRUE 。 如果xQueueSendFromISR()设置这个值为 pdTRUE,则中断退出前需要一次上下文切换。从FreeRTOS V7.3.0 起, pxHigherPriorityTaskWoken 称为一个可选参数,并可以设置为 NULL
  • const BaseType_t xCopyPosition

    1. BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue,
    2. const void * const pvItemToQueue,
    3. BaseType_t * const pxHigherPriorityTaskWoken,
    4. const BaseType_t xCopyPosition )
    5. {
    6. BaseType_t xReturn;
    7. UBaseType_t uxSavedInterruptStatus;
    8. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
    9. uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    10. {
    11. /*判断队列是否已满,而如果是使用覆盖的方式发送数据,无论队
    12. 列满或者没满,都可以发送*/
    13. if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
    14. || ( xCopyPosition == queueOVERWRITE ) )
    15. {
    16. const int8_t cTxLock = pxQueue->cTxLock;
    17. traceQUEUE_SEND_FROM_ISR( pxQueue );
    18. //如果队列没满,可以调用 prvCopyDataToQueue()函数将消息拷贝到消息队列中。
    19. ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
    20. // 判断队列是否上锁,如果队列上锁了,那么队列的等待接收列表就不能被访问。
    21. if( cTxLock == queueUNLOCKED )
    22. {
    23. {
    24. //消息拷贝完毕,那么就看看有没有任务在等待消息, 如果有任务
    25. //在等待获取此消息,就要将任务从阻塞中恢复,
    26. if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
    27. {
    28. /* 将任务从阻塞中恢复 */
    29. if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
    30. {
    31. /* 解除阻塞的任务优先级比当前任务高,记录上下文切换请求,
    32. 等返回中断服务程序后,就进行上下文切换 */
    33. if( pxHigherPriorityTaskWoken != NULL )
    34. {
    35. *pxHigherPriorityTaskWoken = pdTRUE;
    36. }
    37. else
    38. {
    39. mtCOVERAGE_TEST_MARKER();
    40. }
    41. }
    42. else
    43. {
    44. mtCOVERAGE_TEST_MARKER();
    45. }
    46. }
    47. else
    48. {
    49. mtCOVERAGE_TEST_MARKER();
    50. }
    51. }
    52. }
    53. else
    54. {
    55. /* 队列上锁,记录上锁次数,等到任务解除队列锁时,
    56. 使用这个计录数就可以知道有多少数据入队 */
    57. pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
    58. }
    59. xReturn = pdPASS;
    60. }
    61. else
    62. {
    63. /* 队列是满的,因为 API 执行的上下文环境是中断,
    64. 所以不能阻塞,直接返回队列已满错误代码 errQUEUE_FULL */
    65. traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
    66. xReturn = errQUEUE_FULL;
    67. }
    68. }
    69. portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
    70. return xReturn;
    71. }
    72. /*-----------------------------------------------------------*/

17.6.5 从消息队列读取消息函数

功能 正常任务使用 中断使用
不保存消息 xQueueReceive xQueueSendFromISR
保存消息 xQueueReceiveFromISR() xQueuePeekFromISR()
调用的API xQueueGenericReceive xQueueReceiveFromISR()
  1. 可以指定阻塞时间,阻塞期间2有消息才读取,超过阻塞时间,转换为就绪态
  2. 不知道阻塞时间,有就读取
  3. 指定一直读取,直到读取完毕

1. xQueueReceive()与 xQueuePeek()

  1. #define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) \
  2. xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
  3. ( xTicksToWait ), pdFALSE )

实际调用函数:xQueueGenericReceive。
接收的消息是拷贝的
不能在中断中使用,应该使用xQueueReceiveFromISR 。
image.png
使用实例:

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

如果我接收了消息不想删除消息:使用xQueuePeek() 函数。
xQueuePeek()函数接收消息完毕不会删除消息队列中的消息而已。

  1. #define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) \
  2. xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
  3. ( xTicksToWait ), pdTRUE )

2. xQueueReceiveFromISR()与 xQueuePeekFromISR()

xQueueReceiveFromISR()是 xQueueReceive()的中断版本,用于在中断服务程序中接收一个队列消息并把消息从队列中删除;xQueuePeekFromISR()是 xQueuePeek()的中断版本,用于在中断中从一个队列中接收消息, 但并不会把消息从队列中移除。
在中断使用,不带有阻塞。
image.png
image.png
image.png
使用实例:

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

3.从队列读取消息函数xQueueGenericReceive()

参数:
QueueHandle_t xQueue:消息队列句柄
void * const pvBuffer:指针,指向接收到要保存的数据
TickType_t xTicksToWait:超时时间
const BaseType_t xJustPeeking:xJustPeeking 用于标记消息是否需要出队,如果是 pdFALSE, 表
示读取消息之后会进行出队操作, 即读取消息后会把消息从队列中删除

  1. BaseType_t xQueueGenericReceive( QueueHandle_t xQueue,
  2. void * const pvBuffer,
  3. TickType_t xTicksToWait,
  4. const BaseType_t xJustPeeking )
  5. {
  6. BaseType_t xEntryTimeSet = pdFALSE;
  7. TimeOut_t xTimeOut;
  8. int8_t *pcOriginalReadPosition;
  9. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  10. /* 已删除一些断言 */
  11. for( ;; )
  12. {
  13. taskENTER_CRITICAL();
  14. {
  15. const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
  16. /* 看看队列中有没有消息 */
  17. if( uxMessagesWaiting > ( UBaseType_t ) 0 )
  18. {
  19. /*如果有消息,先记录读消息位置,防止仅仅是读取消息, 而不进行消息出队操作*/
  20. pcOriginalReadPosition = pxQueue->u.pcReadFrom;
  21. /* 拷贝消息到用户指定存放区域 pvBuffer */
  22. prvCopyDataFromQueue( pxQueue, pvBuffer );
  23. /* 读取消息并且消息出队 */
  24. if( xJustPeeking == pdFALSE )
  25. {
  26. traceQUEUE_RECEIVE( pxQueue );
  27. /* 获取了消息,当前消息队列的消息个数需要减一 */
  28. pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
  29. /* 判断一下消息队列中是否有等待发送消息的任务 */
  30. if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
  31. {
  32. /* 将任务从阻塞中恢复 */
  33. /* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
  34. if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
  35. {
  36. queueYIELD_IF_USING_PREEMPTION();
  37. }
  38. else
  39. {
  40. mtCOVERAGE_TEST_MARKER();
  41. }
  42. }
  43. else
  44. {
  45. mtCOVERAGE_TEST_MARKER();
  46. }
  47. }
  48. else/* 任务只是看一下消息(peek),并不出队 */
  49. {
  50. traceQUEUE_PEEK( pxQueue );
  51. /* 因为是只读消息 所以还要还原读消息位置指针 */
  52. pxQueue->u.pcReadFrom = pcOriginalReadPosition;
  53. /* 判断一下消息队列中是否还有等待获取消息的任务 */
  54. if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
  55. {
  56. if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
  57. {
  58. queueYIELD_IF_USING_PREEMPTION();
  59. }
  60. else
  61. {
  62. mtCOVERAGE_TEST_MARKER();
  63. }
  64. }
  65. else
  66. {
  67. mtCOVERAGE_TEST_MARKER();
  68. }
  69. }
  70. taskEXIT_CRITICAL();
  71. return pdPASS;
  72. }
  73. else /* 消息队列中没有消息可读 */
  74. {
  75. if( xTicksToWait == ( TickType_t ) 0 )
  76. {
  77. /* 不等待,直接返回 */
  78. taskEXIT_CRITICAL();
  79. traceQUEUE_RECEIVE_FAILED( pxQueue );
  80. return errQUEUE_EMPTY;
  81. }
  82. else if( xEntryTimeSet == pdFALSE )
  83. {
  84. /* 初始化阻塞超时结构体变量,初始化进入
  85. 阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
  86. vTaskSetTimeOutState( &xTimeOut );
  87. xEntryTimeSet = pdTRUE;
  88. }
  89. else
  90. {
  91. mtCOVERAGE_TEST_MARKER();
  92. }
  93. }
  94. }
  95. taskEXIT_CRITICAL();
  96. vTaskSuspendAll();
  97. prvLockQueue( pxQueue );
  98. /* 检查超时时间是否已经过去了*/
  99. if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
  100. {
  101. /* 如果队列还是空的 */
  102. if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
  103. {
  104. /* 将当前任务添加到队列的等待接收列表中
  105. 以及阻塞延时列表, 阻塞时间为用户指定的超时时间 xTicksToWait */
  106. traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
  107. vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
  108. prvUnlockQueue( pxQueue );
  109. if( xTaskResumeAll() == pdFALSE )
  110. {
  111. /* 如果有任务优先级比当前任务高,会进行一次任务切换 */
  112. portYIELD_WITHIN_API();
  113. }
  114. else
  115. {
  116. mtCOVERAGE_TEST_MARKER();
  117. }
  118. }
  119. else
  120. {
  121. /* 如果队列有消息了,就再试一次获取消息 */
  122. prvUnlockQueue( pxQueue );
  123. ( void ) xTaskResumeAll();
  124. }
  125. }
  126. else
  127. {
  128. /* 超时时间已过,退出 */
  129. prvUnlockQueue( pxQueue );
  130. ( void ) xTaskResumeAll();
  131. if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
  132. {
  133. traceQUEUE_RECEIVE_FAILED( pxQueue );
  134. return errQUEUE_EMPTY;
  135. }
  136. else
  137. {
  138. mtCOVERAGE_TEST_MARKER();
  139. }
  140. }
  141. }
  142. }
  143. /*-----------------------------------------------------------*/

总结:接收和发送的函数都是类似的。
以接收为例:

  1. 判断队列中是否有消息
    1. 有消息:读取消。读取完成之后,查看是否需要保留消息
      1. 要保留消息(xJustPeeking == TRUE)
      2. 不保留消息 (xJustPeeking == FALSE)要还原指针

需要查看有没有等待发送消息的任务,如果优先级高要唤醒。

  1. 没有消息:
    1. 不等待直接返回
    2. 需要等待,计算等待的时间
    3. 停止调度(防止优先级翻转)
      1. 如果队列为空,然后添加到阻塞链表中。开始调度
      2. 不为空,说明有消息:再循环上面,开始调度
    4. 如果超时,然后退出,返回errQUEUE_EMPTY

image.png
队列的阻塞和唤醒方式:
队列如果满队,比如所要接受消息。没有消息,在等待
调用阻塞
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

主要加入了两条阻塞的队列:

  • pxCurrentTCB->xEventListItem
    • pxQueue->xTasksWaitingToReceive: 这个主要来查看要接收消息的人有多少个,以便于唤醒
  • pxCurrentTCB->xStateListItem

唤醒:
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
判断(listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL)
( void ) uxListRemove( &( pxTCB->xEventListItem ) );

注意唤醒的是任务优先级最高的:以下是官方文档的原话。

  1. /*The event list is sorted in priority order, so the first in the list can
  2. be removed as it is known to be the highest priority. Remove the TCB from
  3. the delayed list, and add it to the ready list. */

这个是将其放置到阻塞列表中的注释。自己仔细查看代码之后,没有发现这个。因为在
vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );中,将任务添加到了延迟列表中,但是排序的方式是通过xItemvalue排序的,然后唤醒是从头开始唤醒的。调用的是
listGET_OWNER_OF_HEAD_ENTRY( pxEventList );
猜测:在初始化的时候就将对应的优先级放入了xItemvalue了。
注意:xEventListItemxStateListItem使用的不是同一个Item

  1. /* Place the event list item of the TCB in the appropriate event list.
  2. This is placed in the list in priority order so the highest priority task
  3. is the first to be woken by the event. The queue that contains the event
  4. list is locked, preventing simultaneous access from interrupts. */