避免優先權倒置

本文介紹了 Android 的音訊系統如何嘗試避免優先反轉,並重點介紹了您也可以使用的技術。

這些技術對於高效能音訊應用程式的開發人員、OEM 和實現音訊 HAL 的 SoC 供應商可能很有用。請注意,實施這些技術並不能保證防止故障或其他故障,特別是在音訊上下文之外使用時。您的結果可能會有所不同,您應該進行自己的評估和測試。

背景

Android AudioFlinger 音訊伺服器和 AudioTrack/AudioRecord 用戶端實作正在重新架構以減少延遲。這項工作從 Android 4.1 開始,並在 4.2、4.3、4.4 和 5.0 中繼續進行進一步改進。

為了實現更低的延遲,整個系統需要進行許多更改。一項重要的變化是透過更可預測的調度策略將 CPU 資源分配給時間關鍵的執行緒。可靠的調度允許減少音訊緩衝區大小和計數,同時仍避免欠載和溢出。

優先權反轉

優先權倒置是即時系統的經典故障模式,其中較高優先權的任務被無限期地阻塞,等待較低優先權的任務釋放資源,例如互斥鎖(受共享狀態保護)。

在音訊系統中,優先權倒置通常表現為故障(喀嚓聲、彈出聲、遺失)、使用循環緩衝區時重複音訊或回應指令的延遲。

優先反轉的常見解決方法是增加音訊緩衝區大小。然而,這種方法會增加延遲,並且只是隱藏問題而不是解決問題。最好理解並防止優先權反轉,如下所示。

在Android音訊實作中,優先權反轉最容易發生在這些地方。所以你應該把注意力集中在這裡:

  • AudioFlinger 中的普通混音器線程和快速混音器線程之間
  • 快速 AudioTrack 的應用程式回呼線程和快速混音器線程之間(它們都具有較高的優先級,但優先順序略有不同)
  • 快速 AudioRecord 的應用程式回呼線程和快速捕獲線程之間(與之前類似)
  • 在音訊硬體抽象層 (HAL) 實作中,例如用於電話或迴聲消除
  • 在核心的音訊驅動程式中
  • AudioTrack 或 AudioRecord 回呼線程與其他應用程式線程之間(這是我們無法控制的)

常見解決方案

典型的解決方案包括:

  • 禁用中斷
  • 優先權繼承互斥體

停用中斷在 Linux 使用者空間中不可行,且不適用於對稱多處理器 (SMP)。

音訊系統中不使用優先權繼承互斥體(快速使用者空間互斥體),因為它們相對重量級,並且依賴受信任的客戶端。

Android 使用的技術

實驗從「嘗試鎖定」和超時鎖定開始。這些是互斥鎖操作的非阻塞和有界阻塞變體。嘗試鎖定和超時鎖定效果相當好,但容易受到一些模糊故障模式的影響:如果客戶端恰好很忙,則不能保證伺服器能夠存取共享狀態,並且如果出現以下情況,累積超時可能會太長:一長串不相關的鎖都超時了。

我們也使用原子操作,例如:

  • 增量
  • 按位“或”
  • 按位“與”

所有這些都傳回先前的值並包括必要的 SMP 屏障。缺點是它們可能需要無限制的重試。在實踐中,我們發現重試不是問題。

注意:眾所周知,原子操作及其與記憶體屏障的交互作用被嚴重誤解和錯誤使用。為了完整起見,我們在此處提供了這些方法,但建議您也閱讀文章SMP Primer for Android以獲取更多資訊。

我們仍然擁有並使用上述大部分工具,並且最近添加了這些技術:

  • 對資料使用非阻塞單一讀取器單寫入器FIFO 佇列
  • 嘗試在高優先權模組和低優先權模組之間複製狀態而不是共用狀態。
  • 當狀態確實需要共享時,將狀態限制為可以在單一總線操作中原子存取而無需重試的最大大小。
  • 對於複雜的多字狀態,請使用狀態佇列。狀態佇列基本上只是一個非阻塞單讀取器單寫入器 FIFO 佇列,用於狀態而不是數據,除非寫入器將相鄰推送折疊為單一推送。
  • 注意內存屏障以保證 SMP 的正確性。
  • 信任但要驗證。在行程之間共用狀態時,不要假設狀態是格式良好的。例如,檢查索引是否在範圍內。同一進程中的執行緒之間、相互信任的進程(通常具有相同的 UID)之間不需要此驗證。對於諸如 PCM 音訊之類的共享資料來說,也沒有必要這樣做,因為損壞是無關緊要的。

非阻塞演算法

非阻塞演算法一直是最近研究的主題。但除了單一讀取器單寫入器 FIFO 佇列之外,我們發現它們都很複雜且容易出錯。

從 Android 4.2 開始,您可以在以下位置找到我們的非阻塞單讀取器/寫入器類別:

  • 框架/av/include/media/nbaio/
  • 框架/av/media/libnbaio/
  • 框架/av/服務/audioflinger/StateQueue*

這些是專門為 AudioFlinger 設計的,不是通用目的。非阻塞演算法因難以調試而臭名昭著。您可以將此程式碼視為模型。但請注意,可能存在錯誤,並且不保證這些類別適合其他用途。

對於開發人員來說,一些範例 OpenSL ES 應用程式程式碼應更新為使用非阻塞演算法或引用非 Android 開源程式庫。

我們發布了一個專門為應用程式程式碼設計的非阻塞 FIFO 實作範例。請參閱位於平台來源目錄frameworks/av/audio_utils中的這些檔案:

工具

據我們所知,沒有自動工具可以找到優先權倒置,尤其是在優先權倒置發生之前。如果能夠存取整個程式碼庫,一些研究靜態程式碼分析工具就能夠找到優先反轉。當然,如果涉及任意使用者程式碼(因為它是針對應用程式的)或大型程式碼庫(對於 Linux 核心和裝置驅動程式),則靜態分析可能不切實際。最重要的是仔細閱讀程式碼並充分掌握整個系統和互動。 systraceps -t -p等工具對於查看優先反轉發生後非常有用,但不會提前告訴您。

最後一句話

經過所有這些討論後,不要害怕互斥體。當在普通的非時間關鍵用例中正確使用和實現時,互斥體是您日常使用的朋友。但在高優先級和低優先級任務之間以及時間敏感的系統中,互斥體更有可能造成麻煩。