【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
繁體地址,請注明出處:http://hk.pswp.cn/web/82691.shtml
英文地址,請注明出處:http://en.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 實現批量插入的幾種方法。 為什么需要批…