Z tego artykułu dowiesz się, jak system audio Androida próbuje uniknąć odwrócenia priorytetów, i poznasz techniki, których możesz używać.
Te techniki mogą być przydatne dla deweloperów aplikacji audio o wysokiej wydajności, producentów OEM i dostawców układów SoC, którzy wdrażają warstwę HAL audio. Pamiętaj, że wdrożenie tych technik nie gwarantuje uniknięcia błędów ani innych awarii, zwłaszcza jeśli są one używane poza kontekstem audio. Wyniki mogą się różnić, dlatego należy przeprowadzić własną ocenę i testy.
Tło
Serwer audio AudioFlinger w Androidzie oraz implementacja klienta AudioTrack/AudioRecord są przebudowywane w celu zmniejszenia opóźnienia. Prace nad tym rozwiązaniem rozpoczęły się w Androidzie 4.1, a następnie były kontynuowane w wersjach 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 znaczeniu krytycznym z bardziej przewidywalną polityką planowania. Niezawodne planowanie umożliwia zmniejszenie rozmiarów i liczby buforów audio przy jednoczesnym unikaniu niedoborów i nadmiarów.
Odwrócenie priorytetu
Odwrócenie priorytetów to klasyczny tryb awarii systemów czasu rzeczywistego, w którym zadanie o wyższym priorytecie jest blokowane na nieograniczony czas, ponieważ czeka na zwolnienie zasobu (np. stanu współdzielonego chronionego przez) muteks przez zadanie o niższym priorytecie.
W systemie audio inwersja priorytetów zwykle objawia się zakłóceniami (kliknięciem, trzaskiem, przerwą), powtarzaniem dźwięku, gdy używane są bufory cykliczne, lub opóźnieniem w reagowaniu na polecenie.
Popularnym rozwiązaniem problemu z odwróceniem priorytetów jest zwiększenie rozmiaru buforów audio. Ta metoda zwiększa jednak opóźnienie i tylko ukrywa problem, zamiast go rozwiązywać. Lepiej jest zrozumieć i zapobiegać odwróceniu priorytetów, jak pokazano poniżej.
W przypadku implementacji audio na Androidzie odwrócenie priorytetów najprawdopodobniej wystąpi w tych miejscach. Dlatego skup się na tych kwestiach:
- między normalnym wątkiem miksera a szybkim wątkiem miksera w AudioFlinger
- między wątkiem wywołania zwrotnego aplikacji dla szybkiego obiektu AudioTrack a wątkiem szybkiego 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 (podobnie jak poprzednio);
- w implementacji warstwy abstrakcji sprzętowej audio (HAL), np. w przypadku telefonii lub usuwania echa;
- w sterowniku audio w jądrze;
- 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 to:
- wyłączanie przerwań,
- muteksy dziedziczenia priorytetu,
Wyłączanie przerwań nie jest możliwe w przestrzeni użytkownika systemu Linux i nie działa w przypadku symetrycznych wieloprocesorowych systemów komputerowych (SMP).
W systemie audio nie są używane futexy (szybkie mutexy w przestrzeni użytkownika) z dziedziczeniem priorytetu, ponieważ są one stosunkowo złożone i zależą od zaufanego klienta.
Techniki stosowane przez Androida
Eksperymenty rozpoczęte od „try lock” i blokady z limitem czasu. Są to nieblokujące i ograniczone blokujące warianty operacji blokady wzajemnego wykluczania. Próba blokowania i blokowania z limitem czasu działała całkiem dobrze, ale była podatna na kilka niejasnych trybów awarii: serwer nie miał gwarancji dostępu do stanu współdzielonego, jeśli klient był zajęty, a łączny limit czasu mógł być zbyt długi, jeśli wystąpiła długa sekwencja niezwiązanych ze sobą blokad, które wszystkie przekroczyły limit czasu.
Używamy też operacji niepodzielnych, takich jak:
- Zwiększ
- bitowe „lub”
- operacja bitowa „i”
Wszystkie te funkcje zwracają poprzednią wartość i zawierają niezbędne bariery SMP. Wadą jest to, że mogą wymagać nieograniczonej liczby ponownych prób. W praktyce okazało się, że ponowne próby nie stanowią problemu.
Uwaga: operacje niepodzielne i ich interakcje z barierami pamięci są często źle rozumiane i nieprawidłowo używane. Podajemy te metody poniżej, aby zapewnić kompletność informacji, ale zalecamy też przeczytanie artykułu Podstawy SMP na Androidzie w celu uzyskania dodatkowych informacji.
Większość z tych narzędzi nadal jest przez nas używana. Ostatnio dodaliśmy te techniki:
- Używaj kolejek FIFO z jednym czytnikiem i jednym zapisem, które nie blokują dostępu do danych.
- Spróbuj kopiować stan, a nie udostępniać go 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 atomowy w ramach jednej operacji magistrali bez ponawiania.
- W przypadku złożonego stanu wielosłownego użyj kolejki stanu. Kolejka stanu to w zasadzie nieblokująca kolejka FIFO z jednym czytnikiem i jednym zapisem, która służy do przechowywania stanu, a nie danych. Zapisujący łączy sąsiadujące ze sobą operacje push w jedną operację.
- Zwróć uwagę na bariery pamięci, aby zapewnić poprawność SMP.
- Zaufaj, ale sprawdź. Podczas udostępniania stanu między procesami nie zakładaj, że stan jest prawidłowy. Sprawdź na przykład, czy indeksy są w odpowiednim zakresie. Weryfikacja nie jest potrzebna w przypadku wątków w tym samym procesie ani w przypadku procesów, które sobie ufają (zwykle mają ten sam identyfikator UID). Nie jest też konieczne w przypadku udostępnionych danych, takich jak dźwięk PCM, w którym uszkodzenie nie ma znaczenia.
Algorytmy nieblokujące
Algorytmy nieblokujące są ostatnio przedmiotem wielu badań. Jednak z wyjątkiem kolejek FIFO z jednym czytelnikiem i jednym zapisującym, uważamy, że są one skomplikowane i podatne na błędy.
Od Androida 4.2 nasze klasy nieblokujące, z jednym czytnikiem i jednym zapisem, znajdziesz w tych lokalizacjach:
- frameworks/av/include/media/nbaio/
- frameworks/av/media/libnbaio/
- frameworks/av/services/audioflinger/StateQueue*
Zostały one zaprojektowane specjalnie dla AudioFlingera i nie są przeznaczone do ogólnego użytku. Algorytmy nieblokujące są znane z tego, że trudno je debugować. Możesz traktować ten kod jako model. Pamiętaj jednak, że mogą one zawierać błędy i nie gwarantujemy, że będą odpowiednie do innych celów.
W przypadku deweloperów niektóre przykładowe kody aplikacji OpenSL ES należy zaktualizować, aby używały algorytmów nieblokujących lub odwoływały się do biblioteki open source innej niż Android.
Opublikowaliśmy przykładową implementację kolejki FIFO bez blokowania, która jest przeznaczona specjalnie do kodu aplikacji. Te pliki znajdują się w katalogu źródłowym platformy:frameworks/av/audio_utils
Narzędzia
Z naszej wiedzy wynika, że nie ma automatycznych narzędzi do wykrywania odwrócenia priorytetów, zwłaszcza zanim do niego dojdzie. Niektóre narzędzia do statycznej analizy kodu mogą wykrywać odwrócenia priorytetów, jeśli mają dostęp do całej bazy kodu. Oczywiście, jeśli w grę wchodzi dowolny kod użytkownika (jak w tym przypadku w przypadku aplikacji) lub jest to duża baza kodu (jak w przypadku jądra systemu Linux i sterowników urządzeń), analiza statyczna może być niepraktyczna. Najważniejsze jest dokładne przeczytanie kodu i zrozumienie całego systemu oraz interakcji. Narzędzia takie jak systrace i ps -t -p są przydatne do wykrywania odwrócenia priorytetów po jego wystąpieniu, ale nie informują o nim z wyprzedzeniem.
Słowo na koniec
Po tej dyskusji nie bój się mutexów. W zwykłych przypadkach użycia, gdy są prawidłowo używane i wdrażane, muteksy są przydatne w zwykłych, niekrytycznych czasowo przypadkach użycia. Jednak w przypadku zadań o wysokim i niskim priorytecie oraz w systemach wrażliwych na czas mutexy częściej powodują problemy.