Zephyr RTOS -- Threads
文章目錄
- 前言
- Threads - (線程)
- 1. Lifecycle - (生命周期)
- 1.1 Thread Creation - (線程創建)
- 1.2 Thread Termination - (線程終止)
- 1.3 Thread Aborting - (線程中止)
- 1.4 Thread Suspension - (線程掛起/暫停)
- 2. Thread States - (線程狀態)
- 3. Thread Stack objects - (線程堆棧對象)
- 3.1 Kernel-only Stacks - (僅內核堆棧)
- 3.2 Thread stacks - (線程堆棧)
- 4. Thread Priorities - (線程優先級)
- 5. Thread Options - (線程選項)
- 6. Thread Custom Data - (線程自定義數據)
- 7. Implementation - (實現)
- 7.1 Spawning a Thread - (創建線程)
- 7.2 Dropping Permissions - (降低權限)
- 7.3 Terminating a Thread - (終止線程)
- 8. Runtime Statistics - (運行時統計信息)
- 9. Suggested Uses - (建議用途)
- 10. Configuration Options - (配置選項)
- 11. API Reference - (API 參考)
- 參考鏈接
本筆記基于 Zephyr 版本 2.6.0-rc2
?
前言
本人正在學習 Zephyr,一個可移植性較強,可以兼容多種開發板及物聯網設備的操作系統,如果你感興趣,可以點此查看我的 學習筆記總述 進行了解!
?
Threads - (線程)
線程是內核對象,用于太長或太復雜而無法由 ISR 執行的應用程序處理。
應用程序可以定義任意數量的線程 (僅受可用 RAM 限制),每個線程都由生成該線程時分配的線程 ID 引用。
線程具有以下的屬性:
- A stack area(堆棧區域),其是用于所述線程的堆棧的存儲器區域,可以調整堆棧區域的大小以符合線程處理的實際需求。存在特殊的宏來創建和使用堆棧存儲區。
- A thread control block(線程控制塊),用于線程的元數據的專用內核簿記。 這是類型 k_thread 的實例。
- An entry point function(入口函數),在線程啟動時調用。最多可以將 3 個參數值傳遞給此函數。
- A scheduling priority(調度優先級),指示內核的調度如何分配CPU時間線程。參考 Scheduling。
- A set of thread options(線程選項),允許線程在特定情況下接受內核的特殊處理。參考 Thread Options。
- A start delay(啟動延遲),它指定內核在啟動線程之前應等待的時間。
- An execution mode(執行模式),可以是超級用戶模式(supervisor mode)或用戶模式(user mode)。 默認情況下,線程在超級用戶模式下運行,并允許訪問特權 CPU 指令,整個內存地址空間和外圍設備。 用戶模式線程的特權減少了。 這取決于 CONFIG_USERSPACE 選項。 請參閱 User mode(用戶模式)。
?
1. Lifecycle - (生命周期)
1.1 Thread Creation - (線程創建)
線程在被使用之前必須先被創建,內核初始化線程控制塊以及堆棧部分的一端。 線程棧的其余部分通常未初始化。
可以指示內核通過指定超時值來延遲線程的執行,例如,允許線程使用的設備硬件變得可用。
指定啟動延遲 K_NO_WAIT 會指示內核立即啟動線程執行。
內核允許在線程開始執行之前取消延遲的啟動。但是如果線程已經啟動,則取消請求無效。延遲啟動成功取消的線程,必須重新生成后才能使用。
?
1.2 Thread Termination - (線程終止)
線程一旦啟動,通常將永遠執行。但是,線程可以通過從其入口點函數返回來同步結束其執行。這稱為終止。
終止線程負責在返回之前釋放其可能擁有的任何共享資源 (例如互斥體和動態分配的內存),因為內核不會自動回收它們。
在某些情況下,一個線程可能要休眠,直到另一個線程終止。可以使用 k_thread_join() API 來完成。這將阻塞調用線程,直到超時到期,目標線程自行退出或目標線程異常中止(由于 k_thread_abort() 調用或觸發致命錯誤)。
線程終止后,內核保證不會使用線程結構(k_thread)。 然后可以將這種結構的內存重新用于任何目的,包括產生新線程。
請注意,線程必須完全終止,這會在競爭狀態下出現競爭情況,在該競爭情況下,一個線程自己的邏輯會發出信號完成信號,該信號在內核處理完成之前會被另一個線程看到。 在正常情況下,應用程序代碼應使用 k_thread_join() 或 k_thread_abort() 來同步線程終止狀態,而不應依賴于來自應用程序邏輯內部的信號。
原文:
Once a thread has terminated, the kernel guarantees that no use will be made of the thread struct. The memory of such a struct can then be re-used for any purpose, including spawning a new thread. Note that the thread must be fully terminated, which presents race conditions where a thread’s own logic signals completion which is seen by another thread before the kernel processing is complete. Under normal circumstances, application code should use k_thread_join() or k_thread_abort() to synchronize on thread termination state and not rely on signaling from within application logic.
?
1.3 Thread Aborting - (線程中止)
線程可以通過 中止 異步結束其執行。如果線程觸發致命錯誤條件 (例如,取消引用空指針),內核會自動中止線程
線程也可以通過另一個線程 (或由其自身) 調用 k_thread_abort() 被中止。但是,通常最好是發信號通知線程適當地終止自身,而不是中止該線程。
和 線程終止 一樣,內核不會回收異常中止線程所擁有的共享資源
?
1.4 Thread Suspension - (線程掛起/暫停)
如果線程被掛起,可以無限期地阻止其執行。該函數 k_thread_suspend() 可用于掛起任何線程,包括已經調用的線程。一旦掛起,就無法調度線程,直到另一個線程調用 k_thread_resume() 以取消掛起。
掛起已被掛起的線程不會產生任何其他影響。
Note: 線程可以在指定的時間段內阻止自己執行,通過使用 k_sleep() 。 但是,這與掛起線程不同,因為達到時間限制時,休眠線程會自動執行。
?
2. Thread States - (線程狀態)
沒有任何妨礙其執行的因素的線程被視為準備就緒態 (ready),并且有資格被選擇為當前線程。
具有一個或多個阻止其執行的因素的線程被視為未就緒態 (unready),并且不能被選擇為當前線程。
以下的因素使一個線程處于 unready 狀態:
- 線程未被啟動
- 線程正在等待內核對象完成操作 (例如:該線程正在使用不可用的信號量)
- 線程正在等待超時發生
- 線程已被掛起
- 線程被終止或中止
線程狀態切換圖:
?
3. Thread Stack objects - (線程堆棧對象)
每個線程都需要自己的堆棧緩沖區,CPU 才能推送內容。根據配置,必須滿足幾個約束條件:
- 可能需要為內存管理結構保留額外的內存
- 如果啟用了基于保護的堆棧溢出檢測,則必須在堆棧緩沖區之前立即設置一個小的寫保護存儲器管理區域,以捕獲溢出。
- 如果啟用了用戶空間,則必須保留一個單獨的固定大小的特權提升堆棧,以用作處理系統調用的專用內核堆棧。
- 如果啟用了用戶空間,則線程的堆棧緩沖區的大小必須適當調整并對齊,以便可以對內存保護區域進行編程以使其完全適合。
對齊約束可能非常嚴格,例如,某些 MPU 要求其區域的大小為 2 的冪,并且要與其自身的大小對齊。
因此,可移植代碼無法簡單地將任意字符緩沖區傳遞給 k_thread_create()。存在特殊的宏以實例化堆棧,并以K_KERNEL_STACK 和前綴 K_THREAD_STACK。
?
3.1 Kernel-only Stacks - (僅內核堆棧)
如果已知線程永遠不會在用戶模式下運行,或者堆棧用于特殊內容 (例如處理中斷),則最好使用 K_KERNEL_STACK 宏定義堆棧。
這些堆棧可節省內存,因為無需對MPU區域進行編程即可覆蓋堆棧緩沖區本身,并且內核無需為特權提升堆棧或僅與用戶模式線程相關的內存管理數據結構保留額外的空間
如果 CONFIG_USERSPACE 未啟用,則 K_THREAD_STACK 宏的作用與 K_KERNEL_STACK 宏相同。
?
3.2 Thread stacks - (線程堆棧)
如果已知某個堆棧需要托管用戶線程,或者無法確定該線程,請使用 K_THREAD_STACK 宏定義該堆棧。這可能會使用更多的內存,但是堆棧對象適合于承載用戶線程。
如果 CONFIG_USERSPACE 未啟用,則 K_THREAD_STACK 宏的作用與 K_KERNEL_STACK 宏相同。
?
4. Thread Priorities - (線程優先級)
線程的優先級是整數值,可以為負或非負。數字上較低的優先級優先于數字上較高的值。
例如,調度程序為優先級為 4 的線程 A 高于優先級為 7 的線程 B 的優先級。
同樣,優先級為 -2 的線程 C 的優先級高于線程 A 和線程 B。
調度程序根據每個線程的優先級來區分兩類線程:
- 一個協作線程的優先級值為負。 一旦成為當前線程,協作線程將保留為當前線程,直到它執行操作使它變為未就緒態 (unready)。
- 一個可搶占線程具有非負的優先級值,一旦成為當前線程,如果協作線程或具有更高或同等優先級的可搶占線程準備就緒,則可隨時替換可搶占線程。
啟動線程后,可以向上或向下更改線程的初始優先級值。因此,通過更改優先級,可搶占線程有可能變為協作線程,反之亦然。
內核幾乎支持無限數量的線程優先級。配置選項,CONFIG_NUM_COOP_PRIORITIES 和 CONFIG_NUM_PREEMPT_PRIORITIES 指定每種線程類的優先級級別數,從而產生以下可用的優先級范圍:
- 協作線程:(-CONFIG_NUM_COOP_PRIORITIES) to -1
- 搶占線程:0 to (CONFIG_NUM_PREEMPT_PRIORITIES - 1)
?
5. Thread Options - (線程選項)
內核支持一小組線程選項,這些選項允許線程在特定情況下獲得特殊待遇。生成線程時,將指定與線程關聯的選項集。
不需要任何線程選項的線程的選項值為零。需要線程選項的線程通過名稱指定,如果需要多個選項,則使用 | 字符作為分隔符。(即使用按位或運算符組合選項)
支持的線程選項:
- K_ESSENTIAL:此選項將線程標記為必要線程(essential thread),這指示內核將線程的終止或中止視為致命的系統錯誤。默認情況下,該線程不被視為必不可少的線程。
- K_SSE_REGS:此特定于x86的選項表示線程使用CPU的SSE寄存器。 另請參閱 K_FP_REGS。默認情況下,內核在調度線程時不嘗試保存和恢復該寄存器的內容。
- K_FP_REGS:此選項表明線程使用 CPU 的浮點寄存器(CPU’s floating point registers)。這指示內核在調度線程時采取其他步驟來保存和恢復這些寄存器的內容。(有關更多信息,請參見浮點服務(Floating Point Services)) 默認情況下,內核在調度線程時不嘗試保存和恢復該寄存器的內容。
- K_USER:如果CONFIG_USERSPACE 啟用,則該線程將在用戶模式下創建,并具有減少的特權。請參閱用戶模式(User Mode)。否則,此標志不執行任何操作。
- K_INHERIT_PERMS:如果CONFIG_USERSPACE 啟用,則該線程將繼承父線程具有的所有內核對象許可權,但父線程對象除外。請參閱用戶模式(User Mode)
?
6. Thread Custom Data - (線程自定義數據)
每個線程都有一個 32 位的自定義數據區,只能由線程本身訪問,并且可以由應用程序出于其選擇的任何目的使用。線程的默認自定義數據值為零。
Note: 自定義數據支持不適用于 ISR,因為它們在單個共享的內核中斷處理上下文中運行。
默認情況下,線程自定義數據支持是禁用的。配置選項 CONFIG_THREAD_CUSTOM_DATA 可用于啟用支持。
k_thread_custom_data_set() 和 k_thread_custom_data_get() 函數分別用于寫入和讀出的線程的自定義數據。一個線程只能訪問自己的自定義數據,而不能訪問另一個線程的自定義數據。
以下代碼使用自定義數據功能來記錄每個線程調用特定例程的次數。
Note: 顯然,只有一個例程可以使用此技術,因為它壟斷了自定義數據功能的使用。
使用線程自定義數據,通過將自定義數據用作指向線程擁有的數據結構的指針,可以使例程訪問線程特定的信息。
?
7. Implementation - (實現)
7.1 Spawning a Thread - (創建線程)
通過定義線程的堆棧區域和線程控制塊,然后調用 k_thread_create() 來生成線程。
必須使用 K_THREAD_STACK_DEFINE 或 K_KERNEL_STACK_DEFINE 定義堆棧區域,以確保在內存中正確設置了堆棧區域。
堆棧的大小參數必須是以下三個值之一:
- 原始請求的堆棧大小傳遞給 K_THREAD_STACK 或 K_KERNEL_STACK 堆棧實例化宏系列。
- 對于使用 K_THREAD_STACK 宏系列定義的堆棧對象,該對象的返回值 K_THREAD_STACK_SIZEOF()。
- 對于使用 K_KERNEL_STACK 宏系列定義的堆棧對象,該對象的返回值 K_KERNEL_STACK_SIZEOF()。
線程產生函數返回其線程 ID,該 ID 可用于引用線程。
示例: 以下代碼產生一個立即啟動的線程
#define MY_STACK_SIZE 500 #define MY_PRIORITY 5extern void my_entry_point(void *, void *, void *);K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE); struct k_thread my_thread_data;k_tid_t my_tid = k_thread_create(&my_thread_data, my_stack_area,K_THREAD_STACK_SIZEOF(my_stack_area),my_entry_point,NULL, NULL, NULL,MY_PRIORITY, 0, K_NO_WAIT);或者,可以在編譯時通過調用 K_THREAD_DEFINE 來聲明線程。該宏會自動定義堆棧區域,控制塊和線程 ID 變量。上面的代碼可改為下面這種方式:
#define MY_STACK_SIZE 500 #define MY_PRIORITY 5extern void my_entry_point(void *, void *, void *);K_THREAD_DEFINE(my_tid, MY_STACK_SIZE,my_entry_point, NULL, NULL, NULL,MY_PRIORITY, 0, 0);Note: k_thread_create() 的延時參數是一個 k_timeout_t 值,因此 K_NO_WAIT 意味著立即啟動線程。K_THREAD_DEFINE 的對應參數是持續時間(以整數毫秒為單位),因此等效參數為 0。
?
User Mode Constraints - (用戶模式限制)
本部分僅在 CONFIG_USERSPACE 啟用后才適用,并且一個用戶線程嘗試創建一個新線程。該 k_thread_create() API 仍在使用,但必須滿足其他約束條件,否則調用線程將被終止:
- 調用線程必須在子線程和堆棧參數上均具有授予的權限;兩者都由內核作為內核對象進行跟蹤。
- 子線程和堆棧對象必須處于未初始化狀態,即當前未運行,并且未使用堆棧內存。
- 傳入的堆棧大小參數必須等于或小于聲明時的堆棧對象范圍。
- K_USER 必須使用該選項,因為用戶線程只能創建其他用戶線程。
- K_ESSENTIAL 該選項不得被使用,否則用戶線程可能不會被視為必要線程(essential threads)。
- 子線程的優先級必須是有效的優先級值,并且等于或小于父線程的優先級。
?
7.2 Dropping Permissions - (降低權限)
如果啟用了 CONFIG_USERSPACE,則在超級用戶模式下運行的線程可以使用 k_thread_user_mode_enter() API 向用戶模式進行單向轉換。它將重置線程的堆棧內存并將其清零。該線程將被標記為非必需(non-essential)。
?
7.3 Terminating a Thread - (終止線程)
線程通過從其入口點函數返回而終止自身。
以下代碼說明了線程可以終止的方式:
void my_entry_point(int unused1, int unused2, int unused3) {while (1) {...if (<some condition>) {return; /* thread terminates from mid-entry point function */}...}/* thread terminates at end of entry point function */ }如果啟用了 CONFIG_USERSPACE,則中止線程將另外將該線程和堆棧對象標記為未初始化,以便可以重新使用它們。
?
8. Runtime Statistics - (運行時統計信息)
如果 CONFIG_THREAD_RUNTIME_STATS 啟用,則可以收集和檢索線程運行時統計信息 ,例如,線程的執行周期總數。
默認情況下,使用默認內核計時器來收集運行時統計信息。對于某些體系結構,SoC 或開發板,有些計時器可以通過計時功能提供更高的分辨率。這些計時器的使用可以通過啟用 CONFIG_THREAD_RUNTIME_STATS_USE_TIMING_FUNCTIONS。
例如:
k_thread_runtime_stats_t rt_stats_thread;k_thread_runtime_stats_get(k_current_get(), &rt_stats_thread);printk("Cycles: %llu\n", rt_stats_thread.execution_cycles);?
9. Suggested Uses - (建議用途)
使用線程來處理 ISR 無法處理的進程。
使用單獨的線程來處理可以并行執行的邏輯上不同的處理操作。
?
10. Configuration Options - (配置選項)
相關配置選項:
- CONFIG_MAIN_THREAD_PRIORITY
- CONFIG_MAIN_STACK_SIZE
- CONFIG_IDLE_STACK_SIZE
- CONFIG_THREAD_CUSTOM_DATA
- CONFIG_NUM_COOP_PRIORITIES
- CONFIG_NUM_PREEMPT_PRIORITIES
- CONFIG_TIMESLICING
- CONFIG_TIMESLICE_SIZE
- CONFIG_TIMESLICE_PRIORITY
- CONFIG_USERSPACE
?
11. API Reference - (API 參考)
關于線程的一些 API 函數及宏
?
參考鏈接
https://docs.zephyrproject.org/latest/reference/kernel/threads/index.html
總結
以上是生活随笔為你收集整理的Zephyr RTOS -- Threads的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一企一技术研发中心 山东一企一技术
- 下一篇: 前管理员清空了荷兰托管商的所有数据