Zephyr_Thread

1. 線程概述

線程是操作系統能夠進行運算調度的最小單位. 它被包含在進程之中, 是進程的實際運作單位. 一條線程指的是進程中的一個單一順序的控制流, 一個進程可以併發多個線程, 每條線程並行執行不同的任務. 在多核或多 CPU, 或支持 Hyper-threading 的 CPU 上使用多線程程序設計的好處是顯而易見的, 即提高了程序的執行吞吐率. 在單個 CPU 單核的計算機上,使用多線程技術, 也可以吧進程中負責 I/O 處理, 人機交互而常被阻塞的部分與密集計算的部分分開來執行, 編寫專門的 workhorse 線程執行密集計算, 從而提高程序的執行效率.

分以下幾部分概述創建, 調度和刪除獨立可執行線程的內核服務

  1. 生命週期
  2. 調度機制
  3. 自定義數據
  4. 系統線程
  5. 工作隊列線程
  6. 配置選項
  7. API 參考

2. 生命週期(Lifecycle)

線程是用於應用程序處理的內核對象, 它太長或太複雜, ISR 無法執行.

2.1. 概念(Concepts)

一個應用程序可以創建任意多個線程. 每個線程由一個線程 id 引用, 該 id 在線程創建時分配.

線程有以下幾個關鍵屬性:

  • 棧空間 : 線程棧所需的一段內存空間. 堆棧的大小可根據線程處理實際需要進行調整. 存在用於創建和處理內存堆棧區域的特殊宏.
  • 線程控制塊 : 用於線程元數據的私有內核簿記(bookkeeping). 是結構體 struct k_thread 的一個實例.
  • 入口函數 : 線程啓動時調用的函數. 該函數最多可接受 3 個參數值.
  • 調度優先級 : 它指示內核調度程序如何分配 CPU 時間給線程.
  • 線程可選項 : 允許線程在特定的環境下接受內核的特殊處理.
  • 啓動延時 : 指定內核在啓動線程支付那個應該等待多長時間.
  • 執行模式 : 可以是 管理模式 或者 用戶模式. 默認情況下, 線程運行在管理模式下, 該模式下的線程可以訪問特權 CPU 指令, 整個內存地址空間和外設. 用戶模式下線程可訪問特權中的一部分. 取決於配置選項 CONFIG_USERSPACE.

2.2. 創建線程(Thread Creation)

線程必須創建之後才能使用. 內核初始化線程控制塊和堆棧部分的一端. 線程堆棧的剩餘部分通常未初始化.
啓動延時設置爲 K_NO_WAIT 時表明內核將立即啓動線程執行.  否則, 將設置一個超時時間以用於內核在超時時間到期時啓動執行線程. 例如, 允許線程使用的硬件可用時啓動線程.
內核允許在線程開始執行前取消延時啓動. 如果線程已經啓動了, 則取消請求是無效的. 已經成功取消延時啓動的線程必須重新創建才能使用. 

2.3. 終止線程(Thread Termination)

線程一旦啓動, 將永遠執行. 但是, 線程可以通過其入口函數返回來同步結束其執行. 稱之爲線程終止. 
終止的線程負責在返回之前使用它擁有的任何共享資源(如互斥鎖和動態分配的內存), 因爲內核不會自動回收它們.

Note : 內核目前沒有對應用程序重新創建終止線程的能力做出任何聲明.

2.4. 中止線程(Thread Aborting)

線程可以通過執行 aborting 異步結束. 如果線程觸發致命錯誤錯誤(如 : 引用空指針), 內核將自動中止該線程.
線程也可以被其它線程(或它自己)調用 k_thread_abort() 中止. 然而, 通常採用發信號給線程, 讓線程自己結束執行.
線程終止時, 內核不會自動回收該線程鎖擁有的共享資源.

Note : 內核目前沒有對應用程序重新創建中止線程的能力做出任何聲明.

2.5. 掛起線程(Thread Suspension)

如果線程被掛起, 它將在一段不確定的時間內暫停執行. 函數 k_thread_suspend() 用於掛起包括調用線程在內的任何線程, 對已經處於掛起的線程再次掛起時不會產生任何效果.
線程一旦掛起, 則不會被調度, 除非另一個線程調用函數 k_thread_resume() 取消掛起.

Note : 線程可以使用函數 k_sleep() 阻止其執行. 然而, 這不同於掛起線程, 因爲睡眠時間到了之後線程自動變爲可執行.

2.5. 線程的選項(Thread Options)

內核支持一小系列線程選項, 以允許線程在特殊情況下被特殊對待. 這些與線程相關聯的選項在線程創建時就被指定了.
不需要任何線程選項的線程的線程可選項的值爲 0. 如果線程需要可選項, 可通過名字指定, 使用 '|' 支持多個線程可選項. 

支持以下線程可選項 :

  • K_ESSENTIAL : 將線程標記爲必須線程(essential thread). 如果該線程終止或中止, 則內核認爲發生致命系統錯誤. 默認情況下, 線程不會被標記爲必須線程.
  • K_FP_REGS 和 K_SSE_REGS : 這兩個是 X86 相關的選項, 標記線程使用 CPUs 浮點寄存器和 SSE 寄存器. 在調度這樣的線程時, 內核執行額外的步驟保存和恢復這些寄存器的內容. 默認情況下,調度線程時, 內核不會保存和恢復這些寄存器的值.
  • K_USER : 如果 CONFIG_USERSPACE 使能, 線程在用戶模式下創建, 該標記的線程可訪問特權中的一部分. 參見 User Mode. 否則, 該 Flag 什麼也不做.
  • K_INHERIT_PERMS : 如果 CONFIG_USERSPACE 使能, 這個線程將繼承所有父線程所擁有的所有內核對象權限, 除父線程對象外.

2.6. 實現(Implementation)

2.6.1. 創建線程

線程是通過定義它自己的堆棧空間和線程控制塊, 然後再調用函數 k_thread_create() 創建的. 棧空間必須使用 K_THREAD_STACK_DEFINE 定義, 以確保在內存中正確的設置.
線程的創建函數返回線程 id, 該 id 用來引用線程.

以下代碼創建了立刻啓動的線程.

#define MY_STACK_SIZE  500
#define MY_PRIORITY     5   

extern 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     5

extern void my_entry_point(void *, void *, void *);

K_THREAD_DEFINE(my_tid, MY_STACK_SIZE,
                         my_entry_point, NULL, NULL, NULL,
                         MY_PRIORITY, 0, K_NO_WAIT);
2.6.1.1. 用戶模式的約束(User Mode Constraints)

僅在 CONFIG_USERSPACE 使能時, 並且一個用戶線程創建一個新的線程時, 該節才適用. API k_thread_create() 仍然可以使用, 但是有以下限制必須滿足, 否則調用線程將被終止.

  1. 調用線程必須對子線程和堆棧參數都具有授予權限(permissions granted); 兩者都由內核作爲內核對象跟蹤.
  2. 子線程和堆棧對象必須處於未初始化狀態, 如: 它當前沒有運行, 堆棧內存未使用.
  3. 堆棧大小參數必須等於或小於聲明時的堆棧對象的邊界.
  4. 線程可選項必須選擇 K_USER, 用戶線程僅能創建其它的用戶線程.
  5. 線程可選項必須不能選擇 K_ESSENTIAL, 用戶線程不能作爲必須線程.
  6. 子線程的優先級必須是一個有效的優先級, 等於或低於父優先級.
2.6.1.2. 刪除權限(Dropping Permissions)

如果 CONFIG_USERSPACE 使能時, 運行在 管理模式 的線程可以使用 k_thread_user_mode_enter() API 進入用戶模式. 這是一個單向的操作, 並且會復位和清零線程棧空間. 這個線程將被標記爲非必要的(non-essential).

2.6.2. 終止線程

線程可以通過入口函數返回來終止自己. 示例代碼如下:

void my_entry_point(int unused1, int unused2, int unused3) 
{
    while(1) {
        ...
        if (<some condition>) {
            return;
        }
        ... 
    }
}

如果 CONFIG_USERSPACE 使能時, 中止一個線程將額外的標記線程和堆棧對象作爲未初始化的, 以便可以重用他們.

使用建議

使用線程處理那些在 ISR 中不能處理的任務.
建議爲每個邏輯上有差異的任務創建單獨的線程, 讓它們並行執行.

3. 調度機制(Scheduling)

內核是基於優先級方式進行調度, 以便於允許應用程序線程共享 CPU.

3.1. 概述

調度是決定將要執行哪個線程; 被調度器選中的線程稱爲當前線程(current thread).
當調度器改變當前線程 id 時, 或當前線程執行被 ISR 替代時, 內核首先會保存當前線程 CPU 寄存器值. 在線程恢復運行時, 這些寄存器的將被恢復.

3.2. 線程狀態(Thread States)

如果一個線程沒有什麼因素阻礙其運行, 則該線程稱爲已就緒ready), 已就緒的線程可以被選擇爲當前線程.
如果一個線程有一個或多個因素阻礙其運行, 則該線程稱爲非就緒(unready), 並不能選擇爲當前線程.

以下因素會將使線程變爲非就緒:

  • 線程沒有啓動.
  • 線程在等待一個內核對象完成一個操作. (如: 線程正在使用一個不可用的信號量).
  • 線程正在等待超時發生.
  • 線程已經被掛起.
  • 線程已經被終止或中止.

3.3. 線程優先級(Thread Priorities)

線程優先級是個整數值, 可以是負數或非負數. 數字越小, 優先級越高. 例如: 線程 A 的優先級爲 4, 線程 B 的優先級爲 7, 則調度器認爲線程 A 的優先級高於線程 B; 同樣地, 如果線程 C 的優先級是 -2, 則它的優先級高於 A和 B.

調度器根據線程優先級, 將線程分爲以下兩類:

  1. 協作式線程(cooperative thread) : 線程的優先級爲負數, 一旦變爲當前線程, 它將一直執行下去, 直到它採取某種動作使其變爲非就緒.
  2. 可搶佔式線程(preemptible thread) : 線程的優先級爲非負數, 一旦變爲當前線程, 它可以在任何時刻被協作式線程或者優先級更高(或相等)的搶佔式線程替代. 搶佔式線程被替代後, 它依然是就緒的.

線程的初始優先級值可以在線程啓動後動態的增加或減小. 因此, 通過改變線程的優先級, 搶佔式線程可以變爲協作式線程, 反之亦然.

內核實際上可以支持無數個優先等級. 通過配置選項CONFIG_NUM_COOP_PRIORITIESCONFIG_NUM_PREEMPT_PRIORITIES 分別指定各類線程的優先級範圍.

  • 協作式線程(cooperative thread) : -CONFIG_NUM_COOP_PRIORITIES ~ -1
  • 可搶佔式線程(preemptible thread) : 0 ~ CONFIG_NUM_PREEMPT_PRIORITIES - 1

例如: 將協作式線程和可搶佔式線程的優先級數分別配置爲 5 和 10, 則協作式線程的優先級範圍爲(-5 ~ -1), 可搶佔式線程的優先級範圍爲(0 ~ 9).

3.4. 調度算法(Scheduling Algorithm)

內核調度器總是選擇優先級最高的線程作爲當前線程. 當存在多個相同優先級的就緒線程時, 調度器優先選擇等待時間最久的線程.

Note: ISRs 優先於線程, 因此 當前線程 可能會在任意時刻被非屏蔽的 ISR 替代. 這對 協作式線程可搶佔式線程 都成立.

3.5. 協作式時間片(Cooperative Time Slicing)

協作式線程一旦成爲了當前線程, 它將一直執行下去, 直到它採取的某種動作導致自己變爲非就緒線程. 這種方式其實有一個缺陷, 即如果協作式線程需要執行長時間的計算, 將導致包括優先級高於或等於該線程在內的其它所有線程的調度被延遲到一個不可接受的時間之後.

爲了解決這個問題, 協作式線程可以自身時不時地放棄 CPU, 讓其它線程得以執行. 線程放棄 CPU 的方法有以下兩種方式 :

  1. 調用 k_yield( ) 將線程放到調度器維護的按照優先級排列的就緒線程鏈表中, 然後調用調度器. 在該線程被再次調度前, 所有優先級高於或等於該線程的就緒線程都將得以執行. 如果不存在優先級更高或相等的線程, 調度器將不會進行上下文切換, 立即再次調度該線程.
  2. 調用 k_sleep( ) 讓該線程在一段指定時間內變爲非就緒線程. 所有優先級的就緒線程都可能得以執行;不過, 不能保證優先級低於該睡眠線程的其它線程都能在睡眠線程再次變爲就緒線程前執行完.

3.5. 可搶佔式時間片(Preemptive Time Slicing)

搶佔式線程成爲了當前線程後, 它將一直執行下去, 直到有更高優先級的線程變爲就緒線程, 或者線程自己執行了某種動作導致其變爲非就緒線程. 相應地, 如果搶佔式線程需要執行長時間的計算, 將導致包括優先級等於該線程在內的其它所有線程的調度被延遲到一個不可接受的時間之後. 

爲了解決這個問題, 可搶佔式線程可以執行協作式時間片(如上面所述)或者使用調度器的時間片功能, 讓優先級等於該線程的其它線程得以執行.

調度器將時間分割爲一系列的時間片. 時間片的單位是系統滴答時鐘. 時間片的大小是可配置的, 並且可以在程序運行期間修改.

在每個時間片結束時,調度器會檢查當前線程是否是可搶佔的, 如果是, 它將對該線程隱式地調用 k_yield(), 讓其它同優先級的就緒線程在該線程被再次調度前得以執行; 如果沒有同等優先級的就緒線程, 則當前線程繼續執行.

優先級高於指定範圍的線程不用實現搶佔式時間片, 且不能被同優先級的其它線程搶佔. 應用程序只有當處理優先級更低且對時間不敏感的線程時採用搶佔式時間片.

Note : 內核的時間片算法不能保證同等優先級的所有線程佔用的 CPU 時間完全相同, 因爲內核無法測量線程的實際執行時間. 例如, 某個線程可能在時間片快完的時候纔剛剛執行, 但是時間片到後會立即釋放 CPU. 可是, 該算法將確保線程的執行時間超過單個時間片的長度後釋放 CPU. 注: 釋放 CPU之後, 如果沒有更高優先級線程或同等優先級線程就緒, 則 CPU 不會進行上下文切換而再次執行該線程.

3.6. 調度器鎖(Scheduler Locking)

如果搶佔式線程希望在執行某個特殊的操作時不被搶佔, 它可以調用 k_sched_lock( ), 讓調度器將其臨時當做協作式線程, 從而避免被搶佔. 在執行關鍵操作時, 可以防止其他線程干擾.

一旦完成特殊操作, 該線程必須調用 k_sched_unlock( ), 以恢復其可搶佔特性.

如果線程調用了 k_sched_lock( ), 但是隨後執行了一個動作導致其非就緒, 調度器會將這個鎖定的線程切換出去, 以允許其它線程得以執行. 當鎖定的線程再次成爲 當前線程 後, 其不可搶佔狀態依然有效.

Note: 對於可搶佔線程來說, 鎖定調度器比將其優先級更改爲負值更有效地抑制搶佔.

3.7. 元-IRQ優先級(Meta-IRQ Priorities)

當使能 CONFIG_NUM_METAIRQ_PRIORITIES 時, 則在 協作式優先級 的最高優先級之上存在一個 協作優先級的子集優先級空間: 被稱爲 meta-IRQ 線程. 根據它們的優先級進行調度, 但是它們有個特殊的功能, 可以搶佔其它低優先級的線程(包括其它 meta-irq線程). 即使那些線程是協作式線程和加鎖調度器的線程.

這種行爲使得解除 Meta-IRQ 線程的行爲(通過任何方式,例如, 創建它, 調用 k_sem_give( ))等同於低優先級線程執行的同步系統調用, 或者從真正中斷上下文中執行類似ARM 的掛起中斷(ARM-like “pended IRQ”). 其目的是將此功能用於在驅動程序子系統中實現中斷"下文"處理和或“微線程(tasklet)”功能. 該線程一旦被喚醒, 將保證在當前CPU返回到應用程序代碼之前運行.

與其他操作系統中的類似功能不同, meta-IRQ 線程是真正的線程, 並且運行在它自己的堆棧空間, 而不是在每個 CPU 的中斷棧, 在支持的架構上啓用 IRQ 堆棧的設計工作正在進行中.

注意, 因爲這違背了 Zephyr API對協作線程的承諾(也就是說,除非當前線程故意阻塞,否則OS不會調度其他線程), 它應該只在應用程序代碼中使用. 這些線程不是簡單的高優先級線程, 不應該這樣使用.

3.8. 線程睡眠(Thread Sleeping)

線程可以調用 k_sleep( ) 讓其延遲一段指定的時間後再執行. 在線程睡眠的這段時間, CPU 被釋放給其它線程. 指定的時間到達後, 線程將變爲就緒狀態, 然後才能夠再次被調度.

正在睡眠的線程可以被其它線程使用 k_wakeup( ) 喚醒. 這種技術可以讓其它線程給該睡眠線程發送信號, 而不需要睡眠線程定義一個內核同步對象, 如: 信號量. 喚醒一個未睡眠的線程也是允許的, 沒有任何效果.

3.9. 忙等待(Busy Waiting)

線程可以調用 k_busy_wait( ) 執行一個 忙等待 操作. 所謂的忙等待, 指的是線程延遲一段指定的時間後再處理相關任務, 但是它並不會將 CPU 釋放給其它就緒線程.

使用 忙等待 而不使用 線程睡眠 的典型情況是: 由於所需要的延遲太短, 調度器來不及從當前線程切換到其它線程再切換回當前線程.

建議用法

建議在 設備驅動程序 和執行 實時性任務(performance-critical) 時使用協作式線程.

使用 協作線程 實現互斥, 而不需要 內核對象(如: 互斥對象).

使用 搶佔式線程時間更敏感 的處理比 時間不敏感 的處理先執行.

4. 自定義數據(Custom Data)

線程的自定義數據是一個 32 位的特定於線程的值, 應用程序可以將其用於任何目的.

4.1. 概述(Concepts)

每個線程都有一個 32-bit 的自定義數據區域. 自定義數據只能由線程自己訪問, 應用程序可以利用自定義數據實現任何目的. 線程自定義數據的默認值爲 0. 

Note : 在 ISR 中沒有自定義數據, 因爲 ISR 在一個單一的共享內核的中斷處理上下文.

4.2. 實現(Implementation)

4.2.1. 使用自定義數據(Using Custom Data)

默認情況下, 線程 自定義數據 的功能是關閉的。可通過配置選項CONFIG_THREAD_CUSTOM_DATA 用於使能 自定義數據.

函數 k_thread_custom_data_set( )  和 k_thread_custom_data_get( ) 分別用於寫, 讀線程的 自定義數據. 線程只能訪問它自己的 自定義數據, 不能訪問其它線程的 自定義數據.

Note : 當然,只有一個例程可以使用這種技術, 因爲它獨佔了自定義數據.

int call_tracking_routine(void)
{
    u32_t call_count;
    
    if ( k_is_in_isr( ) ) {
        /* ignore any call made by an ISR */
    } else {
        call_count = (u32_t)k_thread_custom_data_get( );
        call_count ++;
        k_thread_custom_data_set( (void *)call_count );
    
    }
    
    /* do rest of routine's processing */
    。。。
}
4.2.2. 建議用法(Suggested Uses)
使用線程自定義數據訪問指定線程信息時, 最好將自定義數據作爲一個指針指向線程自己的某個數據結構體.

5. 系統線程(System Threads)

系統線程是在系統初始化時由內核自動創建的一個線程.

5.1. 概述(Concepts)

內核會創建以下的系統線程.

5.1.1. 主線程(Main thread)

主線程先執行內核初始化, 然後調用應用程序 main( ) 函數(如果存在).

默認情況下, 主線程的優先級是 0, 即優先級最高的 可搶佔式線程. 如果所配置的內核不支持 可搶佔式線程, 則其優先級是 -1, 即優先級最低的 協作式線程.

主線程在執行內核初始化或執行應用程序的 main( ) 函數時是 必須線程(essential thread). 這意味着,如果該線程異常終止, 則內核會認爲產生了一個致命錯誤. 如果應用程序沒有定義 main( ) 函數, 或者它執行後正常返回, 主線程也就結束了, 此時不會拋出錯誤.

5.1.2. 空閒線程(Idle thread)

當系統沒有其它工作需要執行時, 就會執行 空閒線程. 如果可能, 會在 空閒線程 中激活板子的電源管理功能, 以達到省電的目的; 否則, 該線程簡單地執行一個 “do nothing” 的循環. 空閒線程 永遠不會結束. 只要系統一直在運行, 它就一直存在.

空閒線程 的優先級是系統所配置的最低優先級. 如果它是 協作式線程, 它會不斷地釋放 CPU, 以使應用程序線程需要運行時能順利運行.

空閒線程 也是 必須線程, 因此如果它被異常終止了也會致命的系統錯誤.

內核也可能會創建額外的 系統線程, 這依賴於應用程序指定的 開發板配置選項. 例如, 使能 系統工作隊列 將創建一個 系統線程, 該線程負責爲提交給它的 物件(items) 提供服務(參考工作隊列線程).

5.2. 實現(Implementation)

5.2.1. 寫一個 main( ) 函數

內核初始化執行完成之後, 開始執行應用提供的 main( ) 函數. 內核不會向該函數傳遞任何參數.

下面這幾行代碼總結了 main( ) 函數的一般寫法. 在實際應用中, 該函數可以被設計得更加複雜.

void main(void)
{
    /* initialize a semaphore */
    。。。
    /* register an ISR that gives the semaphore */
    。。。
    
    /* monitor the semaphore forever */
    while(1) {
        /* wait for the semaphore to be given by the ISR */
        。。。
        /* do whatever processing is now needed */
        。。。
    }
}

5.2.2. 建議用法(Suggested Uses)

如果應用程序只需要一個線程就能完成, 不要再定義額外的線程, 直接使用主線程就可以了.

6. 工作隊列線程(Workqueue Threads)

工作隊列(workqueue) 是一個內核對象, 它使用專用的線程以 先入先出 方式處理 工作項(work item). 每個工作項由它所指定的函數進行處理. 工作隊列通常用於 ISR 或者高優先級線程將非緊急任務移交給低優先級線程處理, 從而不會影響時間敏感的處理任務.

6.1. 概述(Concepts)

支持定義任意數量的 工作隊列, 每個工作隊列通過它自己的內存地址進行引用.

一個工作隊列有以下關鍵屬性:

  • 隊列(queue) : 已經被添加, 但還沒處理的工作項.
  • 線程(thread) : 處理隊列中的工作項. 線程的優先級是可配的, 既可以是協作式或搶佔式.

工作隊列必須先初始化才能使用. 初始化時會先清空隊列, 並創建一個工作隊列線程.

6.2. 工作項生命週期(Work Item Lifecycle)

支持定義任意數量的 工作項(work items), 每個工作項通過它自己的內存地址進行引用.

一個工作項有以下關鍵屬性:

  • 處理函數(handler function) : 當工作項被處理時, 工作隊列線程將執行該函數. 該函數接受一個參數, 參數爲該工作項自身的地址.
  • 掛起標誌(pending flag) : 內核使用該標誌表示當前工作項是否是一個工作隊列中一個成員.
  • 隊列鏈接(queue link) : 內核使用該鏈接將其鏈接到工作隊列中的下一個工作項.

工作項必須先初始化才能使用. 初始化時會記錄該工作項的處理函數, 並將其標記爲非掛起.

ISR 或線程可以將某個工作項提交到某個工作隊列中. 提交工作項時, 會將其追加到工作隊列的隊列中去. 當工作隊列的線程處理完它隊列裏面的所有工作項後, 該線程會移除一個掛起工作項, 並調用該工作項的處理函數. 一個掛起的工作項可能很快就會被處理, 也可能會在隊列中保留一段時間, 這依賴於工作隊列線程的調度優先級和隊列中其它項的工作需求.

處理函數 可以利用任何線程可用的內核 API. 不過, 使用可能引起阻塞的操作(如: 獲取一個信號量)時一定要當心, 因爲工作隊列在它的上一個處理函數完成前不能處理其隊列中的其它工作項.

如果處理函數不需要參數, 可以將傳入的參數直接忽略. 如果處理函數需要額外的信息, 可以將工作項內嵌到一個更大的數據結構當中. 處理函數可以使用這個參數值計算封裝後的地址, 以此訪問額外的信息.

一個工作項通常會被初始化一次, 然後當它需要執行時會被提交到工作隊列中. 如果 ISR 或者線程嘗試提交一個已經掛起的工作項, 不會有任何效果; 提交後, 工作項會停留在工作隊列中的當前位置, 且只會被執行一次.

處理函數可以將工作項重新提交到工作隊列中,因爲此時工作項已經不再是掛起狀態. 這樣做的好處是, 處理函數可以分階段執行工作, 而不會導致延遲處理工作隊列中的其它工作項.

Note : 一個掛起的工作項在被工作隊列線程處理前不能被改變. 這意味着, 當工作項處於掛起狀態時,它不能被再次初始化. 此外, 在處理函數執行完成前, 處理函數需要的額外信息也不能被改變.

6.3. 延遲工作(Delayed Work)

ISR 或線程可能需要延遲一段指定的事時間後(而不是立即)再調度一個工作項. 通過向工作隊列中提交一個延遲的工作項(delayed work item) (而不是標準工作項)就能達到此目的.

延遲工作項比標準工作項新增瞭如下屬性 :

  • 延遲時間 : 指明需要延遲多久纔將工作項提交到工作隊列中.
  • 工作隊列指示器 : 用於標識工作項所要提交到的工作隊列.

延遲工作項的初始化和提交過程與標準的工作項是類似的, 只是所使用的內核 API 不同. 當發出提交請求時, 內核會初始化一個超時機制, 當指定的延遲到期時就會觸發它. 當超時發生時, 內核會將延遲工作項提交到指定的工作隊列中. 此後, 它會保持掛起狀態, 直到以標準方式處理.

ISR 或線程可以 取消 它已經提交的延遲工作項, 但前提是該工作項的超時還未到期. 取消後, 工作項的超時將被中止並且指定的工作也不會被執行.

取消已經到期的延時遲工作項沒有任何效果; 除非工作項被工作隊列的線程處理並移除了, 否擇它將一直保持掛起狀態. 因此, 當工作項的超時到期後, 工作項總是由工作隊列處理了, 並不能被取消.

6.4. 系統工作隊列(System Workqueue)

內核定義了一個叫作 系統工作隊列 的工作隊列. 所有的應用程序或內核代碼都可以使用該工作隊列. 系統工作隊列是可選的, 且只有當應用程序使用時才存在.

Note: 只有當無法向系統工作隊列提交新的工作項時, 纔去創建額外的工作隊列. 因爲每個新的工作隊列都會佔用很可觀的內存. 如果新工作隊列中的工作項無法與系統工作隊列中已存在的工作項共存時, 可以調整新的工作隊列. 例如,新的工作項執行了阻塞操作會導致其它系統工作隊列被延遲到一個不可接受的程度.

6.5. 實現(Implementation)

6.5.1. 定義一個工作隊列(Defining a Workqueue)

使用類型爲 struct k_work_q 結構體變量可以定義一個工作隊列. 初始化工作隊列時, 需要它自身的線程定義一個棧區, 然後調用函數 k_work_q_start( ). 堆棧必須使用 K_THREAD_STACK_DEFINE 定義, 確保堆棧在內存中地址對齊.

以下代碼定義和初始化了一個工作隊列.

#define MY_STACK_SIZE   512
#define MY_PRIORITY     5

K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE);

struct k_work_q my_work_q;

k_work_q_start(&my_work_q, my_stack_area, 
                    K_THREAD_STACK_SIZEOF(my_stack_area), MY_PRIORITY);

6.5.2. 提交一個工作項(Submitting a Work Item)

使用類型爲 struct k_work 結構體變量可以定義一個工作項. 必須調用 k_work_init( ) 進行初始化.

初始化的工作項可以通過調用 k_work_submit( ) 將其提交到 系統工作隊列 中去. 或者調用 k_work_submit_to_queue( ) 將其提交到 指定的工作隊列 中去.

下面的代碼展示了 ISR 是如何將打印錯誤消息移交給系統工作隊列的過程. 注意, 如果 ISR 重新提交了一個還處於掛起狀態的工作項, 該工作項將不會更改, 且關聯的錯誤消息不會被打印.

struct device_info {
    struct k_work work;
    char name[16];
}my_device;

void my_isr(void *arg)
{
    。。。
    if (error detected) {
        k_work_submit(&my_device.work);
    }
    。。。
}

void print_error(struct k_work *item)
{
    struct device_info *the_device = CONTAINER_OF(item, struct device_info, work);
    printk("Got error on device %s\n", the_device->name);
}

/* initialize name info for a device */
strcpy(my_device.name, "FOO_dev");

/* initialize work item for printing device's error messages */
k_work_init(&my_device.work, print_error);

/* install my_isr( ) as interrupt handler for the device (not shown) */
。。。

6.5.3. 提交一個延遲工作項(Submitting a Delayed Work Item)

使用類型爲 struct k_delayed_work 結構體變量可以定義一個延遲工作項. 必須調用 k_delayed_work_init( ) 進行初始化.

初始化的延遲工作項可以通過調用 k_delayed_work_submit( ) 將其提交到 系統工作隊列 中去. 或者調用 k_delayed_work_submit_to_queue( ) 將其提交到 指定的工作隊列 中去. 可以通過調用 k_delayed_work_cancel( ) 取消一個已經被提交到工作隊列但還沒被處理的延遲工作項.

6.5.4.建議用法(Suggested Uses)

使用系統工作隊列將複雜的中斷相關處理從ISR延遲到協作線程. 這樣的好處是不需要犧牲系統的性能就能響應隨後的中斷, 且不需要應用程序定義額外的線程進行處理.

7. 配置選項(Configuration Options)

相關的配置選項如下:

  • CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE
  • CONFIG_SYSTEM_WORKQUEUE_PRIORITY
  • 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

8. API 參考(API Reference)

8.1. 定義(Defines)

  • K_ESSENTIAL : 系統線程, 不能中止.
  • K_USER : 用戶模式線程
    這個線程已經從管理模式轉到用戶模式, 因此有額外的限制.
  • K_INHERIT_PERMS : 繼承權限
    如果 CONFIG_USERSPACE 使能時, 指示正在創建的線程應該從創建它的線程繼承所有內核對象權限. 反之則沒有任何影響.
  • k_thread_access_grant(thread, …) : 允許線程訪問一系列內核對象
    這是一個便捷的函數. 對於提供的線程, 允許訪問其餘參數, 這些參數必須是指向內核對象的指針.
    線程對象必須初始化過(如: 正在運行). NULL 不應該作爲參數傳遞.
    參數:
    1. thread: 允許訪問的線程對象.
    2. … : 內核對象指針列表.
  • K_THREAD_DEFINE(name, stack_size, entry, p1, p2, p3, prio, options, delay) :靜態定義並初始化一個線程.
    調度器可能會立刻執行或者延遲一段時間執行該線程.
    可以使用 “extern const k_tid_t ;” 訪問線程 id.
    參數:
    1. name : 線程的名稱.
    2. stack_size : 堆棧大小(單位: 字節).
    3. entry : 線程入口函數.
    4. p1,p2,p3 : 入口函數的參數.
    5. prio : 線程優先級.
    6. options : 線程可選項(用於指定線程的分類, 如K_ESSENTIAL, K_FP_REGS …)
    7. delay : 調度的延遲時間(單位 : 毫秒), 或者 K_NO_WAIT(無延遲).
  • Z_WORK_INITIALIZER(work_handler)
  • K_WORK_INITIALIZER
  • K_WORK_DEFINE(work, work_handler) : 初始化一個靜態定義的工作項.
    該宏常用於在首次使用前初始化一個靜態定義的工作隊列工作項. 如 : static K_WORK_DEFINE(, <work_handler>);
    參數:
    1. work : 工作項對象的名稱.
    2. work_handler : 處理工作項時調用的處理函數.

8.2. 重定義(Typedefs)

  • typedef void (*k_thread_user_cb_t)(const struct k_thread *thread, void *user_data)

  • typedef k_work_handler_t : 工作項處理函數類型.
    當工作項被工作隊列處理時, 工作隊列線程執行工作項的處理函數.
    返回值:
    N/A
    參數:
    work : 工作項的地址.

8.3. 函數(Functions)

  • k_thread_foreach(k_thread_user_cb_t user_cb, void *user_data) : 遍歷系統中的所有線程.
    該例程遍歷系統中的所有線程, 並未每個線程調用 user_cb 函數.
    Note: 必須設置 CONFIG_THREAD_MONITOR 後, 該函數纔有效. 使用 irq_lock 保護 _kernel.threads 列表, 這意味着新線程的創建和現有線程的終止將被阻塞, 直到該 API 返回爲止.
    返回值:
    N/A
    參數:

    1. user_cb: 用戶回調函數指針.
    2. user_data: 用戶數據指針.
  • k_tid_t k_thread_create(struct k_thread *new_thread, k_thread_stack_t * stack, size_t stack_size, k_thread_entry_t entry, void *p1, void *p2, void *p3, int prio, u32_t options, s32_t delay) : 創建一個線程.
    就以往來看, 用戶通常會使用堆棧區域的開始部分來存放 struct k_thread 結構體, 儘管堆棧溢出時, 該區域會發生損壞, 堆棧保護功能可能無法檢測到該情況.
    返回值:
    線程的 id.
    參數:

    1. new_thread: 指向未初始化的 struct k_thread 結構體.
    2. stack: 堆棧指針.
    3. stack_size: 堆棧大小(單位: 字節).
    4. entry: 線程入口函數.
    5. p1,p2,p3: 入口函數參數.
    6. prio: 線程優先級.
    7. options: 線程可選項.
    8. delay: 延遲時間(單位:毫秒)
  • FUNC_NORETURN void k_thread_user_mode_enter(k_thread_entry_t entry, void *p1, void *p2, void *p3) : 將線程的特權永久的轉換到用戶模式.
    參數:

    1. entry: 執行的函數.
    2. p1,p2,p3: 函數的參數.
  • static void k_thread_resource_pool_assign(struct k_thread *thread, struct k_mem_pool *pool) :給線程分配一個內存池.
    默認情況下, 不會給線程分配內存池, 除非它們的父線程有個內存池, 線程將繼承該內存池. 多個線程可能被分配相同的內存池.
    更改一個線程的內存池, 將不會從之前的內存池遷移.
    參數:

    1. thread: 爲資源請求分配一個內存池的目標線程, 爲 NULL時, 則線程不在擁有內存池.
    2. pool: 用於資源的內存池.
  • s32_t k_sleep(s32_t duration) : 設置當前線程進入睡眠. 時間爲毫秒.
    返回值:
    如果請求的事件已經到期或者已經過期, 或者線程通過調用 k_wakeup 喚醒, 則返回值爲 0.
    參數:
    duration: 睡眠持續時間, 單位: 毫秒.

  • void k_busy_wait(u32_t usec_to_wait) : 使當前的線程處於忙等待狀態.
    這個例程將會使當前線程空轉 usec_to_wait 毫秒.

  • void k_yield(void) : 該例程導致當前線程將執行任務交給具有相同或更高優先級的另一個線程執行, 如果沒有其它相同或更高優先級的就緒線程, 則該函數立即返回.

  • void k_wakeup(k_tid_t thread) : 喚醒一個睡眠的線程. 如果線程當前沒有睡眠, 則不起任何作用.
    參數:
    thread: 需要喚醒的線程的 id.

  • k_tid_t k_current_get(void) : 獲取當前線程的 id.
    返回值:
    當前線程的 id.

  • void k_thread_abort(k_tid_t thread) : 中止一個線程.
    用於停止一個執行的線程. 這個線程將從所有的內核隊列(如: 就緒隊列, 超時隊列, 或內核對象等待隊列)中刪除. 然而, 該線程所擁有的內核資源(如: 互斥鎖或內存塊)不會被釋放. 需要調用者進行必要的清除.
    參數:
    thread: 被終止的線程的 id.

  • void k_thread_start(k_tid_t thread) : 啓動一個非活動線程.
    如果一個線程使用 線程可選項 K_FOREVER 創建. 它將不會被添加到調度隊列中, 直到調用該函數.
    參數:
    thread: 要啓動的線程的 id.

  • int k_thread_priority_get(k_tid_t thread) : 獲取線程的優先級.
    參數:
    thread: 需要獲取優先級的線程的 id.

  • void k_thread_priority_set(k_tid_thread, int prio) : 設置線程的優先級.
    可以立刻修改線程的優先級.
    根據優先級的設置, 調度器立刻重新調度:

    1. 如果它自己的優先級高於函數調用者的優先級, 並且調用者時可搶佔的, 則線程將被調度.
    2. 如果調用者是對自己進行操作, 它將優先級降低到低於系統中其他線程的優先級, 並且調用者(線程它自己)是可搶佔的, 那麼最高優先級的線程將被調度.

    優先級的取值範圍(-CONFIG_NUM_COOP_PRIORITIES ~ CONFIG_NUM_PREEMPT_PRIORITIES-1), -CONFIG_NUM_COOP_PRIORITIES 爲最高優先級.

    注意: 改變當前在互斥鎖優先級繼承中涉及的線程的優先級可能會導致未定義的行爲.
    參數:
    thread: 需要設置優先級的線程的 id.
    prio: 新的優先級.

  • void k_thread_deadline_set(k_tid_t, int deadline) : 爲調度器設置死線.
    deadline 設置爲當前時間的增量, 單位與 使用 k_cycle_get_32( ) 一致. 調度器(當啓用死線調度時)在具有相同靜態優先級的線程間選擇時, 將選擇下一個即將到期的線程. 不同優先級的線程將依據它們自己的靜態優先級進行調度.

    Note: 即使線程已經"完成"了它的工作, 但是死線爲負數(如: 已過期)則仍被認爲比其他線程優先級更高. 如果不在對它進行調度, 你不許將 deadline 重新設置爲將來的某個時間, 阻塞/掛起線程, 或者通過 k_thread_priority_set( ) 函數修改它的優先級.

    Note: Despite the API naming, the scheduler makes no guarantees the the thread WILL be scheduled within that deadline, nor does it take extra metadata (like e.g. the “runtime” and “period” parameters in Linux sched_setattr()) that allows the kernel to validate the scheduling for achievability. Such features could be implemented above this call, which is simply input to the priority selection logic.
    參數:

    1. thread: 設置死線的線程的 id.
    2. deadline: 一個時間增量, 單位爲 cycle units
  • void k_thread_suspend(k_tid_t thread) : 掛起一個線程.
    這個例程阻止內核調度程序將線程設置爲當前線程. 線程上的所有其他內部操作仍在執行; 例如, 它所等待的任何超時都會持續滴答, 它所等待的內核對象仍然會傳遞給它, 等等.
    如果線程已經被掛起, 操作沒有任何影響.

  • void k_thread_resume(k_tid_t thread) : 恢復一個掛起的線程.
    這個例程允許內核調度程序在線程下一次符合該角色的條件時將線程設置爲當前線程.
    如果線程沒有被掛起, 操作沒有任何影響.

  • void k_sched_time_slice_set(s32_t slice, int prio) : 設置時間片週期和範圍.
    指定調度程序將如何執行可搶佔線程的時間片.

    要使用時間片, 則 slice 不能爲 0. 調度程序確保在賦予具有該優先級的其他線程執行機會之前, 沒有線程運行超過指定的時間限制. 優先級高於 prio 的任何線程都可以被豁免, 並且可以執行任意長度的線程, 而不會因爲時間片而被搶佔.

    時間片只限制線程可以連續執行的最大時間量, 一旦調度器選擇要執行的線程, 在存在更高或相同優先級的線程調度前, 沒有最小的線程執行時間保證.
    噹噹前線程是該優先級中唯一有資格執行的線程時, 該例程沒有效果; 線程在時間片到期後立即重新被調度.

    通過設置 sliceprio 都爲 0, 則可以關閉時間片.

    參數:

    1. slice: 時間片的最大長度(單位: 毫秒)
    2. prio: 具有時間片資格的最高優先級.
  • void k_sched_lock(void) : 鎖調度器.
    這個例程通過調度器將當前線程作爲一個協作線程來防止當前線程被另一個線程搶佔. 如果線程隨後執行了使其變爲未就緒的操作. 則以正常方式切換上下文. 當線程再次成爲當前線程時, 將保持其不可搶佔狀態.

    該例程可以進行遞歸調用.

    Note: 當 ISRs 可以安全地中斷正在執行的操作時, 通常應使用 k_sched_lock( ) 和 k_sched_unlock( ). 但是, 如果操作量非常小, 那麼使用 irq_lock( ) 和 irq_unlock( ) 可以獲得更好的性能.

  • void k_sched_unlock(void) : 解鎖調度器.
    這個例程逆轉之前調用 k_sched_lock( ) 的效果. 線程每次調用 k_sched_lock( ) 時必須調用 k_sched_unlock( ) 一次, 然後纔可以搶佔該線程.

  • void k_thread_custom_data_set(void *value) : 設置當前線程自定義數據.
    自定義數據不是由內核本身使用的, 線程可以自由地使用它, 它可以用作構建線程本地存儲的框架.
    參數:
    value: 新自定義數據值.

  • void * k_thread_custom_data_get(void) : 獲取當前線程的自定義數據.
    返回值:
    當前線程的自定義數據值.

  • void k_thread_name_set(k_tid_t thread_id, char * value ) : 設置當前線程的名稱.
    設置啓用 THREAD_MONITOR 進行跟蹤和調試時要使用的線程的名稱.

  • const char * k_thread_name_get(k_tid_t thread_id) : 獲取線程名字.

工作隊列相關接口函數

  • static void k_work_init(struct k_work * work, k_work_handler_t handler ) : 初始化一個工作項.
    此例程在首次使用工作隊列工作項之前初始化該工作隊列工作項.
    參數:

    1. work: 工作項的地址.
    2. handler: 在工作項執行時調用的處理函數.
  • static void k_work_submit_to_queue(struct k_work_q * work_q, struct k_work * work) : 提交工作項.
    這個例程提交工作項到工作隊列 work_q 進行排隊處理, 如果由於較早的提交, 工作項已經掛起在工作隊列的隊列中, 則此例程對工作項沒有影響. 如果工作項已經被處理, 或者當前正在處理, 則認爲其工作已經完成. 可以重新提交工作項.

    警告: 提交的工作項必須在工作隊列處理後才能修改.
    Note: 可以被 ISRs 調用.
    參數:

    1. work_q: 工作隊列的地址.
    2. work: 工作項的地址.
  • static int k_work_submit_to_user_queue(struct k_work_q * work_q, struct k_work * work) : 提供一個工作項到用戶模式的工作隊列中去.
    將工作項提交給以用戶模式運行的工作隊列. 從調用者的資源池中進行臨時內存分配, 當工作線程使用 k_work 工作項時, 資源池將被釋放. 工作隊列線程必須具有對正在提交的 k_work 工作項的內存訪問權. 調用者必須具有對 work_q 參數的隊列對象授予的權限.

    另外, 它的工作原理與 k_work_submit_to_queue( ) 相同.
    Note: 可以被 ISRs 調用.
    參數:

    1. work_q: 工作隊列的地址.
    2. work: 工作項的地址.
      返回值:
    3. -EBUSY: 如果工作項已經在其他一些工作隊列中.
    4. -ENOMEM: 如果資源池沒有資源爲線程分配.
    5. 0: 成功.
  • static bool k_work_pending(struct k_work * work) : 檢查工作項是否被掛起.
    此例程指示工作項是否在工作隊列的隊列中掛起.
    Note: 可以被 ISRs 調用.
    返回值:

    1. true: 工作項被掛起.
    2. false: 工作項沒被掛起.
      參數:
      work: 工作項的地址.
  • void k_work_q_start(struct k_work_q * work_q, k_thread_stack_t * stack, size_t stack_size, int prio) : 開啓一個工作隊列.
    這個例程啓動工作隊列 work_q. 工作隊列生成其工作處理線程, 該線程將永遠運行.
    參數:

    1. work_q: 工作隊列的地址.
    2. stack: 工作隊列線程棧空間指針, 由 K_THREAD_STACK_DEFINE( ).
    3. stack_size: 工作隊列線程棧的大小(單位: 字節), 它是傳遞給 K_THREAD_STACK_DEFINE( ) 相同的常量, 或是 K_THREAD_STACK_SIZEOF( ).
    4. prio: 工作隊列線程的優先級.
  • void k_work_q_user_start(struct k_work_q * work_q, k_thread_stack_t * stack, size_t stack_size, int prio) : 開啓用戶模式下的一個工作隊列.
    這與 k_work_q_start( ) 的工作方式相同, 只是它可以從用戶模式調用. 並且創建的工作線程將在用戶模式下運行. 調用者必須對工作隊列 work_q 參數的線程和隊列對象都授予權限, 並且對優先級應用與k_thread_create( ) 相同的限制.
    參數:

    1. work_q: 工作隊列的地址.
    2. stack: 工作隊列線程棧空間指針, 由 K_THREAD_STACK_DEFINE( ).
    3. stack_size: 工作隊列線程棧的大小(單位: 字節), 它是傳遞給 K_THREAD_STACK_DEFINE( ) 相同的常量, 或是 K_THREAD_STACK_SIZEOF( ).
    4. prio: 工作隊列線程的優先級.
  • void k_delayed_work_init(struct k_delayed_work * work, k_work_handler_t handler) : 初始化一個延遲工作項.
    此例程在首次使用工作隊列延遲工作項之前初始化該工作隊列.
    參數:

    1. work: 延遲工作項的地址.
    2. handler: 工作項處理時調用的函數.
  • int k_delayed_work_submit_to_queue(struct k_work_q * work_q, struct k_delayed_work * work, s32_t delay) : 提交一個延遲工作項.
    這個例程在延遲毫秒後調度工作隊列 work_q 要處理的工作項的工作. 例程爲工作項啓動異步倒計時, 然後返回給調用者, 只有當倒計時完成時, 工作項才實際提交到工作隊列併成爲掛起.

    提交之前提交的延遲工作項(仍然在倒數)將取消現有的提交, 並使用新的延遲重新啓動倒計時. 請注意, 這種行爲本質上受已有超時和工作隊列的競態條件的限制, 因此必須注意在外部同步此類重新提交.

    Note: 在工作隊列處理延遲的工作項之前, 不能修改它.
    Note: 可以被 ISRs 調用.
    參數:

    1. work_q: 工作隊列的地址.
    2. work: 延遲工作項的地址.
    3. delay: 在提交工作項之前延遲的時間(單位: 毫秒).
      返回值:
    4. 0: 工作項倒計時開始.
    5. -EINVAL: 工作項正在被處理或已經完成它的工作.
    6. -EADDRINUSE: 工作項在一個不同的工作序列中正在掛起.
  • int k_delayed_work_cancel(struct k_delayed_work * work) : 取消一個延遲工作項.
    此例程取消提交延遲的工作項工作. 延遲的工作項只能在倒計時期間取消.

    Note: 可以被 ISRs 調用.
    Note: 對未提交的 k_delayed_work 工作項(即在k_delayed_work_submit_to_queue( ) 調用返回之前)調用該函數的結果是未定義的.
    參數:
    work: 延遲工作項的地址.
    返回值:

    1. 0: 工作項倒計時被取消.
    2. -EINVAL: 工作項正在處理或者已經完成了自己的工作.
  • static void k_work_submit(struct k_work * work) : 提交一個工作項到系統工作隊列.
    這個例程提交要由系統工作隊列處理的工作項. 如果由於較早的提交, 工作項已經掛起在工作隊列的隊列中, 則此例程對工作項沒有影響. 如果工作項已經被處理, 或者當前正在處理, 則認爲其工作已經完成, 可以重新提交工作項.
    **警告: 提交給系統工作隊列的工作項應該避免使用阻塞或 yield 的處理程序, 因爲這可能會阻止系統工作隊列及時處理其他工作項.
    Note: 可以被 ISRs 調用.

  • static int k_delayed_work_submit(struct k_delayed_work * work, s32_t delay) : 提交一個延遲工作項到系統工作隊列.
    這個例程在延遲毫秒之後調度系統工作隊列要處理的工作項. 例程爲工作項啓動異步倒計時, 然後返回給調用者. 只有當倒計時完成時, 工作項才實際提交到工作隊列併成爲掛起.

    提交之前提交的延遲工作項(仍然在倒計時)將取消現有的提交, 並使用新的延遲重新啓動倒計時. 如果工作項當前掛起在工作隊列的隊列上, 因爲倒計時已經完成, 那麼重新提交該工作項就太晚了, 並且重新提交失敗而不會影響工作項. 如果工作項已經被處理, 或者當前正在處理, 則認爲其工作已經完成, 可以重新提交工作項.

    **警告: 提交給系統工作隊列的工作項應該避免使用阻塞或 yield 的處理程序, 因爲這可能會阻止系統工作隊列及時處理其他工作項.
    Note: 可以被 ISRs 調用.
    參數:

    1. work: 延遲工作項的地址.
    2. delay: 在提交工作項之前延遲的時間(單位: 毫秒).
      返回值:
    3. 0: 工作項倒計時開始.
    4. -EINVAL: 工作項正在被處理或已經完成它的工作.
    5. -EADDRINUSE: 工作項在一個不同的工作序列中正在掛起.
  • static s32_t k_delayed_work_remaining_get(struct k_delayed_work * work) : 獲取延遲工作在獲得調度前的剩餘時間.
    這個例程計算延遲工作執行前剩餘的(近似的)時間. 如果延遲的工作沒有等待被調度, 則返回零.
    返回值:
    剩餘時間(單位: 毫秒).
    參數:
    work: 延遲工作項.

  • struct k_thread : 線程結構體.
    結構體在 #include <kernel.h>定義.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章