Z tego artykułu dowiesz się, jak system audio Androida próbuje uniknąć 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óźnienie, 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 przydzielanie zasobów procesora wątkom o krytycznym znaczeniu za pomocą bardziej przewidywalnej polityki planowania. Niezawodne planowanie pozwala zmniejszyć rozmiary i liczby buforów audio, 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 usterka (kliknięcie, szum, utrata sygnału), powtarzany dźwięk (przy użyciu buforów okrężnych) 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 pokazano 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 ze sobą 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 bariery pamięci są często źle rozumiane i nieprawidłowo używane. Podajemy te metody ze względu na ich wszechstronność, ale zachęcamy też do przeczytania artykułu SMP Primer for Android (w języku angielskim) , aby uzyskać więcej informacji.
Nadal używamy większości wymienionych wyżej narzędzi, ale ostatnio dodaliśmy też te techniki:
- Do danych używaj 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 maksymalnego rozmiaru słowa, do którego można uzyskać dostęp w ramach operacji jednoprzewodowej bez powtórzeń.
- 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ą (zazwyczaj mają ten sam identyfikator UID). Nie jest to też konieczne w przypadku udostępnianych danych, takich jak dźwięk 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 AudioFlinger 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 Linuxa 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.
Ostatnie słowo
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 stosowane 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.