February 7, 2018

Деление на ноль

Давно ли вы делили на ноль?



Случайные ошибки


PROGRAM MAIN
VAR
    a, b, c : INT;
END_VAR

a := 12 / 0;

Такую случайную ошибку среда разработки отловит еще на этапе компиляции:



... но стоит только подставить переменную, как деление на ноль произойдет во время работы. И...

PROGRAM MAIN
VAR
    a, b, c : INT;
END_VAR

a := 12 / b; // <<<< !!!!

...TwinCAT переходит с состояние "стоп" (Stop), выбрасывая в консоль сообщение об ошибке:
Error 01.02.2018 15:02:24 456 ms | 'Port_851' (851): Exception (Exception Code: 0xc0000094, Integer divide by zero) in PLC Application Untitled1 Instance, Task PlcTask (EBP: 0xa9b5eeb8, EIP: 0xa7927056, ESP: 0xa9b5ee90)

Что произойдет если у вас несколько задач (тасков)? Остановится ли только "эта" задача или все сразу? Как поведет себя система в многоядерной архитектуре? И что с изолированными (isolated) выделенными ядрами?


Изолируем и форкаем


Добавляю и запускаю вторую, параллельную задачу. И сразу же роняю первую, установив переменную b := 0. Первая задача падает в точку останова, но вторая задача продолжает работать — счетчик тикает:



Пробую восстановить задачу после падения, устранив ошибку: значение переменной b := 1, затем пытаюсь продолжить выполнение первой задачи. Не получается. Пробую подтвердить на экране ПЛК сообщение об ошибке деления на ноль (кнопка ОК). TwinCAT целиком переключается в режим конфигурирования (синий значок), но теперь уже с остановкой всех задач: и ошибочных, и корректных. При этом:
  • Reset Cold — не помогает.
  • Rest Origin — помогает, так как он начисто сносит весь рантайм задачи (это заметно по предложению пересоздать порт при последующем запуске). Затем можно перезапустить задачу заново, но непонятно, что в это время творится с другими задачами.

При всех этих телодвижениях среда разработки VS 2015 ведет себя очень нестабильно, а может быть это контроллер ведет себя некорректно. Не уверен — кто из них. Возможно оба. В надежде повысить стабильность, я выделил одно из ядер ПЛК целиком под TwinCAT: 100% производительности в печенку процессора. Ничего не меняется. Пытаюсь перезапустить ошибочную задачу, но TwinCAT переходит в режим конфигурации, с полной остановкой всех ядер и задач. Версия TwinCAT контроллера: 3.1.4020.32.

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


Отлаживаем


Для отладки и только отладки (почему, я расскажу позже) TwinCAT предлагает нам стандартное средство из проверочных функциональных блоков — Object POUs for implicit checks. Теперь их можно создавать тыкая мышкой в: POUs → Add → POU for implicit checks... → +Division Checks. После этого в ПЛК-проект добавятся несколько автоматически сгенерированных функций. Необходимо перекомпилировать проект и перезагрузить его в контроллер целиком (download). Загрузка проекта на лету (online change) с сохранением данных (значений переменных) на этот раз недоступна, так как в проекте появились новые программные объекты (POUs).

Я экспериментирую с делением целого типа, вот пример такой функции:

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivDInt : DINT
VAR_INPUT
    divisor:DINT;
END_VAR

// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
    CheckDivDInt:=1;
ELSE
    CheckDivDInt:=divisor;
END_IF;
{flow}

Несмотря на предостережение: "НЕ РЕДАКТИРОВАТЬ", — редактировать можно и нужно. Например, добавить сообщение об ошибке в лог и вытащить номер текущего шага из других программных объектов.

Интересно, что эта функция будет вызываться для каждой операции деления! Значением входного параметра divisor будет значение делителя/знаменателя из операции деления, а результат функции (возвращаемое значение) будет подставляться вместо делителя в операции деления. Попробуйте в теле функции заменить CheckDivDInt:=divisor; на CheckDivDInt:=2; и все ваши операции деления, независимо от значения делителя, превратятся в банальным делитель на двойку. Но стоит только поделить на ноль, как вместо делителя (равного нулю) будет подставлена единица.

Не всегда и всюду нужна проверка, поэтому разработчики предоставили нам средство быстрого отключения функции проверки для заданных программных блоков — атрибут {attribute 'no_check'}. Его необходимо добавить в первую строку области объявления переменных, до строк PROGRAM, FUNCTION_BLOCK или FUNCTION. Тем более, что использование автопроверки вызывает дополнительную нагрузку на процессор.


Оценка производительности


Справочная система предупреждает о дополнительной нагрузке на ПЛК при использовании функций проверки деления (а также функций проверки на выход за пределы диапазона или проверки адреса указателя). Давайте измерим эту нагрузку. Для этого я написал специальный тест производительности:

FOR c := 1 TO 1000000 DO
    a := 12 / b;
END_FOR

Управлять нагрузкой будем с помощью количества циклов FOR: от 100 000 до 1 000 000 (столбец A). Непосредственно нагрузку подсмотрим в закладке Online раздела SYSTEM. Всего необходимо рассмотреть три различных случая:
  1. Без контроля деления на ноль, выставлен атрибут "no_check" — зеленый столбец B.
  2. С включенным контролем деления на ноль — желтый столбец C.
  3. С включенным контролем деления на ноль и, через установку переменной b := 0, имитируем деление на ноль в каждой итерации цикла — красный столбец D. Предполагается максимальная нагрузка.
И сразу результат. По вертикальной оси гистограммы отражена нагрузка на процессор ПЛК в процентах (80% — заданный мною потолок для TwinCAT):


No comments

Post a Comment

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