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集群,本地…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...