Мютекс - это объект RTOS, предназначенный для обеспечения конкурентного доступа к общим ресурсам. Мютекс представляет собой двоичный семафор с дополнительными свойствами (например, протоколы обхода неограниченной инверсии приоритетов).
Мютекс может находится в двух состояниях: заблокированном и разблокированном. Ассоциировав мютекс с аппаратным или программным ресурсом приложения, можно обеспечить корректный доступ к ресурсам из нескольких задач - если задача попытается получить доступ к заблокированному ресурсу, она будет переведена в состояние ожидания, но получит управление сразу же после того как ресурс будет разблокирован.
Часто возникает вопрос - а зачем вообще нужно блокировать ресурсы? Все дело в принципе работы вытесняющих RTOS - задача может быть прервана (вытеснена) в любой момент времени. Если в этот момент она использует некий системный ресурс (например, UART), а задача, которая вытеснила текущую так же начнет с ним работать - возникнет закономерный конфликт.
С блокировкой ресурсов тесно связано понятие инверсии приоритетов:
Допустим в системе существуют две задачи с низким (А) и высоким (Б) приоритетом. В момент времени T1 задача (А) блокирует ресурс и начинает его обслуживать. В момент времени T2 задача (Б) вытесняет низкоприоритетную задачу (А) и пытается завладеть ресурсом в момент времени T3. Но так как ресурс заблокирован, задача (Б) переводится в ожидание, а задача (А) продолжает выполнение. В момент времени Т4 задача (А) завершает обслуживание ресурса и разблокирует его. Так как ресурс ожидает задача (Б), она тут же начинает выполнение.
Временной промежуток (T4-T3) называют ограниченной инверсией приоритетов. В этом промежутке наблюдается логическое несоответствие с правилами планирования - задача с более высоким приоритетом находится в ожидании в то время как низкоприоритетная задача выполняется.
Но это еще не самое страшное. Допустим в системе работают три задачи: низкоприоритетная (А), со средними приоритетом (Б) и высокоприоритетная (В):
Если ресурс заблокирован задачей (А), а он требуется задаче (В), то наблюдается та же ситуация - высокоприоритетная задача блокируется. Но допустим, что задача (Б) вытеснила (А), после того как (В) ушла в ожидание ресурса. Задача (Б) ничего не знает о конфликте, поэтому может выполняться сколь угодно долго на промежутке времени (T5-T4). Кроме того, кроме (Б) в системе могут быть и другие задачи, с приоритетами больше (А), но меньше (Б). Поэтому длительность периода (T6-T3) в общем случае неопределена. Такую ситуацию называют неограниченной инверсией приоритетов.
Ограниченной инверсии приоритетов в общем случае избежать невозможно, однако она не так опасна для системы, как неограниченная. Для того чтобы избежать неограниченной инверсии приоритетов, используются два протокола изменения приоритетов задач:
Допустим в системе существуют две задачи с низким (А) и высоким (Б) приоритетом:
В момент T2 задача (Б) вытесняет низкоприоритетную задачу (А) и затем в момент времени T3 пытается захватить заблокированный (А) ресурс.
Протокол наследования приоритета состоит в том, что приоритет задачи (А) повышается до приоритета задачи (Б) в момент времени T3, то есть когда (Б) пытается захватить заблокированный ресурс. Таким образом задачи с приоритетом больше (А) но меньше (Б) не могут реализовать неограниченную инверсию, и задача (Б) получит ресурс сразу после того как (А) его разблокирует.
После того как задача (А) разблокирует ресурс, ее приоритет понижается до исходного.
Протокол увеличения приоритета основан на том факте, что на момент проектирования известны все задачи, которым требуется определенный ресурс, а так же известны приоритеты этих задач. В этом случае ресурсу (мютексу) можно назначить определенное свойство - максимальный приоритет из всех задач, которые могут его заблокировать (потолок).
Допустим в системе существуют три задачи с низким (А), средним (Б) и высоким (В) приоритетом, которые могут заблокировать один ресурс:
В момент времени T3, когда задача (Б) пытается захватить заблокированный (А) ресурс, приоритет (А) повышается до приоритета задачи (В), т.е. до максимального приоритета из всех задач, которые могут владеть ресурсом. Как только задача (А) освобождает ресурс в момент времени T4, ее приоритет понижается до исходного, а приоритет задачи (Б), ожидавшей ресурс повышается до (В).
Таким образом, при использовании протокола увеличения приоритета, задача, захватившая ресурс всегда имеет наивысший приоритет из группы задач, которые могут этим ресурсом владеть. Это позволяет не только избавиться от неограниченной инверсии приоритетов, но и не допустить взаимных блокировок.
Взаимная блокировка - это аварийное состояние системы, которое может возникать при вложенности блокировок ресурсов. Допустим в системе существуют две задачи с низким (А) и высоким (Б) приоритетом, которые используют два ресурса - X и Y:
В момент времени T1 задача (А) блокирует ресурс X. Затем в момент времени T2 задачу (А) вытесняет более приоритетная задача (Б), которая в момент времени T3 блокирует ресурс Y. Если задача (Б) попытается заблокировать ресурс X (T4) не освободив ресурс Y, то она будет переведена в состояние ожидания, а выполнение задачи (А) будет продолжено. Если в момент времени T5 задача (А) попытается заблокировать ресурс Y, не освободив X, возникнет состояние взаимной блокировки - ни одна из задач (А) и (Б) не сможет получить управление.
Взаимная блокировка возможна только если в системе используются вложенный конкурентный доступ к ресурсам. Взаимной блокировки можно избежать, если не использовать вложенность, или если ресурс использует протокол увеличения приоритета.
В TNKernel реализованы мютексы как с протоколом наследования приоритета, так и с протоколом увеличения приоритета.
Протокол наследования приоритета более простой и быстрый, но не позволяет избежать взаимной блокировки. Поэтому для таких мютексов не рекомендуется использовать вложенный доступ. Протокол увеличения приоритета требует больше временных ресурсов и обеспечивает отсутствие взаимной блокировки.
Каждый мютекс ассоциируется со структурой управления:
typedef struct _TN_MUTEX_S { CDLL_QUEUE_S wait_queue; CDLL_QUEUE_S mutex_queue; CDLL_QUEUE_S lock_mutex_queue; TN_UWORD attr; TN_TCB_S * holder; TN_UWORD ceil_priority; TN_WORD cnt; TN_OBJ_ID id_mutex; } TN_MUTEX_S;
В состав структуры мютекса входят следующие элементы:
wait_queue |
Очередь задач, ожидающих освобождение мютекса |
mutex_queue |
Элемент списка заблокированных задачей мютексов |
lock_mutex_queue |
Системная очередь заблокированных мютексов |
attr |
Атрибут (тип обхода инверсии приоритетов) мютекса |
holder |
Указатель на TCB задачи, блокирующей мютекс |
ceil_priority |
Максимальный приоритет из задач, которые могут использовать ресурс (требуется для протокола увеличения приоритета) |
cnt |
Зарезервировано |
id_mutex |
Поле идентификации объекта как мютекса |
TN_DEBUG
. Тем не менее, прямой доступ к элементам структуры мютекса крайне не рекомендуется, так как это является вмешательством в работу планировщика и других сервисов RTOS.
TNKernel имеет следующий набор функций (сервисов) для управления мютексами:
Сервис | Описание | Свойства |
---|---|---|
Создание и удаление мютекса | ||
tn_mutex_create() | Создание мютекса | |
tn_mutex_delete() | Удаление мютекса | |
Блокировка мютекса | ||
tn_mutex_lock() | Блокировка мютекса | |
tn_mutex_lock_polling() | Попытка блокировки мютекса без блокировки задачи | |
Освобождение мютекса | ||
tn_mutex_unlock() | Освобождение мютекса |