В программировании есть такое понятие как Мьютекс (Mutex). Мьютексы, в основном, используются для того, чтоб контролировать доступ к данным в многопоточном приложении. Однако, и сетевые мьютексы - не новость. Протокол modbus подразумевает структуру сети вида master-slave, и хоть по tcp мастером может быть каждый, это не решает проблему контроля доступа к удаленным ресурсам.

Проблематика

В последнее время все чаще приходится работать с распределенными modbus-сетями. Рассмотрим пример: есть два сервера (master) и один модуль ввода (slave). Оба читают некоторые значения и могут изменять уставки этого модуля ввода. Теперь представим, что в некотором регистре находится битовая маска из уставок или, например счетчик. Сервер A делает уставку в бит 1 регистра уставок, а сервер B делает уставку в бит 2 того же регистра, но так как сервер B не знает, что сервер А только что изменил уставку, то он затирает новое значение бита 1.

Основных решений тут два:

  1. В лоб: перед тем, как записать уставку - читаем текущее значение, добавляем свой бит, пишем уставку. Вероятность того, что сервер А в этот момент попытается изменить уставку крайне мала. С таким решением можно жить, но осадочек остается и некоторая вероятность возникновения неисправности по этой причине не будет давать спокойно спать по ночам;
  2. Использовать сетевые мьютексы (подробно об этом ниже).


Есть разные виды мьютексов, но для наших задач достаточно самого простого:

Классический мьютекс

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

Реализация в Modbus

Теперь можно рассмотреть простейшую реализацию сетевых мьютексов в Modbus. для мьютекса на slave-устройстве выделается один регистр под мьютекс, в который можно как писать, так и читать из него (для R/W-мьютекса требуется три регистра).

Алгоритм

Перед тем, как изменить уставку, сервер читает значение регистра с мьютексом, если регистр пустой, то он записывает туда свой ID. После этого можно снова прочитать значение регистра с мьютексом и убедиться, что в нем теперь записан нужный ID (lock()). Теперь можно производить запись уставки. После того, как все уставки сделаны - очищаем регистр с мьютексом (unlock()). Если при первом или втором чтении оказалось, что в регистре с мьютексом указан чужой ID, то нужно читать значение регистра до тех пор, пока он не будет обнулен.

Заключение

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

Отсюда мое предложение к компании ОВЕН: можно ли добавить в прошивки пустой holding регистр, который можно использовать для реализации сетевых мьютексов? Да, я в курсе, что есть протокол ОВЕН, DCOM и даже MQTT, который тут больше подходит, но они есть не во всех устройствах, особенно, если речь про RS485, с которыми можно работать через банальный шлюз, да и городить огород из кучи разных протоколов в одной сети, зачастую, неудобно.

Что скажете, участники форума? Возможно, я просто отстал от жизни и не знаю, что все уже украдено до нас, так что буду рад дельным советам и критике.