суббота, 16 июля 2016 г.

CMSIS RTOS + IEC 61131-3 + OpenCL = параллельные программы для контроллеров

Чисто гипотетически хочу рассмотреть возможность реализации подмножества OpenCL на контроллере под управлением CMSIS RTOS.

Зачем это нужно? "Игра в биссер" - предчувствие. Эту статью я для себя пишу, нужно быть на одной волне чтобы понять, что я хочу сделать. Однако, если найдется такой человек, мне не хватает общения по теме. Я сам не понимаю до конца, надеюсь чем больше нажимаю кнопки на клавиатуре, тем более явно буду представлять идею. Подмножество OpenCL, которое меня интересует, - возможность запускать на контроллере код подготовленный для отдельного work-item в терминологии OpenCL. Мое пространство программирования -- это около двухсот контроллеров различного назначения, я не способен за время проекта, например за пол-года, написать десять разных программ и заставить их работать друг с другом. Я хочу сформулировать подход, который позволил бы создать два программных продукта и позволил бы распределять задачи по контроллерам совершенно различной конфигурации аппаратуры, но под управлением единой операционной системы. Возможно ли описать мою систему из двухсот контроллеров единым графом и автоматически синтезировать код (прошивку) для каждого контроллера? Для начала думаю про хорошее, хорошо стандартизированное и удобное API.

Какие языки мне понятны, из чего делаем выбор и для каких целей.
1) Си + CMSIS RTOS. Прикладные программы пишу на Си+Glib. Когда пишу на контроллеры, у меня есть некоторое подмножество объектов реализованных для контроллеров, использую такие же библиотечные функции как в Glib.
2) Структурированные данные обычно обрабатываю в XML. Из стандарта BACnet вынес концепцию веб сервиса с форматами JSON.
3) Языки программирования ПЛК -- не эксперт в этой области, но все больше задумываюсь про интеграцию языков IEC 61131-3 OpenPLC XML в качестве описания своей системы на уровне блоков. Не все из этого хочу брать. Пока что мне нужны объекты SFC - графический язык для представления последовательностей обработки.
3.1) Мне понятно, как на языке SFC - описывать последовательности команд, я понимаю что можно при обработке графа использовать OpenCL.
3.2) LD  язык контактно релейных схем -- применение понятно, но про этот язык я думаю совсем странно, как промежуточный формат представления синтезированных функций. Я могу описать логику и по логике синтезировать LD для графического представления. На самом деле я хочу сделать другое, вместо логических функций и блочных диаграмм я хочу встроить интерфейс для обучения, в API самой операционной системы. Логические схемы можно синтезировать.
3.3)  FSM - конечные автоматы. Этого языка нет в стандарте IEC 61131-3, я понимаю, что можно описать средствами SFC, но выглядеть будет уродливо. Такой объект мне нужен для описания работы контроллеров. FSM можно синтезировать, и представлять в форме таблиц. FSM находится в близких отношениях с SFC. SFC доделывает то что начато в FSM. Структуру любой программы можно разложить на блоки или стадии обработки [LD|FBD - подготовка входных сигналов] --> [FSM - изменение состояния] -->[SFC] -->[FBD - выходные данные].
3.4) FBD -- графический язык функциональных блочных диаграмм, мне не нравится - это не то что я хочу видеть, я не хочу использовать стандартную функцию И или ИЛИ, мне проще этот блок описать на языке Си. От FBD мне нужно описание подготовки-преобразования сигналов, важным элементом являются таймеры, блоки DSP и всякие регуляторы. Мои функциональные блоки должны работать по событиям. Есть другой стандарт описания функциональных блоков IEC 61499, он мне тоже не нравится. Я не хочу явно думать про синхронизацию блоков. Мои функциональные блоки должны выполняться по событиям, как SFC, события явным образом не должны присутствовать в описании блока, чтобы не перегружать описание.
Подводя итог всего пункта (3), скажу что все эти графические языки имеют единый формат текстового представления -- XML. Который я и намериваюсь использовать для промежуточного, представления -- описания системы, по которому синтезируется код контроллеров.
4) Контроллер рождается с набором функциональных блоков представляющих собой интерфейс к аппаратуре. Это описание является исходным, концептуально должно работать, как DeviceTree в процессе инициализации ядра Linux. Мне удобно считать, что вся периферия настраивается в процессе инициализации по описанию DeviceTree. Сам формат DTS/DTB мне не нравится, хочу другой. Думаю про бинарный структурированный формат данных из BACnet. Подойдет любой формат типа TLV. Мне от этого формата нужно, чтобы код синтезировался на основании этого описания. Часть описания попадало бы на вход инициализации функциональных блоков.

Итак, вот что в итоге представляет собой моя программка:
Отдельный файл проекта, который описывает функциональные блоки в формате XML. Из описания синтезируется статическое описание диаграммы. Функциональные блоки на языке Си -- это могут быть просто функции. Но лучше если это будут именно блоки, как это понимает язык Си {...}. Эти блоки должны быть синтезируемые без изменения и анализа внутренней структуры блока.

Описание функционального блока можно выполнить ... на языке OpenCL для устройства. С единственной разрешенной Run-time стандартной библиотекой. По сути это может быть просто язык Си. И это собственно то, что я хочу упростить для разработки. В качестве библиотеки пойдут неблокирующие интерфейсы синхронизации при работе с разделяемыми ресурсами, допускается использовать функцию printf() для целей отладки и все как в OpenCL включая нотацию параметров __global,  __local. Возможно еще чего-нибудь надо добавить, например у меня в программе присутствуют статические переменные, которые сохраняются во флеш, при выключении устройства и хранятся между сессиями -- условно назовем словом конфигурационные параметры. В состав ядра операционки мне надо добавить файл заголовка CL.h для эмуляции всего того что есть в языке OpenCL и сфомулировать некоторое подмножество функций API, которое позволит эмулировать и отлаживать код на ПК и переносить его без изменения в контроллер. -- Это то что надо осваивать прикладному программисту.


В операционную систему мне нужно добавить несколько интерфейсов.
В планировщик задач нужно добавить поток команд. В этом потоке элементом синхронизации является список событий типа cl_event. Само событие не имеет параметров, используется только идентификатор. Поэтому я смотрю на объекты которые присутствуют в других API, нахожу близкое понятие Conditional, которые используются совместно с Mutex. В Glib появился объект GCond, аналогичные вызовы есть в POSIX и Windiows API. Назвоем это условными мьютексами или условными бинарными семафорами. В ядре моей операционке есть переключение задач по семафорам и по сигналам задачи. Достаточно легко встроить дополнительное условие osEventCondition в процесс переключение задач по семафору. Однако это не все, что мне нужно для реализации потока события. Мне нужно, чтобы в результате работы задачи когда "на завершение" (atexit) запускалась бы следующая задача в том же тайм слоте- на том же стеке без задержки. Поэтому я говорю про API osThreadPool - резервирование некоторого количества стеков, тайм-слотов. Некоторое количество ресурса системы - стеки для запуска потоков может быть выделено в единый пул для запуска задач из потока. Запуск задач производится по завершении блока, условием является набор блоков набор, условий. Ожидание готовности по условию Condition может производится на семафоре, который является счетчиком свободных слотов - стеков в пуле.

Попробую описать методы нашего потока команд: создать объект "поток" - по статическому описанию графа. Запустить поток, приостановить поток. Из всего этого, при условии что поток описывается статически, в прикладной программе ничего не нужно. Поток должен уметь останавливаться и запускаться корректно, чтобы не терять данные. Конфигурация потока, все переменные обозначенные, как "конфигурация" должны сохраняться, но это все должно быть вынесено в системную логику. И единственным местом в операционке, которое об этом знает - это управление питанием и перепрошивка контроллера. В операционке есть несколько мест, которые тормозят переключение задач. Параметры потока команд: число тредов в пуле, вернее сам пул является параметром потока, функция планировщика которая запускается atexit, число событий, каждое событие-условие определяется идентификатором cl_event. Номер события - перечисление функциональных блоков, используемых в описании потока. Число условий не должно превышать число функциональных блоков, хочется сократить число идентификаторов.

Очередь команд -- это центральное понятие OpenCL. Можно на уровне операционной системы создать понятие очередь команд, встроить очередь команд в планировщик. Обработчик очереди команд - это фоновый процесс в недрах ОС, который можно реализовать на условных мьютексах и семафорах. Нужно, чтобы при выходе из треда система выставляла событие -- завершение процесса, причем событие - это системный объект, который в CMSIS RTOS не задан. В контроллере описание потока должно быть статическим и оптимизированным. Для описания параллельной програмки состоящей из блоков можно использовать следующее описание:


osCondEnumerate(osCond(name), ... ); -- перечисление условий, которые используются для управления потоками, полный список. Сюда входят идентификаторы функциональных блоков и идентификаторы событий которые выставляются в программе. Перечисление выполняется в файле заголовка.
osCondListDef(list_name, osCond(name), ...); -- перечисление условий/событий
osBlockDef(name, argument, osСondList(list_name)); -- name - имя блока и оно же имя события, которое входит в перечисление osCondEnumerate.
osThreadDef(flow_cb, priority, num_threads, stack_size); -- flow_cb - функция планировщика.
Запускается все функцией osThreadCreate(osThread(name), osСondList(flow_name));  
В качестве аргумента передается описание потока - список элементов, в список входят функциональные блоки и не входят условия, которые выставляются в программе.
Прикладные функции osCondClear(osCond(name)), osCondSignal(osCond(name));

Для описания потока мне не понадобилось вводить что-то новое в API CMSIS задач. Оказывается osThreadPool можно описать через параметр instances в определении процесса osThreadDef, этот параметр ранее в моей реализации CMSIS RTOS никак не использовался. Остальные функции и определения надо реализовать, и этот набор не завязан на функционал операционки. Для реализации функции обработки потока мне понадобится функция osMutexCondWait(osMutex(), osCond(name), millisec), которая выполняется на мьютексе.

Есть одна сложность - в какой памяти живет множество объектов osCond или ассоциированные с ними флаги. Делать эти флаги глобальными не хочется. Эти флаги относятся к определению потока. Отдельного определения потока я не сделал. Каждый тред, который запускается должен иметь ссылку на это множество. osCond проверяет планировщик задач, откуда он знает где расположено множество флагов osCond.


Вопрос реализации. Предложенное API позволяет реализовать на базе GLib, pthread, и OpenCL все вышеперечисленное?! ... Надо попробовать.

Чтобы понять, что на самом деле является альтернативой параллельному программированию прошу пример вынести. В последний раз, когда мне надо было описать взаимодействие с технологической установкой, я описал FSM, который порождал последовательности обработки, со своими задержками и процедурами. При этом, когда выполняется последовательность обработки не выполняется FSM, вторую последовательность запустить сложно. Концепция в том, чтобы на каждый отдельный объект нужно запускать свой процесс, чтобы в каждом процессе образовалась своя FSM и свои последовательности типа SFC. В чем выигрыш нового подхода, мы может быть уменьшим линейную сложность программки, разбив программку на множество мелких блоков. Я не знаю, что мы выиграли. Я ушел от множества операций над флажками типа flag_set(osCond, __условие_выставления_флага__); Пришел к планировщику, который просматривает список вперед и анализирует те же флаги. if (flag_set(osCond, __условие_выставления_флага__)) __выполнить_обработку__

Комментариев нет:

Отправить комментарий