李皇谛>RTOS 实时操作系统(嵌入式)
RTOS(Real-time Operating System,實時操作系統)是以任務運行時間管理作為核心框架的操作系統。
RTOS常常以嵌入到微機(微控制器/微處理器/其他微機芯片)的形式存在,用於幫助微機完成實時性要求嚴格的工作環境。
- 如果以遊戲玩家的思維去理解這種操作系統,你可以想象它是個回合制遊戲。
基本概念
我們常用的電腦、手機所使用的操作系統均為「分時操作系統」,優先保證每個應用程序的功能可被完整實現,進而保證程序的完整性、數據的準確性以及用戶交互的實時性。分時操作系統允許用戶在硬件性能範圍內,實現高負荷軟件順利運行(比如玩遊戲、渲染視頻或設計)以及海量程序或服務的並發運行(比如網頁瀏覽、辦公軟件和社交軟件一起運行)。
實時操作系統更多用於對程序響應周期提出嚴格要求的運行環境,比如微機(常見為MCU微控制器)、人身安全保護處理器(自動生產線上的急停按鈕或者門鎖傳感器)、重要功能模塊(比如汽車的制動監測系統)等。為了儘可能削減多餘干擾因素,在一個大型系統中,實時操作系統一般都以嵌入到多枚微機芯片的形式出現。
掛起3個任務
刪除3個任務
刪除任務留下的內存
分時操作系統以及實時系統,都以處理器高速計算的形式達成對人機界面的構建、程序的運行以及功能的實現;不論是哪種操作系統,每個程序在單位運行時間都存在着允許活動的時間段(亦稱「時間片」),這些活動時間經過操作系統以及用戶的聯合控制,保證每個程序都能高效執行,呈現出計算機以及微機能處理多個程序以及功能的效果。
RTOS基礎知識點
任務
由用戶編寫並且要求處理器如實進行的步驟序列就被稱為「程序」。
為區分用戶程序與RTOS系統專用程序,我們常說的「用戶程序」一般都被稱為「任務」。
不同類型的任務具有不同的編程形式,並且支持用戶程序管理工作模式和優先級,有時候程序員還需要根據任務狀況分配內存,防止其它程序運行效率遭到降低。
[重要]為什麼RTOS需要聲明任務跳轉位置?
在裸機環境下的子函數不需要添加跳轉指令,因為處理器常常會在調用子函數之後,在編譯後的「裸機程序」自動回到調用前的父函數。
編譯器會在每個函數的末尾段,自動填充程序跳轉指令到最終的機器碼文件(待燒錄程序)中,因為每個函數運行結束之前的判斷策略以及結束之後的動作都是固定不變的,
正所謂「善戰者無赫赫之功」,這種「傻瓜式」函數切換策略容易讓RTOS新手忘卻了編譯器自動添加的必要步驟。
然而在RTOS中,儘管RTOS自動保存了函數的起始位置,但任務/函數的切換順序是隨時可變的,因此不能依賴編譯器自動給定的程序跳轉指令去跳轉其他任務,需要用戶在任務中提供「任務的終點」讓RTOS確認任務切換時機。
不能確認任務切換時機,對於RTOS而言是最為致命的,因為RTOS不能獲取編譯器給定「任務跳轉」的位置。如果一個任務不給RTOS設立一個任務結束的跳轉位置,就容易因為跳轉到編譯器自動添加的指令,跑飛到其它程序。
如果存在需要動態內存分配的任務、燒錄前加密、指定絕對內存位置等多次更改函數位置的步驟,可能會陷入比「跑飛」後果還嚴重的「堆棧溢出」,輕則導致處理器停機,重則導致系統結構解體。
工作模式與優先級管理
- 任務管理功能
RTOS通常會為用戶留下用於任務管理的API函數,常用的任務管理功能如下:
- 創建/註冊一項任務。
- 可在創建期間指定內存分配模式(自動/動態 或者 手動/靜態),以及指定需要傳遞的形式參數。
- 刪除/註銷一項任務,可以是自身任務或其他任務。
- 註銷任務之後,任務使用的棧(局部變量或中間數據)會被刪除。
- 調整自身任務/其他任務的優先級。
- 添加阻塞/等候條件,以及解除其他任務阻塞狀態。
- 添加掛起/無限期阻塞指令,以及復原被掛起的其他任務。
- 掛起任務後,該任務將不限期暫停執行,但RTOS會保留任務所用棧。
- 優先級管理
不同的RTOS會有不同的優先級仲裁算法,有些RTOS的優先級數字越大、等級越低,有些則截然相反。
在同屬「就緒」狀態的任務隊列中,優先級高的任務會被搶先運行,運行順序從優先級高的任務到優先級低的任務。
高優先級任務執行完成或超時切換後,將從任務列表中尋找次高優先級任務,直到最低優先級任務執行完畢或之前執行過的高優先級任務解除阻塞。
系統空閒進程固定為最低優先級;系統任務管理器、任務切換服務以及系統心跳中斷服務固定為RTOS的最高優先級。
- 任務狀態
不同的RTOS會區分不同的任務狀態,常見的任務狀態如下:
- 正在運行 :當前時間片中正在運行的任務。
- 準備就緒 :放入後續時間段等候運行的任務。若上一「正在運行」的任務發生了任務切換(自行切換或超時切換),將從「準備就緒」中挑選優先級最高的任務升級為「正在運行」狀態。
- 阻塞/有條件等候 :等候部分觸發條件而暫停執行的任務。這種狀態下的任務往往在等候RTOS給定的內存工具抵達一定狀態,才會被RTOS放回「準備就緒」隊列。根據阻塞條件的不同,可能會存在多個相似任務隊列。
- 掛起/無限期暫停 :長期暫停執行的任務,恢復運行的方法只有「通過其他任務復原或解除掛起」。
- 曾運行過 :隱藏隊列,不可被用戶手動切換。曾經「正在運行」的任務進行任務切換後被歸類的隊列,一旦抵達固定時間周期,「曾運行過」的任務隊列會重新進入「準備就緒」排隊。「曾運行過」隊列實用性不高,一般會被以「延時阻塞」的方式替代。
- 已註銷 :隱藏隊列。被刪除/註銷的任務會被RTOS刪除任務管理塊。若被刪除的任務曾使用動態內存分配方式,RTOS會在空閒任務中回收曾經自動分配的棧(內存)。
任務編程形式
熟悉Arduino編程環境的程序員容易把用戶程序分為兩種,一種是「單步程序」,一種是「循環程序」。在使用RTOS時,程序員可以劃分不定式的任務形式,而常用的任務形式有以下四種:「有限次數任務」、「無限循環任務」、「先處理協作任務」和「後處理協作任務」。
- 單步程序(有限次數任務)
編寫有限次數任務時,需要確定任務循環次數(一般為1次),抵達任務循環次數之後,為了避免RTOS進入任務切換時機後,再次執行跑飛到其它程序,需要在任務切換指令之前,將該任務註銷(刪除)。
- 循環程序(無自行刪除任務的條件)
一般情況下,循環程序按照單獨執行的形式以一個大的無限循環語句包裹起來,不過要注意的是,還需要在循環語句的最後一句中添加「任務跳轉位置」或者「阻塞條件」,以此趕在RTOS超時檢測前完成任務切換,避免對RTOS的實時性造成破壞。
- 協作任務
循環程序的一種特殊情形,通過協調信號與動作之間的先後順序,以及需要聯動協作的任務以完成同步處理。
- 「先處理協作任務」在完成當前任務之後,為自己添加條件性阻塞。
- 「後處理協作任務」會先等待某些信號或阻塞條件解除,才會繼續自己的任務。
- 「中間步驟協作任務」會在自己的任務前後,先等部分阻塞條件解除,然後在自己任務完成後添加另一種阻塞條件。
堆棧與內存分配模式
系統內核
- 「臨界區」與調度管理
- 可拖延中斷服務
- 系統心跳定時器中斷服務
- 基礎內存結構:鍊表
內存工具
RTOS會為用戶提供專用的內存工具,這些內存工具由RTOS自動管理,同時會綁定等候狀態變化的「阻塞中」任務列表。
用戶既可以通過API添加內存工具,也可以在任務中添加面向指定內存工具的「阻塞條件」,由RTOS在運行其他任務的過程中,代為等候內存工具的變化。
內存工具狀態發生改變時,RTOS會根據任務阻塞條件,將符合「阻塞條件」的任務升級為「準備就緒」狀態。
一般情況下,這些內存工具用於任務之間的相互協調,因為狀態變化往往伴隨着一些任務需要搶先執行,或者阻止某些高優先級任務突然運行,用於實現「線程安全」,避免任務發生共用資源的衝突。
常見的內存工具有「信號量」、「事件標誌組」、「數據隊列」和「即時信箱」。
- 「信號量」、「事件標誌組」屬於開關類內存工具;「數據隊列」和「即時信箱」屬於數據類內存工具。
- 以下速查手冊(說明文檔)的參考操作系統為FreeRTOS.
信號量
信號量(Semaphore)是實現基本任務協調的內存工具。相比於裸機系統,任務對信號量的讀寫嚴格按照RTOS給定的任務優先級順序,並且允許任務等候對應信號量抵達「已用狀態」。
適用場合:軟件狀態開關(二值信號量)、硬件使用狀況指示器(互斥信號量)、緩存用量指示器(計數器信號量)、公共數據緩存空間(互斥計數信號量)
| FreeRTOS動畫演示信號量操作過程 |
|---|
|
|
任務可以對信號量進行以下操作:
- 基礎
🛠️創建 🗑️刪除 ➕「給出」(計數遞增) ➖「取走」(計數遞減)
- 任務阻塞
⏳等信號量被給出後,解除阻塞並取走信號量
- 特殊
🔍查詢信號量數據 🔍👤查詢占有者(僅限「互斥」) 🔒鎖定/解鎖信號量(禁止「互斥」)
- 名詞俗語互譯
- 「給出」信號量:Give / 掛載 / 放入 / 計數器 +1
- 「取走」信號量:Take / 卸載 / 取出 / 計數器 -1
- 一般情況下,新建的信號量為空狀態,計數器為0,需要任務先行「給出」信號量。
信號量在RTOS中會出現四種模式,分別是「二進制信號量」、「計數式信號量」、「互斥二值信號量」、「互斥遞歸信號量」。
- 「互斥遞歸信號量」等同於「互斥計數式信號量」。
- 「二進制」 對 「計數式」:
- 二進制信號量的允許值僅為0-1,相當於一個開關;
- 計數式信號量允許在有數值的情況下繼續給出或取走,直至抵達限值。
- 「互斥」:一旦該信號量被「給出」,信號量將被「獨占」,僅允許「給出」過的任務進行控制(寫入)。
- 獨占信號量的任務,在「取走」二值信號量或將計數信號量「取走」至「0」時,將解除對該信號量的獨占。
- 任務可以讀出或者等待被其他任務「給出」的互斥信號量,但不能對該互斥信號量進行寫入。
- 對於互斥二值信號量或互斥計數信號量,任務可以查詢占有者。
- 相比於「事件標誌組」,信號量在等候被他人給出之後,等到被給出將同時取走信號量,這種自動操作不可由用戶取消。
- 請使用「事件標誌組」以實現「A任務發送信號後,B任務收到信號後不刪信號、C任務收到信號後再刪除信號」的操作。
事件標誌組
事件標誌組 是「二值信號量」的進階版本。RTOS既能保證各任務對事件標誌的讀寫操作有序可控,又能通過事件標誌實現各種條件下的自動處理(自動化)。
適用場合:狀態機、並發事件呼叫器
任務可以對事件標誌組執行以下操作:
- 基礎
🛠️創建一組事件標誌 🗑️刪除一組事件標誌 ➕置位1個事件標誌 ➖復位1個事件標誌 🔍⊙ 檢查事件標誌/標誌組狀態
- 任務阻塞
⏳等分組中的一個/多個事件標誌被置位(可選與/或邏輯)
- 自動化
⏏️等事件標誌後自動清除標誌位
- 特殊
✏️⏩批處理事件標誌組 🔐管理事件標誌組可用標誌位長度
- 對事件標誌組的操作,都以一個「事件組」作為基本單位。不論是對事件標誌位(bit)進行讀寫,還是等候某個事件標誌被置位,都要選定標誌所在分組。
- 使用「與邏輯(AND)」時,要求設置的多個標誌位全部為1才會解除阻塞,使用「或邏輯(OR)」時,僅需至少1個標誌位為1即可解除阻塞。
- 管理事件標誌組的可用標誌位的長度後,超出長度的位將被設置為「禁用」,不可用作事件標誌位。
數據隊列
數據隊列(Queue)是用於解決數據緩衝的內存工具。相比於裸機系統自行編寫的「FIFO」隊列,RTOS也會嚴格調整任務的讀寫順序,避免數據操作順序不可控的問題。
適用場合:軟件通信/函數傳遞緩衝區(順序隊列)、操作歷史記錄器(逆序隊列)
| FreeRTOS動畫演示RTOS隊列操作過程 |
|---|
|
|
任務可以對數據隊列執行以下操作:
- 基礎
🛠️創建 🗑️刪除 ✏️▶️排隊寫入數據(FIFO) ▶️📬讀出數據 ✏️⏩插隊寫入數據
- 任務阻塞
⏳等隊列有數據被寫入
- 特殊
🔍% 查詢隊列已用量/可用量 🔍? 查詢隊列是否為空/為滿 ✏️◀️排隊寫入數據(LIFO) 🗑️🗞️清空/重置隊列
- 罕見
✏️🔄️覆寫即將讀出的數據 📬👁️🗨️偷瞄數據(Peek,讀出但保留數據)
- 一般情況下,數據隊列的組成方式一般為「先入先出隊列(FIFO)」,最早寫入隊列的數據會被讀出,讀出的數據會從隊列中移除,第二早寫入的數據將成為下次讀出的數據。
- 數據逆向寫入隊列(插隊),將成為最先被讀出的數據。
- 有些RTOS實現「插隊寫入數據」的方法是「創建後入先出隊列(LIFO)」,比如CosyOS.
即時信箱
即時信箱(Mailbox)亦稱「通知」、「短信」,是直接向任務發送一行數據的內存工具。RTOS會把任務請求的數據發送在目標任務的任務管理塊,不僅大幅減少操作步驟,而且不需要用戶額外創建內存工具,因為即時信箱是跟隨任務創建而共生的。
適用場合:代替傳入函數將數據傳到任務中、嚴格控制內存大小的應用場合、上述內存工具替代品
任務可以進行與即時信箱相關的以下操作:
- 基礎
📤向任務的信箱發送/替換通知 📥接收來自信箱的通知
- 任務阻塞與自動化
📥⏳等候信箱郵件並進入阻塞狀態 📤🔔發送郵件並解除其他任務的阻塞狀態
- 罕見
🪄對任務信箱進行類信號量管理 🗑️清除任務信箱 📤📚發送通知並構建通知歷史 🔍 讀取其他任務的信箱內容
- CosyOS不具備即時信箱功能,所謂「私信」實質為1行深度的「順序隊列」。
非RTOS管轄內存
不使用上述工具所創建的內存均屬於「非RTOS管轄內存」。
典型RTOS舉例
- 經典RTOS
- μC/OS系列 :嵌入式RTOS的鼻祖,功能強大,但上手難度大,移植難度也大。
- FreeRTOS :體積小巧、社區友好型RTOS,並且官方網站的API文檔豐富(已包含中文)、移植難度低,但功能不強悍,不支持80x51,被STC51愛好者魔改移植成功。
- CosyOS :內核仲裁期間不會破壞中斷的操作系統,對STC51作出了特化設計,仍在公測階段。
- 物聯網系統特化RTOS
- RT-Thread
- Huawei Lite OS
- 阿里雲OS(AliOS Things)