本書的原著為:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,講解的是嵌入式系統設計模式,是一本不可多得的好書。
本系列描述我對書中內容的理解。本文章描述嵌入式并發和資源管理模式之五:保護調用模式
保護調用模式
(Guarded Call Pattern) 是一種 任務協作模式
。在軟件設計中,任務協作模式是用于協調不同任務之間通訊和同步的策略。它旨在確保任務能夠高效、有序地執行,并處理任務之間的依賴關系、優先級沖突和資源共享等問題。
保護調用模式用于 序列化
對某些服務的訪問。當多個調用者同時調用這些服務時,可能會以某種方式產生干擾。所以不能直接調用這些服務,而是通過提供鎖定機制來序列化訪問,防止其他線程在鎖定期間調用這些服務。具有這套保護機制的調用服務,稱為 保護調用模式
。簡而言之,此模式通過 鎖
來確保同時只有一個線程能夠使用特定資源或服務,從而避免潛在的并發問題。
摘要
在搶占式多任務環境中,保護調用模式
使用 信號量
來保護一組相關的函數。這些函數組合起來提供某種 服務
。這樣就能防止多個客戶端同時訪問這些服務,這個過程被稱為 互斥
。
然而,如果不與其它模式混合使用,這種模式可能會導致不受控制的優先級翻轉。
問題
該模式解決的是線程之間的同步或數據交換問題。在這種情況下,可能無法等待 異步會合
,可以更及時地進行 同步會合
,但必須小心進行,以避免數據損壞和計算錯誤。
異步
和同步
:異步是一種處理并發操作的方法,允許程序在等待某些操作完成(如 I/O 操作)的同時,繼續執行其他任務。這種方法與同步操作相對,同步操作會阻塞程序執行,直到等待的操作完成為止。比如,用隊列
來實現異步,用函數調用來實現同步。
會合
:即同步點,任務的動作序列中的一個特定動作,任務會在此等待,直到其他任務也達到相應的同步點。
在異步環境中,線程之間的會合可能是不確定的,因此等待異步會合可能不是一種可靠或高效的方法。因此書中說
保護調用模式
可能不適用于異步會合。
模式結構
模式結構如下圖所示:
在這種情況下,多個 搶占式任務
(PreemptiveTasks)通過資源模塊提供函數訪問 受保護資源
(GuardedResource)。在這些函數內部,會調用 鎖定
和 釋放
資源的操作。調度器支持阻塞,當任務調用已被鎖定的信號量時,將其放置在阻塞隊列中,并在該信號量釋放時解除其阻塞。調度器必須將信號量的 lock()
函數實現為 臨界區
,以消除競態條件的可能性。
模式詳情
受保護的資源
受保護的資源
是一個共享資源,它使用互斥信號量來保護自己提供的函數,強制調用者互斥訪問。在它提供的函數內部,當訪問之前,會調用關聯的 信號量
實例的 lock()
函數。如果信號量處于未鎖定狀態,則該資源被鎖定;如果資源已處于鎖定狀態,則信號量會通知 靜態優先級調度器
阻塞當前正在運行的任務。重要的是,特定資源實例的相關函數必須共享同一個信號量類的實例。這確保了它們作為一個單元受到保護,防止多個 搶占式任務
同時訪問。
特定資源實例的相關函數必須共享同一個信號量類的實例
:這是確保資源正確保護的關鍵。如果每個函數都有自己的信號量實例,那么它們就無法有效地協調對共享資源的訪問。相反,通過共享同一個信號量實例,它們可以確保在任何時候只有一個任務能夠訪問該資源。
搶占式任務
搶占式任務
表示一個通過搶占式多任務調度器運行的任務。它通過調用 受保護的資源
的函數來訪問該資源,而這些函數又受到信號量的保護。這樣確保了搶占式任務在訪問受保護的資源時不會受到其他任務的干擾,從而保證了數據的一致性和系統的穩定性。
信號量
這里 信號量
使用的是互斥信號量,用于 序列化
對 受保護的資源
的訪問。受保護的資源的受保護函數在被調用時會調用信號量的 lock()
函數,并在服務完成后調用 release()
函數。當關聯的信號量被鎖定時,嘗試調用服務的其他客戶端線程將被阻塞,直到信號量解鎖。這個元素通常由 RTOS(實時操作系統)提供。
效果
保護調用模式提供了對資源的及時訪問,并通過鎖定機制防止了多個同時訪問,這些同時訪問可能會導致數據損壞或系統錯誤行為。當資源未被鎖定時,訪問可以立即進行,沒有任何延遲,從而保證了系統的實時性。如果資源被鎖定,調用者必須等待鎖釋放,這可能導致調用者被阻塞一段時間。然而,如果不當地使用這種模式,可能會導致不受控制的優先級反轉。
實現策略
實現 保護調用模式
的關鍵部分在于 互斥量
的實現。通常,RTOS 會提供這個信號量(互斥量是信號量的一種)。一般會有以下操作:
- 創建一個信號量
- 銷毀一個信號量
- 鎖定信號量
- 釋放信號量
相關模式
保護調用模式在搶占式多任務環境中使用,比如使用 靜態優先級調度器
的環境。與 臨界區模式 相比,它不會干擾不需要訪問資源的更高優先級任務的執行;與 隊列模式 相比,它響應更迅速,因為當資源有效時,操作系統會解除等待資源的任務。
這個模式有一類非常重要的變體,它使用 優先級繼承
的概念來解決 不受控制的優先級反轉
的問題。基本思想是,每個資源都有一個額外的屬性(變量),稱為優先級上限(priority ceiling),它等于能夠訪問該資源的最高優先級任務的優先級。
注:實際上,目前主流的 RTOS 提供的互斥量都是具有優先級繼承的。
為什么要使用具有優先級繼承的互斥量來實現保護調用模式?
不使用具有優先級繼承的互斥量來實現保護調用模式,稱為 原始的保護調用模式
,它存在一個根本的問題是:會產生不受控制的優先級反轉
問題。即,不需要該資源的中等優先級任務可以搶占當前擁有被阻塞的高優先級任務所需資源的低優先級任務。這樣的中等優先級任務可能有任意多個,導致高優先級任務被連鎖阻塞。這句話可能很繞,我來舉一個例子:
如下圖所示,任務 A 和任務 Z 會使用資源 R ,而任務 X 和任務 Y 并不直接使用資源 R,但它們會影響任務 A 和任務 Z 的執行。
最壞情況如下所示:
- 任務 Z 首先運行并鎖定了資源 R。
- 任務 A 開始運行,任務 A 優先級更高,它立即搶占任務 Z。任務 A 運行到需要訪問資源 R 的時候,因得不到資源 R 而被阻塞。
- 此時,任務 Z 繼續運行
- 但緊接著任務 Y 開始運行,由于任務 Y 優先級高于任務 Z,因此它立即搶占任務 Z。
- 在任務 Y 運行期間,任務 X 變得可以運行,并且由于任務 X 的優先級高于任務 Y,因此它立即搶占任務 Y。
這樣,任務 A 被三個低優先級的任務(Z、Y 和 X)所阻塞,無法訪問所需的資源 R。
由于任務 Y 和任務 X 的執行,最壞情況下任務 A 需要 190 ms ( 任務 Z 鎖定資源 10 ms + 任務 Y 執行時間 100 ms + 任務 X 執行時間 80 ms)才能獲得資源 R 。這導致了任務 A 錯過其截止時間,因為它無法在 50ms 的周期內完成任務。這種現象稱為 不受控制的優先級反轉
,是一個嚴重的問題,因為它可能導致高優先級任務無法及時完成,從而影響系統的實時性能和可靠性。
還是上面圖中所示的情況,我們再來看一下使用 優先級繼承
機制后,帶來的變化:
- 任務 Z 首先運行并鎖定了資源 R。
- 任務 A 開始運行,任務 A 優先級更高,它立即搶占任務 Z。任務 A 運行到需要訪問資源 R 的時候,因得不到資源 R 而被阻塞。此時,任務 Z 的優先級會被
提升
到任務 A 的優先級,即優先級為 1 。 - 此時,任務 Z 繼續運行
- 緊接著,任務 Y 處于就緒狀態,但是由于任務 Z 的優先級已經提升到和任務 A 相同的優先級,因此任務 Y 無法搶占任務 Z 的執行。
- 相同的原因,就緒狀態的任務 X 同樣無法搶占任務 Z 的執行。
- 任務 Z 完成了對資源 R 的使用并解鎖了它。在優先級繼承機制下,當任務Z釋放資源R時,它的優先級將從提升的優先級(=1)降低回其原始優先級(=99)。
- 任務 A 會立即解除阻塞,并獲得資源 R 的使用權。任務 A 可以在截止時間之前完成任務。
通過這個簡單的例子,我們可以看到優先級繼承如何防止了不受控制的優先級反轉的問題,這是普通的計數信號量所無法避免的。
實例
見原書。
讀后有收獲,資助博主養娃 - 千金難買知識,但可以買好多奶粉 (〃‘▽’〃)
、