Unikaj odwrócenia priorytetów

Z tego artykułu dowiesz się, jak system audio Androida próbuje unikać odwrócenia priorytetów, oraz poznasz techniki, których możesz użyć.

Te techniki mogą być przydatne dla deweloperów aplikacji audio o wysokiej wydajności, OEM-ów i dostawców SoC, którzy wdrażają interfejs HAL dźwięku. Pamiętaj, że zastosowanie tych technik nie gwarantuje zapobiegania problemom z glitchami ani innym błędom, zwłaszcza jeśli są one używane poza kontekstem audio. Wyniki mogą się różnić. Należy przeprowadzić własną ocenę i testy.

Tło

Aby zmniejszyć opóźnienia, zmieniamy architekturę serwera audio AudioFlinger i klienta AudioTrack/AudioRecord na Androidzie. Te działania rozpoczęły się w Androidzie 4.1 i ciągnęły się przez kolejne wersje: 4.2, 4.3, 4.4 i 5.0.

Aby osiągnąć tak niskie opóźnienie, konieczne było wprowadzenie wielu zmian w całym systemie. Jedną z ważnych zmian jest przypisywanie zasobów procesora do wątków o krytycznym znaczeniu czasowym przy użyciu bardziej przewidywalnej polityki planowania. Niezawodne harmonogramowanie pozwala zmniejszyć rozmiary i liczby buforów dźwięku, a jednocześnie unikać niedoborów i przekroczeń.

odwrócenie priorytetów.

Odwrócenie priorytetów to klasyczny sposób działania systemów w czasie rzeczywistym, w którym zadanie o wyższym priorytecie jest blokowane przez nieograniczony czas, czekając na uwolnienie zasobu przez zadanie o niższym priorytecie, np. przez muteks.

W systemie audio odwrócenie priorytetów zwykle objawia się jako glitch (kliknięcie, szum, przerwa), powtarzany dźwięk, gdy używane są bufory pętli, lub opóźnienie w odpowiedzi na polecenie.

Typowym obejściem problemu odwrócenia priorytetów jest zwiększenie rozmiarów buforów audio. Ta metoda zwiększa jednak opóźnienia i tylko ukrywa problem, a nie go rozwiązuje. Lepiej jest zrozumieć i zapobiegać odwróceniu priorytetów, jak widać poniżej.

W implementacji dźwięku na Androidzie odwrócenie priorytetów jest najbardziej prawdopodobne w tych miejscach. Na co więc należy zwrócić uwagę:

  • między wątkiem normalnego miksera a wątkiem szybkiego miksera w AudioFlinger
  • między wątkiem wywołania aplikacji dla szybkiego AudioTrack i szybkiego wątku miksera (oba mają podwyższony priorytet, ale nieco inne priorytety)
  • między wątkiem wywołania zwrotnego aplikacji dla szybkiego AudioRecord a wątkiem szybkiego przechwytywania (podobny do poprzedniego)
  • w ramach implementacji warstwy abstrakcji sprzętowej (HAL) dla dźwięku, np. w przypadku telefonii lub eliminacji echa;
  • w sterowniku audio w rdzeniu.
  • między wątkiem wywołania zwrotnego AudioTrack lub AudioRecord a innymi wątkami aplikacji (nie mamy na to wpływu);

Typowe rozwiązania

Typowe rozwiązania:

  • wyłączanie przerw
  • blokady dziedziczenia priorytetów,

Wyłączenie przerwań nie jest możliwe w przestrzeni użytkownika Linuksa i nie działa w przypadku procesorów SMP (Symmetric Multi-Processor).

Dziedziczenie priorytetów futexes(szybkie semafory w przestrzeni użytkownika) nie są używane w systemie audio, ponieważ są stosunkowo ciężkie i zależą od zaufanego klienta.

Techniki używane przez Androida

Eksperymenty rozpoczęte z „próbą zablokowania” i blokowaniem z czasem oczekiwania. Są to warianty operacji blokowania mutexa w wersji nieblokującej i z ograniczonym blokowaniem. Try lock i lock with timeout działały całkiem nieźle, ale były podatne na kilka niejasnych trybów awarii: serwer nie miał gwarancji dostępu do współdzielonego stanu, jeśli klient był zajęty, a czas oczekiwania mógł być zbyt długi, jeśli miała miejsce długa sekwencja niezwiązanych blokad, które wszystkie miały ustawiony czas oczekiwania.

Używamy też operacji bezwarunkowych, takich jak:

  • zwiększ
  • bitowa „lub”.
  • bitowe „i”

Wszystkie zwracają poprzednią wartość i obejmują niezbędne bariery SMP. Ich wadą jest to, że mogą wymagać nieograniczonej liczby prób. W praktyce okazało się, że ponowne próby nie stanowią problemu.

Uwaga: operacje atomowe i ich interakcje z barierami pamięci są często źle rozumiane i używane nieprawidłowo. Podajemy te metody ze względu na ich wszechstronność, ale zachęcamy też do przeczytania artykułu SMP Primer for Android (Wprowadzenie do SMP na Androida), aby uzyskać więcej informacji.

Nadal używamy większości wymienionych wyżej narzędzi, a niedawno dodaliśmy te techniki:

  • Do danych należy używać nieblokujących kolejek FIFO z jednym czytnikiem i jednym zapisem.
  • Spróbuj kopiować stan, zamiast udostępniać stan między modułami o wysokim i niskim priorytecie.
  • Jeśli stan musi być udostępniany, ogranicz go do słowa o maksymalnym rozmiarze, do którego można uzyskać dostęp w ramach operacji jednoprzewodowej bez ponownych prób.
  • W przypadku złożonego stanu wielowyrazowego użyj kolejki stanów. Kolejka stanów to w podstawie nieblokująca kolejka FIFO z jednym czytnikiem i jednym zapisującym, używana do stanu, a nie danych. Różnica polega na tym, że zapisujący łączy sąsiednie przesunięcia w pojedyncze przesunięcie.
  • Zwróć uwagę na bariery pamięci, które mogą wpływać na poprawność SMP.
  • Zaufaj, ale zweryfikuj. Podczas udostępniania stanu między procesami nie zakładaj, że stan jest poprawnie sformułowany. Sprawdź na przykład, czy indeksy są w zadanych granicach. Ta weryfikacja nie jest potrzebna w przypadku wątków w tym samym procesie ani w procesach, które wzajemnie sobie ufają (i które zazwyczaj mają ten sam identyfikator UID). Nie jest to też konieczne w przypadku udostępnianych danych, takich jak dźwięk w formacie PCM, gdzie uszkodzenie nie ma znaczenia.

Algorytmy nieblokujące

Algorytmy nieblokujące były ostatnio przedmiotem wielu badań. Jednak z wyjątkiem kolejek FIFO z jednym czytnikiem i jednym zapisem okazały się one skomplikowane i podatne na błędy.

Począwszy od Androida 4.2, nasze nieblokujące klasy jednoczytnika/jednopisarza znajdziesz w tych lokalizacjach:

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

Te funkcje zostały zaprojektowane specjalnie dla AudioFlingera i nie są przeznaczone do ogólnego użytku. Algorytmy niezablokowujące są znane z trudnego debugowania. Ten kod możesz traktować jako model. Pamiętaj jednak, że mogą wystąpić błędy, a klasy niekoniecznie będą odpowiednie do innych celów.

Deweloperzy powinni zaktualizować część przykładowego kodu aplikacji OpenSL ES, aby używać algorytmów niezablokowanych lub odwoływać się do biblioteki open source innej niż Android.

Opublikowaliśmy przykładową implementację FIFO bez blokowania, która została zaprojektowana specjalnie na potrzeby kodu aplikacji. W katalogu źródłowym platformy frameworks/av/audio_utils znajdziesz te pliki:

Narzędzia

O ile nam wiadomo, nie ma automatycznych narzędzi do wykrywania odwrócenia priorytetów, zwłaszcza zanim się ono pojawi. Niektóre narzędzia do analizy kodu statycznego mogą znajdować priorytetowe inwersje, jeśli mają dostęp do całego kodu źródłowego. Oczywiście, jeśli chodzi o dowolny kod użytkownika (jak w przypadku aplikacji) lub dużą bazę kodu (jak w przypadku jądra Linux i sterowników urządzeń), analiza statyczna może być niepraktyczna. Najważniejsze jest bardzo uważne przeczytanie kodu i dokładne zrozumienie całego systemu oraz interakcji. Narzędzia takie jak systrace i ps -t -p są przydatne do sprawdzania odwrócenia priorytetów po jego wystąpieniu, ale nie informują o tym z wyprzedzeniem.

Na koniec

Po tej dyskusji nie bój się już więcej semaforów. Blokady mutli są przydatne w przypadku zwykłego użycia, gdy są prawidłowo używane i wdrażane w zwykłych przypadkach użycia, które nie są krytyczne pod względem czasu. Jednak w przypadku zadań o wysokim i niskim priorytecie oraz w systemach, w których czas odgrywa ważną rolę, mutexy mogą powodować problemy.