了解RTOS任務
超級循環(huán)編程范式通常是嵌入式系統(tǒng)工程師最先接觸到的編程方法之一。用超級循環(huán)實現(xiàn)的程序有一個單一的頂層循環(huán),在系統(tǒng)需要執(zhí)行的各種功能之間循環(huán)。這些簡單的while循環(huán)很容易創(chuàng)建和理解(當它們很小的時候)。在FreeRTOS中,任務與超級循環(huán)非常相似--主要區(qū)別在于,系統(tǒng)可以有一個以上的任務,但只有一個超級循環(huán)。
在本章中,我們將仔細研究超級循環(huán)和用它們實現(xiàn)一定程度的并行性的不同方法。之后,將對超級循環(huán)和任務進行比較,并從理論上介紹任務執(zhí)行的思維方式。最后,我們將看看任務是如何通過RTOS內(nèi)核實際執(zhí)行的,并比較兩種基本的調(diào)度算法。
超級循環(huán)編程介紹
所有的嵌入式系統(tǒng)都有一個共同的特性--它們沒有退出點。由于其性質(zhì),嵌入式代碼通常被期望總是可用的--靜靜地在后臺運行,處理內(nèi)務工作,并隨時準備接受用戶的輸入。與旨在啟動和停止程序的桌面環(huán)境不同,如果微控制器退出main()函數(shù),它就沒有任何事情可做。如果發(fā)生這種情況,很可能是整個設備已經(jīng)停止運作。由于這個原因,嵌入式系統(tǒng)中的main()函數(shù)從不返回。與應用程序不同的是,應用程序是由其主機操作系統(tǒng)啟動和停止的,大多數(shù)基于嵌入式MCU的應用程序在上電時開始,在系統(tǒng)斷電時突然結束。由于這種突然的關閉,嵌入式應用程序通常沒有任何通常與應用程序相關的關閉任務,如釋放內(nèi)存和資源。
下面的代碼代表了超級循環(huán)的基本思想:
void main ( void )
{
    while(1)
    {
        func1();
        func2();
        func3();
        //do useful stuff, but don't return
        //(otherwise, where would we go. . what would we do. . .?!)
    }
}
雖然非常簡單,但前面的代碼有許多值得指出的特點。while循環(huán)從不返回--它一直在執(zhí)行同樣的三個函數(shù)(這是故意的)。這三個看似無害的函數(shù)調(diào)用可以在實時系統(tǒng)中隱藏一些令人討厭的驚喜。
基本的超級循環(huán)
這個從不返回的主循環(huán)一般被稱為超級循環(huán)。超級循環(huán)總是很有趣,因為它可以控制系統(tǒng)中的大多數(shù)事情--除非超級循環(huán)使之發(fā)生,否則下圖中的任何事情都無法完成。這種類型的設置非常適合于非常簡單的系統(tǒng),只需要執(zhí)行一些不需要花費大量時間的任務?;镜某壯h(huán)結構非常容易編寫和理解;如果你想解決的問題可以用一個簡單的超級循環(huán)來完成,那么就使用一個簡單的超級循環(huán)。下面是前面介紹的代碼的執(zhí)行流程--每個函數(shù)都是按順序調(diào)用的,而且循環(huán)永不退出:

實時系統(tǒng)中的超級環(huán)路
當簡單的超級循環(huán)快速運行時(通常是因為它們的功能/責任有限),它們的響應速度相當快。然而,超級循環(huán)的簡單性可能是一種祝福,也是一種詛咒。由于每個函數(shù)總是跟在前面的函數(shù)后面,它們總是以相同的順序被調(diào)用,并且完全相互依賴。一個函數(shù)引入的任何延遲都會傳播到下一個函數(shù),從而導致執(zhí)行該循環(huán)迭代的總時間增加(如下圖所示)。如果func1在循環(huán)中執(zhí)行一次需要10毫秒,而下一次需要100毫秒,那么func2在循環(huán)中第二次被調(diào)用的時間就不會像第一次那樣快:

讓我們更深入地看一下這個問題。在上圖中,func3負責檢查一個代表外部事件的標志的狀態(tài)(這個事件是一個信號的上升沿)。func3檢查標志的頻率取決于func1和func2執(zhí)行的時間。一個設計良好、反應靈敏的超級循環(huán)通常會執(zhí)行得非???,檢查事件的頻率要比事件發(fā)生的頻率高(呼出B)。當外部事件確實發(fā)生時,該循環(huán)直到func3的下一次執(zhí)行才檢測到該事件(呼出A、C和D)。注意,在事件產(chǎn)生和func3檢測到它之間有一個延遲。還要注意的是,這個延遲并不總是一致的:這種時間上的差異被稱為抖動。
在許多基于超級循環(huán)的系統(tǒng)中,與被輪詢的緩慢發(fā)生的事件相比,超級循環(huán)的執(zhí)行速度非常高。我們在頁面上沒有足夠的空間來顯示循環(huán)在檢測到事件之間執(zhí)行數(shù)百次(或數(shù)千次)的迭代!這就是所謂的抖動!
如果系統(tǒng)在響應事件時有一個已知的最大抖動量,它被認為是確定性的。也就是說,它將在事件發(fā)生后的指定時間內(nèi)對事件作出可靠的反應。高水平的確定性對于實時系統(tǒng)中的時間關鍵型組件是至關重要的,因為如果沒有它,系統(tǒng)可能無法及時響應重要的事件。
考慮到循環(huán)反復檢查硬件標志的事件(這被稱為輪詢)。循環(huán)越緊密,標志被檢查的速度就越快--當標志經(jīng)常被檢查時,代碼將對感興趣的事件做出更多的反應。如果我們有需要及時采取行動的事件,我們可以只寫非常緊密的循環(huán),等待重要事件的發(fā)生。這種方法是有效的--但前提是該事件是系統(tǒng)唯一感興趣的事情。如果整個系統(tǒng)唯一的責任就是觀察該事件(沒有后臺I/O、通信等),那么這是一個有效的方法。這種類型的情況在今天復雜的現(xiàn)實世界的系統(tǒng)中很少發(fā)生。響應性差是單純基于輪詢的系統(tǒng)的局限性。接下來,我們將看看如何在我們的超級循環(huán)中獲得更多的并行性。
用超級循環(huán)實現(xiàn)并行操作
盡管基本的超級循環(huán)只能按順序通過函數(shù),但仍有辦法實現(xiàn)并行化。單片機有一些不同類型的專用硬件,它們被設計用來減輕CPU的一些負擔,同時還能實現(xiàn)高度響應的系統(tǒng)。本節(jié)將介紹這些系統(tǒng)以及如何在超級循環(huán)風格的程序中使用它們。
中斷
對單一事件進行輪詢不僅在CPU周期和功率方面是浪費的--它還會導致系統(tǒng)對其他事物沒有反應,這通常是應該避免的。那么,我們怎樣才能讓單核處理器并行地做事情呢?嗯,我們不能--畢竟只有一個處理器。...但由于我們的處理器很可能每秒運行數(shù)百萬條指令,所以有可能讓它執(zhí)行足夠接近于并行的事情。MCU還包括用于生成中斷的專用硬件。中斷向MCU提供信號,使其在事件發(fā)生時直接跳到中斷服務程序(ISR interrupt service routine )。這是一個非常關鍵的功能,ARM Cortex-M內(nèi)核為其提供了一個標準化的外設,稱為嵌套向量中斷控制器(NVIC nested vector interrupt controller。NVIC提供了一種處理中斷的通用方法。這個術語的嵌套部分標志著即使是中斷也可以被其他具有更高優(yōu)先級的中斷打斷。這相當方便,因為它允許我們將系統(tǒng)中時間最關鍵的部分的延遲和抖動量降到最低。
那么,中斷如何融入超級循環(huán),以更好地實現(xiàn)并行活動的假象?ISR內(nèi)部的代碼通常被保持得盡可能短,以盡量減少在中斷中花費的時間。這一點很重要,有幾個原因。如果中斷發(fā)生得很頻繁,而且ISR包含很多指令,那么ISR就有可能在被再次調(diào)用之前不返回。對于UART(universal asynchronous receiver / transmitter)或SPI(serial peripheral interface)等通信外設來說,這將意味著數(shù)據(jù)丟失(這顯然是不可取的)。保持代碼簡短的另一原因是其他中斷也需要得到服務,這就是為什么把任何責任推給不在ISR上下文中運行的代碼是個好主意。
為了快速了解ISR是如何導致抖動的,讓我們看看簡單的例子:外部模數(shù)轉(zhuǎn)換器(ADC analog to digital converter )向MCU發(fā)出信號,表示已經(jīng)采集了讀數(shù),轉(zhuǎn)換結果準備傳送給MCU(參考這里的硬件圖):

在ADC硬件中,有一個引腳專門用來指示模擬值的讀數(shù)已被轉(zhuǎn)換為數(shù)字表示,并準備傳輸給MCU。然后,MCU將通過通信介質(zhì)(圖中的COM)啟動傳輸。
接下來,讓我們看看相對于轉(zhuǎn)換準備線的上升沿,ISR調(diào)用如何隨著時間的推移而相互疊加。下圖顯示了六個不同的ISR被調(diào)用以響應信號的上升沿的情況。硬件中的上升沿與固件中的ISR被調(diào)用之間的少量時間是最小延遲。ISR響應中的抖動是許多不同周期中延遲的差異:

有不同的方法來最小化關鍵ISR的延時和抖動。在基于ARM Cortex-M的MCU中,中斷優(yōu)先級是靈活的--在運行時可以為單個中斷源分配不同的優(yōu)先級。重新確定中斷優(yōu)先級的能力是確保系統(tǒng)中最重要的部分在需要時獲得CPU的一種方式。
如前所述,保持在中斷中執(zhí)行的代碼量盡可能短是很重要的,因為在ISR中的代碼將優(yōu)先于任何不在ISR中的代碼(例如main())。此外,較低優(yōu)先級的ISR不會被執(zhí)行,直到較高優(yōu)先級的ISR中的所有代碼都被執(zhí)行,并且ISR退出--這就是為什么保持ISR的簡短是重要的。嘗試限制ISR的責任(以及因此而產(chǎn)生的代碼)總是好主意。
當多個中斷被嵌套時,它們不會完全返回--實際上ARM Cortex M處理器有非常有用的功能,叫做中斷-尾部鏈。如果處理器檢測到一個中斷即將退出,但另一個中斷正在等待,那么下一個ISR將被執(zhí)行,而處理器不會完全恢復中斷前的狀態(tài),這進一步減少了延遲。
中斷和超級循環(huán)
在ISR中實現(xiàn)最小指令和責任的一種方法是在ISR中做盡可能少的工作,然后設置標志,由超級循環(huán)中運行的代碼來檢查。這樣一來,中斷就可以盡快得到服務,而不需要整個系統(tǒng)都致力于等待該事件。在下圖中,注意到在最后由func3處理之前,中斷是如何被多次產(chǎn)生的。
根據(jù)該中斷試圖實現(xiàn)的具體目標,它通常會從相關的外設中獲取一個值并將其推入數(shù)組(或從數(shù)組中獲取值并將其送入外設寄存器)。在我們的外部ADC的情況下,ISR(每次ADC執(zhí)行轉(zhuǎn)換時觸發(fā))將出去到ADC,傳輸數(shù)字化的讀數(shù),并將其存儲在RAM中,設置標志,表明一個或多個值已經(jīng)準備好進行處理。這使得中斷可以被多次服務,而不涉及高層代碼:

在通信外設傳輸大塊數(shù)據(jù)的情況下,可以用數(shù)組作為隊列來存儲要傳輸?shù)捻椖?。在整個傳輸結束時,可以設置標志來通知主循環(huán)的完成。有很多例子可以說明隊列值是合適的情況。例如,如果需要對數(shù)據(jù)塊進行一些處理,首先收集數(shù)據(jù),然后在中斷之外一起處理整個數(shù)據(jù)塊,這往往是有利的。中斷驅(qū)動的方法并不是實現(xiàn)這種阻斷數(shù)據(jù)的唯一方法。
DMA
還記得處理器不可能真正做到并行的說法嗎?這仍然是事實。然而......現(xiàn)代的MCU不僅僅包含一個處理核心。當我們的處理核心在處理指令時,還有許多其他硬件子系統(tǒng)在MCU內(nèi)努力工作。這些努力工作的子系統(tǒng)之一被稱為直接內(nèi)存訪問控制器(DMA Direct Memory Access Controller):

前面的圖是非常簡化的硬件框圖,顯示了從RAM到UART外設的兩個不同的數(shù)據(jù)路徑的視圖。
在沒有DMA的情況下,從UART接收字節(jié)流,來自UART的信息將進入UART寄存器,被CPU讀取,然后推送到RAM進行存儲:
- CPU必須檢測到何時收到單獨的字節(jié)(或字),要么通過輪詢UART寄存器標志,要么通過設置中斷服務例程,當一個字節(jié)準備好時就啟動。
 - 字節(jié)從UART傳輸后,CPU可以將其放入RAM進行進一步處理。
 - 步驟1和2重復進行,直到收到整個信息。
 
當DMA被用在同樣的場景中時,會發(fā)生以下情況:
- CPU為傳輸配置DMA控制器和外圍設備。
 - DMA控制器負責UART外設和RAM之間的所有傳輸。這不需要CPU的干預。
 - 當整個傳輸完成時,CPU會得到通知,它可以直接處理整個字節(jié)流。
 
大多數(shù)程序員發(fā)現(xiàn)DMA幾乎是神奇的,如果他們習慣于處理超級循環(huán)和ISR??刂破鞅慌渲脼樵谕庠O需要時向外設傳輸內(nèi)存塊,然后在傳輸完成時提供通知(通常是通過一個中斷)--就是這樣!這就是DMA!
當然,這種便利也是有代價的。最初設置DMA傳輸確實需要一些時間,所以對于小的傳輸,實際上可能比使用中斷或輪詢的方法要花費更多的CPU時間來設置傳輸。
還有一些需要注意的地方:每個MCU都有特定的限制,所以在指望DMA對系統(tǒng)的關鍵設計組件的可用性之前,一定要閱讀數(shù)據(jù)手冊、參考手冊和勘誤表的細節(jié):
MCU內(nèi)部總線的帶寬限制了可以可靠地放在單一總線上的對帶寬要求高的外設的數(shù)量。
偶爾,映射到外設的DMA通道的有限可用性也使設計過程復雜化。
這些類型的原因就是為什么要讓所有的團隊成員參與到嵌入式系統(tǒng)的早期設計中來,而不是直接把它扔到墻上。
DMA對于有效地訪問大量的外設是很好的,使我們有能力為系統(tǒng)添加越來越多的功能。然而,當我們開始向超級循環(huán)添加越來越多的代碼模塊時,子系統(tǒng)之間的相互依賴關系也變得更加復雜。在下一節(jié)中,我們將討論為復雜系統(tǒng)擴展超級循環(huán)的挑戰(zhàn)。
擴展超級循環(huán)
現(xiàn)在已經(jīng)有了能夠可靠地處理中斷的響應系統(tǒng)。也許我們已經(jīng)配置了DMA控制器來處理通信外圍設備的繁重工作。為什么我們需要實時操作系統(tǒng)?嗯,你完全有可能不需要! 如果系統(tǒng)只處理有限的任務,而且沒有特別復雜或耗時的,那么可能就不需要比超級循環(huán)更復雜的東西。
但是,如果系統(tǒng)還要負責生成用戶界面(UI),運行復雜的耗時的算法,或者處理復雜的通信棧,那么這些任務很可能要花費非同小可的時間。如果帶有大量動畫的華麗奪目的用戶界面因為MCU正在處理從關鍵的傳感器收集數(shù)據(jù)而開始有點結巴,那也沒什么大不了的。要么動畫可以回調(diào),要么取消,而實時系統(tǒng)的重要部分則保持原樣。但是,如果那個動畫看起來仍然非常好,即使有一些來自傳感器的數(shù)據(jù)被遺漏,又會發(fā)生什么呢?
在我們的行業(yè)中,這個問題每天都有各種不同的方式。有時,如果系統(tǒng)設計得足夠好,丟失的數(shù)據(jù)會被檢測到并被標記出來(但它不能被恢復:它永遠消失了)。如果設計團隊真的很幸運,它甚至可能在內(nèi)部測試中以這種方式失敗。然而,在許多情況下,遺漏的傳感器數(shù)據(jù)將完全沒有被注意到,直到有人注意到其中一個讀數(shù)似乎有點偏離......有時。如果每個人都很幸運,關于粗略讀數(shù)的錯誤報告可能包括一個提示,即它似乎只在有人在前面板上(玩那些花哨的動畫)時發(fā)生。這至少會給被指派去調(diào)試這個問題的可憐的固件工程師一個提示--但我們往往甚至沒有那么幸運。
這些是需要實時操作系統(tǒng)的系統(tǒng)類型。保證時間最緊迫的任務在必要時總是在運行,并在有空閑時間時將低優(yōu)先級的任務調(diào)度到運行,這是搶占式調(diào)度器的一個強項。在這種類型的設置中,關鍵的傳感器讀數(shù)可以被推到他們自己的任務中,并分配一個高優(yōu)先級--當需要處理傳感器的時候,有效地中斷了系統(tǒng)中的任何其他任務(除了ISR)。那個復雜的通信堆??梢员环峙湟粋€比關鍵傳感器更低的優(yōu)先級。最后,具有花哨動畫的華麗用戶界面得到了剩余的處理器周期。它可以自由地執(zhí)行任意多的滑動阿爾法混合動畫,但只有在處理器沒有其他更好的事情可做的時候。
RTOS任務與超級循環(huán)相比較
到目前為止,我們只是非常隨意地提到了任務,但任務到底是什么?思考任務的簡單方法是,它只是另一個主循環(huán)。在一搶占式RTOS中,任務和超級循環(huán)之間有兩個主要區(qū)別:
- 
每個任務都收到它自己的私有堆棧。與共享系統(tǒng)堆棧的main中的超級循環(huán)不同,任務收到自己的堆棧,系統(tǒng)中的其他任務不會使用。這允許每個任務擁有自己的調(diào)用堆棧,而不干擾其他任務。
 - 
每個任務都有分配給它的優(yōu)先級。這個優(yōu)先級允許調(diào)度員決定哪個任務應該運行(目標是確保系統(tǒng)中最高優(yōu)先級的任務總是在做有用的工作)。
考慮到這兩個特點,每個任務都可以被編程,好像它是處理器唯一要做的事情。你有一個你想看的單一標志和一些計算的華美的動畫要攪和嗎?沒問題:只需對任務進行編程,并給它分配合理的優(yōu)先級,相對于系統(tǒng)的其他功能而言。搶占式調(diào)度器將始終確保最重要的任務在有工作要做時被執(zhí)行。當一個較高優(yōu)先級的任務不再有有用的工作要做,并且它正在等待系統(tǒng)中的其他東西時,較低優(yōu)先級的任務將被切換到上下文并允許運行。
 
用RTOS任務實現(xiàn)并行操作
早些時候,我們看了在三個函數(shù)中循環(huán)的超級循環(huán)?,F(xiàn)在讓我們把這三個函數(shù)中的每一個移到自己的任務中。我們將用這三個簡單的任務來研究以下內(nèi)容:
- 
理論上的任務編程模型: 如何在理論上描述這三個任務
 - 
實際的輪流調(diào)度: 任務在使用輪回調(diào)度算法執(zhí)行時是什么樣子的
 - 
實際的搶占式調(diào)度: 使用搶占式調(diào)度執(zhí)行的任務是什么樣子的?
在現(xiàn)實世界的程序中,每個任務幾乎都沒有單一的函數(shù);我們只是把它作為類似于前面的過于簡單的超級循環(huán)的例子。
 
理論上的任務編程模型
下面是使用超級循環(huán)來執(zhí)行三個函數(shù)的偽代碼。同樣的三個函數(shù)也包含在基于任務的系統(tǒng)中--每個RTOS任務(在右邊)包含與左邊的超級循環(huán)的函數(shù)相同的功能。當我們討論使用超級循環(huán)與使用調(diào)度器的任務驅(qū)動方法時,代碼執(zhí)行方式的差異時,這點將被繼續(xù)使用:

你可能會注意到超級循環(huán)實現(xiàn)和RTOS實現(xiàn)之間的直接區(qū)別是無限的while循環(huán)的數(shù)量。在超級循環(huán)的實現(xiàn)中,只有一個無限的while循環(huán)(在main()中),但是每個任務都有自己的無限的while循環(huán)。
在超級循環(huán)中,在調(diào)用下一個函數(shù)之前,被超級循環(huán)執(zhí)行的三個函數(shù)分別運行到完成,然后循環(huán)繼續(xù)到下一個迭代(如下圖所示):

在RTOS的實現(xiàn)中,每個任務本質(zhì)上是它自己的小的無限的while循環(huán)。超級循環(huán)中的函數(shù)總是一個接一個地被調(diào)用(由超級循環(huán)中的邏輯協(xié)調(diào)),而任務可以簡單地被認為是在調(diào)度器啟動后的所有并行執(zhí)行。下面是一個執(zhí)行三個任務的實時操作系統(tǒng)的圖示:

在圖中,你會注意到每個while循環(huán)的大小是不一樣的。這是使用并行執(zhí)行任務的調(diào)度器相對于超級循環(huán)的許多好處之一--程序員不需要立即關注最長的執(zhí)行循環(huán)的長度會拖累其他更緊密的循環(huán)。圖中描述了任務2的循環(huán)比任務1長很多。在超級循環(huán)系統(tǒng)中,這將導致func1的功能執(zhí)行頻率降低(因為超級循環(huán)需要先執(zhí)行func1,然后是func2,最后是func3)。在基于任務的編程模型中,情況并非如此--每個任務的循環(huán)可以被認為是與系統(tǒng)中的其他任務隔離的--而且它們都是并行運行的。
這種隔離和感知的并行執(zhí)行是使用實時操作系統(tǒng)的一些好處;它為程序員減輕了一些復雜性。所以,這是概念化任務的最簡單的方法--它們只是獨立的無限的while循環(huán),都是平行執(zhí)行的......在理論上。在現(xiàn)實中,事情并沒有這么簡單。在接下來的兩節(jié)中,我們將瞥見幕后發(fā)生的事情,使其看起來像是任務在并行執(zhí)行。
循環(huán)排程
概念化實際任務執(zhí)行的最簡單方法之一是輪流調(diào)度。在輪流調(diào)度中,每個任務得到一小塊時間來使用處理器,這是由調(diào)度器控制的。只要任務有工作要執(zhí)行,它就會執(zhí)行。就該任務而言,它完全擁有自己的處理器。調(diào)度器負責處理為下一個任務切換適當上下文的所有復雜問題:

這和之前展示的三個任務是一樣的,只不過不是理論上的概念化,而是通過任務的循環(huán)的每一次迭代都是隨著時間的推移而列舉的。因為循環(huán)調(diào)度器給每個任務分配了相等的時間片,最短的任務(任務1)已經(jīng)執(zhí)行了將近六次循環(huán),而最慢的循環(huán)任務(任務2)只完成了第一次循環(huán)。任務3已經(jīng)執(zhí)行了三次循環(huán)。
執(zhí)行相同函數(shù)的超級循環(huán)與執(zhí)行這些函數(shù)的輪回調(diào)度例程之間的一個極其重要的區(qū)別是這樣的: 任務3在任務2之前完成了其適度緊密的循環(huán)。當超級循環(huán)以串行方式運行函數(shù)時,函數(shù)3甚至不會開始,直到函數(shù)2運行完成。所以,雖然調(diào)度器沒有為我們提供真正的并行性,但每個任務都得到了它公平的CPU周期份額。所以,在這種調(diào)度方案下,如果任務有較短的循環(huán),它將比有較長循環(huán)的任務執(zhí)行得更頻繁。
所有這些切換都有一個(輕微的)代價--調(diào)度器需要在任何有上下文切換的時候被調(diào)用。在這個例子中,任務沒有明確地調(diào)用調(diào)度器來運行。在FreeRTOS運行在ARM Cortex-M上的情況下,調(diào)度器將被從SysTick中斷中調(diào)用(更多細節(jié)可在第7章FreeRTOS調(diào)度器中找到)。為了確保調(diào)度器內(nèi)核非常有效,并盡可能地減少運行時間,我們付出了相當多的努力。然而,事實是,它將在某些時候運行并消耗CPU周期。在大多數(shù)系統(tǒng)中,少量的開銷通常不會被注意到(或顯著),但在某些系統(tǒng)中,它可能成為問題。例如,如果一個設計處于可行性的極端邊緣,因為它有非常嚴格的時間要求和非常少的空閑CPU周期,如果超級循環(huán)/中斷方法已經(jīng)被仔細地描述和優(yōu)化,那么增加的開銷可能是不可取的(或者完全有必要)。然而,最好是盡可能地避免這種類型的情況,因為即使是在中等復雜的系統(tǒng)中,忽視中斷堆積(或嵌套條件語句偶爾需要更長的時間)并導致系統(tǒng)錯過最后期限的可能性也是非常大的。
基于搶占式的調(diào)度
搶占式調(diào)度提供了一種機制,以確保系統(tǒng)總是在執(zhí)行其最重要的任務。搶占式調(diào)度算法將優(yōu)先考慮最重要的任務,不管系統(tǒng)中還有什么事情發(fā)生--除了中斷,因為它們發(fā)生在調(diào)度器下面,總是有更高的優(yōu)先級。這聽起來非常直接--而且確實如此--只是有一些細節(jié)需要考慮到。
讓我們看一下同樣的三個任務。這三個任務都有相同的功能:簡單的while循環(huán),無休止地增加不穩(wěn)定的變量。
現(xiàn)在,考慮以下三種情況,看看這三個任務中哪個會得到上下文。下圖中的任務與之前介紹的輪流調(diào)度的任務相同。三個任務中都有足夠多的工作要做,這將防止任務脫離上下文:

那么,當三個不同的任務被設置為三組不同的優(yōu)先級(A、B、C)時會發(fā)生什么?
A(左上角): 任務1在系統(tǒng)中擁有最高的優(yōu)先級--它獲得了所有的處理器時間 不管任務1執(zhí)行了多少次迭代,如果它是系統(tǒng)中優(yōu)先級最高的任務,并且它有工作要做(不需要等待系統(tǒng)中的其他東西),它將被賦予上下文并運行。
B(右上方): 任務2是系統(tǒng)中優(yōu)先級最高的任務。由于它有足夠多的工作要做,不需要等待系統(tǒng)中的其他東西,任務2將被賦予上下文。由于任務2被配置為系統(tǒng)中的最高優(yōu)先級,它將執(zhí)行,直到它需要在系統(tǒng)中等待其他東西。
C(左下角): 任務3被配置為系統(tǒng)中的最高優(yōu)先級任務。沒有其他任務運行,因為它們的優(yōu)先級較低。
現(xiàn)在,很明顯,如果你真的在設計一個需要多個任務并行運行的系統(tǒng),如果系統(tǒng)中所有的任務都需要100%的CPU時間,并且不需要等待任何東西,那么搶占式調(diào)度器就沒有什么用了。這種設置對于實時系統(tǒng)來說也不是很好的設計,因為它完全超載了(而且忽略了系統(tǒng)所要執(zhí)行的三個主要功能中的兩個)!這種情況被稱為 "任務"!所呈現(xiàn)的情況被稱為任務饑餓,因為只有系統(tǒng)中優(yōu)先級最高的任務獲得了CPU時間,而其他任務則被剝奪了處理器時間。
另一個值得指出的細節(jié)是,調(diào)度器仍然以預定的時間間隔運行。無論系統(tǒng)中發(fā)生了什么,調(diào)度器都會勤奮地按照預定的時間間隔運行。
這有一個例外。FreeRTOS有無滴答的調(diào)度器模式,旨在用于極低功率的設備,它可以防止調(diào)度器在相同的預定間隔內(nèi)運行。
這里顯示了一個使用搶占式調(diào)度器的更現(xiàn)實的用例:

在這種情況下,任務1是系統(tǒng)中優(yōu)先級最高的任務(它也恰好很快完成執(zhí)行)--任務1只有在調(diào)度器需要運行的時候才會被剝奪上下文;否則,它將保持上下文直到?jīng)]有任何額外的工作要執(zhí)行。
任務2是下一個最高優(yōu)先級的任務--你也會注意到,這個任務被設置為在每個RTOS調(diào)度器的勾選中執(zhí)行一次(由向下的箭頭表示)。任務3是系統(tǒng)中優(yōu)先級最低的任務:只有當系統(tǒng)中沒有其他值得做的事情時,它才會得到上下文。在這張圖中,有三個要點值得關注:
A:任務2有上下文。即使它被調(diào)度器打斷了,但在調(diào)度器運行后,它又立即得到了上下文(因為它還有工作要做)。
B: 任務2已經(jīng)完成了迭代0的工作,調(diào)度器已經(jīng)運行并確定(因為系統(tǒng)中沒有其他任務需要運行)任務3可以擁有處理器時間。
C:任務2已經(jīng)開始運行迭代4,但是任務1現(xiàn)在有一些工作要做--盡管任務2還沒有完成該迭代的工作。任務1立即被調(diào)度器切換到執(zhí)行其更高優(yōu)先級的工作。在任務1完成了它需要做的事情后,任務2被切換回來完成迭代4。這一次,迭代運行到下一個tick,任務2再次運行(迭代5)。在任務2迭代5完成后,沒有更高優(yōu)先級的工作要執(zhí)行,所以系統(tǒng)中最低優(yōu)先級的任務(任務3)再次運行??雌饋砣蝿?終于完成了迭代0,所以它進入了迭代1并繼續(xù)運行......。
希望你還在聽我說! 如果沒有,那也沒關系,因為這只是一個非常抽象的例子。關鍵的啟示是,系統(tǒng)中優(yōu)先級最高的任務是優(yōu)先的。
RTOS任務與超級循環(huán)的對比--利與弊
超級循環(huán)對于責任有限的簡單系統(tǒng)是非常好的。如果系統(tǒng)足夠簡單,它們可以在響應事件時提供非常低的抖動,但前提是循環(huán)足夠緊密。隨著系統(tǒng)越來越復雜,獲得更多的責任,輪詢率會下降。這種輪詢率的降低導致對事件的響應抖動大得多。中斷可以被引入到系統(tǒng)中,以應對抖動的增加。隨著基于超級循環(huán)的系統(tǒng)變得越來越復雜,跟蹤和保證對事件的反應能力變得越來越難。
對于那些不僅有耗時的任務,而且還需要對外部事件有良好的響應能力的更復雜的系統(tǒng),實時操作系統(tǒng)變得非常有價值。對于實時操作系統(tǒng)來說,系統(tǒng)復雜性、ROM、RAM和初始設置時間的增加是為了換取一個更容易理解的系統(tǒng),它可以更容易地保證對外部事件的及時響應。
                        電子發(fā)燒友App
                    
                
                
          
        
        












           
            
            
                
            
評論