当前位置: 首页 > news >正文

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 消…...

机器学习深度学习——权重衰减

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——模型选择、欠拟合和过拟合 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你…...

【Linux】线程互斥 -- 互斥锁 | 死锁 | 线程安全

引入互斥初识锁互斥量mutex锁原理解析 可重入VS线程安全STL中的容器是否是线程安全的? 死锁 引入 我们写一个多线程同时访问一个全局变量的情况(抢票系统)&#xff0c;看看会出什么bug&#xff1a; // 共享资源&#xff0c; 火车票 int tickets 10000; //新线程执行方法 vo…...

【vue-pdf】PDF文件预览插件

1 插件安装 npm install vue-pdf vue-pdf GitHub&#xff1a;https://github.com/FranckFreiburger/vue-pdf#readme 参考文档&#xff1a;https://www.cnblogs.com/steamed-twisted-roll/p/9648255.html catch报错&#xff1a;vue-pdf组件报错vue-pdf Cannot read properti…...

Flink集群运行模式--Standalone运行模式

Flink集群运行模式--Standalone运行模式 一、实验目的二、实验内容三、实验原理四、实验环境五、实验步骤5.1 部署模式5.1.1 会话模式&#xff08;Session Mode&#xff09;5.1.2 单作业模式&#xff08;Per-Job Mode&#xff09;5.1.3 应用模式&#xff08;Application Mode&a…...

Spring整合JUnit实现单元测试

Spring整合JUnit实现单元测试 一、引言 在软件开发过程中&#xff0c;单元测试是保证代码质量和稳定性的重要手段。JUnit是一个流行的Java单元测试框架&#xff0c;而Spring是一个广泛应用于Java企业级开发的框架。本文将介绍如何使用Spring整合JUnit实现单元测试&#xff0c…...

Spring Boot学习路线1

Spring Boot是什么&#xff1f; Spring Boot是基于Spring Framework构建应用程序的框架&#xff0c;Spring Framework是一个广泛使用的用于构建基于Java的企业应用程序的开源框架。Spring Boot旨在使创建独立的、生产级别的Spring应用程序变得容易&#xff0c;您可以"只是…...

管理类联考——写作——论说文——实战篇——标题篇

角度3——4种材料类型、4个立意对象、5种写作态度 经过审题立意后&#xff0c;我们要根据我们的立意&#xff0c;确定一个主题&#xff0c;这个主题必须通过文章的标题直接表达出来。 标题的基本要求 主题清晰&#xff0c;态度明确 第一&#xff0c;阅卷人看到一篇论说文的标…...

idea中设置maven本地仓库和自动下载依赖jar包

1.下载maven 地址&#xff1a;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编辑&#xff1a;新增本地仓库路径 <localRepository>D:\apache-…...

前缀和差分

前缀和 前缀和&#xff1a;一段序列里的前n项和 给出n个数&#xff0c;在给出q次问询&#xff0c;每次问询给出L、R&#xff0c;快速求出每组数组中一段L至R区间的和 给出一段数组&#xff0c;每次问询为求出l到r区间的和 普通方法&#xff1a;L到R进行遍历&#xff0c;那么…...

Golang GORM 模型定义

模型定义 参考文档&#xff1a;https://gorm.io/zh_CN/docs/models.html 模型一般都是普通的 Golang 的结构体&#xff0c;Go的基本数据类型&#xff0c;或者指针。 模型是标准的struct,由Go的基本数据类型、实现了Scanner和Valuer接口的自定义类型及其指针或别名组成&#x…...

微服务的各种边界在架构演进中的作用

演进式架构 在微服务设计和实施的过程中&#xff0c;很多人认为&#xff1a;“将单体拆分成多少个微服务&#xff0c;是微服务的设计重点。”可事实真的是这样吗&#xff1f;其实并非如此&#xff01; Martin Fowler 在提出微服务时&#xff0c;他提到了微服务的一个重要特征—…...

使用 docker-compose 一键部署多个 redis 实例

目录 1. 前期准备 2. 导入镜像 3. 部署redis master脚本 4. 部署redis slave脚本 5. 模板文件 6. 部署redis 7. 基本维护 1. 前期准备 新部署前可以从仓库&#xff08;repository&#xff09;下载 redis 镜像&#xff0c;或者从已有部署中的镜像生成文件&#xff1a; …...

14-测试分类

1.按照测试对象划分 ①界面测试 软件只是一种工具&#xff0c;软件与人的信息交流是通过界面来进行的&#xff0c;界面是软件与用户交流的最直接的一层&#xff0c;界面的设计决定了用户对设计的软件的第一印象。界面如同人的面孔&#xff0c;具有吸引用户的直接优势&#xf…...

打开域名跳转其他网站,官网被黑解决方案(Linux)

某天打开网站&#xff0c;发现进入首页&#xff0c;马上挑战到其他赌博网站。 事不宜迟&#xff0c;不能让客户发现&#xff0c;得马上解决 我的网站跳转到这个域名了 例如网站跳转到 k77.cc 就在你们部署的代码的当前文件夹下面&#xff0c;执行下如下命令 find -type …...

redis总结

1.redis redis高性能的key-value数据库&#xff0c;支持持久化&#xff0c;不仅仅支持简单的key-value&#xff0c;还提供了list&#xff0c;set&#xff0c;zset&#xff0c;hash等数据结构的存储&#xff0c;支持数据的备份&#xff08;master-slave模式&#xff09; redis&…...

现代C++中的从头开始深度学习:激活函数

一、说明 让我们通过在C中实现激活函数来获得乐趣。人工神经网络是生物启发模型的一个例子。在人工神经网络中&#xff0c;称为神经元的处理单元被分组在计算层中&#xff0c;通常用于执行模式识别任务。 在这个模型中&#xff0c;我们通常更喜欢控制每一层的输出以服从一些约束…...

python怎么实现tcp和udp连接

目录 什么是tcp连接 什么是udp连接 python怎么实现tcp和udp连接 什么是tcp连接 TCP&#xff08;Transmission Control Protocol&#xff09;连接是一种网络连接&#xff0c;它提供了可靠的、面向连接的数据传输服务。 在TCP连接中&#xff0c;通信的两端&#xff08;客户端和…...

java设计模式-观察者模式(jdk内置)

上一篇我们学习了 观察者模式。 观察者和被观察者接口都是我们自己定义的&#xff0c;整个设计模式我们从无到有都是自己设计的&#xff0c;其实&#xff0c;java已经内置了这个设计模式&#xff0c;我们只需要定义实现类即可。 下面我们不多说明&#xff0c;直接示例代码&am…...

秒级体验本地调试远程 k8s 中的服务

点击上方蓝色字体&#xff0c;选择“设为星标” 回复”云原生“获取基础架构实践 背景 在这个以k8s为云os的时代&#xff0c;程序员在日常的开发过程中&#xff0c;肯定会遇到各种问题&#xff0c;比如&#xff1a;本地开发完&#xff0c;需要部署到远程k8s集群&#xff0c;本地…...

CV前沿方向:Visual Prompting 视觉提示工程下的范式

prompt在视觉领域&#xff0c;也越来越重要&#xff0c;在图像生成&#xff0c;作为一种可控条件&#xff0c;增进交互和可控性&#xff0c;在多模态理解方面&#xff0c;指令prompt也使得任务灵活通用。视觉提示工程&#xff0c;已然成为CV一个前沿方向&#xff01; 下面来看看…...

Redis五大基础类型解析

1.String类型 特征&#xff1a;即存储字符串的类型&#xff0c;单个字符串存储量最大不超过512MB 常用业务场景&#xff1a;⽤来存储JSON序列化之后对象 底层编码&#xff1a; int编码 数据结构特点&#xff1a;ptr指针直接指向字符串常量池中对应字符串地址&#xff0c;而…...

在CSDN学Golang云原生(服务网格istio)

一&#xff0c;在Kubernetes上部署istio 在Kubernetes上部署istio&#xff0c;可以按照以下步骤进行&#xff1a; 安装Istio 使用以下命令从Istio官网下载最新版本的Istio&#xff1a; curl -L https://istio.io/downloadIstio | ISTIO_VERSION<VERSION> sh - 其中&…...

Golang 获取本地 IP 地址方法

在 Golang 中&#xff0c;使用 net 包可以很方便地获取到本机IP地址。 借助 net.InterfaceAddrs 方法 简单示例代码如下&#xff1a; package mainimport ("fmt""net" )func main() {addrList, err : net.InterfaceAddrs()if err ! nil {panic(err)}for…...

抖音seo短视频账号矩阵系统技术开发简述

说明&#xff1a;本开发文档适用于抖音seo源码开发&#xff0c;抖音矩阵系统开发&#xff0c;短视频seo源码开发&#xff0c;短视频矩阵系统源码开发 一、 抖音seo短视频矩阵系统开发包括 抖音seo短视频账号矩阵系统的技术开发主要包括以下几个方面&#xff1a; 1.前端界面设…...

运维高级--shell脚本完成分库分表

为什么要进行分库分表 随着系统的运行&#xff0c;存储的数据量会越来越大&#xff0c;系统的访问的压力也会随之增大&#xff0c;如果一个库中的表数据超过了一定的数量&#xff0c;比如说MySQL中的表数据达到千万级别&#xff0c;就需要考虑进行分库分表&#xff1b; 其…...

Mysql 忘记密码怎么重置密码(详细步骤)

每种方法都有其适用的情况&#xff0c;根据具体情况选择合适的方法。无论选择哪种方法&#xff0c;请务必在重置密码后及时删除临时用户并重新启动 MySQL 服务。 一、使用 mysqladmin 重置密码 停止服务 # systemctl 启动的使用这个停止 $ sudo systemctl stop mysql# mac 本机…...

机器学习深度学习——图像分类数据集

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——softmax回归&#xff08;下&#xff09; &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习…...

【PWN · 栈迁移】[BUUCTF]ciscn_2019_es_2

第一道栈迁移题目&#xff0c;跌跌撞撞理解了 前言 当前溢出可用空间比较少时&#xff08;极端情况下仅能覆写ebp和ret&#xff09;&#xff0c;可以通过栈迁移的方式&#xff0c;扩大shellcode的容纳空间&#xff0c;其核心是将esp移动到一段shellocode开头。而esp总是由ebp赋…...

网络编程(13): 网络通信常用命令(后续待补充)

ifconfig 一般用于查看网卡信息 ping 一般用于侦测本机到目标网络主机的网络是否通常: ping ip/域名 telnet 可以用于指定ip地址和端口的侦听服务是否存在&#xff1a;telnet ip port, 也可以模拟客户端给服务器发数据 netstat 用于查看网络连接状态 -a: 显示所有选项 -t&#…...

网站推广优化的公司/免费的推广引流软件

前言 Hi~&#xff0c;我是 2020 届物联网专业毕业生&#xff0c;现就读于杭州。谨以此文来记录我的秋招以及入门前端以来的学习历程&#xff0c;如有错误&#xff0c;希望大家能及时提出&#xff01; 面试情况 前前后后一共面试了 14 家公司的前端岗&#xff0c;按城市划分为&…...

网站设计与规划/搜外友链

官方文档&#xff1a; 高德地图API官网 高德地图2.0参考手册 高德地图JS API 2.0 示例 在项目中使用 vue-amap 高德地图JSAPI在Vue框架下使用 高德地图在线 JS API 示例 一、账号准备 首先&#xff0c;需要注册并登录高德地图开放平台&#xff0c;申请密钥。操作指引&#x…...

遵义做网站建设哪家公司好/软件工程培训机构哪家好

转载 https://jingyan.baidu.com/article/b0b63dbf570c094a49307072.html...

三合一网站系统/快速网站搭建

一&#xff1a;首先建立普通java项目 二&#xff1a;创建目录lib 导入jar包 并添加到lib仓库 三&#xff1a;编写测试代码 public class JdbcDemo1 {public static void main(String[] args) throws ClassNotFoundException, SQLException {//1:注册驱动Class.forName("c…...

淘宝做网站推广怎么样/头条号权重查询

Reflect对象与Proxy对象一样&#xff0c;也是 ES6 为了操作对象而提供的新 API Reflect设计目的&#xff1a; 将Object对象的一些明显属于语言内部的方法&#xff08;比如Object.defineProperty&#xff09;&#xff0c;放到Reflect对象上。修改某些Object方法的返回结果&#…...

真人做爰直播全集播放网站/企业网站推广策划

Linux设备驱动工程师之路——触摸屏驱动s3c2410_ts.c分析 K-Style 转载请自 http://blog.csdn.net/ayangke 一、触摸屏硬件知识 1.模块原理图 S3C2440有8路的ADC通道其中触摸屏控制器接口XP,XM,YP,YM与四路ADC通道复用四个IO引脚。从原理图看出8路ADC只有一个A/D转换器&am…...