避免優先級倒置

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

這些技術可能對正在實施音頻 HAL 的高性能音頻應用程序、OEM 和 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)。

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

Android 使用的技術

實驗以“嘗試鎖定”和超時鎖定開始。這些是互斥鎖操作的非阻塞和有界阻塞變體。嘗試鎖定和超時鎖定工作得相當好,但容易受到一些模糊的故障模式的影響:如果客戶端碰巧很忙,則不能保證服務器能夠訪問共享狀態,並且累積超時可能太長,如果有一長串不相關的鎖都超時了。

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

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

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

注意:原子操作及其與內存屏障的交互是出了名的嚴重誤解和錯誤使用。為了完整起見,我們在此處包含這些方法,但建議您也閱讀文章SMP Primer for Android以獲取更多信息。

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

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

非阻塞算法

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

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

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

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

對於開發人員,應更新一些示例 OpenSL ES 應用程序代碼以使用非阻塞算法或引用非 Android 開源庫。

我們發布了一個專為應用程序代碼設計的示例非阻塞 FIFO 實現。請參閱位於平台源目錄frameworks/av/audio_utils中的這些文件:

工具

據我們所知,沒有自動工具可以找到優先級反轉,尤其是在它發生之前。如果能夠訪問整個代碼庫,一些研究靜態代碼分析工具能夠找到優先級反轉。當然,如果涉及到任意用戶代碼(因為它在這裡是針對應用程序的)或者是一個大型代碼庫(針對 Linux 內核和設備驅動程序),靜態分析可能是不切實際的。最重要的是要非常仔細地閱讀代碼並很好地掌握整個系統和交互。 systraceps -t -p之類的工具對於在發生優先級反轉後查看它很有用,但不要提前告訴您。

最後一句話

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