Zephyr RTOS 兩種建立Thread的方式
Thread (執行緒)
在 Zephyr OS中,每個獨立的功能或任務可以放在不同的執行緒中執行,內部使用了輕量級的排程器,可以根據不同執行緒的優先級,以及執行緒本身的狀態(ready, running, pending 等),來決定哪一個執行緒能夠先被 CPU 執行,執行緒可以有以下幾個性質:
- 執行緒優先級(Priority):數值越小表示優先級越高(Preemptive scheduling 的情況下)。
- 堆疊大小(Stack Size):每個執行緒必須設定自己的堆疊空間,Zephyr 會在執行緒切換時儲存或恢復執行緒的上下文。
- 執行緒生命週期(Lifecycle):可以在編譯期就被預先配置好,或在執行期動態配置與建立。
Zephyr 建立執行緒的兩種常見方法
在編譯期間(Compile Time)建立執行緒
Zephyr 提供了一個巨集 K_THREAD_DEFINE()
用於在編譯期就宣告並建立執行緒。只要程式一開啟並初始化 RTOS 後,這些執行緒就會自動被建立並進入排程器的管理。
K_THREAD_DEFINE()
|
|
- name:此執行緒的識別名稱,同時會生成一個 k_tid_t 型態的變數。
- stack_size:此執行緒的堆疊大小(以 byte 為單位)。
- entry_fn:此執行緒進入點函式(thread function),開始執行時要跑哪個函式。
- p1, p2, p3:最多可傳入三個參數給進入點函式使用(都為 void* 型態)。
- prio:優先級(數值越小,優先級越高)。
- options:執行緒選項,通常先給 0 表示沒有特別的額外設定。
- delay:執行緒開始之前的延遲時間(ticks 或毫秒),若填 0 表示不延遲,立即就緒。
在執行期間(Run Time)建立執行緒
另一種是在程式運行期間建立執行緒,使用 API 函數k_thread_create()
建立執行緒。這種方法在有些情況下會更具彈性,例如依據狀態或條件,動態啟動或終止執行緒,以免靜態建立過多浪費資源。
k_thread_create()
|
|
- new_thread:指向使用者自行宣告的 struct k_thread 物件,用來儲存此執行緒的控制區塊。
- stack:指向此執行緒對應的堆疊空間(需先以
K_THREAD_STACK_DEFINE()
靜態或動態配置)。 - stack_size:此堆疊的大小。
- entry:執行緒執行的進入點函式。
- p1, p2, p3:最多可傳入三個參數到執行緒函式。
- prio:優先級。
- options:執行緒選項,常用為 0。
- delay:執行緒開始前是否要延遲。
範例
以下兩段程式碼,分別示範用 K_THREAD_DEFINE()
(在編譯期間)或 k_thread_create()
(在執行期間)建立兩個執行緒,分別執行 LED 漸亮漸暗(fade_led) 與 LED 閃爍(toggle_led) 的功能
github repo
以下是在Nucleo-F303K8的執行結果,左邊LED執行toggle,右邊執行fading
在編譯期間(Compile Time)動態建立執行緒範例
K_THREAD_DEFINE(fade_tid, 512, fade_led, NULL, NULL, NULL, 5, 0, 0);
- 建立一個名為 fade_tid 的執行緒,堆疊大小 512 bytes,執行函式為 fade_led,優先級 5。
- p1, p2, p3 都填 NULL,表示不需要傳遞參數。
K_THREAD_DEFINE(toggle_tid, 512, toggle_led, NULL, NULL, NULL, 5, 0, 0);
- 同樣建立名為 toggle_tid 的執行緒,stack大小、優先級參數與上面一致,執行函式為 toggle_led。
這些執行緒在編譯的時候就已經被「靜態配置」了,所以當系統啟動並執行到 main() 時,它們也就由排程器自動啟動並不斷執行。
|
|
在執行期(Run Time)動態建立執行緒範例
若想要在程式執行的過程中(例如:由某個事件觸發)再建立執行緒,可以使用以下的做法:
1. 用 K_THREAD_STACK_DEFINE(my_stack_area, STACK_SIZE)
; 定義stack。
2. 定義一個 struct k_thread my_thread_data; 作為執行緒控制區塊(Thread Control Block, TCB)。
3. 呼叫 k_thread_create(),傳入上述的stack、控制區塊、進入點函式及其他參數來完成建立。
以下是一個範例,展示如何在 main() 函式中動態建立執行緒(此示例僅顯示與執行緒有關的部分,省略週邊初始化等重複程式碼):
|
|
比較
建立方式 | 特點 | 典型使用時機 |
---|---|---|
Compile Time 建立 | K_THREAD_DEFINE() | 一開始已知需要的執行緒,以及系統資源充足時 |
Run Time 動態建立 | k_thread_create() ,在程式執行時期再分配資源 | 不確定需要多少執行緒,或需要彈性創建/釋放 |
- 如果確定在整個系統的生命週期中,執行緒數量固定,或是系統比較單純(例如一個簡單的多任務控制),用編譯期方式能減少程式碼的複雜度,也能讓程式一啟動就快速就緒。
- 如果系統中會動態產生/取消某些任務(如:偵測到新裝置才開新任務等),就可以使用動態建立的方式,提高系統彈性。
總結
- 靜態(編譯期)建立執行緒:
- 使用
K_THREAD_DEFINE()
- 簡單、快速、程式架構清晰
- 適用於固定且少數的長期執行緒
- 使用
- 動態(執行期)建立執行緒:
- 使用
k_thread_create()
- 需要自行管理堆疊、控制區塊等
- 適用於需要彈性創建和銷毀大量或不定數量執行緒的應用場景
- 使用