Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2740
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Запуск FreeRTOS

639

Если вы помните, в этой главе мы видели, что HAL всегда пытается защитить параллельный доступ к периферийным устройствам с помощью макроса __HAL_LOCK(). Тем не менее, нет гарантии, что в многопоточной среде этот макрос предотвратит условия гонок, поскольку операция блокировки не выполняется атомарно.

В то время как семафоры лучше всего подходят для синхронизации действий потоков, мьютексы и критические секции являются способом защиты разделяемых ресурсов в конкурентном программировании. FreeRTOS предоставляет нам оба примитива, в то время как уровень CMSIS-RTOS устанавливает только понятие мьютекса. Тем не менее, критические секции оказываются полезными в нескольких ситуациях, и иногда они представляют более лучшее решение проблем, которые потребуют от разработчика больше усилий при программировании во избежание едва уловимых условий, таких как инверсия приоритетов.

23.6.1. Мьютексы

Мьютекс (Mutex) – это сокращение от MUTual EXclusion (что в переводе с английского взаимное исключение), и они являются своего рода бинарными семафорами, используемыми для управления доступом к разделяемым ресурсам. С концептуальной точки зрения мьютексы отличаются от семафоров по двум причинам:

мьютекс всегда должен быть взят и затем выпущен, чтобы уведомить, что защищенный ресурс теперь снова доступен, в то время как семафор может быть выпущен даже для пробуждения заблокированного потока (мы видели этот режим в Примере 4); более того, обычно мьютекс берется и выпускается одним и тем же потоком32;

мьютекс реализует наследование приоритетов (priority inheritance) – возможность минимизации проблемы инверсии приоритетов, которую мы проанализируем позже.

Чтобы использовать мьютексы, нам нужно определить макрос configUSE_MUTEXES в файле FreeRTOSConfig.h и установить его равным 1. Мьютекс определяется с помощью макроса osMutexDef(), который принимает имя мьютекса в качестве единственного параметра, и он эффективно создается функцией

osMutexId osMutexCreate(const osMutexDef_t *mutex_def);

Аналогично семафорам, чтобы получить мьютекс, мы используем функцию

osStatus osMutexWait(osMutexId mutex_id, uint32_t millisec);

и чтобы выпустить его, мы используем функцию:

osMutexRelease(osMutexId mutex_id);

32 Однако, в отличие от других операционных систем, во FreeRTOS не реализована проверка того, что только поток, получивший мьютекс, может его выпустить.

Запуск FreeRTOS

640

Наконец, чтобы уничтожить мьютекс, мы должны явно вызвать функцию

osStatus osMutexDelete(osMutexId mutex_id);

23.6.1.1.Проблема инверсии приоритетов

Мьютексы могут представлять нежелательную едва уловимую проблему, хорошо известную в литературе как проблема инверсии приоритетов (priority inversion). Давайте рассмотрим этот сценарий с помощью рисунка 13.

Рисунок 13: Диаграмма схематизирует проблему инверсии приоритетов

ThreadL(), ThreadM() и ThreadH() – три потока с возрастающим приоритетом (L обозначает низкий, M – средний и H – высокий). ThreadL() начинает свое выполнение и получает мьютекс, используемый для защиты разделяемого ресурса. Во время выполнения ThreadH() возвращается в режиме готовности к выполнению, и его выполнение планируется с более высоким приоритетом. Однако также ему необходимо получить тот же самый мьютекс, и он возвращается в состояние «заблокирован». Внезапно поток со средним приоритетом ThreadM() становится доступным, и он планируется для выполнения с приоритетом выше, чем у ThreadL(). Поэтому ThreadL() также не может закончить свою работу, и мьютекс остается заблокированным, предотвращая выполнение ThreadH(). В этом случае мы получаем практический эффект: приоритет между ThreadL() и ThreadH() инвертируется, поскольку ThreadH() не может быть выполнен, пока ThreadL() не выпустит мьютекс.

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

Рисунок 14 ясно показывает этот процесс. ThreadL() начинает свое выполнение и получает мьютекс. Во время выполнения ThreadH() возвращается в режиме готовности к выполнению, и его выполнение планируется с более высоким приоритетом. Однако ему также необходимо получить тот же самый мьютекс, и он возвращается в состояние «заблокирован». На этот раз приоритет ThreadL() увеличивается до того же ThreadH(), что предотвращает выполнение ThreadM(). ThreadL() планируется снова, и он может выпустить мьютекс, позволяя запустить ThreadH(). Наконец, ThreadM() может выполниться, поскольку приоритет ThreadL() уменьшается до его первоначального приоритета, когда он выпускает мьютекс.