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

【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();}}}
}
/*-----------------------------------------------------------*/

四、性能提示

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

后记

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

相关文章:

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

前言 本篇文章主要对 FreeRTOS 中消息队列的概念和相关函数进行了详解消息队列【下篇】详细剖析了消息队列中发送、接收时队列消息控制块中各种指针的行为&#xff0c;以及几个发送消息和接收消息的函数的运作流程笔者有关于 【FreeRTOS】【应用篇】消息队列【上篇】——队列基…...

芯片技术的崭新时代:探索未来的可能性

引言 芯片作为现代科技领域的核心&#xff0c;扮演着无可替代的角色。从智能手机到数据中心&#xff0c;从医疗设备到智能家居&#xff0c;芯片技术已经深刻地改变了我们的生活。然而&#xff0c;随着技术的不断发展&#xff0c;芯片行业也在经历着一场前所未有的变革。本文将…...

博流RISC-V芯片Eclipse环境搭建

文章目录 1、下载 Eclipse2、导入 bouffalo_sdk3、编译4、烧录5、使用ninja编译 之前编译是通过 VSCode 编译&#xff0c;通过手工输入 make 命令编译&#xff0c;我们也可以通过 Eclipse 可视化 IDE 来编译、烧录。 1、下载 Eclipse 至 Eclipse 官网 https://www.eclipse.org…...

智慧水产养殖方案,守护养殖水产品安全!

水产品在人们的饮食文化中占据着举足轻重的地位&#xff0c;更是人们摄入蛋白质的重要来源。因此&#xff0c;保障食品安全&#xff0c;提升养殖水产品的品质至关重要然。而传统的人工观察水产养殖方式较为单一&#xff0c;难以及时发现水质问题和投喂情况&#xff0c;容易导致…...

前端vue引入高德地图入门教程

距离上一篇关于前端项目中使用高德地图的文章已经将近5年之久&#xff0c; 这是我的第一篇关于高德地图的文章 这期间前端技术日新月异&#xff0c;5年前JQuery还如日中天&#xff0c;如今已经销声匿迹&#xff0c;很少有公司招聘还在要求JQuery&#xff0c;更多的是Vue、React…...

【LeetCode题目详解】第八章 贪心算法 part05 435. 无重叠区间 763.划分字母区间 56. 合并区间 (day36补)

本文章代码以c为例&#xff01; 一、力扣第435题&#xff1a;无重叠区间 题目&#xff1a; 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 示例 1: 输入: intervals [[1,…...

数据的语言:学习数据可视化的实际应用

数据可视化应该学什么&#xff1f;这是一个在信息时代越来越重要的问题。随着数据不断增长和积累&#xff0c;从社交媒体到企业业务&#xff0c;从科学研究到医疗健康&#xff0c;我们都面临着海量的数据。然而&#xff0c;数据本身往往是冰冷、抽象的数字&#xff0c;对于大多…...

【Flutter】Flutter简介

Flutter是Google开发的一款用于构建高性能、高保真移动应用程序的开源UI工具包。它允许开发人员使用Dart语言来构建跨平台的移动应用程序&#xff0c;并提供了丰富的UI组件、动画效果和手势识别等功能。 以下是Flutter入门的一些详细介绍&#xff1a; Flutter概述 Flutter是一…...

做区块链卡牌游戏有什么好处?

区块链卡牌游戏是一种基于区块链技术的创新性游戏形式&#xff0c;它将传统的卡牌游戏与区块链技术相结合&#xff0c;实现了去中心化、数字化资产的交易和收集。这种新型游戏形式正逐渐在游戏行业引起了广泛的关注和热潮。本文将深入探讨区块链卡牌游戏的定义、特点以及其在未…...

C语言每日一练------Day(5)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;错误的集合 密码检查 &#x1f493;博主csdn个人主页&#xff1a;小小u…...

(Windows )本地连接远程服务器(Linux),免密码登录设置

在使用VScode连接远程服务器时&#xff0c;每次打开都要输入密码&#xff0c;以及使用ssh登录或其它方法登录&#xff0c;都要本地输入密码&#xff0c;这大大降低了使用感受&#xff0c;下面总结了免密码登录的方法&#xff0c;用起来巴适得很&#xff0c;起飞。 目录 PowerSh…...

Python 面试:异常处理机制

格式&#xff1a; 继承Exception实现自定义异常。 注意&#xff1a;这里是继承Exception类&#xff0c;而不是BaseException类&#xff0c;因为继承BaseException可能会导致捕获不到自定义异常。 class MyException(Exception):passtry:raise MyException(my salary is too…...

Matlab图像处理-水平镜像

镜像变换 镜像变换又常称为对称变换&#xff0c;它可以分为水平对称、垂直对称等多种变换。对称变换后&#xff0c;图像的宽和高不变。 图像的镜像分为两种垂直镜像和水平镜像。 水平镜像即将图像左半部分和右半部分以图像竖直中轴线为中心轴进行对换&#xff1b; 竖直镜像…...

Ansys Zemax | 手机镜头设计 - 第 2 部分:使用 OpticsBuilder 实现光机械封装

本文是3篇系列文章的一部分&#xff0c;该系列文章将讨论智能手机镜头模块设计的挑战&#xff0c;从概念、设计到制造和结构变形的分析。本文是三部分系列的第二部分。概括介绍了如何在 CAD 中编辑光学系统的光学元件以及如何在添加机械元件后使用 Zemax OpticsBuilder 分析系统…...

【GPT,Flask】用Python Flask结合OpenAI的GPT API构建一个可自主搭建的内容生成应用网站

【背景】 自己构建模型并进行训练需要很高的知识,技能和资源门槛。如今,通过OpenAI提供的API,则可以快速通过GPT能力构建可以提供内容生成服务的在线网站。这套框架可以提供给用户,用户可以利用该框架在自己的环境(比如自己的公司内)构建内容生成服务。你也可以自己上线…...

vue + electron

node 版本 v14.19.3 npm 版本 6.14.17 要是node-sass报错执行命令&#xff1a; npm uninstall node-sass sass-loader npm i node-sass4.14.1 sass-loader7.3.1 --save -dev首先安装依赖 npm install electron npm install electron-packagerelectronRun.js放在根目录下 con…...

spring中LocalDateTime 转成字符串的时候注意事项

ApiOperation("查询课程发布信息") ResponseBody GetMapping("/r/coursepublish/{courseId}") public CoursePublish getCoursepublish(PathVariable("courseId") Long courseId) { CoursePublish coursePublish coursePublishService.getC…...

vue数组对象中按某一字段排序

给下列数组字段中的month排序 第一步&#xff1a;methods中写一个方法如下&#xff1a; sortBy(attr, rev) {//第二个参数没有传递 默认升序排列if(rev undefined) {rev 1;} else {rev (rev) ? 1 : -1;}return function(a, b) {a a[attr];b b[attr];if(a < b) {retu…...

yolov5和yolov7部署的研究

1.结论 onnx推理比torch快3倍, openvino比onnx快一丢丢。 | yolov7.pt 转 onnx python export.py --weights best_31.pt --grid --end2end --simplify --topk-all 10 --iou-thres 0.65 --conf-thres 0.65 --img-size 320 320 --max-wh 200可以看到yolov7的 onnx是包括nms…...

【JavaEE进阶】拦截器与统一功能处理

文章目录 一. 用户登录权限效验1. 最初用户登录验证2. Spring AOP 用户统一登录的验证3. Spring拦截器3.1 自定义拦截器3.2 将自定义拦截器设置到当前的项目中 4. 拦截器实现的原理 二. 统一的异常处理1. 统一的异常处理优点2. 统一的异常处理实现 三. 统一数据返回格式1. 统一…...

2023年智慧政务一网通办云平台顶层设计与建设方案PPT

导读&#xff1a;原文《2023年智慧政务一网通办云平台顶层设计与建设方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 部分内容&#xff1a; 喜欢文章&#…...

安防监控/视频汇聚平台EasyCVR调用rtsp地址返回的IP不正确是什么原因?

安防监控/云存储/磁盘阵列存储/视频汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等&#xff0c;能对外分发RTSP、RT…...

媒体服务器与视频服务器有什么区别

媒体服务器与视频服务器有什么区别 流媒体服务器用在远程教育&#xff0c;视频点播、网络电台、网络视频等方面。 直播过程中就需要使用流媒体服务器&#xff0c;一个完整的直播过程&#xff0c;包括采集、处理、编码、封包、推流、传输、转码、分发、解码、播放等过程&#xf…...

菜鸟教程《Python 3 教程》笔记(11):循环语句

菜鸟教程《Python 3 教程》笔记&#xff08;11&#xff09; 11 循环语句11.1 while 循环11.1.1 while 循环使用 else 语句 11.2 for 语句11.2.1 for...else 11.3 break 和 continue 语句及循环中的 else 子句 11 循环语句 出处&#xff1a; 菜鸟教程 - Python3 循环语句 11.1…...

【DevOps视频笔记】8. Jenkins 配置

一、Jenkins 入门配置 1. 工具 / 插件 介绍 二、插件和工具配置 1. 配置 JDK 和 Maven Stage 1&#xff1a;将服务器中 JDK 和 Maven 映射到 jenkins 容器中 Stage 2&#xff1a;jenkins 全局配置中 -- 指定JAVA_HOME目录 Stage 3&#xff1a;jenkins 全局配置中 -- 指定…...

C# 在Color[] colorTable中快速找到Color的索引位置

C# 在Color[] colorTable中快速找到Color的索引位置 第一种方法&#xff1a; 如果您需要在Color[] colorTable中快速查找特定Color的索引位置&#xff0c;可以使用C#的Array.FindIndex方法。这个方法接受一个回调函数作为参数&#xff0c;该函数定义了如何判断数组元素是否与…...

go学习笔记 炒土豆丝

今天的主菜是土豆&#xff0c;就来个土豆丝吧。我的大致流程如下&#xff1a; 1.挑选白瓤土豆&#xff0c;洗它 2.土豆去皮 3.土豆切片&#xff0c;切丝&#xff0c;丝要粗细均匀 4.清洗几遍土豆丝&#xff0c;去除上面的淀粉&#xff0c;在清水中泡一小会 5.起锅&#xff0c;放…...

FPGA VR摄像机-拍摄和拼接立体 360 度视频

本文介绍的是 FPGA VR 相机的第二个版本&#xff0c;第一个版本是下面这样&#xff1a; 第一版地址&#xff1a; ❝ https://hackaday.io/project/26974-vr-camera-fpga-stereoscopic-3d-360-camera ❞ 本文主要介绍第二版本&#xff0c;第二版本的 VR 摄像机&#xff0c;能够以…...

vue集成mars3d后,basemaps加不上去

首先&#xff1a; <template> <div id"centerDiv" class"mapcontainer"> <mars-map :url"configUrl" οnlοad"onMapload" /> </div> </template> <script> import MarsMap from ../component…...

油管视频直接生成PPT的AI工具!剖析c.ai和Pi的用户需求;独立创业者的操作指南;广告大佬的三个AI绘画实战 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 2023 CCF BDCI 数字安全公开赛&#xff0c;大模型安全竞赛等你「码」力全开 网站&#xff1a;https://www.datafountain.cn/special/B…...

WebSocket- 前端篇

官网代码 // 为了浏览器兼容websocketconst WebSocket window.WebSocket || window.MozWebSocket// 创建连接 this.socket new WebSocket(ws://xxx)// 连接成功this.socket.onopen (res)>{console.log(websocket 连接成功)this.socket.send(入参字段) // 传递的参数字段}…...

如何在 Python 中将图像转换为 PDF

一、说明 如何使得图像转化成pdf文件&#xff0c; 想要将一个或多个图像转换为 PDF 文档&#xff1f;看看img2pdf和PyPDF2软件包就是您的最佳选择。 二、需要哪些程序包&#xff1f; 首先&#xff0c;您只需要一个 Python 环境&#xff0c;最好是 3.10 或更高版本。本教程中的代…...

使用python编写脚本测试目标主机的TCP端口连通性

使用Python的Socket模块的connect()函数来尝试连接目标主机的特定端口。如果连接成功&#xff0c;则说明该端口是打开的&#xff1b;否则&#xff0c;该端口是关闭的。 下面是一个示例脚本&#xff0c;可以检测目标IP的22端口是否开启&#xff1a; import socket def check_po…...

华为云云服务器评测|基于华为云云耀云服务器L实例开展性能评测,例如 MySQL、Clickhouse、Elasticsearch等等

在当今云计算时代&#xff0c;越来越多的企业和个人开始选择将应用部署在云服务器上&#xff0c;以便更好地满足高性能、可靠性和可扩展性等需求。而华为云云耀云服务器L实例不仅提供了高性能和可靠性的计算和存储资源&#xff0c;而且具有灵活和高效的成本控制&#xff0c;深受…...

Git分布式版本控制系统与github

第四阶段提升 时 间&#xff1a;2023年8月29日 参加人&#xff1a;全班人员 内 容&#xff1a; Git分布式版本控制系统与github 目录 一、案例概述 二、版本控制系统 &#xff08;一&#xff09; 本地版本控制 &#xff08;二&#xff09;集中化的版本控制系统 &…...

基于Java+SpringBoot+Vue前后端分离中国陕西民俗网设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…...

CSS3D+动画

CSS3D 1.css3D 给父元素设置 perspective:景深:近大远小的效果900-1200px这个范围内 transform-style:是否设置3D环境 flat 2D环境 默认值 perserve-3D环境 3D功能函数 1.位移: translateZ()translate3D(x,y,z) <!DOCTYPE html> <html lang"en"><h…...

list对象中如何根据对象中某个属性去重使用Java8流实现

list对象中如何根据对象中某个属性去重使用Java8流实现? 在 Java 8 的流操作中&#xff0c;可以使用 distinct 方法来对一个对象流进行去重&#xff0c;但是默认情况下它会使用对象的 equals() 方法来判断重复。如果你希望根据对象的某个属性进行去重&#xff0c;则可以使用 …...

2023 在Windows上的安装Faiss-GPU(使用anaconda)

该方法安装完 faiss-gpu 之后&#xff0c;不仅会装这个库&#xff0c;还会装很多依赖库。为了防止自己本地一些同名库的版本被修改&#xff0c;建议新建一个虚拟环境来安装。如果本地库版本修改对自己没影响&#xff0c;也可以忽略。 你好&#xff0c;我是悦创。 登录网站&…...

HTML及CSS入门及精通

前言 HTML&#xff08;超文本标记语言&#xff09;和CSS&#xff08;层叠样式表&#xff09;是构建网页的两个基本技术。HTML用于定义网页的结构和内容&#xff0c;而CSS用于控制网页的样式和布局。本教程将介绍HTML和CSS的入门知识&#xff0c;并逐步引导您掌握更高级的技巧和…...

frp实现二级代理

kali是攻击机 &#xff08;192.168.0.106&#xff09; windows server2012是边界服务器&#xff0c;拥有两个网卡&#xff0c;作为一级代理&#xff0c; &#xff08;192.168.0.108&#xff0c;10.10.10.136&#xff09; ad01是内网机器&#xff0c;不出网 &#xff08;10.10.1…...

Vue组件设置背景色

vh&#xff1a;浏览器视区高度百分值 wh&#xff1a;浏览器视区宽度百分值 min-height&#xff1a;最小高度&#xff0c;其他时候自适应 给组件根标签设置&#xff1a;min-height&#xff1a;100vh&#xff0c;就可以正常添加背景色&#xff0c;而且背景色随内容展开而自适…...

Java+Github+Jenkins部署

Java项目—Jenkins部署笔记 一&#xff0c;准备 一台服务器操作系统&#xff0c;示例为ubuntu 22.0.4 可运行lsb_release -a查看 二&#xff0c;安装 docker 更新软件包列表&#xff1a; sudo apt update安装必要的软件包&#xff0c;以便使用HTTPS通过APT下载软件包&#x…...

vue使用命令npm install 报错 cb() never called!

一.错误说明,npm本身下载就慢&#xff0c;有可能是网络的问题。 二.解决方案,把npm设置成淘宝镜像后,再重新npm install npm config set registry https://registry.npm.taobao.org 三.还是不行&#xff0c;还会出现同样的问题&#xff0c;那接下来先清理一下npm缓存 npm cache…...

什么是LatexEasy及其在数学排版中的作用

LatexEasy是一种强大的排版系统&#xff0c;特别擅长处理数学公式和科技文档。它基于 TeX&#xff0c;是由计算机科学家 Donald Knuth 开发的。LaTeX 可以让你专注于内容&#xff0c;而不必过多关心排版细节&#xff0c;特别适用于数学家、工程师和科学研究者。 什么是 LaTeX&…...

axios 和fetch的取舍,以及比较

废话不多说&#xff0c;直接直捣黄龙&#xff1a; 区别 相同点 都是一种基于promise的异步解决方案。都可以解决回调地狱问题 不同点 axios是一个封装好的库&#xff0c;需要npm进行安装&#xff0c;fetch是es6新增的api 语法&#xff1a; fetch(url, { method: GET, // o…...

K-Means(K-均值)聚类算法理论和实战

目录 K-Means 算法 K-Means 术语 K 值如何确定 K-Means 场景 美国总统大选摇争取摆选民 电商平台用户分层 给亚洲球队做聚类 ​编辑 其他场景 K-Means 工作流程 K-Means 开发流程 K-Means的底层代码实现 K-Means 的评价标准 K-Means 算法 对于 n 个样本点来说&am…...

Python-pyqt不同窗口数据传输【使用静态函数】

文章目录 前言程序1&#xff1a;caogao1.py输入数据界面程序2&#xff1a;caogao2.py接收数据界面 程序3 &#xff1a;将输入数据界面和接收数据界面组合成一个总界面讲解 总结 前言 在编写pyqt 页面时有时候需要不同页面进行数据传输。本文讲解静态函数方法。直接看示例。 程…...

百度垂类离线计算系统发展历程

作者 | 弘远君 导读 本文以百度垂类离线计算系统的演进方向为主线&#xff0c;详细描述搜索垂类离线计算系统发展过程中遇到的问题&#xff0c;以及对应的解决方案。架构演进过程中一直奉行“没有最好的架构&#xff0c;只有最合适的架构”的宗旨&#xff0c;面对不同阶段遇到的…...

ubuntu 安装 指定版本:nodejs

通过 PPA 安装指定或最新版本的 nodejs 那么就需要使用 nodesource 来安装指定版本的 nodejs 了。其需要下载一个脚本&#xff0c;运行此脚本会在 ubuntu 里添加一个 nodejs 源&#xff0c;然后用 apt 就可以下载指定的 nodejs 了。 PPA 的全称为 personal package archive 。要…...