FreeRTOS源碼分析六:vTaskDelay vs xTaskDelayUntil任務延時

系列文章目錄

FreeRTOS源碼分析一:task創建(RISCV架構)
FreeRTOS源碼分析二:task啟動(RISCV架構)
FreeRTOS源碼分析三:列表數據結構
FreeRTOS源碼分析四:時鐘中斷處理響應流程
FreeRTOS源碼分析五:資源訪問控制(一)


文章目錄

  • 系列文章目錄
  • 前言
  • 無符號溢出
  • tick 溢出的幾種情況
  • vTaskDelayUntil
  • vTaskDelay
    • prvAddCurrentTaskToDelayedList
  • 附:一個數學證明
      • 1) “溢出 ? wake < startTick ”
      • 2) “不溢出 ? wake > startTick ”
  • 總結


前言

// vTaskDelay - 簡單的相對延遲
void vTaskDelay( const TickType_t xTicksToDelay );// xTaskDelayUntil - 精確的絕對延遲
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement );

這兩個 API 都可以使當前任務進入延遲,并延遲一定的時間。本文從實現層面介紹他們的區別。


無符號溢出

在 C 語言中,無符號整數unsigned intuint32_tTickType_t 等)是按模運算定義的。這一規則由 C 標準規定:

無符號整數運算的結果是對 2N2^N2N 取模的值,其中 NNN 是該類型的比特寬度。

例如:

  • uint8_t 范圍是 0 ~ 255
  • 任何運算結果超出這個范圍,就會自動對 256 取模
  • 這不是溢出錯誤,而是自然回繞(wrap-around)

當結果超過最大值時,從 0 重新開始計數:

uint8_t a = 250;
uint8_t b = a + 10; // 250 + 10 = 260
// 按 256 取模:260 - 256 = 4
// b == 4

對于 32 位 tick 計數器:

uint32_t tick = 0xFFFFFFFE; // 最大值前兩步
tick++; // 0xFFFFFFFF
tick++; // 溢出后回到 0

  • FreeRTOS 的 TickType_t 通常是 無符號 32 位(范圍 0 ~ 4,294,967,295)
  • 每次 tickCount++,溢出時自動從 0 重新開始
  • 不需要手動處理溢出運算,無符號加減法天生支持回繞
  • 比較邏輯需要自己設計,FreeRTOS 通過“雙鏈表 + 溢出判斷”來規避直接比較的問題

tick 溢出的幾種情況

#define mainQUEUE_SEND_FREQUENCY_MS        pdMS_TO_TICKS( 1000 )/* Initialise xNextWakeTime - this only needs to be done once. */xNextWakeTime = xTaskGetTickCount();for( ; ; ){....../* Place this task in the blocked state until it is time to run again. */vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS );
}

這是 vTaskDelayUntil 的一般用法。我們一般要在某個位置獲取當前 tickCount 記為 startTick,隨后調用 vTaskDelayUntil 延遲當前任務從 startTick 的一段時間。

我們以三個變量代替三個時間:startTick 為 xTaskGetTickCount 調用時返回的 tickCount。nowTick 為用戶調用 vTaskDelayUntil 時的 tickCount。而 tickDelay 為用戶指定的延遲 tick 數。

這里會存在兩個溢出的情況:

  • startTicknowTick 之間,系統 tick 已經溢出。這個表現為:nowTick < startTick
  • startTickstartTick + tickDelay 之間,系統 tick 會發生溢出。這個表現為:startTick + tickDelay < startTick
  • 那么,為什么溢出之后會表現為 startTick + tickDelay < startTick ?附中有明確的數學證明。

這里我們另外定義一個變量:wake = startTick + tickDelaywake 可能溢出或沒有

那么,我們結合上面這兩種情況,在先判斷 從 startTicknowTick 之間,系統 tick 溢出情況之后再判斷后面定時器的溢出情況,則有以下情況:

  • nowTick >= startTick 表明系統計數器未溢出,wake < startTick (wake 本身溢出了) 或 wake > nowTick >= startTick (wake 未溢出) 這兩種情況都表明任務的待喚醒時間尚未抵達,任務需阻塞。若以上都不滿足(等價于 wake >= startTickwake <= nowTick),說明“計劃喚醒點已經過去”,不阻塞,立刻返回。
  • nowTick < startTick 表明系統計數器溢出,只有當 wake 也溢出且 wake > nowTick 時才會阻塞:條件是 (wake < startTick ) && (wake > nowTick)。直觀解釋:大家都已經跨到新一圈了,而且 wake 還在 nowTick 之后,才需要等;否則就是“錯過了”或“在舊圈里”,不阻塞。

vTaskDelayUntil

這個時候來看 vTaskDelayUntil 的源碼就非常清晰了。

xTaskDelayUntil 用于延遲任務從 *pxPreviousWakeTime 到 *pxPreviousWakeTime + xTimeIncrement 這段時間
@return pdTRUE:本次調用確實延遲了任務(進入延遲列表)。 pdFALSE:任務未延遲(喚醒點已過,立即返回)。

BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement )
{TickType_t xTimeToWake;BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;/* 參數有效性檢查 */configASSERT( pxPreviousWakeTime );configASSERT( ( xTimeIncrement > 0U ) );/* 掛起調度器,防止 tickCount 在計算期間發生變化 */vTaskSuspendAll();{/* 緩存當前系統 tick 計數(在本代碼塊中不會變化) */const TickType_t xConstTickCount = xTickCount;configASSERT( uxSchedulerSuspended == 1U );/* 計算下一次喚醒的時間點(無符號加法,可能會溢出) */xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;/* ---- 溢出情況分析 ----* 如果當前 tickCount < 上一次的喚醒時間,說明 tickCount 已經溢出過。* 在這種情況下,我們只有在 “下一次喚醒時間也發生溢出且它仍大于當前 tickCount” 時才需要延遲。* 這樣處理是因為這種情況等價于沒有溢出。*/if( xConstTickCount < *pxPreviousWakeTime ){if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) ){xShouldDelay = pdTRUE;}}else{/* ---- 未溢出情況 ----* 如果下一次喚醒時間溢出(xTimeToWake < prevWake),或* 下一次喚醒時間仍在未來(xTimeToWake > 當前 tickCount),則需要延遲。*/if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) ){xShouldDelay = pdTRUE;}}/* 更新 pxPreviousWakeTime,為下一次調用做準備 */*pxPreviousWakeTime = xTimeToWake;if( xShouldDelay != pdFALSE ){/* 將當前任務加入延遲列表。* 這里需要的是“等待的時間”而不是“目標喚醒時間”,* 所以要減去當前 tickCount 得到阻塞時長。*/prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );}}/* 恢復調度器,如果期間有更高優先級任務就緒,這里可能會發生任務切換 */xAlreadyYielded = xTaskResumeAll();/* 若 ResumeAll 未觸發切換(返回 pdFALSE),仍主動 yield 一次:*  1) 讓同優先級任務獲得公平的時間片/協作式讓出;*  2) 在禁搶占或端口差異下,統一通過顯式 yield 兌現切換。* 若此時系統中沒有更高/同優先級可運行任務,此調用幾乎為空操作。 */if( xAlreadyYielded == pdFALSE ){taskYIELD_WITHIN_API();}/* 返回本次調用是否真的延遲了任務 */return xShouldDelay;
}
  • 函數用于周期性任務的延時,保證任務喚醒的時間間隔固定,不受執行時間波動影響。
  • vTaskDelay() 不同,它基于絕對喚醒時間pxPreviousWakeTime)而非相對延遲。
  • 每次調用都會將 pxPreviousWakeTime 累加 xTimeIncrement,而不是更新為當前時間。這避免了周期漂移。
  • xTaskResumeAll() 會恢復調度器、處理掛起期間的就緒任務,并可能立即觸發切換。
  • 若未觸發切換(返回 pdFALSE),仍調用 taskYIELD_WITHIN_API()

vTaskDelay

vTaskDelay → 基于相對時間延遲任務,延遲是“從執行 vTaskDelay 開始算”,可能因執行時間累積產生周期漂移。

void vTaskDelay( const TickType_t xTicksToDelay )
{BaseType_t xAlreadyYielded = pdFALSE;/* 延時時間為 0 時,不阻塞任務,只是強制進行一次任務切換。 */if( xTicksToDelay > ( TickType_t ) 0U ){/* 掛起調度器,防止任務狀態修改過程被調度打斷。 */vTaskSuspendAll();{configASSERT( uxSchedulerSuspended == 1U );/* 當前任務不可能在事件列表中(它正運行著),因此直接將它* 從就緒列表移除,并加入延遲列表。 */prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );}/* 恢復調度器:* - 將掛起期間的 pending ready 任務并入就緒列表。* - 處理掛起期間累積的 tick(xPendedTicks)。* - 檢查是否需要立即任務切換,必要時發起切換。*/xAlreadyYielded = xTaskResumeAll();}/* 如果恢復調度器時沒有觸發切換(返回 pdFALSE),* 仍然調用一次 yield:*   - 兌現同優先級任務的時間片輪轉。*   - 處理 xYieldPending 標志(中斷中可能置位的“應切換”標志)。*/if( xAlreadyYielded == pdFALSE ){taskYIELD_WITHIN_API();}
}

prvAddCurrentTaskToDelayedList

prvAddCurrentTaskToDelayedList() 的主要功能是 把當前正在運行的任務移出就緒隊列,并加入到合適的延時/掛起隊列中,以實現延時阻塞功能(包括 Tick 溢出情況處理和無限期阻塞)。

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,const BaseType_t xCanBlockIndefinitely )
{TickType_t xTimeToWake;const TickType_t xConstTickCount = xTickCount;  // 當前系統 Tick 值的快照List_t * const pxDelayedList = pxDelayedTaskList;           // 當前延時任務列表List_t * const pxOverflowDelayedList = pxOverflowDelayedTaskList; // Tick 溢出延時列表#if ( INCLUDE_xTaskAbortDelay == 1 ){/* 進入延時隊列前,先清除任務的 ucDelayAborted 標志位* 用于檢測任務離開阻塞態時,是否是被中止延時(Abort Delay)喚醒的 */pxCurrentTCB->ucDelayAborted = ( uint8_t ) pdFALSE;}#endif/* 1. 從就緒隊列移除當前任務*    因為任務狀態鏈表項在就緒隊列和阻塞隊列中是同一個 list item,*    所以必須先從就緒隊列刪除才能放到阻塞隊列。 */if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){/* 如果該優先級的就緒任務已經被清空,就更新優先級位圖,* 表示該優先級上已無就緒任務 */portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );}#if ( INCLUDE_vTaskSuspend == 1 ){/* 2. 判斷是否是無限期阻塞(portMAX_DELAY 且允許無限阻塞) */if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) ){/* 直接加入掛起任務列表(xSuspendedTaskList),* 保證它不會因為時間到而被喚醒,必須手動喚醒。 */listINSERT_END( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );}else{/* 3. 計算喚醒時間(可能會發生無符號溢出,但內核會處理) */xTimeToWake = xConstTickCount + xTicksToWait;/* 設置鏈表項的值為喚醒時間(用于延時隊列的排序) */listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );if( xTimeToWake < xConstTickCount ){/* 4. Tick 計數溢出* 如果喚醒時間比當前 Tick 值小,說明加法結果溢出,* 則把任務放入溢出延時隊列。 */vListInsert( pxOverflowDelayedList, &( pxCurrentTCB->xStateListItem ) );}else{/* 5. Tick 未溢出* 直接加入當前延時隊列(pxDelayedList),* 按喚醒時間升序插入。 */vListInsert( pxDelayedList, &( pxCurrentTCB->xStateListItem ) );/* 如果該任務的喚醒時間比當前系統記錄的最早喚醒時間還早,* 更新 xNextTaskUnblockTime(優化 Tick 處理,減少無意義掃描)。 */if( xTimeToWake < xNextTaskUnblockTime ){xNextTaskUnblockTime = xTimeToWake;}}}}
}

這里我們回顧:FreeRTOS源碼分析四:時鐘中斷處理響應流程 中提到,時鐘中斷會調用函數 xTaskIncrementTick,它會對系統 tick 加1,當檢測到 tick 溢出時,會交換兩個延時隊列。如下所示:

BaseType_t xTaskIncrementTick( void )
{TCB_t * pxTCB;                          // 任務控制塊指針TickType_t xItemValue;                  // 延遲列表項的值(喚醒時間)BaseType_t xSwitchRequired = pdFALSE;   // 是否需要任務切換標志/* 時鐘遞增應該在每個內核定時器事件上發生。* 如果調度器被掛起,則遞增待處理的時鐘計數。 */if( uxSchedulerSuspended == ( UBaseType_t ) 0U ){// === 調度器未被掛起的情況 ===/* 小優化:在此代碼塊中時鐘計數不會改變 */const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;/* 遞增RTOS時鐘,如果溢出到0則切換延遲和溢出延遲列表 */xTickCount = xConstTickCount;// 處理時鐘計數器溢出的情況(從最大值回繞到0)if( xConstTickCount == ( TickType_t ) 0U ){taskSWITCH_DELAYED_LISTS();  // 切換延遲任務列表}....................................
}

而宏 taskSWITCH_DELAYED_LISTS 則會切換兩個任務列表的角色:

#define taskSWITCH_DELAYED_LISTS()                                                \do {                                                                          \List_t * pxTemp;                                                          \\/* The delayed tasks list should be empty when the lists are switched. */ \configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );               \\pxTemp = pxDelayedTaskList;                                               \pxDelayedTaskList = pxOverflowDelayedTaskList;                            \pxOverflowDelayedTaskList = pxTemp;                                       \xNumOfOverflows = ( BaseType_t ) ( xNumOfOverflows + 1 );                 \prvResetNextTaskUnblockTime();                                            \} while( 0 )

恰與這里兩個延時隊列想對應,當用戶延時會發生在 tick 溢出之后時,則加入溢出隊列。而當溢出發生,則溢出隊列就變為當前的延時隊列。

這樣上一輪“溢出延時鏈表”里的任務會自然地在新的 tick 空間里按照時間順序等待喚醒。xNextTaskUnblockTime 被重置,用于后續快速判斷“下一個到期點”。

附:一個數學證明

因為是無符號模 2N2^N2N 加法,而且 tickDelay > 0(源碼里有 configASSERT( xTimeIncrement > 0U ))。設:

  • startTick ∈ [0, 2^N-1]
  • tickDelay ∈ [1, 2^N-1]
  • 真實和 S = startTick + tickDelay
  • 存回寄存器/變量的結果 wake = S mod 2^N

結論: 溢出當且僅當 wake < startTick 。證明分兩步:

1) “溢出 ? wake < startTick ”

若溢出,則 S ≥ 2^N,所以

wake=S?2N=startTick+tickDelay?2N=startTick?(2N?tickDelay).wake = S - 2^N = startTick + tickDelay - 2^N = startTick - (2^N - tickDelay). wake=S?2N=startTick+tickDelay?2N=startTick?(2N?tickDelay).

因為 tickDelay ≥ 1,故 (2^N - tickDelay) ≤ 2^N - 1 且至少為 1,于是

wake=startTick?(2N?tickDelay)?≥1≤startTick?1<startTick.wake = startTick - \underbrace{(2^N - tickDelay)}_{\ge 1} \le startTick - 1 \;<\; startTick . wake=startTick?1(2N?tickDelay)??startTick?1<startTick.

所以一旦溢出,wake 一定小于 startTick

2) “不溢出 ? wake > startTick ”

若不溢出,則 S < 2^Nwake = S = startTick + tickDelay。又因 tickDelay ≥ 1

wake=startTick+tickDelay≥startTick+1>startTick.wake = startTick + tickDelay \ge startTick + 1 > startTick . wake=startTick+tickDelaystartTick+1>startTick.

(只有當 tickDelay = 0 才可能 wake = startTick ,但這被斷言禁止了。)

因此,判斷溢出最簡單的辦法就是:做完 wake = startTick + tickDelay 的無符號加法后,看 wake < startTick 是否成立。成立就說明發生了進位丟棄。


總結

完結撒花!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/92641.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92641.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92641.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux學習-應用軟件編程(fread/fwrite,流定義相關接口)

freadsize_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 功能按塊從文件讀取數據&#xff0c;從文件中讀 nmemb 個、每個 size 字節的元素&#xff0c;存入 ptr 指向的內存。參數- ptr &#xff1a;存儲讀取數據的內存首地址&#xff08;需提前分配足夠…

IP分片(IP Fragmentation)

目錄 一、核心概念:MTU與分片的必要性 二、IP分片的關鍵字段(IPv4頭部) 三、分片與重組流程 1. 分片過程(發送端或中間路由器) 2. 重組過程(接收端) 四、IPv4與IPv6分片的差異 五、分片的潛在問題與風險 六、總結 一、傳輸效率降低,帶寬開銷增加 二、可靠性降低,數據丟…

高并發內存池 內存釋放回收(6)

文章目錄前言一、threadcache回收內存二、centralcache回收內存三、pagecache回收內存總結前言 Hello&#xff0c;我們繼續乘勝追擊 ??本篇難度較大&#xff0c;大家要好好學一下 一、threadcache回收內存 當某個線程申請的對象不用了&#xff0c;可以將其釋放給 thread cac…

2438. 二的冪數組中查詢范圍內的乘積

2438. 二的冪數組中查詢范圍內的乘積 初始理解題目 首先&#xff0c;我們需要清楚地理解題目在說什么。題目給出一個正整數 n&#xff0c;要求我們構造一個數組 powers&#xff0c;這個數組滿足以下條件&#xff1a; 元素性質?&#xff1a;數組中的每個元素都是 2 的冪。即…

【PyTorch學習筆記 - 01】 Tensors(張量)

最近項目需要優化一下目標檢測網絡&#xff0c;在這個過程中發現還是得增加對框架底層的掌握才可行。于是準備對pytorch的一些基本概念做一些再理解。參考PyTorch的wiki&#xff0c;對自己的學習過程做個記錄。 Tensors 是一種特殊的數據結構&#xff0c;與數組和矩陣非常相似…

【C/C++】(struct test*)0->b 講解

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 一、什么是結構體成員的偏移量&#xff1f; 二、為什么需要計算偏移量&#xff1f; 三、如何計算偏移量&#xff1f; 四、總結 一、什么是結構體成員的偏移量&#…

使用Pytest進行接口自動化測試(三)

&#xff08;一&#xff09;YAML 之前在項目中&#xff0c;我們也是用過YAML來做配置文件&#xff0c;他用于以人類可讀的形式存儲信息&#xff0c; 特點: 一種簡易的可讀語言&#xff0c;用于人和計算機交換數據 通常用來存儲配置信息 跟python類似&…

算法訓練營day46 647. 回文子串、516.最長回文子序列、動態規劃總結篇

今天是動態規劃的最后一篇內容了&#xff0c;本篇主要是針對回文字符串這種“與眾不同”的遞推規律來進行講解 647. 回文子串 統計并返回這個字符串中 回文子串 的數目 暴力解法 兩層for循環&#xff0c;遍歷區間起始位置和終止位置&#xff0c;然后還需要一層遍歷判斷這個區…

Qt界面優化

1.QSS在網頁前端開發領域中&#xff0c;CSS 是一個至關重要的部分&#xff0c;描述了一個網頁的 “樣式”&#xff0c;從而起到對網頁美化的作用。所謂樣式&#xff0c;包括不限于大小、位置、顏色、背景、間距、字體等等。網頁開發作為 GUI 的典型代表&#xff0c;也對于其他客…

week1+2+3

408 計組 1.基本組成2.數據的表示和運算定點數&#xff1a;把數字分為定點整數和定點小數分開存儲 浮點數&#xff1a;用科學計數法存儲 原碼 -全部取反-> 反碼 反碼 1->補碼 補碼 -符號位取反->移碼帶余除法&#xff1a;設x,m∈Z&#xff0c;m>0則存在唯一的整數q…

java8中javafx包缺少報錯

今天拉取一個jdk1.8的項目里面有一個代碼用到了javafx&#xff0c;這個我記得是jdk中的包&#xff0c;正常不應該報錯的。然后發現jdk中還真沒有&#xff0c;查了一下是因為版本問題。 Java 8 及之前&#xff1a;Oracle JDK 自帶 JavaFX&#xff0c;OpenJDK 通常不包含Java 9 …

day072-代碼檢查工具-Sonar與maven私服-Nexus

文章目錄0. 老男孩思想-選對池塘釣美人魚1. 代碼回滾方案2. SonarQube2.1 代碼檢查工具2.2 部署sonarqube2.2.1 軟件要求2.2.2 安裝軟件2.2.3 啟動sonar2.2.4 部署插件2.3 sonar檢查java代碼2.3.1 創建sona項目2.3.2 分析java代碼2.3.3 Jenkins結合sonar檢查代碼2.4 sonar檢查非…

【前端基礎】15、列表元素、表格元素、表單元素(注:極其粗略的記載。)

一、列表元素 1、什么是列表元素2、有序列表&#xff08;ol、li&#xff09; ol有序列表 直接子元素只能是li。 li列表中的每一項。3、無序列表&#xff08;ul、li&#xff09; ol無序列表 直接子元素只能是li。 li列表中的每一項。4、定義列表&#xff08;dl、dt、dd&#xff…

IRFBG30PBF Vishay威世MOSFET場效應管

IRFBG30PBF Vishay威世&#xff1a;超快MOSFET 場效應管一、產品定位IRFBG30PBF 是Vishay威世推出的600V/30A N溝道功率MOSFET&#xff0c;采用第五代TrenchFET技術&#xff0c;專為開關電源、電機驅動、新能源逆變器等高功率場景設計。以85mΩ超低導通電阻和超快反向恢復&…

【07-AGI的討論】

AI ANI&#xff1a;artificial narrow intelligence; 如 智能音箱&#xff1b;自動駕駛汽車&#xff0c;網絡搜索&#xff0c;其他用于專業特定事項的工具&#xff1b; AGI&#xff1a;artificial general intelligence; building AI systems that could do anything a typical…

[激光原理與應用-225]:機械 - 3D圖與2D圖各自的作用

在機械設計與加工領域&#xff0c;3D圖和2D圖是兩種核心的工程表達方式&#xff0c;它們在產品設計、制造、裝配及維護等環節中扮演不同角色&#xff0c;具有互補性。以下是它們各自的作用及具體應用場景的詳細解析&#xff1a;一、3D圖的作用1. 直觀展示產品全貌三維可視化&am…

【從零開始java學習|第一篇】java中的名詞概念(JDK、JVM、JRE等等)

目錄 一、核心運行環境三要素&#xff08;JVM/JRE/JDK&#xff09; 二、常用開發指令&#xff08;JDK 自帶工具&#xff09; 三、一些其他概念 四、總結核心邏輯鏈 要入門 Java&#xff0c;理解核心概念之間的關系是基礎。以下是 Java 中最核心的基礎概念、工具及相關名詞的…

UVa12345 Dynamic len(set(a[L:R]))

[TOC](UVa12345 Dynamic len(set(a[L:R]))) 題目鏈接 UVA - 12345 Dynamic len(set(a[L:R])) 題意 有編號從 0 到 n?1 的 n 個數&#xff0c;有兩種操作&#xff1a; Q L R 詢問編號 L 到編號 R?1 的數中有多少個不同的數字。M X Y 將編號為 X 的數字改為 Y。 你的任務就是…

[Ubuntu] VNC連接Linux云服務器 | 實現GNOME圖形化

將桌面環境修改為 GNOME 并通過 VNC 遠程訪問的步驟 & TightVNC 的安裝與配置說明&#xff1a;1. 安裝 GNOME 桌面環境 sudo apt update sudo apt install ubuntu-gnome-desktop -y2. 安裝 TightVNC 服務器 sudo apt install tightvncserver -y3. 初始化 VNC Server 并設置…

進程、網絡通信方法

一、進程間通信(IPC)方法 適用于同一臺主機上的進程間數據交換。 管道(Pipe) 匿名管道:單向通信,僅用于父子進程。 命名管道(FIFO):通過文件系統路徑訪問,支持無親緣關系進程。 消息隊列(Message Queue) 結構化消息(類型+數據),按類型讀取,支持異步通信。…