Skip to content

Latest commit

 

History

History
76 lines (48 loc) · 11 KB

comport.md

File metadata and controls

76 lines (48 loc) · 11 KB

Управление аппаратными узлами посредством COM-порта

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

Разрабатывая код взаимодействия с устройством через виртуальный COM-порт, имеет смысл попытаться ответить на следующие вопросы:

  • Планируется ли использование разных операционных систем (Linux, Windows)?
  • Будут ли команды к устройству отправляться из разных потоков исполнения (см. multi-threading)?
  • Будут ли использоваться разные типы запросов, например: отправка команды на исполнение и систематический опрос состояния устройства. Если ответ "будут", то каким образом следует прерывать систематический опрос состояния устройств для отправки команд?
  • Должен ли API класса работы с устройством через COM-порт быть асинхронным для прикладного программиста?
  • Следует ли использовать низкоуровневый код, или можно создать переносимый, высокоуровневый код? Каковы потери связанные с более высоким уровнем абстракции?
  • Какие образом следует бороться с зависаниями: устройства, драйвера порта?

Ответы на эти вопросы могут привести к разнообразным техническим решениям, например:

  • Используются функции Windows API: CreateFile(), CloseHandle(), WriteFile(), ReadFile(), GetOverlappedResult(), SetCommTimeouts(), CancelIo()
  • Используются функции потокового ввода/вывода в Linux: open(), close(), read(), write(). Для настройки порта используется библиотека termios
  • Используется многопоточная модель, в которой есть отдельный поток polling-а устройства с целью кэширования актуального состояния узла. Для выполнения команды используется второй поток. Коммуникационный порт является разделяемым ресурсом, доступ к которому организуется посредством объектов синхронизации операционной системы (чаще всего - mutex)

Инженерный подход предполагает формулировку критериев, на основании которых следует принимать решение о выборе конкретных технических решений. К таким критериям следует отнести:

  1. Обеспечение минимального времени отклика при выполнении обмена данными
  2. Гибкость решения (простота адаптации при изменении условий использования кода)
  3. Эффективное использование вычислительных ресурсов процессора и памяти
  4. Простоту прикладного кода
  5. Простоту реализации библиотеки для управления узлом посредством COM-порта

Критерии 4 и 5 являются субъективными. Для критериев 1-3 можно попытаться разработать методику оценки, например, используя Google Benchmark.

Принимания во внимание принцип Zero Cost Abstractions в C++, можно предположить, что затраты вычислительных ресурсов при использовании Boost.Asio будут сопоставимы с низко-уровневым Windows API. Тем более, с учётом того факта, что платформозависимый код Boost.Asio в Windows использует те же самые функции API: CreateFile(), CloseHandle(), WriteFile(), ReadFile(), GetOverlappedResult().

Однако, было бы интересно понять, применим ли Benchmarking для сбора метрик при выборе инструментальных средств и подходов при работе с узлами по USB. Можно ли "в цифрах" сравнить асинхронную и многопоточную реализацию?

Анти-паттерны (по результатам представленных решений задач)

При анализе выполненных контрибьюторами тестовых задач был выявлен ряд анти-паттернов, которые рекомендуется принимать во внимание при разработке кода.

Интуитивно, более простое решение кажется более правильным и это является причиной того, что некоторые разработчики решили не использовать событийную модель для межтопочного взаимодействия, предпочитая синхронизацию через переменную, проверяемую через некоторый период времени. Исключительно негативный вариант - синхронизация через статическую переменную без модификатора volitile.

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

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

Рекомендуются для прочтения статья Потоки, блокировки и условные переменные в C++11 и её вторая часть. Под событийной моделью подразумевается использование condition_variable и методы notify_one() и notify_all().

Представленные решения и выводы

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

Одним из способов выполнения сравнения может быть применение Google Benchmark. При проведении сравнения кажется разумным выполнить следующие этапы:

  1. Определить возможные варианты решения задачи
  2. Выдвинуть гипотезу о том, как эти варианты должны работать
  3. Получить метрики и проанализировать соответствие ожидаемого и фактического результата
  4. При значительном расхождении ожиданий и результатов попытаться идентифицировать причины, скорректировать код, или гипотезу
  5. Добиться соответствия ожидаемого и фактического результата
  6. Зафкисировать гипотезу, результаты и выводы

Benchmarking разных подходов при разработке библиотеки работы с COM-портом (CommPortTest)

В решении контрибьютора осуществляется проверка производительности трёх подходов:

  • низкоуровневый однопоточный
  • однопоточный с использованием асинхронного кода Boost.Asio
  • многопоточный с использованием событийной модели

Изначальная гипотеза была следующей:

  • Boost.Asio менее эффективен на 15-20% (overhead), чем низко-уровневый код, т.к. создаёт дополнительный уровень абстракции
  • Многопоточность требует кратно больше ресурсов, чем однопоточный код

Полученные результаты:

Вывод по полученным результатам: инструментальные средства (Google Benchmark) ориентированы, в первую очередь, на сравнение однопоточной производительнеости и не очень хорошо подходит для конкретной задачи - выбора решения для разработки библиотеки работы с COM-портом.