Как избежать инверсии приоритетов

В этой статье объясняется, как аудиосистема Android пытается избежать инверсии приоритетов, и освещаются методы, которые вы также можете использовать.

Эти методы могут быть полезны разработчикам высокопроизводительных аудиоприложений, OEM-производителям и поставщикам SoC, которые реализуют аудио HAL. Обратите внимание, что реализация этих методов не гарантирует предотвращения сбоев или других сбоев, особенно если они используются вне аудиоконтекста. Ваши результаты могут отличаться, и вам следует провести собственную оценку и тестирование.

Фон

Аудиосервер Android AudioFlinger и реализация клиента AudioTrack/AudioRecord перестраиваются для уменьшения задержки. Эта работа началась в Android 4.1 и продолжилась дальнейшими улучшениями в 4.2, 4.3, 4.4 и 5.0.

Для достижения этой более низкой задержки потребовалось много изменений во всей системе. Одним из важных изменений является назначение ресурсов ЦП критичным ко времени потокам с более предсказуемой политикой планирования. Надежное планирование позволяет уменьшить размеры и количество аудиобуферов, избегая при этом недозаполнения и переполнения.

Инверсия приоритета

Инверсия приоритета — это классический режим отказа систем реального времени, когда задача с более высоким приоритетом блокируется на неограниченное время, ожидая, пока задача с более низким приоритетом освободит ресурс, такой как (общее состояние, защищенное) мьютексом .

В аудиосистеме инверсия приоритета обычно проявляется как сбой (щелчок, треск, выпадение), повторяющийся звук при использовании кольцевых буферов или задержка ответа на команду.

Распространенным обходным путем для инверсии приоритета является увеличение размера аудиобуфера. Однако этот метод увеличивает задержку и просто скрывает проблему, а не решает ее. Лучше понимать и предотвращать инверсию приоритета, как показано ниже.

В реализации Android-аудио в этих местах чаще всего происходит инверсия приоритета. И поэтому вам следует сосредоточить свое внимание здесь:

  • между обычным потоком микшера и потоком быстрого микшера в AudioFlinger
  • между потоком обратного вызова приложения для быстрого потока AudioTrack и потока быстрого микшера (они оба имеют повышенный приоритет, но немного разные приоритеты)
  • между потоком обратного вызова приложения для быстрой AudioRecord и потоком быстрого захвата (аналогично предыдущему)
  • в реализации уровня аппаратной абстракции аудио (HAL), например, для телефонии или подавления эха
  • в звуковом драйвере ядра
  • между потоком обратного вызова AudioTrack или AudioRecord и другими потоками приложения (это вне нашего контроля)

Общие решения

Типичные решения включают в себя:

  • отключение прерываний
  • мьютексы приоритетного наследования

Отключение прерываний невозможно в пользовательском пространстве Linux и не работает для симметричных мультипроцессоров (SMP).

Фьютексы приоритетного наследования (быстрые мьютексы пользовательского пространства) не используются в аудиосистеме, потому что они относительно тяжеловесны и полагаются на доверенного клиента.

Методы, используемые Android

Эксперименты начались с «попробовать блокировку» и блокировки с тайм-аутом. Это неблокирующие и ограниченно блокирующие варианты операции блокировки мьютекса. Попытка блокировки и блокировки с тайм-аутом работала довольно хорошо, но была подвержена нескольким неясным режимам сбоя: серверу не гарантировалась возможность доступа к общему состоянию, если клиент был занят, и кумулятивный тайм-аут мог быть слишком длинным, если была длинная последовательность несвязанных блокировок, время ожидания которых истекло.

Мы также используем атомарные операции , такие как:

  • приращение
  • побитовое "или"
  • побитовое "и"

Все они возвращают предыдущее значение и включают необходимые барьеры SMP. Недостатком является то, что они могут требовать неограниченного количества повторных попыток. На практике мы обнаружили, что повторные попытки не являются проблемой.

Примечание. Атомарные операции и их взаимодействие с барьерами памяти, как известно, плохо понимаются и используются неправильно. Мы включили эти методы здесь для полноты картины, но рекомендуем вам также прочитать статью SMP Primer для Android для получения дополнительной информации.

У нас все еще есть и используется большинство из вышеперечисленных инструментов, и недавно мы добавили следующие методы:

  • Используйте неблокирующие очереди FIFO с одним устройством чтения и записи для данных.
  • Попробуйте скопировать состояние, а не делиться состоянием между модулями с высоким и низким приоритетом.
  • Когда состояние необходимо совместно использовать, ограничьте состояние до слова максимального размера, к которому можно получить атомарный доступ в режиме одной шины без повторных попыток.
  • Для сложного состояния из нескольких слов используйте очередь состояний. Очередь состояния — это, по сути, просто неблокирующая очередь FIFO с одним считывателем и одной записью, используемая для состояния, а не для данных, за исключением того, что записывающее устройство сворачивает соседние сообщения в одно сообщение.
  • Обратите внимание на барьеры памяти для корректности SMP.
  • Доверяй, но проверяй . При совместном использовании состояния между процессами не думайте, что состояние правильно сформировано. Например, проверьте, что индексы находятся в допустимых пределах. Эта проверка не требуется между потоками одного и того же процесса, между процессами взаимного доверия (которые обычно имеют один и тот же UID). Это также не нужно для общих данных , таких как аудио PCM, где повреждение несущественно.

Неблокирующие алгоритмы

Неблокирующие алгоритмы были предметом многих недавних исследований. Но, за исключением FIFO-очередей с одним считывателем и одной записью, мы обнаружили, что они сложны и подвержены ошибкам.

Начиная с Android 4.2, вы можете найти наши неблокирующие однократные классы чтения/записи в следующих местах:

  • рамки/av/include/media/nbaio/
  • рамки/av/медиа/libnbaio/
  • frameworks/av/services/audioflinger/StateQueue*

Они были разработаны специально для AudioFlinger и не являются универсальными. Неблокирующие алгоритмы печально известны тем, что их трудно отлаживать. Вы можете смотреть на этот код как на модель. Но имейте в виду, что могут быть ошибки, и не гарантируется, что классы подходят для других целей.

Для разработчиков некоторые примеры кода приложения OpenSL ES должны быть обновлены, чтобы использовать неблокирующие алгоритмы или ссылаться на библиотеку с открытым исходным кодом, отличную от Android.

Мы опубликовали пример реализации неблокирующего FIFO, специально разработанного для кода приложения. Посмотрите эти файлы, расположенные в исходном каталоге платформы frameworks/av/audio_utils :

Инструменты

Насколько нам известно, не существует автоматических инструментов для обнаружения инверсии приоритетов, особенно до того, как она произойдет. Некоторые исследовательские инструменты статического анализа кода способны находить инверсии приоритетов, если имеют доступ ко всей кодовой базе. Конечно, если используется произвольный пользовательский код (как здесь для приложения) или большая кодовая база (как для ядра Linux и драйверов устройств), статический анализ может оказаться нецелесообразным. Самое главное — очень внимательно прочитать код и хорошо понять всю систему и взаимодействия. Такие инструменты, как systrace и ps -t -p , полезны для наблюдения за инверсией приоритета после того, как она произошла, но не сообщают об этом заранее.

Последнее слово

После всего этого обсуждения не бойтесь мьютексов. Мьютексы — ваш друг для обычного использования, если они используются и правильно реализованы в обычных случаях использования, не критичных ко времени. Но между задачами с высоким и низким приоритетом и в чувствительных ко времени системах мьютексы, скорее всего, вызовут проблемы.