August 4, 2020

Всё есть файл

В Unix/Linux устройства могут выступать в качестве файла или бинарного/символьного потока. Такая философия позволяет работать с потоком данных устройства, как с обычным файлом: открываем-читаем-пишем. Все что нужно — это иметь права доступа к устройству и знать протокол и структуру данных. В Windows иногда тоже можно. Попробуйте, например, создать на рабочем столе файл с именем com2.txt. Будет занято.


Источник


Сначала нужно определиться, что именно будем читать. Для начала я не стал брать последовательные порты или другие устройства, а решил воспользоваться псевдоустройством... поэтому будем читать случайные числа из /dev/urandom. Это не совсем то, что мне хотелось бы проверить, но пока обойдемся этим. Как вариант можно считать память операционной системы через /dev/mem. Сервис TwinCAT под BSD запускается с root доступом, поэтому проблем возникнуть не должно.

Прочитанное из устройства, можно просто сохранить в массив, но мы сделаем два дела сразу — кроме чтения устройства, сохраним результат в файл, лежащий на другом ПЛК. Итого: два ПЛК (один с TC/BSD, другой с Windows), соединены обычной локальной сетью Ethernet. На одном ПЛК читаем случайные числа из файла-псевдоустройства, а результат сохраняем на другой ПЛК в настоящий файл.


Код


Код настолько простой, что смотреть особенно не на что: ряд обычных файловых операций. Именно в этом заключается преимущество подхода "всё есть файл".

PROGRAM MAIN
VAR
state: INT;
buf: byte;
tmpStr: STRING;
hDev, hResFile: UINT;
RUN: BOOL;
Open: FB_FileOpen := (ePath := PATH_GENERIC);
Read: FB_FileRead := (sNetId := '');
Write: FB_FileWrite := (sNetId := sRemoteNetId);
Close: FB_FileClose;
END_VAR
VAR CONSTANT
sDevPath: STRING := '/dev/urandom';
sResPath: STRING := 'c:\dev\urandom.txt';
sRemoteNetId: STRING := '172.17.176.49.1.1';
END_VAR
CASE state OF
0:
IF RUN THEN
Open (bExecute := FALSE);
Write(bExecute := FALSE);
Read (bExecute := FALSE);
Close(bExecute := FALSE);
state := 100;
END_IF
100:
Open(
bExecute := TRUE,
sNetId := '',
nMode := FOPEN_MODEREAD OR FOPEN_MODEBINARY,
sPathName := sDevPath );
IF NOT Open.bBusy THEN
hDev := Open.hFile;
Open(bExecute := false);
state := 110;
END_IF
110:
Open(
bExecute := TRUE,
sNetId := sRemoteNetId,
nMode := FOPEN_MODEWRITE OR FOPEN_MODEBINARY,
sPathName := sResPath );
IF NOT Open.bBusy THEN
hResFile := Open.hFile;
Open(bExecute := false);
state := 200;
END_IF
200:
Read(
bExecute := TRUE,
hFile := hDev,
pReadBuff := ADR(buf),
cbReadLen := SIZEOF(buf) );
IF NOT Read.bBusy THEN
Read(bExecute := false);
state := SEL(Read.cbRead < 1, INT#210, INT#300);
END_IF
210:
tmpStr := CONCAT(BYTE_TO_STRING(buf), '$r$n');
Write(
bExecute := TRUE,
hFile := hResFile,
pWriteBuff := ADR(tmpStr),
cbWriteLen := INT_TO_UDINT(LEN(tmpStr)) );
IF NOT Write.bBusy THEN
Write(bExecute := FALSE);
state := SEL(RUN, INT#300, INT#200);
END_IF
300:
Close(
bExecute := TRUE,
sNetId := '',
hFile := hDev );
IF NOT Close.bBusy THEN
Close(bExecute := FALSE);
state := 310;
END_IF
310:
Close(
bExecute := TRUE,
sNetId := sRemoteNetId,
hFile := hResFile );
IF NOT Close.bBusy THEN
Close(bExecute := FALSE);
RUN := FALSE;
state := 0;
END_IF
END_CASE
END_PROGRAM

Открывать несколько файлов можно с помощью одного и того же ФБ. Главное делать это последовательно: сначала один, затем другой. Основная задача получить хендлер файла для дальнейших файловых операций. Нам нужно получить два хэндлера, от двух файлов, заданных следующими путями: sDevPath — задает путь к источнику '/dev/urandom' и sResPath — путь к файлу с результатом 'c:\dev\random.txt'. Сразу видно — где Юникс, а где Виндовс (подсказка, обратить внимание на /слэши/ в путях). В финале добавим в константы VAR CONSTANT адрес удаленного ПЛК: sRemoteNetId = '172.17.176.49.1.1'.

Будем читать бинарные данные, то есть числа в виде потока байтов. Поэтому при открытии файла необходимо установить флаг бинарного режима чтения nMode := ... FOPEN_MODEBINARY. Кстати, читать можно и блоками по несколько килобайт за раз, но в данном случае так проще сохранять числа в виде текста.

В остальном все очень просто: открываем, читаем, обрабатываем-конвертируем и сохраняем. Преобразование значения байта как числа из диапазона 0..255 в текстовый вид делается в строке:
tmpStr := CONCAT(BYTE_TO_STRING(buf), '$r$n');
... а в конце добавляем символ '$r$n' — перевод каретки CRLF, таким образом выстраивая числа в столбик. Позднее, я засуну эти случайные числа в Эксель для анализа.

Набрав достаточное количество чисел, стоит вежливо остановить процесс через принудительную установку переменной RUN в значение FALSE. Резкая остановка работы программы, чревато тем, что на приемной стороне файл с результатом останется открытым и занятым: ни прочитать, ни удалить. Если такое произойдет, необходимо на приемной стороне вручную перезапустить системный сервис TwinCAT System Service или выполнить из командной строки с привелегиями администратора: powershell -command "Restart-Service TcSysSrv -Force". Заметьте, какой уровень доверия возникает на двух ПЛК, между которыми налажен роутинг. Это к вопросу о безопасности и отказоустойчивости по обе стороны сетевого кабеля.


Анализируем случайные числа


В Юникс системах есть несколько генераторов случайных чисел. Они отличаются надежностью, скоростью, блокировками, чем-либо еще, поэтому их несколько. Анализировать можно даже случайные числа, тем более, что они псевдослучайные. Например, можно посмотреть как числа распределены и устраивает ли это раработчика, технолога или просто любопытного человека.


Интересно посмотреть как распределяется нагрузка по ядрам. Всего виртуальной машине выделены два ядра, но не факт, что ядра настоящие: возможно, что и просто два потока (хост с гипертредингом). Зато видно как нагружен TcSystemService — системный сервис TwinCAT:


Вместо диспетчера задач здесь используется утилита top или можно установить более красивый htop: doas pkg install htop. На картинке выше используется htop.

No comments

Post a Comment

Note: Only a member of this blog may post a comment.