AG
Все записи

Race Condition в embedded-системах

2 min read
Embeddedrace conditionсинхронизация

Race Condition (состояние гонки) - это ситуация, когда два или более асинхронно выполняющихся фрагмента кода (например, прерывание и фоновый цикл) одновременно получают доступ к общему ресурсу и пытаются его изменить. При этом результат зависит от времени или порядка выполнения операций.

Причины возникновения

  • Отсутствие синхронизации между фрагментами кода, которые обращаются к общему ресурсу.
  • Частое использование прерываний - они могут вызывать race condition из-за взаимодействия между задачами приложения и обработчиками прерываний.
  • Оптимизация компилятора, которая может менять порядок операций, даже если исходный код выглядит безопасным.

Race condition может привести к непредсказуемому поведению системы. Вот некоторые возможные последствия:

  • порча данных,
  • уязвимости,
  • взаимные блокировки.

Как обнаружить

  • Проверка кода и подключение статического анализа. Изучи кодовую базу, выпиши критические участки, где осуществляется доступ к общим ресурсам или их изменение. Ищи шаблоны, в которых состояние считывается и записывается без надлежащих механизмов синхронизации. Подключи уже наконец clang-tidy или аналог статического анализатора!
  • Ведение логов и отслеживание. Добавь подробное ведение журнала логов для критических участков кода. Изучая логи, можно определить порядок выполнения и выявить аномалии, указывающие на состояние гонки.
  • Динамический анализ и стресс-тестирование. Проводи стресс-тесты, имитирующие сценарии с высокой степенью параллелизма, чтобы выявить условия гонки.
  • Проверка блокировки оптимизаций компилятора. На всех ли критических несинхронизируемых ресурсах стоит модификатор volatile?

Как поправить

  • Используй атомарные операции, чтобы избежать промежуточных состояний. Можно блокировать прерывания для bare-metal или применять std::atomic из стандартной библиотеки.
  • Блокировки или мьютексы. Обеспечь взаимное исключение при доступе к общим ресурсам средствами объектов синхронизации операционной системы.
  • Используй неизменяемые структуры данных. Неизменяемый формат класса в C++ достигается с помощью ключевого слова const. Оно позволяет объявить переменные-члены и методы класса как неизменяемые.
  • Применяй транзакционную память. Убедись, что группы операций выполняются транзакционно. У меня для этого есть простой паттерн.
  • Избегай общего состояния, используя передачу сообщений для взаимодействия задач.
  • Используй команды для управления параллелизмом более низкого уровня.
  • Применяй такие инструкции, как барьеры и защелки. В embedded-системах барьеры часто реализуются через инструкции памяти:
    • DMB (Data Memory Barrier) - гарантирует выполнение всех предыдущих операций записи перед продолжением.
    • DSB (Data Synchronization Barrier) - блокирует выполнение до завершения всех операций с памятью.
    • ISB (Instruction Synchronization Barrier) - очищает кэш инструкций, что важно при самомодифицируемом коде.