避免優先順序倒置

本文將說明 Android 音訊系統如何嘗試避免優先順序反轉,並強調您也可以使用的技巧。

這些技巧可能對開發高效音訊應用程式的開發人員、OEM 和 SoC 供應商 (正在實作音訊 HAL) 有所助益。請注意,實作這些技巧並不能保證能避免發生錯誤或其他失敗情形,尤其是在音訊內容以外的情況下。結果可能因人而異,請自行評估及測試。

背景

我們正在重新設計 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)。

由於優先順序繼承 futex (快速使用者空間互斥鎖) 相對較重,且需要信任的用戶端,因此不會用於音訊系統。

Android 使用的技巧

實驗會以「try lock」開始,並以逾時鎖定。這些是互斥鎖定作業的非阻斷和受限阻斷變體。嘗試鎖定和設有逾時期限的鎖定功能運作得相當良好,但容易發生幾種不易察覺的失敗模式:如果用戶端恰好忙碌,伺服器無法保證能夠存取共用狀態;如果有長串的無關鎖定都逾時,累積的逾時期限可能會過長。

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

  • 增加
  • 位元「or」
  • 位元「and」

所有這些都會傳回先前的值,並包含必要的 SMP 障礙。缺點是可能需要無限次重試。實際上,我們發現重試並不會造成問題。

注意:不可拆分的作業及其與記憶體屏障的互動,向來都很容易遭到誤解和誤用。我們在此提供這些方法,以便完整說明,但建議您也閱讀「 Android 適用的 SMP 入門指南」一文,進一步瞭解相關資訊。

我們仍保留並使用上述大部分工具,並最近新增了以下技術:

  • 請為資料使用非阻斷的單一讀取器單一寫入器 FIFO 佇列
  • 請嘗試複製狀態,而非在高優先順序和低優先順序模組之間共用狀態。
  • 當需要共用狀態時,請將狀態限制為最大大小的,以便在單一匯流排作業中以原子方式存取,且無需重試。
  • 如果是複雜的多字狀態,請使用狀態佇列。狀態佇列基本上只是一個非阻斷的單讀取器單寫入 FIFO 佇列,用於狀態而非資料,但寫入器會將相鄰的推送內容合併為單一推送內容。
  • 請留意記憶體屏障,確保 SMP 正確無誤。
  • 信任但仍須驗證。在程序之間共用狀態時,請勿假設狀態已正確格式化。例如,檢查索引是否在範圍內。在同一個程序的執行緒之間,以及在互相信任的程序 (通常具有相同的 UID) 之間,則不需要進行這項驗證。對於共用資料 (例如 PCM 音訊,因為損毀情形不重要),也無需進行壓縮。

非阻斷式演算法

非阻斷演算法是近期許多研究的主題。不過,除了單一讀取器單一寫入 FIFO 佇列外,我們發現這些佇列都很複雜且容易出錯。

自 Android 4.2 起,您可以在下列位置找到非阻斷的單一讀取/寫入類別:

  • frameworks/av/include/media/nbaio/
  • frameworks/av/media/libnbaio/
  • frameworks/av/services/audioflinger/StateQueue*

這些工具專為 AudioFlinger 設計,並非通用工具。非阻斷式演算法因難以偵錯而惡名昭彰。您可以將這段程式碼視為模型。但請注意,這些類別可能會有錯誤,且不保證適合用於其他用途。

開發人員應更新部分 OpenSL ES 應用程式範例程式碼,以便使用非阻斷演算法或參照非 Android 開放原始碼程式庫。

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

工具

據我們所知,目前沒有任何自動工具可找出優先順序倒置問題,尤其是在問題發生前。部分研究靜態程式碼分析工具可在能存取整個程式碼集的情況下,找出優先順序倒置情形。當然,如果涉及任意使用者程式碼 (如應用程式) 或大型程式碼庫 (如 Linux 核心和裝置驅動程式),靜態分析可能就不適用。最重要的是仔細閱讀程式碼,並充分掌握整個系統和互動情形。systraceps -t -p 等工具可在發生優先順序反轉後,讓您查看相關資訊,但不會事先通知您。

最後一句話

在討論結束後,請不要害怕互斥鎖。在一般非時間敏感的用途中,如果正確使用及實作互斥鎖,它們就是一般用途的好幫手。但在高優先順序和低優先順序工作之間,以及在時間敏感的系統中,Mutex 更有可能造成問題。