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 usterek lub 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óźnień. Prace nad tym rozwiązaniem rozpoczęły się w Androidzie 4.1 i 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 użyciem bardziej przewidywalnych zasad planowania. Niezawodne planowanie umożliwia zmniejszenie rozmiarów i liczby buforów dźwięku przy jednoczesnym unikaniu niedoborów i nadmiarów.
Odwrócenie priorytetów
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ż oczekuje na zwolnienie zasobu przez zadanie o niższym priorytecie. Zasobem tym może być np. (chroniony przez) muteks.
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 czas oczekiwania 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 priorytetu najprawdopodobniej wystąpi w tych miejscach. Dlatego skup się na tych kwestiach:
- między normalnym wątkiem miksera a szybkim wątkiem miksera w usłudze 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ętu audio (HAL), np. w przypadku telefonii lub eliminowania 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 muteksy w przestrzeni użytkownika) z dziedziczeniem priorytetów, ponieważ są one stosunkowo ciężkie 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 atomowe i ich interakcje z barierami pamięci są często źle rozumiane i nieprawidłowo używane. Podajemy te metody, aby zapewnić kompletność informacji, ale zalecamy też przeczytanie artykułu Podstawy SMP na Androidzie , w którym znajdziesz więcej informacji.
Większość z tych narzędzi nadal jest przez nas używana. Ostatnio dodaliśmy te techniki:
- Używaj nieblokujących kolejek FIFO z jednym czytnikiem i jednym zapisem do przesyłania 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 na 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ć prawidłowe działanie 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. Ta 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 to 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 okazały się one skomplikowane i podatne na błędy.
Od Androida 4.2 nasze nieblokujące klasy 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ą ogólnego przeznaczenia. 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 dla 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łego 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.