【freertos-kernel】stream_buffer

文章目录

  • 补充
    • 任务通知
      • 发送
      • 处理
        • ulTaskGenericNotifyTake
        • xTaskGenericNotifyWait
      • 清除
        • xTaskGenericNotifyStateClear
        • ulTaskGenericNotifyValueClear
  • 结构体
    • StreamBufferHandle_t
    • StreamBufferCallbackFunction_t
  • 创建
    • xStreamBufferGenericCreate
      • stream buffer的类型
  • 删除
    • vStreamBufferDelete
  • 发送
      • prvWriteBytesToBuffer
      • prvWriteMessageToBuffer
      • prvBytesInBuffer
  • 接收
    • xStreamBufferReceive

补充

任务通知

任务通知(Task Notifications)是一种轻量级的、快速的机制,用于在任务之间或中断与任务之间传递简单的信号或少量数据。相比传统的队列、信号量等同步机制,任务通知具有更低的资源消耗和更高的效率。
还记得吗,每个任务控制块都有这么两个字段

//状态
#define taskNOT_WAITING_NOTIFICATION              ( ( uint8_t ) 0 ) /* Must be zero as it is the initialised value. */
#define taskWAITING_NOTIFICATION                  ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED                 ( ( uint8_t ) 2 )//字段
#if ( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];	//通知值volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];	//通知状态
#endif

通过这些,可以实现多种同步模式:

  • 简单通知:仅设置通知状态。
  • 带值的通知:除了设置状态外,还可以传递一个32位的数值。
  • 带掩码的通知:使用特定的位掩码来更新通知值。

大体流程如下
在这里插入图片描述

发送

xTaskGenericNotify是所有任务通知 API(如 xTaskNotify, xTaskNotifyIndexed, xTaskNotifyAndQuery, xTaskNotifyAndQueryIndexed 等)的底层通用函数,用于向一个任务发送通知,并根据不同的操作类型更新该任务的通知值。

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,//要通知的任务句柄UBaseType_t uxIndexToNotify,//通知索引号,表示使用哪个通知槽位uint32_t ulValue,//要传递给接收任务的通知值eNotifyAction eAction,//通知操作方式(设置、增加、覆盖写等)uint32_t * pulPreviousNotificationValue )//可选输出参数,返回该任务在此索引上的旧通知值
{TCB_t * pxTCB;BaseType_t xReturn = pdPASS;uint8_t ucOriginalNotifyState;configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );configASSERT( xTaskToNotify );pxTCB = xTaskToNotify;taskENTER_CRITICAL();{if( pulPreviousNotificationValue != NULL ){	//保存旧的通知值(如果需要)*pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];}ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];//记录原始通知状态并标记为“已收到”pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;switch( eAction )//根据通知操作类型处理通知值{case eSetBits://将通知值与 ulValue 做按位或操作(常用于标志位)pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;break;case eIncrement://通知值自增 1( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;break;case eSetValueWithOverwrite://直接设置通知值,不管之前有没有被读取过pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;break;case eSetValueWithoutOverwrite://如果上次通知还没被读取,则不更新值,返回 pdFAILif( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ){	//目标任务在此次通知发送之前 没有处于“已收到通知”状态(即上次的通知已经被读取或清除)pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;}else{xReturn = pdFAIL;}break;case eNoAction://不改变通知值break;default:configASSERT( xTickCount == ( TickType_t ) 0 );break;}if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )//如果目标任务正在等待通知,将其唤醒{listREMOVE_ITEM( &( pxTCB->xStateListItem ) );prvAddTaskToReadyList( pxTCB );configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );#if ( configUSE_TICKLESS_IDLE != 0 ){prvResetNextTaskUnblockTime();}#endiftaskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxTCB );}}taskEXIT_CRITICAL();return xReturn;
}

vTaskGenericNotifyGiveFromISR()xTaskGenericNotifyFromISR() 都是用于从ISR 中向任务发送通知的函数,vTaskGenericNotifyGiveFromISR() 专门用于递增通知值并可能唤醒任务,而 xTaskGenericNotifyFromISR() 则提供了更多种操作类型来更新通知值,适合需要传递更多控制信息或执行复杂同步逻辑的应用场景。

处理

ulTaskGenericNotifyTake

处理任务通知的通用函数之一,它允许一个任务等待直到其接收到的通知计数值不为零

uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWaitOn,BaseType_t xClearCountOnExit,//如果设置为非零(pdTRUE),当函数返回时将清除通知计数;如果设置为零(pdFALSE),则将通知计数减一TickType_t xTicksToWait )
{uint32_t ulReturn;BaseType_t xAlreadyYielded, xShouldBlock = pdFALSE;configASSERT( uxIndexToWaitOn < configTASK_NOTIFICATION_ARRAY_ENTRIES );vTaskSuspendAll();//挂起调度器{taskENTER_CRITICAL();{if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] == 0U ){	//当前任务的通知计数若为零,则将任务的状态设为taskWAITING_NOTIFICATION并根据超时时间决定是否需要阻塞(xShouldBlock)。pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;if( xTicksToWait > ( TickType_t ) 0 ){xShouldBlock = pdTRUE;}}}taskEXIT_CRITICAL();if( xShouldBlock == pdTRUE ){	//如果需要阻塞,则将当前任务添加到延迟列表中等待超时或被通知唤醒。prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );}}xAlreadyYielded = xTaskResumeAll();//尝试恢复调度器,并判断是否发生了上下文切换(xAlreadyYielded)if( ( xShouldBlock == pdTRUE ) && ( xAlreadyYielded == pdFALSE ) ){	//如果需要阻塞且没有发生上下文切换,则触发一次任务切换taskYIELD_WITHIN_API();}taskENTER_CRITICAL();{	//获取当前通知值,并根据xClearCountOnExit决定如何更新通知计数ulReturn = pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ];if( ulReturn != 0U ){if( xClearCountOnExit != pdFALSE ){pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] = ( uint32_t ) 0U;}else{pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] = ulReturn - ( uint32_t ) 1;}}//将任务状态重置为taskNOT_WAITING_NOTIFICATIONpxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskNOT_WAITING_NOTIFICATION;}taskEXIT_CRITICAL();return ulReturn;
}
xTaskGenericNotifyWait

等待某个任务的通知被触发(即收到通知),并且支持在进入和退出时对通知值进行位清除操作

BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWaitOn,uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t * pulNotificationValue,TickType_t xTicksToWait )
{BaseType_t xReturn, xAlreadyYielded, xShouldBlock = pdFALSE;configASSERT( uxIndexToWaitOn < configTASK_NOTIFICATION_ARRAY_ENTRIES );vTaskSuspendAll();{taskENTER_CRITICAL();{	//检查是否已有通知到达if( pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] != taskNOTIFICATION_RECEIVED ){	//如果目标任务 尚未收到通知(状态不是 taskNOTIFICATION_RECEIVED)pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] &= ~ulBitsToClearOnEntry;//清除一些标志位(按位与非)pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;//设置状态为 taskWAITING_NOTIFICATION,表示开始等待if( xTicksToWait > ( TickType_t ) 0 ){	//如果设置了超时时间,则标记需要阻塞xShouldBlock = pdTRUE;}}}taskEXIT_CRITICAL();if( xShouldBlock == pdTRUE ){	//如果需要阻塞,将任务加入延迟队列prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );}}xAlreadyYielded = xTaskResumeAll();//恢复调度器并判断是否需要调度if( ( xShouldBlock == pdTRUE ) && ( xAlreadyYielded == pdFALSE ) ){	//如果任务被阻塞且没有发生调度,则手动触发一次调度taskYIELD_WITHIN_API();}taskENTER_CRITICAL();{	//再次进入临界区处理通知结果if( pulNotificationValue != NULL ){*pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ];}if( pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] != taskNOTIFICATION_RECEIVED ){	//如果仍然没有收到通知, 返回 pdFALSExReturn = pdFALSE;}else{	//如果收到了通知, 根据 ulBitsToClearOnExit 清除指定的标志位pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] &= ~ulBitsToClearOnExit;xReturn = pdTRUE;}//将通知状态重置为 taskNOT_WAITING_NOTIFICATIONpxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskNOT_WAITING_NOTIFICATION;}taskEXIT_CRITICAL();return xReturn;
}

清除

xTaskGenericNotifyStateClear
//xTask若是RECEIVED状态, 改为 WAITING 状态
BaseType_t xTaskGenericNotifyStateClear( TaskHandle_t xTask,UBaseType_t uxIndexToClear )
{TCB_t * pxTCB;BaseType_t xReturn;configASSERT( uxIndexToClear < configTASK_NOTIFICATION_ARRAY_ENTRIES );pxTCB = prvGetTCBFromHandle( xTask );taskENTER_CRITICAL();{if( pxTCB->ucNotifyState[ uxIndexToClear ] == taskNOTIFICATION_RECEIVED ){pxTCB->ucNotifyState[ uxIndexToClear ] = taskNOT_WAITING_NOTIFICATION;xReturn = pdPASS;}else{xReturn = pdFAIL;}}taskEXIT_CRITICAL();return xReturn;
}
ulTaskGenericNotifyValueClear
//清除xTask的uxIndexToClear的通知值的ulBitsToClear位(掩码)
uint32_t ulTaskGenericNotifyValueClear( TaskHandle_t xTask,UBaseType_t uxIndexToClear,uint32_t ulBitsToClear );

结构体

StreamBufferHandle_t

typedef struct StreamBufferDef_t * StreamBufferHandle_t;typedef struct StreamBufferDef_t
{volatile size_t xTail;//指向缓冲区中下一个要读取的位置volatile size_t xHead;//指向缓冲区中下一个可以写入的位置size_t xLength;//缓冲区的总大小size_t xTriggerLevelBytes;//触发接收任务唤醒的最小数据量volatile TaskHandle_t xTaskWaitingToReceive;//正在等待接收数据的任务句柄volatile TaskHandle_t xTaskWaitingToSend;//当前正在等待发送数据的任务句柄uint8_t * pucBuffer; //指向实际用于存储数据的缓冲区内存uint8_t ucFlags;//用于标识 Stream Buffer 的状态, 缓冲区类型,分配方式等#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxStreamBufferNumber;//调试用编号,用于跟踪每个 Stream Buffer#endif#if ( configUSE_SB_COMPLETED_CALLBACK == 1 )//发送/接收完成后的回调函数指针StreamBufferCallbackFunction_t pxSendCompletedCallback;StreamBufferCallbackFunction_t pxReceiveCompletedCallback;#endifUBaseType_t uxNotificationIndex;//用于内部通知机制的索引
} StreamBuffer_t;

StreamBufferCallbackFunction_t

typedef void (* StreamBufferCallbackFunction_t)( StreamBufferHandle_t xStreamBuffer,BaseType_t xIsInsideISR,BaseType_t * const pxHigherPriorityTaskWoken );

创建

xStreamBufferGenericCreate

初始化一个StreamBufferHandle_t并返回,设置字段,分配结构体和缓冲区的内存,具体代码就细说了。

StreamBufferHandle_t xStreamBufferGenericCreate( size_t xBufferSizeBytes,size_t xTriggerLevelBytes,BaseType_t xStreamBufferType,//缓冲区类型:普通流缓冲区、消息缓冲区等StreamBufferCallbackFunction_t pxSendCompletedCallback,StreamBufferCallbackFunction_t pxReceiveCompletedCallback );

stream buffer的类型

#define sbTYPE_STREAM_BUFFER             ( ( BaseType_t ) 0 )
#define sbTYPE_MESSAGE_BUFFER            ( ( BaseType_t ) 1 )
#define sbTYPE_STREAM_BATCHING_BUFFER    ( ( BaseType_t ) 2 )

其他创建相关的宏或函数都是调用xStreamBufferGenericCreate,区别在于stream buffer的类型以及带不带回调函数。3种类型×2种带不带回调函数,一共是六个宏(动态,如果加上静态分配的就是12个),关于message_buffer的单独在message_buffer.h里。

删除

vStreamBufferDelete

释放内存
动态创建的就调用vPortFree,因为动态创建的时候结构体和数据区的内存是一起申请的,所以会一起释放。
静态创建的只会清除结构体,但数据区没释放。

void vStreamBufferDelete( StreamBufferHandle_t xStreamBuffer )

发送

size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,const void * pvTxData,size_t xDataLengthBytes,TickType_t xTicksToWait )
{StreamBuffer_t * const pxStreamBuffer = xStreamBuffer;size_t xReturn, xSpace = 0;size_t xRequiredSpace = xDataLengthBytes;TimeOut_t xTimeOut;size_t xMaxReportedSpace = 0;configASSERT( pvTxData );configASSERT( pxStreamBuffer );//计算所需空间,如果是MESSAGE_BUFFER还有加上消息长度占用的空间xMaxReportedSpace = pxStreamBuffer->xLength - ( size_t ) 1;if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_MESSAGE_BUFFER ) != ( uint8_t ) 0 ){xRequiredSpace += sbBYTES_TO_STORE_MESSAGE_LENGTH;configASSERT( xRequiredSpace > xDataLengthBytes );if( xRequiredSpace > xMaxReportedSpace ){	//直接设置不等待xTicksToWait = ( TickType_t ) 0;}}else{if( xRequiredSpace > xMaxReportedSpace ){	//限制请求的空间不超过最大值xRequiredSpace = xMaxReportedSpace;}}if( xTicksToWait != ( TickType_t ) 0 ){vTaskSetTimeOutState( &xTimeOut );//计时do{	//等待空间可用taskENTER_CRITICAL();{xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );//当前可用空间 xSpaceif( xSpace < xRequiredSpace ){	//清除旧通知( void ) xTaskNotifyStateClearIndexed( NULL, pxStreamBuffer->uxNotificationIndex );configASSERT( pxStreamBuffer->xTaskWaitingToSend == NULL );pxStreamBuffer->xTaskWaitingToSend = xTaskGetCurrentTaskHandle();}else{taskEXIT_CRITICAL();break;}}taskEXIT_CRITICAL();//等待通知( void ) xTaskNotifyWaitIndexed( pxStreamBuffer->uxNotificationIndex, ( uint32_t ) 0, ( uint32_t ) 0, NULL, xTicksToWait );pxStreamBuffer->xTaskWaitingToSend = NULL;} while( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE );}if( xSpace == ( size_t ) 0 ){	//重新检查空间(以防在等待期间有其他任务释放了空间)xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );}xReturn = prvWriteMessageToBuffer( pxStreamBuffer, pvTxData, xDataLengthBytes, xSpace, xRequiredSpace );if( xReturn > ( size_t ) 0 ){	//如果成功写入数据,并且缓冲区中的数据量达到了触发级别,则调用 prvSEND_COMPLETED() 通知等待接收的任务。if( prvBytesInBuffer( pxStreamBuffer ) >= pxStreamBuffer->xTriggerLevelBytes ){prvSEND_COMPLETED( pxStreamBuffer );}}return xReturn;
}

prvWriteBytesToBuffer

向StreamBuffer_t 的缓冲区(环形)写入数据,返回新的 head 位置。

static size_t prvWriteBytesToBuffer( StreamBuffer_t * const pxStreamBuffer,const uint8_t * pucData,size_t xCount,size_t xHead )
{size_t xFirstLength;configASSERT( xCount > ( size_t ) 0 );xFirstLength = configMIN( pxStreamBuffer->xLength - xHead, xCount );//确定从当前位置 xHead 到缓冲区末尾还有多少空间configASSERT( ( xHead + xFirstLength ) <= pxStreamBuffer->xLength );//把数据从 pucData 拷贝xFirstLength长度到 pxStreamBuffer->pucBuffer[xHead] 开始的位置( void ) memcpy( ( void * ) ( &( pxStreamBuffer->pucBuffer[ xHead ] ) ), ( const void * ) pucData, xFirstLength );if( xCount > xFirstLength )//判断是否还有剩余数据需要写入{configASSERT( ( xCount - xFirstLength ) <= pxStreamBuffer->xLength );//如果一次没写完(比如缓冲区末尾空间不足),则从缓冲区起始位置开始写剩下的部分。可以看出在维护一个环形缓冲区( void ) memcpy( ( void * ) pxStreamBuffer->pucBuffer, ( const void * ) &( pucData[ xFirstLength ] ), xCount - xFirstLength );}xHead += xCount;	//更新并返回新的 head 位置if( xHead >= pxStreamBuffer->xLength ){	//如果超出缓冲区总长度,则用减法回绕xHead -= pxStreamBuffer->xLength;}return xHead;
}

prvWriteMessageToBuffer

将一条完整消息写入 Stream Buffer

static size_t prvWriteMessageToBuffer( StreamBuffer_t * const pxStreamBuffer,const void * pvTxData,//数据源地址size_t xDataLengthBytes,//数据长度size_t xSpace,//当前缓冲区可用空间大小size_t xRequiredSpace )//写入该消息所需的最小空间(包括消息头)
{size_t xNextHead = pxStreamBuffer->xHead;configMESSAGE_BUFFER_LENGTH_TYPE xMessageLength;if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_MESSAGE_BUFFER ) != ( uint8_t ) 0 )//判断是否为 Message Buffer{	//如果是,则每条消息都要先写一个长度字段xMessageLength = ( configMESSAGE_BUFFER_LENGTH_TYPE ) xDataLengthBytes;configASSERT( ( size_t ) xMessageLength == xDataLengthBytes );if( xSpace >= xRequiredSpace ){	//如果有足够空间,把消息长度写入缓冲区头部,更新 xNextHead 指针位置xNextHead = prvWriteBytesToBuffer( pxStreamBuffer, ( const uint8_t * ) &( xMessageLength ), sbBYTES_TO_STORE_MESSAGE_LENGTH, xNextHead );}else{	//直接返回 0,表示本次发送失败。xDataLengthBytes = 0;}}else	//如果不是 Message Buffer{	//不需要写入长度字段xDataLengthBytes = configMIN( xDataLengthBytes, xSpace );}if( xDataLengthBytes != ( size_t ) 0 ){	//实际写入数据,更新 xHead 指针pxStreamBuffer->xHead = prvWriteBytesToBuffer( pxStreamBuffer, ( const uint8_t * ) pvTxData, xDataLengthBytes, xNextHead );}return xDataLengthBytes;
}

prvBytesInBuffer

返回当前缓冲区中的有效数据量

static size_t prvBytesInBuffer( const StreamBuffer_t * const pxStreamBuffer )
{size_t xCount;xCount = pxStreamBuffer->xLength + pxStreamBuffer->xHead;xCount -= pxStreamBuffer->xTail;if( xCount >= pxStreamBuffer->xLength ){xCount -= pxStreamBuffer->xLength;}return xCount;
}

接收

xStreamBufferReceive

size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,void * pvRxData,size_t xBufferLengthBytes,TickType_t xTicksToWait )
{StreamBuffer_t * const pxStreamBuffer = xStreamBuffer;size_t xReceivedLength = 0, xBytesAvailable, xBytesToStoreMessageLength;configASSERT( pvRxData );configASSERT( pxStreamBuffer );//判断缓冲区类型并设置消息头长度if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_MESSAGE_BUFFER ) != ( uint8_t ) 0 ){xBytesToStoreMessageLength = sbBYTES_TO_STORE_MESSAGE_LENGTH;}else if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_BATCHING_BUFFER ) != ( uint8_t ) 0 ){xBytesToStoreMessageLength = pxStreamBuffer->xTriggerLevelBytes;}else{xBytesToStoreMessageLength = 0;}if( xTicksToWait != ( TickType_t ) 0 ){	//进入临界区检查是否有数据可读taskENTER_CRITICAL();{xBytesAvailable = prvBytesInBuffer( pxStreamBuffer );if( xBytesAvailable <= xBytesToStoreMessageLength ){( void ) xTaskNotifyStateClearIndexed( NULL, pxStreamBuffer->uxNotificationIndex );configASSERT( pxStreamBuffer->xTaskWaitingToReceive == NULL );pxStreamBuffer->xTaskWaitingToReceive = xTaskGetCurrentTaskHandle();}}taskEXIT_CRITICAL();if( xBytesAvailable <= xBytesToStoreMessageLength ){( void ) xTaskNotifyWaitIndexed( pxStreamBuffer->uxNotificationIndex, ( uint32_t ) 0, ( uint32_t ) 0, NULL, xTicksToWait );pxStreamBuffer->xTaskWaitingToReceive = NULL;xBytesAvailable = prvBytesInBuffer( pxStreamBuffer );}}else{	//非阻塞模式直接检查数据量xBytesAvailable = prvBytesInBuffer( pxStreamBuffer );}if( xBytesAvailable > xBytesToStoreMessageLength ){	//实际读取数据xReceivedLength = prvReadMessageFromBuffer( pxStreamBuffer, pvRxData, xBufferLengthBytes, xBytesAvailable );if( xReceivedLength != ( size_t ) 0 ){	//如果成功读取到数据,调用 prvRECEIVE_COMPLETED() 通知等待发送的任务可以继续发送了prvRECEIVE_COMPLETED( xStreamBuffer );}}return xReceivedLength;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/82691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在word中点击zotero Add/Edit Citation没有反应的解决办法

重新安装了word插件 1.关掉word 2.进入Zotero左上角编辑-引用 3.往下滑找到Microsoft Word&#xff0c;点重新安装加载项

新华三H3CNE网络工程师认证—Easy IP

Easy IP 就是“用路由器自己的公网IP&#xff0c;给全家所有设备当共享门牌号”的技术&#xff01;&#xff08;省掉额外公网IP&#xff0c;省钱又省配置&#xff01;&#xff09; 生活场景对比&#xff0c;想象你住在一个小区&#xff1a;普通动态NAT&#xff1a;物业申请了 …

算法打开13天

41.前 K 个高频元素 &#xff08;力扣347题&#xff09; 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 示例 1: 输入: nums [1,1,1,2,2,3], k 2 输出: [1,2]示例 2: 输入: nums [1], k 1 输出: …

LabVIEW与PLC液压泵测控系统

针对液压泵性能测试场景&#xff0c;采用LabVIEW与西门子 PLC 控制系统&#xff0c;构建高精度、高可靠性的智能测控系统。通过选用西门子 PLC、NI 数据采集卡、施耐德变频电机等&#xff0c;结合LabVIEW 强大的数据处理与界面开发能力&#xff0c;实现液压泵压力、流量、转速等…

应急响应靶机-web2-知攻善防实验室

题目&#xff1a; 前景需要&#xff1a;小李在某单位驻场值守&#xff0c;深夜12点&#xff0c;甲方已经回家了&#xff0c;小李刚偷偷摸鱼后&#xff0c;发现安全设备有告警&#xff0c;于是立刻停掉了机器开始排查。 这是他的服务器系统&#xff0c;请你找出以下内容&#…

Python制作史莱姆桌面宠物!可爱的

史莱姆桌面宠物 一个可爱的桌面史莱姆宠物&#xff0c;它会在您的任务栏上移动并提供可视化设置界面。 这里写目录标题 史莱姆桌面宠物功能特点安装与运行直接运行方式创建可执行文件 使用说明自定义GIF说明打包说明开源地址 功能特点 可爱的史莱姆在任务栏上自动移动支持…

vue3 自动导入自己的js文件中的函数

vue3 自动导入自己的js文件中的函数 vite.config.js import AutoImport from unplugin-auto-import/viteexport default defineConfig({resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}},plugins: [vue(),AutoImport({imports: [vue, vue-router, pini…

Mobile App UI自动化locator

在开展mobile app UI层自动化测试时&#xff0c;编写目标元素的locator是比较耗时的一个环节&#xff0c;弄清楚locator背后的逻辑&#xff0c;可以有效降低UI层测试维护成本。此篇博客以webdriverioappium作为UI自动化工具为例子&#xff0c;看看有哪些selector方法&#xff0…

44、web实验-后台管理系统基本功能

44、web实验-后台管理系统基本功能 “44、web实验-后台管理系统基本功能”通常指的是在Web开发学习过程中&#xff0c;关于构建后台管理系统的实践环节&#xff0c;主要涉及实现一个具备基本功能的后台管理系统。以下是该实验的主要内容&#xff1a; #### 实验目标 - 掌握后台管…

【Flask】:轻量级Python Web框架详解

什么是Flask&#xff1f; Flask是一个用Python编写的轻量级Web应用框架。它被称为"微框架"(microframework)&#xff0c;因为它核心简单但可扩展性强&#xff0c;不强制使用特定的项目结构或库。Flask由Armin Ronacher开发&#xff0c;基于Werkzeug WSGI工具包和Jin…

MAC电脑怎么通过触摸屏打开右键

在Mac电脑上&#xff0c;通过触摸屏打开右键菜单的方法如下&#xff1a; 法1:双指轻点&#xff1a;在触控板上同时用两根手指轻点&#xff0c;即可触发右键菜单。这是Mac上常用的右键操作方法。 法2:自定义触控板角落&#xff1a;可以设置触控板的右下角或左下角作为右键区域…

AI炼丹日志-26 - crawl4ai 专为 AI 打造的爬虫爬取库 上手指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇&#xff1a; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 大数据篇 300&#xff1a; Hadoop&…

java32

1.反射 获取类&#xff1a; 获取构造方法&#xff1a; 获取权限修饰符&#xff1a; 获取参数信息&#xff1a; 利用反射出来的构造器来创建对象&#xff1a; 获取成员变量&#xff1a; 获取成员方法&#xff1a; 综合练习&#xff1a; 动态代理&#xff1a;

OpenStack组件:放置服务(Placement)安装

OpenEuler的安装_openeuler5.1.0-249-CSDN博客 OpenStack云计算平台基础环境准备_openstack基础环境配置-CSDN博客 OpenStack组件&#xff1a;镜像服务&#xff08;Glance&#xff09;安装-CSDN博客 OpenStack组件&#xff1a;认证服务&#xff08;Keystone&#xff09;安装…

整合swagger,以及Knife4j优化界面

因为是前后端项目&#xff0c;需要前端的参与&#xff0c;所以一个好看的接口文档非常的重要 1、引入依赖 美化插件其中自带swagger的依赖了 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-spring-boot-starter&…

STM32——CAN总线

STM32——CAN总线 1. CAN总线基础概念 1.1 CAN总线简介 控制器局域网&#xff08;Controller Area Network, CAN&#xff09;是由Bosch公司开发的串行通信协议&#xff0c;专为汽车电子和工业控制设计&#xff0c;具有以下核心特性&#xff1a; 多主控制架构&#xff1a;所有…

什么是数据倾斜?如何优化?

什么是数据倾斜?如何优化? 一、数据倾斜的定义与表现 数据倾斜是指在大规模数据处理系统中,数据分布严重不均匀的现象,导致某些计算节点负载远高于其他节点。这种现象在分布式计算框架(如Hadoop、Spark)和分布式数据库(如Hive、HBase)中尤为常见。 关键特征:少数节点…

大模型数据流处理实战:Vue+NDJSON的Markdown安全渲染架构

在Vue中使用HTTP流接收大模型NDJSON数据并安全渲染 在构建现代Web应用时&#xff0c;处理大模型返回的流式数据并安全地渲染到页面是一个常见需求。本文将介绍如何在Vue应用中通过普通HTTP流接收NDJSON格式的大模型响应&#xff0c;使用marked、highlight.js和DOMPurify等库进…

第11期_网站搭建_极简云 单码网络验证修复版本 虚拟主机搭建笔记

系统搭建环境 1、Nginx 最佳 2、php 7.2 3、MySql 5.6 后台地址 域名/admin 后台账号 admin 密码 123456 我使用宝塔面板的后门校验&#xff0c;没有发现有后门的现象&#xff0c;使用的话&#xff0c;建议再次核查一下。也希望各位 有能力的也核查一下。 夸克网盘下载地址&…

.net ORM框架dapper批量插入

.NET ORM 框架 Dapper 批量插入全解析 在 .NET 开发中&#xff0c;与数据库交互是常见需求。Dapper 作为轻量级的 ORM&#xff08;对象关系映射&#xff09;库&#xff0c;在简化数据库交互方面表现出色。今天我们就来深入探讨 Dapper 实现批量插入的几种方法。 为什么需要批…