FreeRTOS源码分析-7 消息队列
目录
1 消息队列的概念和作用
2 应用
2.1功能需求
2.2接口函数API
2.3 功能实现
3 消息队列源码分析
3.1消息队列控制块
3.2消息队列创建
3.3消息队列删除
3.4消息队列在任务中发送
3.5消息队列在中断中发送
3.6消息队列在任务中接收
3.7消息队列在中断中接收
1 消息队列的概念和作用
消息队列(queue),可以在任务与任务间、中断和任务间传递消息实现任务接收来自其他任务或中断的不固定长度的消息。后面的二值信号量、互斥信号量等都是基于消息队列衍生出来的。

队列是什么?

解决三个问题:
- 排队无序的问题(先入先出,有序)
- 不可能插队的问题(在一端插入)
- 高效的问题(只要入队都能买到票)
FreeRTOS程序中的消息队列
中断和任务不断的发送消息
在固定时间内等待(Timeout,相当于osdelay挂起)消息,没有消息的时候把cpu交给其他任务。


“深度”。在队列创建时需要设定其深度和每个单元的大小。
通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写人也是有可能的。
向队列写入数据是通过字节拷贝把数据存储到队列中,从队列读出数据使得把队列中的数据拷贝删除。
在FreeROTS中,操作消息队列控制块,只要对头有消息,就会取,直到去完为止。
2 应用
2.1功能需求
- 1、使用消息队列检测串口输入
- 2、通过串口发送字符串openled6,openled7,openled8,openled9,分别打开板载led6,led7,led8, led9
- 3、通过串口发送字符串closeled6,closeled7,closeled8,closeled9,分别关闭板载led6,led7,led8, led9
2.2接口函数API
一般在调度器开启之前创建

可以发送在队头、队尾,一般先入先出,有紧急消息可以队头插队,常用的是Send和SendtoBack

启动调度器之前是不能调用此函数的,因为是在中断中触发的,在中断中不能阻塞。第三个参数NULL,已经不用了。

第三个参数NULL,已经不用了。 
2.3 功能实现
CubeMX功能配置
led端口配置、usart1中断配置、创建消息队列


消息队列接收和发送功能开发
串口中断使能
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(uartHandle->Instance==USART1){/* USER CODE BEGIN USART1_MspInit 0 *//* USER CODE END USART1_MspInit 0 *//* USART1 clock enable */__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**USART1 GPIO Configuration PA9 ------> USART1_TXPA10 ------> USART1_RX */GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF7_USART1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* USART1 interrupt Init */HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);HAL_NVIC_EnableIRQ(USART1_IRQn);/* USER CODE BEGIN USART1_MspInit 1 *///开启接收中断__HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);/* USER CODE END USART1_MspInit 1 */}
}
串口中断服务函数入队操作
void USART1_IRQHandler(void)
{uint8_t u8Data;//接收中断标志位if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) == SET){//读取接收寄存器u8Data = huart1.Instance->DR;//进行入队操作xQueueSendFromISR(CmdQueueHandle,&u8Data,NULL);}HAL_UART_IRQHandler(&huart1);
}
从消息队列出队一直等待,当接收到第一个消息循环从消息队列出队,阻塞等待50ms,完成消息接收
uint8_t u8CmdBuff[20]; //全局变量void Usart_Task(void const * argument)
{uint8_t u8Index;for(;;){//每次读取消息之前,把索引初始化为0u8Index = 0;//1、一直等待接收消息,第一个消息应该放在消息缓冲区的第一个元素上if(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],portMAX_DELAY)==pdPASS){while(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],50)){}u8CmdBuff[u8Index] = '\0';//保证一包完成字符串信息vParseString(u8CmdBuff);//完成解析以后,要清空接收缓冲区memset(u8CmdBuff,0,20);}}
}
消息解析控制功能开发
根据板载LED端口数量循环遍历,openledX字符串进行比较,如果字符串比较成功打开相关led端口
根据板载LED端口数量循环遍历,closeledX字符串进行比较,如果字符串比较成功打开相关led端口
uint8_t *OpenString[LED_NUM] = {"openled6",
"openled7",
"openled8",
"openled9",};uint8_t *CloseString[LED_NUM] = {"closeled6",
"closeled7",
"closeled8",
"closeled9",};void vParseString(uint8_t *buff){uint8_t i;for(i=0;i<LED_NUM;i++){if(strcmp((char const*)buff,(char const*)OpenString[i]) == 0){HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_RESET);printf("Cmd is %s\n",OpenString[i]);return;}}for(i=0;i<LED_NUM;i++){if(strcmp((char const*)buff,(char const*)CloseString[i]) == 0){HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_SET);printf("Cmd is %s\n",CloseString[i]);return;}}}
3 消息队列源码分析
3.1消息队列控制块

3.2消息队列创建

//queue.h//队列实现,实际是xQueueGenericCreate实现的
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )//队列的创建类型
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) //基本类型
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) //互斥锁
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) //计数信号量
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) //二值信号量
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) //递归互斥锁/*参数:uxQueueLength 队列长度uxItemSize 队列项大小ucQueueType 队列类型返回值:QueueHandle_t 队列的句柄 其实就是队列控制块地址
*/
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{Queue_t *pxNewQueue;size_t xQueueSizeInBytes;uint8_t *pucQueueStorage;//队列内存空间为空if( uxItemSize == ( UBaseType_t ) 0 ){/*队列字节大小赋值为0 */xQueueSizeInBytes = ( size_t ) 0;}else{/* 队列字节大小赋值为 长度*每个队列项大小 */xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); }// 申请内存空间 消息队列控制块大小+消息队列空间大小pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );if( pxNewQueue != NULL ){/* 找到消息队列操作空间的首地址 */pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );//初始化一个新的消息队列prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );}return pxNewQueue;
}/*参数:uxQueueLengthuxItemSizepucQueueStorage:队列操作空间的首地址ucQueueType:队列类型pxNewQueue:队列的句柄
*/
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )
{( void ) ucQueueType;if( uxItemSize == ( UBaseType_t ) 0 )//是否有队列空间{/* 把队列控制块首地址赋值到队列头指针 ??这是互斥信号使用,后面分析*/pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;}else{/* 把队列空间的首地址赋值给队列头指针 */pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;}/* 长度、单元大小 */pxNewQueue->uxLength = uxQueueLength;pxNewQueue->uxItemSize = uxItemSize;//队列重置函数( void ) xQueueGenericReset( pxNewQueue, pdTRUE );}/*参数:xQueue 队列句柄xNewQueue 操作队列的状态是什么,新建pdTRUE还是已经创建好了返回值:*/
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{Queue_t * const pxQueue = ( Queue_t * ) xQueue;//进入临界段,这时候操作队列控制块,不允许打断taskENTER_CRITICAL();{/*1、头地址赋值2、等待处理的消息个数为03、写入指针赋值为队列头指针4、读出指针写入最后一个可用消息5、赋值队列锁位解锁状态*/pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;pxQueue->pcWriteTo = pxQueue->pcHead;pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );pxQueue->cRxLock = queueUNLOCKED;pxQueue->cTxLock = queueUNLOCKED;//判断是否位新建队列if( xNewQueue == pdFALSE )//不是新任务{/* 判断发送等待列表里面是否有任务 */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//新建队列,直接初始化 发送和接收 列表项{/* Ensure the event queues start in the correct state. */vListInitialise( &( pxQueue->xTasksWaitingToSend ) );vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );}}taskEXIT_CRITICAL();/* A value is returned for calling semantic consistency with previousversions. */return pdPASS;
}
3.3消息队列删除
void vQueueDelete( QueueHandle_t xQueue )
{Queue_t * const pxQueue = ( Queue_t * ) xQueue;#if ( configQUEUE_REGISTRY_SIZE > 0 ){vQueueUnregisterQueue( pxQueue );}#endif#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) ){/* 释放消息队列的内存空间 */vPortFree( pxQueue );}#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) ){/* 释放消息队列的内存空间 */if( pxQueue->ucStaticallyAllocated == ( uint8_t ) pdFALSE ){vPortFree( pxQueue );}else{mtCOVERAGE_TEST_MARKER();}}#else{( void ) pxQueue;}#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
3.4消息队列在任务中发送

分支一:
- 队列未满或覆盖入队,那么看下是否有接收任务,有的话解除一个接收任务,退出临界段。
- 这队列已满,不需要等待的话直接退出临界段。如果需要等待,初始化超时结构体。最后退出临界段挂起调度器。
分支二:
- 锁定队列,判断超时等待是否溢出,溢出解锁跌了恢复调度。
- 没有溢出,再次判断一下队列是否满,没满解锁队列恢复调度器,如果满了将任务插入等待发送列表,解锁队列,恢复调度。
问:为什么在进入临界段?
因为操作的消息是共享资源,可以被多个任务或中断接收和发送,那么操作的时候是不希望被别的任务打断的。
问:为什么要挂起调度器?
不让内核调度,不让其他任务打断下面程序的处理。
问:为什么要锁定队列?
因为消息队列是可以从任务中发送的,锁定队列是告诉任务不要打断下面程序的查询队列和插入队列,否则整个程序会打乱。
/*queueSEND_TO_BACK ?由于信号量都是有消息队列实现的,这个时候,操作系统,定义了消息队列的类型类型:#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) 1从队尾加入#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) 2从对头加入#define queueOVERWRITE ( ( BaseType_t ) 2 ) 3覆盖入队
*/
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )/*最终实现消息队列发送的是xQueueGenericSend接口参数:xQueue 消息队列句柄pvItemToQueue 要发送的消息的地址xTicksToWait 超时时间xCopyPosition 队列操作类型返回值:BaseTyp_t*/
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;/* 使用for循环的目的:为了快速处理消息拷贝,消息处理功能*/for( ;; ){//进入临界段taskENTER_CRITICAL();{/* 1、判断消息队列是否满了2、判断是否允许覆盖入队任何一个成立,执行入队操作*/if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){//拷贝数据到队列操作空间内xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );#if ( configUSE_QUEUE_SETS == 1 ){//宏定义不看}#else /* configUSE_QUEUE_SETS */{/* 判断等待接收的列表是否为空. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){//移除等待接收任务的列表,改变等待接收任务的状态为就绪态if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* 操作成功,进行上下文切换,让任务赶紧处理 */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}//如果在拷贝数据的时候提示需要调度else if( xYieldRequired != pdFALSE ){/* 再次调度,进行上下文切换*/queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_QUEUE_SETS *///退出临界段,返回成功taskEXIT_CRITICAL();return pdPASS;}else //如果不允许入队{//是否需要阻塞if( xTicksToWait == ( TickType_t ) 0 ){/* 不需要阻塞,退出临界段,之后返回队列队满 */taskEXIT_CRITICAL();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}//超时结构体是否操作过else if( xEntryTimeSet == pdFALSE ){/* 超时结构体初始化 */vTaskSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. */mtCOVERAGE_TEST_MARKER();}}}//退出临界段taskEXIT_CRITICAL();/* 后面的代码都是允许阻塞处理 *//* 1、挂起了调度器 ----不让其他任务打断2、队列上锁------不让中断打断 (因为之前已经退出临界段了)*/vTaskSuspendAll();prvLockQueue( pxQueue );/* 判断阻塞时间是否超时了 */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){//判断队列是否满if( prvIsQueueFull( pxQueue ) != pdFALSE ){//队满,把当前任务,添加到等待发送的事件列表中,内部还把任务添加到延时列表中去traceBLOCKING_ON_QUEUE_SEND( pxQueue );vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );/* 解锁*/prvUnlockQueue( pxQueue );/* 恢复调度器 */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;}}
}
3.5消息队列在中断中发送

问:上锁的目的是什么?
如果上锁了,在中断中不会处理,上面3.5在任务中发送的功能。如果中断能打断,那么中断也能操作队列、中断也能操作队列,那么优先级会出现混乱。没有上锁,那么可以操作。
/*最终调用发送消息接口是xQueueGenericSendFromISR???为什么由于信号量都是有消息队列实现的,这个时候,操作系统,定义了消息队列的类型类型:#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) 1、从队尾加入#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) 2、从队头加入#define queueOVERWRITE ( ( BaseType_t ) 2 ) 3、覆盖入队
*/
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
/*参数:xQueuepvItemToQueueNULLqueueSEND_TO_BACK返回值:BaseType_t */
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;/*带返回值的关闭中断,需要保存上次关闭中断的状态值,恢复时候,写入 */uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{//队满?覆盖入队?if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){//获取了队列发送锁的状态值const int8_t cTxLock = pxQueue->cTxLock;//拷贝数据到队列操作空间内( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );/* 判断队列是否上锁 *//*#define queueUNLOCKED ( ( int8_t ) -1 ) 解锁状态#define queueLOCKED_UNMODIFIED ( ( int8_t ) 0 ) 上锁状态初值*/if( cTxLock == queueUNLOCKED ){#if ( configUSE_QUEUE_SETS == 1 )#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 //队列已经上锁{/* 发送锁加一 */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;
}
//队列上锁
把发送和接受锁都赋值为上锁的初始值
#define prvLockQueue( pxQueue ) \taskENTER_CRITICAL(); \{ \if( ( pxQueue )->cRxLock == queueUNLOCKED ) \{ \( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \} \if( ( pxQueue )->cTxLock == queueUNLOCKED ) \{ \( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \} \} \taskEXIT_CRITICAL()/*队列解锁参数:消息队列句柄
*/
static void prvUnlockQueue( Queue_t * const pxQueue )
{/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. *//* 进入临界段 */taskENTER_CRITICAL();{//获取发送锁的状态值int8_t cTxLock = pxQueue->cTxLock;/* 遍历解锁 直到解锁为止 */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 )#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 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();
}
3.6消息队列在任务中接收

分支一:
是出队删除,还是出队不删除,不删除是留个多个任务读取一个消息的情况使用的。
分支二:
接收消息为空,需要判断是否允许阻塞
/*最终调用发送消息接口是xQueueGenericReceive???为什么队列出队,有两种模式一种是:出队后,删除已经读取到队列项或者消息空间另一种是:出队后,不删除,然后恢复出队记录地址,让其他任务或者中断,继续读取使用类型:pdFALSE 删除pdTRUE 不删除*/
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait )
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )//重点分析这一个,其他流程和发送流程差不多//判断是否删除已经接收到的消息空间if( xJustPeeking == pdFALSE ){traceQUEUE_RECEIVE( pxQueue );//更新消息等待的记录值,让它减一pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;}else{//不删除 就重新赋值未读取消息之前的地址,到出队指针pxQueue->u.pcReadFrom = pcOriginalReadPosition;}
3.7消息队列在中断中接收

源码分析,参考3.5中断中发送最后一部分。
相关文章:
FreeRTOS源码分析-7 消息队列
目录 1 消息队列的概念和作用 2 应用 2.1功能需求 2.2接口函数API 2.3 功能实现 3 消息队列源码分析 3.1消息队列控制块 3.2消息队列创建 3.3消息队列删除 3.4消息队列在任务中发送 3.5消息队列在中断中发送 3.6消息队列在任务中接收 3.7消息队列在中断中接收 1 消…...
机器学习深度学习——权重衰减
👨🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习&&深度学习——模型选择、欠拟合和过拟合 📚订阅专栏:机器学习&&深度学习 希望文章对你…...
【Linux】线程互斥 -- 互斥锁 | 死锁 | 线程安全
引入互斥初识锁互斥量mutex锁原理解析 可重入VS线程安全STL中的容器是否是线程安全的? 死锁 引入 我们写一个多线程同时访问一个全局变量的情况(抢票系统),看看会出什么bug: // 共享资源, 火车票 int tickets 10000; //新线程执行方法 vo…...
【vue-pdf】PDF文件预览插件
1 插件安装 npm install vue-pdf vue-pdf GitHub:https://github.com/FranckFreiburger/vue-pdf#readme 参考文档:https://www.cnblogs.com/steamed-twisted-roll/p/9648255.html catch报错:vue-pdf组件报错vue-pdf Cannot read properti…...
Flink集群运行模式--Standalone运行模式
Flink集群运行模式--Standalone运行模式 一、实验目的二、实验内容三、实验原理四、实验环境五、实验步骤5.1 部署模式5.1.1 会话模式(Session Mode)5.1.2 单作业模式(Per-Job Mode)5.1.3 应用模式(Application Mode&a…...
Spring整合JUnit实现单元测试
Spring整合JUnit实现单元测试 一、引言 在软件开发过程中,单元测试是保证代码质量和稳定性的重要手段。JUnit是一个流行的Java单元测试框架,而Spring是一个广泛应用于Java企业级开发的框架。本文将介绍如何使用Spring整合JUnit实现单元测试,…...
Spring Boot学习路线1
Spring Boot是什么? Spring Boot是基于Spring Framework构建应用程序的框架,Spring Framework是一个广泛使用的用于构建基于Java的企业应用程序的开源框架。Spring Boot旨在使创建独立的、生产级别的Spring应用程序变得容易,您可以"只是…...
管理类联考——写作——论说文——实战篇——标题篇
角度3——4种材料类型、4个立意对象、5种写作态度 经过审题立意后,我们要根据我们的立意,确定一个主题,这个主题必须通过文章的标题直接表达出来。 标题的基本要求 主题清晰,态度明确 第一,阅卷人看到一篇论说文的标…...
idea中设置maven本地仓库和自动下载依赖jar包
1.下载maven 地址:maven3.6.3 解压缩在D:\apache-maven-3.6.3-bin\apache-maven-3.6.3\目录下新建文件夹repository打开apache-maven-3.6.3-bin\apache-maven-3.6.3\conf文件中的settings.xml编辑:新增本地仓库路径 <localRepository>D:\apache-…...
前缀和差分
前缀和 前缀和:一段序列里的前n项和 给出n个数,在给出q次问询,每次问询给出L、R,快速求出每组数组中一段L至R区间的和 给出一段数组,每次问询为求出l到r区间的和 普通方法:L到R进行遍历,那么…...
Golang GORM 模型定义
模型定义 参考文档:https://gorm.io/zh_CN/docs/models.html 模型一般都是普通的 Golang 的结构体,Go的基本数据类型,或者指针。 模型是标准的struct,由Go的基本数据类型、实现了Scanner和Valuer接口的自定义类型及其指针或别名组成&#x…...
微服务的各种边界在架构演进中的作用
演进式架构 在微服务设计和实施的过程中,很多人认为:“将单体拆分成多少个微服务,是微服务的设计重点。”可事实真的是这样吗?其实并非如此! Martin Fowler 在提出微服务时,他提到了微服务的一个重要特征—…...
使用 docker-compose 一键部署多个 redis 实例
目录 1. 前期准备 2. 导入镜像 3. 部署redis master脚本 4. 部署redis slave脚本 5. 模板文件 6. 部署redis 7. 基本维护 1. 前期准备 新部署前可以从仓库(repository)下载 redis 镜像,或者从已有部署中的镜像生成文件: …...
14-测试分类
1.按照测试对象划分 ①界面测试 软件只是一种工具,软件与人的信息交流是通过界面来进行的,界面是软件与用户交流的最直接的一层,界面的设计决定了用户对设计的软件的第一印象。界面如同人的面孔,具有吸引用户的直接优势…...
打开域名跳转其他网站,官网被黑解决方案(Linux)
某天打开网站,发现进入首页,马上挑战到其他赌博网站。 事不宜迟,不能让客户发现,得马上解决 我的网站跳转到这个域名了 例如网站跳转到 k77.cc 就在你们部署的代码的当前文件夹下面,执行下如下命令 find -type …...
redis总结
1.redis redis高性能的key-value数据库,支持持久化,不仅仅支持简单的key-value,还提供了list,set,zset,hash等数据结构的存储,支持数据的备份(master-slave模式) redis&…...
现代C++中的从头开始深度学习:激活函数
一、说明 让我们通过在C中实现激活函数来获得乐趣。人工神经网络是生物启发模型的一个例子。在人工神经网络中,称为神经元的处理单元被分组在计算层中,通常用于执行模式识别任务。 在这个模型中,我们通常更喜欢控制每一层的输出以服从一些约束…...
python怎么实现tcp和udp连接
目录 什么是tcp连接 什么是udp连接 python怎么实现tcp和udp连接 什么是tcp连接 TCP(Transmission Control Protocol)连接是一种网络连接,它提供了可靠的、面向连接的数据传输服务。 在TCP连接中,通信的两端(客户端和…...
java设计模式-观察者模式(jdk内置)
上一篇我们学习了 观察者模式。 观察者和被观察者接口都是我们自己定义的,整个设计模式我们从无到有都是自己设计的,其实,java已经内置了这个设计模式,我们只需要定义实现类即可。 下面我们不多说明,直接示例代码&am…...
秒级体验本地调试远程 k8s 中的服务
点击上方蓝色字体,选择“设为星标” 回复”云原生“获取基础架构实践 背景 在这个以k8s为云os的时代,程序员在日常的开发过程中,肯定会遇到各种问题,比如:本地开发完,需要部署到远程k8s集群,本地…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)
零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...
