Найти тему

Межпроцессовое взаимодействие в Codesys. Разделяемая память(Shared memory) в АСУТП.

Оглавление

Рано или поздно возникает желание запустить на ПЛК какой-нибудь скриптик или своего демона, который бы гораздо быстрее и проще решал определенную задачу, чем стандартными инструментами среды разработки.

При таком подходе появляется очень интересный вопрос: «А как обмениваться данными между двумя различными процессами?»

Существуют различные решения: через файл, обмен через сокеты и разделяемая память. И как раз, на основах разделяемой памяти, или shared memory, я решил остановиться.

Разделяемая память

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

И в целом это очень здорово, но есть и подводные камни такой технологии. За целостность данных отвечаете вы, так что стоит задуматься о реализации примитивов синхронизации, которые позволят избежать «Состояния гонки», также стоит продумать взаимодействия между ППО и какой-то софтиной.

Shared memory в Codesys

Для работы с разделяемой памятью в Codesys существует библиотека SysShm. Данная библиотека предоставляет набор функций:

Набор функций библиотеки SysShm
Набор функций библиотеки SysShm

Для теста возможностей я решил реализовать самый минимальный базовый функционал.

  • Создание области памяти
  • Запись в эту область
  • Чтение данных
  • Закрытие области памяти

Создание области разделяемой памяти

Начну сразу с листинга кода

ShmHandle:=SysSharedMemoryCreate(pResult:=ADR(ptrResult),
pszName:=Name,
pulSize:=ADR(pulSize),
ulPhysicalAddress:=0);

Для создания shared memory требуется функция SysSharedmemoryCreate.

Функция возвращает переменную типа RTS_IEC_HANDLE. Данный тип представляет собой псевдоним для POINTER TO BYTE.

Входными параметрами являются

  • pResult — указатель на переменную, которая будет хранить результат выполнения функции
  • pszName — переменная типа String с именем области памяти
  • pulSize — указатель на размер области памяти в байтах
  • ulPhysicalAddress — опциональный адрес памяти

После выполнения функции необходимо проверить результат выполнения. Все возможные результаты перечислены в библиотеке CmpErrors

Результат выполнения функции
Результат выполнения функции

Запись в память

Тут все тоже просто

SysSharedMemoryWrite(hShm:=ShmHandle,
ulOffset:=0,
pbyData:=ADR(Data),
ulSize:=4,
pResult:=ADR(ptrResult));

На вход данной функции необходимо передать:

  • hShm — RTS_IEC_HANDLE, который вернула функция создания области памяти
  • ulOffset — отступ в байтах
  • pbyData — указатель на область памяти с данными, которые требуется записать в общую память
  • ulSize — размер данных для записи
  • pResult — указатель на переменную, которая будет хранить результат выполнения функции

Возвращает функция количество записанных байтов.

-4

Набор данных которые были записаны

-5

Чтение из Shared memory сторонней программой

Вот в этом моменте и начинается магия. Теперь набор этих данных можно вычитать при помощи сторонней программы, которая будет запущена на ПЛК. Для запуска программы стоит помнить, что системный вызовы в операционных системах отличаются, а также стоит правильно скомпилировать программу под нужную архитектуру.

Если с ОС еще понятно, то для определения архитектуры процессора на система linux необходимо в терминале ПЛК, доступ к которому вы можете получить по SSH ввести следующую команду

cat /proc/cpuinfo

У меня полученный результат выглядел так:

processor : 0
model name : ARMv7 Processor rev 2 (v7l)

И этого мне вполне достаточно, чтобы скомпилировать под нужный процессор.

После компиляции заливает на программу на плк, можно при помощи SCP и запускаем ее.

Логика моей программы проста. Она считывает данные из общей памяти, выводит в терминале эти данные и сколько байт считалось, а после записывает туда новый набор данных.

Результат выполнения
Результат выполнения

Чтение из памяти

Последний этап этого действия

SysSharedMemoryRead(hShm:=ShmHandle,
ulOffset:=0,
pbyData:=ADR(Data),
ulSize:=4,
pResult:=ADR(ptrResult));

На вход данной функции необходимо передать:

  • hShm — RTS_IEC_HANDLE, который вернула функция создания области памяти
  • ulOffset — отступ в байтах
  • pbyData — указатель на область памяти куда будут прочитаны данные
  • ulSize — размер данных для чтения
  • pResult — указатель на переменную, которая будет хранить результат выполнения функции
Выполненная функция
Выполненная функция
Прочитанные данные
Прочитанные данные

Готово.

Закрытие области разделяемой памяти

Тут все еще проще

SysSharedMemoryClose(hShm:=ShmHandle);

На вход просто передаем RTS_IEC_HANDLE и получаем CmpErrors код, чтобы мы могли отследить выполнение функции.

Теперь со стороны ППО мы не можем ни прочитать, ни записать данные.

При попытке взаимодействия с закрытым RTS_IEC_HANDLE ППО контроллера выдаст исключение, а сам ПЛК уйдет в стоп.

Вывод

Очень удобный инструмент для своих задач, не обязательно, что он когда-то потребуется. Как и любая работа с памятью, требует четкого понимания действий программиста. Также при работе с данными может возникнуть состояние гонки, что тоже придется решать вам, а так пользуйтесь на здоровье.

Почта для связи и предложений: info@engcore.ru

Канал с новостями в Телеграмм: https://t.me/wtfcontrolsengineer

Блог: https://blog.engcore.ru/
Чат:
https://t.me/wtfplc