# ThreadX ## ThreadX 的應用包含四種類型的程式執行:初始化、執行緒執行、中斷服務程序 (ISR) 和應用程式計時器。 ![image](https://hackmd.io/_uploads/HJ3D1GEVkl.png) ### 初始化 初始化包括從處理器重置到進入執行緒計畫循環入口點之間的所有程式執行。 tx_kernel_enter() :ThreadX 的核心初始化函數,進入 ThreadX 的內核運行環境,負責協調各種內部 ThreadX 資料結構的初始化,然後調用定義函數 tx_application_define。 tx_application_define() :負責定義所有初始應用程式的任務。 :::spoiler main.c ```c= int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_ThreadX_Init(); //調用 ThreadX 的核心初始化函數 tx_kernel_enter() while (1) { HAL_GPIO_TogglePin(GPIOD,LD_3_Pin); HAL_Delay(1000); } } ``` ::: ![image](https://hackmd.io/_uploads/HJtcPMVNkx.png) ### 執行緒執行 初始化完成後,ThreadX 會進入其執行緒計畫循環。 計畫循環的功能是尋找已準備好執行的應用程式執行緒。找到已就緒的執行緒後,ThreadX 將控制權交給該執行緒。 當執行緒準備好執行時,它會處於「就緒」狀態。在處於就緒狀態的執行緒中,只有優先級最高的就緒執行緒才會被選中執行。當發生這種情況時,ThreadX 會執行該執行緒,並將其狀態更改為「執行中」。 如果有更高優先級的執行緒已經準備就緒,當前正在執行的執行緒會回到「就緒」狀態,並且系統會開始執行新的高優先級執行緒,並將其狀態更改為「執行中」。 每次發生執行緒搶佔時,執行緒會在「就緒」和「執行中」狀態之間進行切換。 --- tx_thread_sleep():用來讓當前執行緒暫停執行,並在指定的時鐘周期後自動恢復執行。它可以用來控制執行緒的執行頻率、實現延遲,或者讓其他更高優先級的執行緒有機會運行。 tx_thread_resume():手動啟動執行緒。 tx_thread_suspend():此服務用於暫停指定的應用程式執行緒。執行緒可以調用此服務來暫停自身。 tx_thread_terminate():不管指定的執行緒是否處於暫停狀態,此服務都將終止該執行緒。執行緒可以調用此服務來終止自身。強制終止指定的執行緒,但其內存資源仍然保留,需要手動調用 tx_thread_delete 刪除。 tx_thread_delete():刪除指定的應用程式執行緒。由於指定的執行緒必須處於 已終止(Terminated) 或 已完成(Completed) 狀態,因此不能從嘗試刪除自身的執行緒中調用此服務。 :::spoiler app_azure_rtos.c ```c= #include "app_azure_rtos.h" // 引入 Azure RTOS 庫 #include <stdint.h> // 引入標準整數類型庫 #define THREAD_STACK_SIZE 1024 // 定義每個執行緒的堆疊大小為 1024 個byte #define THREAD_COUNT 4 // 定義需要創建的執行緒數量為 4 // 定義用於記憶體池的緩衝區 static UCHAR tx_byte_pool_buffer[TX_APP_MEM_POOL_SIZE]; static TX_BYTE_POOL tx_app_byte_pool; // 定義記憶體池控制塊 // 定義堆疊區域,每個執行緒使用不同的堆疊空間 uint8_t thread_stack[THREAD_COUNT][THREAD_STACK_SIZE]; // 定義三個執行緒的控制塊 TX_THREAD thread_0, thread_1, thread_2; // 宣告執行緒函數的原型 VOID my_thread_0_entry(ULONG initial_input); VOID my_thread_1_entry(ULONG initial_input); VOID my_thread_2_entry(ULONG initial_input); // 定義應用程式初始化函數 VOID tx_application_define(VOID *first_unused_memory) { // 創建執行緒 0,設定堆疊、優先級為 5,並自動啟動 tx_thread_create(&thread_0, "my_thread_0", my_thread_0_entry, 0, thread_stack[0], THREAD_STACK_SIZE, 5, 5, 5, TX_AUTO_START); // 創建執行緒 1,設定堆疊、優先級為 7,並自動啟動 tx_thread_create(&thread_1, "my_thread_1", my_thread_1_entry, 0, thread_stack[1], THREAD_STACK_SIZE, 7, 7, 5, TX_AUTO_START); // 創建執行緒 2,設定堆疊、優先級為 9,並自動啟動 tx_thread_create(&thread_2, "my_thread_2", my_thread_2_entry, 0, thread_stack[2], THREAD_STACK_SIZE, 9, 9, 5, TX_AUTO_START); } ``` ::: ![image](https://hackmd.io/_uploads/By1V4IEE1l.png) :::spoiler app_threadx.c ```c= #include "app_threadx.h" // 引入 ThreadX 應用程式相關頭文件 #include "main.h" // 引入主程式頭文件,包含硬體配置和初始化等 extern UART_HandleTypeDef huart1; // 宣告外部變數,UART 配置 extern TX_THREAD thread_0, thread_1, thread_2; // 宣告外部變數,執行緒控制塊 // App_ThreadX_Init 函數,用於初始化 ThreadX 記憶體池 UINT App_ThreadX_Init(VOID *memory_ptr) { UINT ret = TX_SUCCESS; // 預設返回值為成功 TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr; // 記憶體池指標轉換 (void)byte_pool; // 暫時不使用 byte_pool,這裡可根據需要修改 return ret; // 返回初始化結果 } // MX_ThreadX_Init 函數,進行 ThreadX 作業系統的啟動 void MX_ThreadX_Init(void) { tx_kernel_enter(); // 啟動 ThreadX 作業系統核心,進入無限迴圈,開始執行線程 } // my_thread_0_entry 函數,執行緒 0 的主程式邏輯 VOID my_thread_0_entry(ULONG initial_input) { while(1) // 無窮迴圈,使執行緒持續運行 { // 切換 LED 燈號,使用 GPIO 控制連接在 GPIOD 的 LED 燈 HAL_GPIO_TogglePin(GPIOD, LD_4_Pin); // 傳送 "0" 訊息至 UART,讓外部裝置(如電腦)收到訊息 HAL_UART_Transmit(&huart1, (uint8_t *)"0\n", 2, 1000); // 使執行緒休眠 40 毫秒,讓其他執行緒有機會執行 tx_thread_sleep(40); } } // my_thread_1_entry 函數,執行緒 1 的主程式邏輯 VOID my_thread_1_entry(ULONG initial_input) { // 切換 LED 燈號,使用 GPIO 控制連接在 GPIOD 的 LED 燈 HAL_GPIO_TogglePin(GPIOD, LD_2_Pin); // 傳送 "1" 訊息至 UART,讓外部裝置(如電腦)收到訊息 HAL_UART_Transmit(&huart1, (uint8_t *)"1\n", 2, 1000); } // my_thread_2_entry 函數,執行緒 2 的主程式邏輯 VOID my_thread_2_entry(ULONG initial_input) { // 切換 LED 燈號,使用 GPIO 控制連接在 GPIOD 的 LED 燈 HAL_GPIO_TogglePin(GPIOD, LD_3_Pin); // 傳送 "7" 訊息至 UART,讓外部裝置(如電腦)收到訊息 HAL_UART_Transmit(&huart1, (uint8_t *)"7\n", 2, 1000); // 嘗試刪除執行緒 1,如果刪除成功,則傳送 "SUCCESS" 訊息 UINT status = tx_thread_delete(&thread_1); if (status == TX_SUCCESS) { // 傳送 "SUCCESS" 訊息至 UART HAL_UART_Transmit(&huart1, (uint8_t *)"SUCCESS\n", sizeof("SUCCESS\n"), 1000); } // 傳送 "8" 訊息至 UART,讓外部裝置(如電腦)收到訊息 HAL_UART_Transmit(&huart1, (uint8_t *)"8\n", 2, 1000); } ``` ::: ![image](https://hackmd.io/_uploads/ry0vBI4EJe.png) ![image](https://hackmd.io/_uploads/ryeysSLENke.png) # MessageQueues ## 訊息佇列 (Message Queues) 訊息佇列是 ThreadX 中用於執行緒間通訊的重要工具。它允許執行緒以先進先出的方式傳遞訊息。透過訊息佇列,可以有效管理執行緒間的資料共享與同步,實現高效的通訊模式。 ### 建立訊息佇列 Creating Message Queues 訊息佇列由應用程式執行緒在初始化期間或執行時建立。應用程式中的訊息佇列數量沒有限制。 ### 訊息大小Message Size 每個訊息佇列都支援許多固定大小的訊息。可用的訊息大小範圍為 1 到 16 個 32-bit的word(含)。訊息大小在建立佇列時指定。如果應用程式的訊息超過 16 個word,則必須透過指標傳遞。為此,可以建立訊息大小為 1 個word的佇列(足以容納一個指標),然後傳送和接收訊息指標,而非整個訊息。 ### 訊息佇列容量Message Queue Capacity 佇列的容量是佇列中可容納的訊息數量,這取決於訊息大小與建立佇列時提供的記憶體區域大小。佇列的總訊息容量計算方式為:將記憶體區域的總字節數除以每條訊息的字節數。 例如,若訊息佇列的訊息大小為 1 個 32-bit的word(4-byte),且建立時提供了 100-byte的記憶體區域,則該佇列的容量為 25 條訊息。 ### 執行緒掛起Thread Suspension 執行緒可能在嘗試從佇列傳送或接收訊息時進入掛起狀態。執行緒掛起通常發生在等待來自空佇列的訊息時。不過,執行緒也可能在嘗試向已滿的佇列傳送訊息時掛起。 當掛起的條件解決後,請求的服務將完成,掛起的執行緒也會隨之恢復。如果同一佇列中有多個執行緒掛起,這些執行緒將按照掛起的順序 (FIFO) 恢復。 然而,如果應用程式在取消執行緒掛起的佇列服務之前調用 tx_queue_prioritize,可以改變恢復順序。佇列的優先級服務會將優先級最高的執行緒移到掛起列表的最前面,而其餘掛起的執行緒則按照原本的 FIFO 順序恢復。 此外,所有佇列掛起都可以設定超時。超時的作用是指定執行緒保持掛起狀態的最大計時器時鐘週期數。如果發生超時,執行緒會被恢復,並且該服務將返回相應的錯誤代碼。 ### 佇列傳送通知Queue Send Notification 某些應用程式可能會發現,在將訊息放入佇列時接收通知十分有用。ThreadX 透過 tx_queue_send_notify 服務提供此功能。該服務會將提供的應用程式通知函數註冊到指定的佇列。只要有訊息傳送到佇列,ThreadX 就會呼叫此應用程式通知函數。 應用程式通知函數中的具體處理方式由應用程式自行決定,但通常包括恢復相應的執行緒以處理新的訊息。 相關 API: ```c= // 建立佇列 tx_queue_create(TX_QUEUE *queue_ptr, CHAR *name_ptr, UINT message_size, VOID *queue_start, ULONG queue_size); ``` #### 佇列容量 (Queue Capacity) 佇列容量由訊息大小及提供的記憶體大小決定。 計算公式: 總記憶體字節數 ÷ 每條訊息的字節數。 例如:訊息大小為 4-byte,記憶體大小為 100-byte,則容量為 25 條訊息。 #### 執行緒掛起 (Thread Suspension) 掛起條件: 接收空佇列的訊息 傳送訊息至已滿佇列 執行緒會進入掛起狀態,並按 FIFO 順序 恢復。 使用 tx_queue_prioritize 可改變順序,將最高優先級執行緒移至最前。 支援超時: 設定執行緒的最大掛起時間,超時後返回錯誤代碼。 相關 API: ```c= // 從佇列接收訊息 tx_queue_receive(TX_QUEUE *queue_ptr, VOID *destination_ptr, ULONG wait_option); // 傳送訊息至佇列 tx_queue_send(TX_QUEUE *queue_ptr, VOID *source_ptr, ULONG wait_option); // 調整佇列中訊息的優先順序 tx_queue_prioritize(TX_QUEUE *queue_ptr); ``` #### 佇列傳送通知 (Queue Send Notification) 註冊應用程式通知函數,當有訊息傳送至佇列時觸發通知。 通知函數內可處理新增的訊息,或恢復相應執行緒。 相關 API: ```c= // 設定佇列傳送通知回呼 tx_queue_send_notify(TX_QUEUE *queue_ptr, VOID (*queue_send_notify)(TX_QUEUE *)); ``` #### 其他功能 清空佇列: 使用 tx_queue_flush 清空所有訊息。 獲取資訊: 使用 tx_queue_info_get 查詢佇列當前狀態(名稱、訊息數量等)。 效能分析: 使用 tx_queue_performance_info_get 或 tx_queue_performance_system_info_get 獲取效能數據。 相關 API: ```c= // 清空佇列 tx_queue_flush(TX_QUEUE *queue_ptr); // 獲取佇列資訊 tx_queue_info_get(TX_QUEUE *queue_ptr, CHAR **name, ULONG *enqueued, ULONG *available_storage, TX_THREAD **first_suspended, ULONG *suspended_count, TX_QUEUE **next_queue); // 獲取佇列效能資訊 tx_queue_performance_info_get(TX_QUEUE *queue_ptr, ULONG *messages_sent, ULONG *messages_received, ULONG *empty_suspensions, ULONG *full_suspensions, ULONG *full_errors, ULONG *timeouts); // 獲取全域佇列效能資訊 tx_queue_performance_system_info_get(ULONG *messages_sent, ULONG *messages_received, ULONG *empty_suspensions, ULONG *full_suspensions, ULONG *full_errors, ULONG *timeouts); ``` # 信號燈 Semaphore ## 信號燈 (Semaphores) 信號燈是 ThreadX 提供的一種用於執行緒同步與資源管理的核心工具。它通過計數機制實現執行緒間的協作,常被用於事件通知與互斥操作。 #### 計數信號燈 (Counting Semaphores) 功能: 使用 32 位計數器來追蹤資源的可用性或事件的觸發狀態。 範圍: 計數值範圍為 0 至 4,294,967,295。 操作: tx_semaphore_get: 減少計數,當計數為 0 時執行緒進入掛起狀態。 tx_semaphore_put: 增加計數,並恢復掛起的執行緒(若有)。 ![image](https://hackmd.io/_uploads/S1KW_aUSJg.png) 相關 API: ```c= // 取得信號量 tx_semaphore_get(TX_SEMAPHORE *semaphore_ptr, ULONG wait_option); // 釋放信號量 tx_semaphore_put(TX_SEMAPHORE *semaphore_ptr); ``` #### 建立與刪除信號燈 建立: 使用 tx_semaphore_create 定義信號燈名稱及初始計數值。 刪除: 使用 tx_semaphore_delete 清除信號燈,釋放相關資源。 相關 API: ```c= // 建立信號量 tx_semaphore_create(TX_SEMAPHORE *semaphore_ptr, CHAR *name_ptr, ULONG initial_count); // 刪除信號量 tx_semaphore_delete(TX_SEMAPHORE *semaphore_ptr); ``` #### 互相排斥 (Mutual Exclusion) 用途: 控制執行緒對共享資源的訪問,通常信號燈初始值設為 1(即二進制信號燈)。 注意事項: 避免執行緒多次取得同一信號燈,否則可能導致無限期掛起。 #### 事件通知 (Event Notification) 模式: 生產者-消費者模式。 生產者: 使用 tx_semaphore_put 增加信號燈計數。 消費者: 使用 tx_semaphore_get 等待信號燈變為可用。 工具: 可使用 tx_semaphore_ceiling_put 設置計數上限,避免計數器溢出。 相關 API: ```c= // 設定信號量上限 tx_semaphore_ceiling_put(TX_SEMAPHORE *semaphore_ptr, ULONG ceiling); ``` #### 執行緒掛起 (Thread Suspension) 觸發條件: 當信號燈計數為 0 時,執行緒會掛起等待。 恢復順序: 按 FIFO 恢復掛起執行緒,可使用 tx_semaphore_prioritize 調整優先順序。 相關 API: ```c= // 調整信號量中執行緒的優先順序 tx_semaphore_prioritize(TX_SEMAPHORE *semaphore_ptr); ``` #### 信號燈放置通知 (Semaphore Put Notification) 功能: 使用 tx_semaphore_put_notify 註冊通知函數,在信號燈被放置時執行自定義邏輯。 相關 API: ```c= // 設定信號量釋放通知回呼 tx_semaphore_put_notify(TX_SEMAPHORE *semaphore_ptr, VOID (*semaphore_put_notify)(TX_SEMAPHORE *)); ``` #### 效能與資訊查詢 獲取信號燈狀態及效能數據,包含當前計數值、掛起執行緒數量等資訊。 相關 API: ```c= // 獲取信號量資訊 tx_semaphore_info_get(TX_SEMAPHORE *semaphore_ptr, CHAR **name, ULONG *current_value, TX_THREAD **first_suspended, ULONG *suspended_count, TX_SEMAPHORE **next_semaphore); // 獲取信號量效能資訊 tx_semaphore_performance_info_get(TX_SEMAPHORE *semaphore_ptr, ULONG *puts, ULONG *gets, ULONG *suspensions, ULONG *timeouts); // 獲取全域信號量效能資訊 tx_semaphore_performance_system_info_get(ULONG *puts, ULONG *gets, ULONG *suspensions, ULONG *timeouts); ``` ### 抱死 Deadly Embrace 使用信號燈進行互相排斥時,最有趣且最危險的陷阱之一是抱死。 抱死或稱為死鎖(Deadlock),是指兩個或多個執行緒在嘗試取得彼此已擁有的信號燈時,陷入無限期掛起的情況。 範例說明 這種情況可以用兩個執行緒與兩個信號燈的範例來說明: 假設第一個執行緒擁有第一個信號燈,而第二個執行緒擁有第二個信號燈。 如果第一個執行緒嘗試取得第二個信號燈,同時第二個執行緒嘗試取得第一個信號燈,那麼兩個執行緒都將進入抱死狀態。 結果 兩個執行緒將無限期地保持掛起狀態。 與這些執行緒相關聯的資源也將被永久鎖定,無法釋放。 圖 8 展示了這一範例中的抱死情況。 ![image](https://hackmd.io/_uploads/SkuodKYE1l.png) 圖8. 掛起執行緒的範例 對於即時系統,可以透過對執行緒獲取訊號燈的方式設置一些限制來防止抱死。執行緒每次只能擁有一個訊號燈。 或者,如果執行緒按照相同的順序獲取多個訊號燈,則可以同時擁有這些訊號燈。在前面的範例中,如果第一個和第二個執行緒按照順序獲取第一個和第二個訊號燈,就可以防止抱死的發生。==也可以使用相關的掛起超時機制,從抱死狀態中恢復。== # 互斥鎖 Mutexes 除了信號燈,ThreadX 還提供互斥鎖(Mutex)。互斥鎖實際上是二進制信號燈,這意味著每次只有一個執行緒可以擁有互斥鎖。此外,同一執行緒可以多次成功獲取已經擁有的互斥鎖,具體來說最多可以執行 4,294,967,295 次的成功獲取操作。互斥鎖有兩種操作:tx_mutex_get 和 tx_mutex_put。 get:取得目前不被其他執行緒擁有的互斥鎖。 put:釋放之前獲取的互斥鎖。 針對執行緒要釋放的互斥鎖,執行的釋放操作次數必須等於之前的獲取操作次數。 每個互斥鎖都是一個共用資源。ThreadX 對於互斥鎖的使用方式沒有任何限制。 需要注意的是,ThreadX 的互斥鎖僅用於實現互斥功能。與計數信號燈不同,互斥鎖不能用作事件通知的方式。 ### 互斥體互相排斥 Mutex Mutual Exclusion 與「計數信號燈」部分的討論類似,互斥體用於控制執行緒對特定應用區域(也稱為關鍵區段或應用資源)的訪問。 當互斥體可用時,其所有權計數為 0。當執行緒獲取互斥體後,每成功執行一次 Get 操作,所有權計數會增加一次;每成功執行一次 Put 操作,所有權計數會減少一次。 #### Mutex特點與注意事項 優先級繼承: 防止優先級反轉,持有互斥鎖的執行緒可以暫時提升到與最高優先級掛起執行緒相同的優先級。 互斥鎖僅用於實現互斥功能,不能用作事件通知工具。 每次釋放互斥鎖的次數必須與之前的獲取次數相等,否則可能導致應用錯誤。 #### 基本操作: tx_mutex_get: 獲取互斥鎖,當互斥鎖被其他執行緒持有時,當前執行緒將進入掛起狀態。 tx_mutex_put: 釋放互斥鎖,釋放次數需與獲取次數相等。 相關 API: ```c= // 獲取互斥鎖 tx_mutex_get(TX_MUTEX *mutex_ptr, ULONG wait_option); // 釋放互斥鎖 tx_mutex_put(TX_MUTEX *mutex_ptr); ``` #### 互相排斥 (Mutual Exclusion) 互斥鎖用於控制執行緒對關鍵區域或共享資源的訪問。 互斥鎖的所有權計數: 初始值為 0。 每次獲取成功,計數增加 1;每次釋放成功,計數減少 1。 #### 創建與刪除互斥鎖 (Creating and Deleting Mutexes) 創建: 使用 tx_mutex_create 創建互斥鎖,可選擇是否啟用優先級繼承功能。 刪除: 使用 tx_mutex_delete 刪除互斥鎖,釋放相關資源。 ```c= // 建立互斥鎖 tx_mutex_create(TX_MUTEX *mutex_ptr, CHAR *name_ptr, UINT inherit); // 刪除互斥鎖 tx_mutex_delete(TX_MUTEX *mutex_ptr); ``` #### 執行緒掛起 (Thread Suspension) 當執行緒嘗試獲取已被其他執行緒持有的互斥鎖時,將進入掛起狀態。 持有互斥鎖的執行緒釋放後,掛起的執行緒將按 FIFO 順序依次恢復。 若啟用了 優先級繼承 或調用了 tx_mutex_prioritize,系統將優先恢復優先級最高的執行緒。 相關 API: ```c= // 調整互斥鎖的優先順序 tx_mutex_prioritize(TX_MUTEX *mutex_ptr); ``` # 事件標誌 Event Flags 事件標誌為執行緒同步提供了強大的工具。每個事件標誌由一個位元表示。事件標誌以 32 位一組的形式排列。執行緒可以同時對組中的所有 32 個事件標誌執行操作。事件由 tx_event_flags_set 設置,並由 tx_event_flags_get 檢索。 可透過在當前事件標誌與新事件標誌之間執行邏輯 AND/OR 運算來設置事件標誌。邏輯運算的類型(AND 或 OR)在 tx_event_flags_set 調用中指定。 可使用類似的邏輯選項來檢索事件標誌。檢索請求可以指定需要滿足所有指定的事件標誌(邏輯 AND)。檢索請求還可以指定只需滿足任一指定的事件標誌即可(邏輯 OR)。與事件標誌檢索相關的邏輯運算類型在 tx_event_flags_get 調用中指定。 ==如果在檢索請求中指定了 TX_OR_CLEAR 或 TX_AND_CLEAR,則滿足檢索請求的事件標誌會被清除(例如,設置為 0)。== ### 創建事件標誌組 Creating Event Flags Groups 事件標誌組可以由應用程式執行緒在初始化階段或執行期間創建。創建事件標誌組時,組中的所有事件標誌初始值均為零。應用程式中事件標誌組的數量沒有任何限制。 ### 執行緒掛起 Thread Suspension 當應用程式執行緒嘗試從組中獲取任意邏輯組合的事件標誌時,可能會被掛起。當事件標誌被設置後,系統會檢查所有掛起執行緒的獲取請求。所有目前包含所需事件標誌的執行緒都會被恢復執行。 ### 事件標誌設置通知 Event Flags Set Notification 某些應用程式可能會發現,在設置事件標誌時收到通知非常有用。ThreadX 通過 tx_event_flags_set_notify 服務提供此功能。該服務允許應用程式將通知函數註冊到指定的事件標誌組。 只要事件標誌組中的標誌被設置,ThreadX 就會調用該應用程式的通知函數。通知函數內的具體處理由應用程式決定,但通常包括恢復對應的執行緒以處理新的事件標誌。 # Block Pools ### 內存塊池 Memory Block Pools 在即時應用程式中,快速且確定地分配內存始終是一個挑戰。為此,ThreadX 提供了創建和管理多個固定大小內存塊池的功能。 由於內存塊池由固定大小的塊組成,因此不會出現碎片問題,而碎片可能導致本質上不確定的行為。此外,分配和釋放固定大小內存塊所需的時間與執行簡單鏈接列表操作的時間相當。另外,內存塊的分配和取消分配可以在可用列表的開頭完成,這提供了最快的鏈接列表處理速度,並有助於將實際的內存塊保存在緩存中。 固定大小內存池的主要缺點是缺乏靈活性。池中的塊大小必須足夠大,才能滿足其用戶在最壞情況下的內存需求。如果對同一個池發出了許多不同大小的內存請求,則可能會導致內存浪費。 一種可能的解決方案是創建多個不同的內存塊池,這些池包含不同大小的內存塊。 每個內存塊池都是一個公用資源。ThreadX 對內存池的使用方式沒有任何限制。 ### 創建內存塊池 Creating Memory Block Pools 內存塊池可以由應用程式執行緒在初始化期間或運行時創建。應用程式中內存塊池的數量沒有任何限制。 ### 內存塊大小 Memory Block Size 如前所述,內存塊池包含許多固定大小的區塊。區塊的大小(以字節為單位)在創建池時指定。 ### 執行緒掛起 Thread Suspension 當應用程式執行緒嘗試從內存塊池中獲取內存塊,而池中已無可用內存塊時,該執行緒可能會被掛起。當有內存塊釋放回池時,系統會將此內存塊分配給掛起的執行緒,並恢復該執行緒的執行。 如果同一內存塊池中有多個掛起的執行緒,這些執行緒將按照掛起順序(FIFO)恢復。 不過,如果應用程式在取消執行緒掛起的塊釋放操作之前調用了 tx_block_pool_prioritize,則可以改變恢復順序。塊池的優先級設置服務會將優先級最高的執行緒移動到掛起列表的最前面,而其他掛起的執行緒則仍然保持 FIFO 順序恢復。 ### 覆蓋記憶體區塊 Overwriting Memory Blocks 務必確保使用者在操作已分配的記憶體區塊時,不會在其邊界之外進行寫入。如果發生這種情況,將會破壞相鄰的記憶體區域(通常是後續區域)。其結果是不可預測的,且通常會對應用程式造成嚴重影響。 # Byte Pool ### 記憶體位元組池 Memory Byte Pools ThreadX 的記憶體位元組池類似於標準 C 的堆分配機制。但與標準 C 堆的不同之處在於,ThreadX 支援建立多個記憶體位元組池。此外,執行緒可以掛起於池中,等待所請求的記憶體變得可用。 每個記憶體位元組池都是一種共用資源。除了不能從中斷服務常式(ISR)呼叫記憶體位元組池服務之外,ThreadX 對於如何使用池並沒有任何限制。 ### 建立記憶體位元組池 Creating Memory Byte Pools 記憶體位元組池可以由應用程式執行緒在初始化階段或執行期間建立。應用程式中記憶體位元組池的數量沒有任何限制。 ### 池容量 Pool Capacity 記憶體位元組池中可分配的位元組數量會略小於建立時指定的位元組數量。這是因為可用記憶體區域的管理需要一些額外的開銷。 在執行期間,記憶體池的開銷可能會增加。如果分配的是奇數位元組數,系統會進行填充,以確保下一個記憶體區塊的正確對齊。此外,隨著池變得更加零碎,開銷也會隨之增加。 ### 執行緒掛起 Thread Suspension 在等待池中的記憶體位元組時,應用程式執行緒可能會被掛起。當有足夠的連續記憶體可用時,系統將為已掛起的執行緒提供其請求的記憶體,並恢復該執行緒。 如果同一記憶體位元組池中有多個執行緒掛起,系統將按照這些執行緒掛起的順序(FIFO)提供記憶體並恢復它們。 然而,如果應用程式在旗標觸發釋放字節取消執行緒掛起之前呼叫了 tx_byte_pool_prioritize,則可以進行優先順序的恢復。記憶體池的優先順序服務會將最高優先順序的執行緒移至掛起清單的最前端,而其他掛起的執行緒則繼續依照 FIFO 順序進行處理。 ### 非確定性行為 Nondeterministic Behavior 雖然記憶體位元組池提供了最靈活的記憶體分配,但這些池也可能受到一些非確定性行為的影響。例如,記憶體位元組池可能擁有 2,000 位元組的可用記憶體,但可能無法滿足 1,000 位元組的分配請求。這是因為無法保證有多少可用位元組是連續的。 即使池中存在一塊 1,000 位元組的可用記憶體,也無法保證找到該記憶體塊需要的時間。為了找到這塊 1,000 位元組的記憶體,可能需要搜尋整個記憶體池,這是一個完全可能發生的情況。 ### 覆蓋記憶體區塊 Overwriting Memory Blocks 務必確保已分配記憶體的使用者不會在其邊界之外進行寫入操作。如果發生這種情況,則會損壞其相鄰的記憶體區域(通常是後續的區域)。這種情況的結果無法預測,且對程式執行來說通常是災難性的。