即時作業系統 (RTOS) 及相關應用
資料提供者:DigiKey 北美編輯群
2021-02-25
什麼是 RTOS?
即時作業系統 (RTOS) 是一種輕量型作業系統,可在資源及時間受限的設計中,簡化多工作業及任務整合,而嵌入式系統通常就有這種受限情況。此外,「即時」一詞是指執行時間的可預測性/確定性,而非原始速度,因此 RTOS 通常會因其確定性而證實能滿足高難度的即時要求。
RTOS 的主要概念:
任務
任務也可稱為處理程序/執行緒,是在無限迴路中執行的獨立函數,而每個函數通常會負責一項功能。任務是在其專屬的時間 (時間隔離) 與記憶體堆疊 (空間隔離) 中獨立執行。使用硬體記憶體保護單元 (MPU),即可保證任務間的空間隔離,且 MPU 會限制可存取的記憶體區域,並在存取違規時觸發故障例外情形。一般而言,內部周邊裝置都有對應的記憶體,因此 MPU 也可對周邊裝置的存取進行限制。
任務有以下不同狀態:
- 封鎖 – 任務正在等待事件發生 (例如延遲逾時、資料/資源的可用性)
- 就緒 – 任務已預備好在 CPU 執行,但因為其他任務正佔用 CPU,因此仍未執行
- 執行中 – 已分配任務在 CPU 執行
排程器
RTOS 中的排程器會控制 CPU 要執行的任務,且有不同的排程演算法可供選擇。通常包含:
- 搶先式 – 若另一項任務的優先權更高且已就緒,就會中斷目前任務的執行
- 合作式 – 若目前執行的任務自行讓步,才會進行任務切換
搶先式排程容許更高優先權的任務將更低優先權的任務中斷,以符合即時性的限制,但代價是相關內容切換時負擔會更高。
任務間通訊 (ITC)
若有多項任務時,通常要彼此分享資訊或事件。最簡單的分享方式是直接讀/寫 RAM 中的共用全域變數,但此法可能因競用情況而有資料毀損的風險,因此不是理想方法。比較合適的方式是讀/寫 setter 與 getter 函數可存取的檔案範圍靜態變數,並且可停用中斷或使用 setter/getter 函數內部的互斥物件 (mutex),藉此避免競用情況。比較簡潔的作法是使用訊息佇列等執行緒安全的 RTOS 物件,以在工作之間傳遞資訊。
RTOS 物件除了分享資訊外,也能將任務的執行同步化,因為可封鎖任務,等到 RTOS 物件可用時才執行。多數的 RTOS 具有以下物件:
- 訊息佇列
- 先進先出 (FIFO) 的資料傳遞佇列
- 資料可透過複製或參照 (指標) 的方式傳遞
- 可在任務之間,或中斷與任務之間傳遞資料
- 旗升號
- 可視為基準計數器,以記錄特定資源的可用性
- 可以是二進制或計數旗升號
- 可用來保護資源的使用或將任務執行同步化
- Mutex
- 此物件類似於二進位旗升號,通常用來保護單一資源 (MUTual EXclusion) 的使用
- FreeRTOS mutex 本身具備優先權繼承機制,可避免發生優先權逆轉問題 (即高優先權任務等待低優先權任務)。
- 信箱
- 用來分享單一變數的簡易儲存位置
- 可視為單一元素佇列
- 事件群組
- 多種情況 (旗升號、佇列、事件旗標等的可用性) 所形成的群組
- 可封鎖任務,也可以等到特定情況組合達成
- 在 Zephyr 中以輪詢 API 提供,在 FreeRTOS 則以 QueueSets 提供
系統節拍
RTOS 需要時間基準以測量時間,而基準的形式通常是系統節拍計數器變數,且會在週期性硬體計時器中斷時遞增。透過系統節拍,應用程式只須使用單一硬體計時器,除了時間為準的服務之外,也可維持更多服務 (任務執行間隔、等候逾時、時間分割)。然而,節拍率更高只會增加 RTOS 時間基準解析度,而不會讓軟體的執行速度更快。
為何要使用 RTOS
歸納整理
應用程式始終可以用裸機方式編寫程式碼,但隨著程式碼複雜度增加,若具備某種結構,將有助於管理應用程式的不同部分,使其保持分離。此外,透過結構化的開發方式及熟悉的設計語言,即使是新進的團隊成員也能理解程式碼,因而更快地投入工作。RFCOM Technologies 已經在不同的 RTOS 上使用多種微控制器開發應用程式,例如 Texas Instruments 的 Hercules、Renesas 的 RL78 與 RX,以及 STMicroelectronics 的 STM32 等。由於設計模式相似,因此可以在不同的微控制器上,甚至在不同的 RTOS 上開發應用程式。
模組化
各個擊破。將各種功能分開到不同任務中,就可輕鬆添加新功能而不會破壞其他功能;前提是新功能不可讓 CPU 與週邊裝置等共用資源超載。未使用 RTOS 的開發作業,通常會陷入大型無限迴路中,而所有功能都混合在該迴路中。若對迴路內的任何功能進行變更,則會對其他功能造成影響,導致軟體難以修改及維護。
通訊堆疊和驅動程式
TCP/IP、USB、BLE 堆疊與圖形庫等許多額外的驅動程式或堆疊,全都是針對現有的 RTOS 而開發,或移植到現有 RTOS 當中。因此應用程式開發人員可以專注在軟體的應用層,大幅縮短上市時間。
秘訣
靜態分配
針對 RTOS 物件使用記憶體靜態分配,也就是在程式編譯時,為每個 RTOS 物件保留 RAM 中的記憶體堆疊。xTaskCreateStatic() 即是 freeRTOS 的靜態分配函數範例。此函數可確保 RTOS 物件成功建立,而無需煩惱可能要處理分配失敗的情況,也可讓應用程式更具確定性。
在決定任務所需的堆疊大小時,任務能以較大 (綽綽有餘) 的堆疊大小來執行,然後可在執行時間內檢查堆疊使用率,以判定高水位標示。市面上也有靜態堆疊分析工具可供使用。
作業系統抽象層 (OSAL) 與有意義的抽象化
如同硬體抽象層 (HAL) 一樣,使用 RTOS 抽象層能讓應用程式軟體輕鬆移轉至其他 RTOS。各種 RTOS 的功能其實非常相似,因此建立 OSAL 應該不會太複雜。例如:
直接使用 freeRTOS API:
if( xSemaphoreTake( spiMutex, ( TickType_t ) 10 ) == pdTRUE ) { //dosomething }
將 RTOS API 打包到 OSAL 中:
if( osalSemTake( spiMutex, 10 ) == true) { //dosomething }
在任務間通訊使用抽象層,可提升程式碼的易讀性,並將 RTOS 物件的範圍減至最小:
if( isSpiReadyWithinMs( 10 ) ) { //doSomething }
此外,如果有一個以上的 SPI 模組可用,抽象化也可讓編程器改變底層使用的 RTOS 物件 (例如從 mutex 變成計數旗升號)。OSAL 與其他抽象層也有助於軟體測試,方法是簡化單元測試期間 mock 函數的插入。
選擇節拍間隔
在理想情況下,節拍率越小越好,因為負擔會較低。為了選擇合適的節拍率,開發人員可以列出應用程式模組的計時限制 (重複間隔、逾時持續時間等)。如果某些異常值模組需要短間隔時間,則可考慮針對異常模組使用專屬的計時器中斷,而不是增加 RTOS 節拍率。如果高頻函數間隔非常短 (如寫入暫存器以開啟或關閉 LED),則可在中斷服務常式 (ISR) 當中完成,否則可以使用延遲中斷處理。延遲中斷處理是一種讓中斷運算延遲進入 RTOS 任務的技巧。ISR 只會透過 RTOS 物件產生事件,然後事件就會將 RTOS 任務解除封鎖,並且進行運算。
低功率應用的節拍抑制
若系統將長時間閒置時,無節拍的閒置狀態會將節拍中斷停用。嵌入式韌體有個重要方法可降低功耗,就是讓系統盡可能長時間處於低功率模式。無節拍閒置的實作方式是停用週期性節拍中斷,然後設定倒數計時器,以便在封鎖的任務即將執行時予以中斷。如果沒有任何任務等待逾時,節拍中斷可以無限期地停用,直到另一個中斷發生為止 (例如按下按鈕)。以低功耗藍牙 (BLE) 信標為例,就可讓 MCU 在廣播時間間隔之間進入深度睡眠。如圖 1 所示,信標多數時間都處於深度睡眠模式,僅耗用數十 µA。
結論
RTOS 可提供眾多功能,例如排程器、任務、任務間通訊 RTOS 物件,以及通訊堆疊與驅動程式。RTOS 能讓開發人員專注於嵌入式軟體的應用層,協助其輕鬆快速設計多工作業軟體。然而,如同其他任何工具一樣,若要發揮更多價值,就要正確地使用。為了打造安全、高效率的嵌入式軟體,開發人員應瞭解 RTOS 功能的使用時機以及 RTOS 的配置方法。

聲明:各作者及/或論壇參與者於本網站所發表之意見、理念和觀點,概不反映 DigiKey 的意見、理念和觀點,亦非 DigiKey 的正式原則。