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

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

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

Фон

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

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

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

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

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

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

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

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

Распространенные решения

Обычно проблемы решаются следующим образом:

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

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

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

Технологии, используемые в Android

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

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

  • больше
  • побитовое ИЛИ;
  • побитовое "и"

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

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

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

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

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

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

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

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

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

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

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

Инструменты

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

Заключение

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