Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Система обмена сообщения между отдельными модулями программы
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Слесарь
Здравствуйте!
Программирую на С под 8-бит микроконтроллер. Для улучшения динамики работы ПО хочу сделать нечто подобное как сообщения Виндовс. Каждому отдельному модулю ПО выделяется процессорное время, только не квант времени как в Виндовс, а передается процессор на обработку всего что обрабатывает модуль, естественно, модуль должен как можно скорее вернуть процессор для работы следующих модулей. Должен проверить свои данные и сообщения, если новых данных/сообщений нет, вернуть процессор для работы других модулей ПО, следующий по порядку, по кругу.

Отдельные модули ПО могут добавлять сообщения в общий список, другие модули могут получать сообщения и обрабатывать.
Планирую сделать две функции доступные из всех модулей ПО SendMessage и GetMessage, в функции будет передаваться/приниматься хейдер сообщенияи данные. Функция SendMessage добавляет сообщение с указанием хейдера в динамичный список сообщений. Функция GetMessage извлекает сообщение с указанным хейдером или если сообщение отсутствует возвращает NO_MESSAGE.

Вопрос в следующем:
Допустим один модуль шлет сообщения, но еще не существует модуля который может эти сообщения обрабатывать, список сообщений будет расти. Какое должно быть время жизни сообщений, чтоб не забить всю память? Сообщение должно пройти через все модули ПО по кругу и должно уничтожится вернувшись в модуль отправитель?

Правильно ли я мыслю?
zöner
чем не устроили статические переменные ?
Слесарь
много потребуется статических переменных. не наглядно будет выглядеть программа.
с сообщениями интересней и более похоже на функционированием в реальном мире, лучше воспринимается, легче писать, модуль шлет сообщение и в сообщении данные. Все действие отражать на память как-то не кошерно...
С сообщениями более приближенно к многопотоковости и многозадачности.
zöner
данная схема как-то не вяжется с 8-битными контроллерами.
Слесарь
надо писать динамичное ПО, с минимумом проверок условий и задержек Delay, для процессоров с любой разрядностью.
Конечно в утопичных двоичных системах без проверки условий никак, но это, где можно, должно быть максимально скрыто от программиста. В идеале, программист должен описывать только обработчики тех или иных сообщений о тех или иных событиях в своих программах.
ARV
ну так в винде не совсем так: там в каждом приложении своя очередь, PostMessage сразу обращается к очереди нужного приложения. в вашем случае вместо приложений некие модули, т.е. функция отправки сообщения должна перебрать имеющиеся в системе "модули" и отдать сообщение нужному, а если такового не нашлось - убить сообщение, т.е. не помещать его никуда.
общий список сообщений - это что-то не то...
Слесарь
Цитата(ARV @ May 26 2013, 17:50) *
ну так в винде не совсем так: там в каждом приложении своя очередь, PostMessage сразу обращается к очереди нужного приложения. в вашем случае вместо приложений некие модули, т.е. функция отправки сообщения должна перебрать имеющиеся в системе "модули" и отдать сообщение нужному, а если такового не нашлось - убить сообщение, т.е. не помещать его никуда.
общий список сообщений - это что-то не то...


Да. В Виндовс надо заранее регистрировать в системе обработчики сообщения для каждого модуля в загружаемом в память и инициализирующемся приложении. Как понимаю, такой принцип основан на динамическом выделении памяти и регистрации той или иной памяти в системе, но в среде МК статическое выделение памяти, наверное по этому общий список сообщений, память выделена статически, изменяются только данные.
zöner
посмотрите в сторону Protothreads - там есть неблокирующее ожидание условий.
и главное - без привязки к какой-либо архитектуре, чистый С.
расход кода на задачу - мизерный.
ARV
Цитата(Слесарь @ May 26 2013, 19:35) *
Да. В Виндовс надо заранее регистрировать в системе обработчики сообщения для каждого модуля в загружаемом в память и инициализирующемся приложении. Как понимаю, такой принцип основан на динамическом выделении памяти и регистрации той или иной памяти в системе, но в среде МК статическое выделение памяти, наверное по этому общий список сообщений, память выделена статически, изменяются только данные.
когда-то я был одержим подобной идеей - сделать событийно-ориентированную систему... предполагалось, что сообщение будет в виде структуры тип-счетчик-параметры: тип определяет назначение события/сообщения, т.е. по этому полю обработчик определяет, что это его событие/сообщение, счетчик содержит количество однотипных событий, имевших место между реакциями обработчика, ну а параметры - это дополнительные сведения, которые могут быть произвольными. например, обрабатывается энкодер - ставится в очередь событие "поворот на один щелчок вправо". обработчик ищет в очереди событие энкодера, когда захочет и видит, что счетчик равен 20 - значит, с момента прошлого просмотра очереди энкодер повернулся на 20 щелчков, соответственно и обабатывает. таким образом, в очередь не попадут два однотипных события - об этом должна позаботиться функция PostMessage, которая смотрит список и изменяет счетчик для уже имеющихся. и только если такого еще нет в очереди - добавляет его с нулевым счетчиком.

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

Слесарь
Цитата(zцner @ May 26 2013, 19:10) *
посмотрите в сторону Protothreads - там есть неблокирующее ожидание условий.

Скорее всего у меня тож самое, только без макросов в которые завернут С код. Да и ссылками на функции я не оперирую, у меня просто бесконечный цикл, типо так:
Код
main()
{
    while(1)
    {
        MSC1205_Task();
        SIM900_Task();
        IR_Task();
        TimeTask();
        PRESS_Task();
    }


Еще я создаю централизованную систему обмена сообщениями между отдельными модулями, вот например:
Код
SIM900_Task()
{
char power;
char dtmfStD;
char dtmfCode;
MESSAGE message;
  
  // удаление ранее отправленных сообщений если таковые имелись
    DeleteMessage( MESSAGE_SIM900_DTMF );
    DeleteMessage( MESSAGE_SIM900_RING );
    
    // ....
    
    // Посылка новых сообщений если произошло событие
    if( power && dtmfStD )
    {
        SendMessage( MESSAGE_SIM900_DTMF, dtmfCode );
    }
    
    if( sim900_response_char == 0x32 ) // 2 -ring
    {
        sim900_response_char = 0x00;
        SendMessage( MESSAGE_SIM900_RING, 0 );
    }

// прием сообщений от других модулей

    message = GetMessage( MESSAGE_SIM900_DISCONNECT );
    if( message.message != MESSAGE_NO )
    {
        dtmfOn = 0;
    
        str32[0] = 0x00;
        strcatpgm2ram( str32, ( void *)"ATH" );
        SIM900_SendATComand( str32 );
    }
Petr_I
Цитата(Слесарь @ May 26 2013, 19:35) *
...но в среде МК статическое выделение памяти, наверное по этому общий список сообщений, память выделена статически, изменяются только данные.

Я бы предложил кольцевой буфер сообщений.
Новое сообщение записывается в голову буффера, если там есть необслуженное сообщение оно просто переписывается. Указатель перемещаем на следующий элемент. Обслуженное заменяется на NO_MESSAGE, чтобы по нескольку раз не обрабатывалось в функции GetMassage.
Можно в качестве оптимизации времени поиска сообщения добавить уазатель на хвост как в FIFO, но добавятся дополнительные проверки.

Цитата(Слесарь @ May 26 2013, 19:35) *
Допустим один модуль шлет сообщения, но еще не существует модуля который может эти сообщения обрабатывать, список сообщений будет расти.

По идее такое может быть только в случае ошибки или при отладке, т.к. линковка в МК статическая пожизни.
Может Вы перестраховываетесь?
А если Вы динамически подгружаете модули, то без регистрации в ОС никак не обойтись.
Слесарь
Цитата(Petr_I @ May 26 2013, 20:35) *
По идее такое может быть только в случае ошибки или при отладке, т.к. линковка в МК статическая пожизни.
Может Вы перестраховываетесь?
А если Вы динамически подгружаете модули, то без регистрации в ОС никак не обойтись.

Описывая функционал модуля могу записать все посылаемые сообщения и записать все обработчики получаемых сообщений, а модуль на противоположной стороне можно оставить на потом или этот модуль будет писать другой человек ориентируясь по описанию интерфейсов и списку обрабатываемых сообщений.
В среде МК динамически подгружать программные модули думаю пока нецелесообразно. Если модуль не используется (есть такой бит данных) он просто сразу возвращает процессор для работы других модулей.
Petr_I
Цитата(Слесарь @ May 26 2013, 22:07) *
Описывая функционал модуля могу записать все посылаемые сообщения и записать все обработчики получаемых сообщений, а модуль на противоположной стороне можно оставить на потом или этот модуль будет писать другой человек ориентируясь по описанию интерфейсов и списку обрабатываемых сообщений.

Довольно странный стиль программирования, особенно для 8-ми разрадных МК.
Это обычно для ПК так пишут.
Не проще ли самоу сделать функции-заглушки для всех модулей и отдать другому чаловеку эти функции с описанием. А в очереди сделать контроль появления левых и необработанных сообщений для отладки.
Все равно собирать все куски и отлаживаться придется одному человеку.
Слесарь
Все дело в том, что могу мыслить только естественными категориями, вот есть некий источник данных, например термометр, который способен периодически производить замер температуры, этот термометр оформлен в отдельный программный модуль в ПО микроконтроллера. И есть некий прибор отображающий температуру, этот прибор так же оформлен в некий отдельный программный модуль. Для меня становится доступной и понятной работа устройства, когда первый модуль периодически шлет данные о температуре, а второй модуль по мере поступления новых данных эти данные выводит на дисплей. Мне непонятно когда эта задача выполнена с использованием одной статической переменной temp, если это выполнено именно так, как второму модулю узнать что данные о температуре актуальны? быть может первый модуль отвалился и ничего не обрабатывает по причине физической поломки датчика.
Использование системы сообщений, это прекрасный и естественный образ программирования, когда приходят сообщения, тут сразу информация о актуальности данных и сами данные.

и эти же температурные сообщения может читать и третий модуль, например управляющий ТЭНом, когда стоит задача поддерживать температуру.
Разве такой стиль программирования не является более естественным? Разве все естественное не упрощает понимание сути вещей? А быстрое понимание как известно ускоряет процесс программирования.


Еще хочу отказаться от всех Delay() в своем ПО. В программных обработчиках всех последовательных протоколов и обработчиках динамической индикации. Создам модуль отвечающий за генерацию сообщений о системных ТИКах. Например, сообщение каждую микросекунду, миллисекунду, секунду, час, сутки, неделя, месяц, год. Естественно эти сообщения будут доступны для всех модулей ПО, и каждый модуль будет ждать необходимые ему ТИКовые сообщения перед выполнением той или иной задачи, например задачи вывода на шину 1-wire очередного информационного бита.

Хочу чтоб ПО рассматривалось максимально приближенно к ПО основанному на параллельном выполнении множества задач.
zöner
Цитата
Мне непонятно когда эта задача выполнена с использованием одной статической переменной temp, если это выполнено именно так, как второму модулю узнать что данные о температуре актуальны?
очень просто - через вторую стат.переменную (напр. t_rdy). Первый модуль проверяет что t_rdy==0, если так, пишет в temp температуру и 1цу в t_rdy. Второй модуль ждет 1цу - если есть, читает значение temp, и сбрасывает t_rdy в 0.
Так как t_rdy 8-битная, доступ к ней атомарный и никаких доп.средств синхронизации не нужно.
Хотя в большинстве случаев и этого не нужно, можно просто периодически обновлять значение на индикаторе без синхронизации.

В приведенном примере видно что используется просто стек сообщений (или кольцевой буфер) - в одном месте добавляются, в другом - читаются и удаляются.
Petr_I
Цитата(Слесарь @ May 27 2013, 01:47) *
Использование системы сообщений, это прекрасный и естественный образ программирования, когда приходят сообщения, тут сразу информация о актуальности данных и сами данные.

Не всегда. В вашем примере если функция вывалится до (или вы забудите написать) DeleteMessage(), сразу получите проблему.
Вообще принято сообщение удалять из очереди при его обработке, т.е. в GetMessage.
И очередь событий нужна, когда у вас действительно ОЧЕРЕДЬ, т.е. одинаковых событий может быть больше чем одно и они должны выполняться в последовательности их поступления.
А судя по вашему примеру это явно избыточные функции.

Есть более простые решения, например:
задайте структуру типа {int flag;int data1;...}
Определите массив таких структур с числом элементов соответствующим общему количеству сообщений.
Определите ваши сообщения типа
#define MESSAGE_SIM900_DTMF 1 /*Индекс в массиве*/
И ваша функция GetMessage будет возвращать структуру из массива по индесу.
flag - используете как флаг актуальноти или счетчик обращений.
Слесарь
Цитата(zöner @ May 27 2013, 01:08) *
очень просто - через вторую стат.переменную (напр. t_rdy).

Такой стиль программирования не для меня. С таким стилем приходится много думать и помнить, с этим постоянно проблемы.

Вы сами подумайте, одно дело видеть как сигнал поступает и вы его обрабатываете, или другое дело вы сами заботитесь о том чтоб удостоверится о наличие сигнала дополнительной переменной или функцией чтоб потом сигнал обработать. какой стиль более естественный для восприятия?

Цитата(Petr_I @ May 27 2013, 01:46) *
Не всегда. В вашем примере если функция вывалится до (или вы забудите написать) DeleteMessage(), сразу получите проблему.
Вообще принято сообщение удалять из очереди при его обработке, т.е. в GetMessage.
И очередь событий нужна, когда у вас действительно ОЧЕРЕДЬ, т.е. одинаковых событий может быть больше чем одно и они должны выполняться в последовательности их поступления.
А судя по вашему примеру это явно избыточные функции.

Есть более простые решения, например:
задайте структуру типа {int flag;int data1;...}
Определите массив таких структур с числом элементов соответствующим общему количеству сообщений.
Определите ваши сообщения типа
#define MESSAGE_SIM900_DTMF 1 /*Индекс в массиве*/
И ваша функция GetMessage будет возвращать структуру из массива по индесу.
flag - используете как флаг актуальноти или счетчик обращений.

Я подвожу к тому чтоб создать некий шаблон оформления программ, на автомате создаем Task() функцию для каждого модуля ПО, как было описано выше в эту функцию поочередно передается процессор, если никаких действий выполнять не требуется, из функции необходимо сделать возврат. В начале функции удаляем все старые сообщения которые могут быть посланы функцией, далее проверяем нет ли новых сообщений которые эта функция может обработать от других модулей, обрабатываем их, и на последок, если произошли события, отправляем новые сообщения из этой функции, возвращаем процессор для других задач. Так поступает любой программный модуль.
Будет удобно создать конструктор который будет генерировать основной шаблон функции Task.
Вы работали когда-нить с MSVS ? Кликаешь вызов конструктора, в окне конструктора вводишь список всех обрабатываемых сообщений, вводишь список сообщений которые будут посылаться из модуля, вводишь другие параметры и нажимаешь ОК, конструктор создает общий шаблон фукции на C, далее программист просто впишет код обработчиков тех или иных входящих сообщений, опишет события при которых будут посланы исходящие сообщения. Никакой путаницы быть не должно.
А если еще многое спрятать под макросы, то должно получиться очень читабельно. хотя макросы я не очень люблю

ДА. Очередь сообщений должна быть правильной, сначала старые одноименные сообщения, потом только новые.

Единственно что сейчас меня беспокоит в таком подходе, это если делать временное тактирование для кождого модуля через систему сообщений. Допустим, в системе требуется самая минимальная задержка для программного обработчика протокола 1-wire, это 6 мкс. , то есть модуль генерирующий сообщения о времени должен каждые 6 мкс слать сообщение MESSAGE_TACT_6MKS, это сообщение должно просуществовать пока не исполнятся все модули ПО и удалиться. Не будет ли разовая работа всех модулей занимать больше времени чем 6 мкс? сообщения могут накапливаться и не успевать вовремя выполняться.
ARV
Цитата(Слесарь @ May 27 2013, 10:45) *
Не будет ли разовая работа всех модулей занимать больше времени чем 6 мкс? сообщения могут накапливаться и не успевать вовремя выполняться.

я вам предложил вариант, когда проблемы с накоплением однотипных сообщений нет. в том числе и с таймерными. если у вас в системе предусмотрено всего 10 разновидностей сообщений, то под очередь достаточно выделить статический массив на эти самые 10 структур, не более. делать реализацию протокола 1-wire через систему сообщений для задержек считаю нецелесообразным
AlexandrY
Цитата(Слесарь @ May 27 2013, 09:45) *
Единственно что сейчас меня беспокоит ....
... это сообщение должно просуществовать пока не исполнятся все модули ПО и удалиться...


Хоть я и не улавливаю тут полностью вашу идею, но скажу что в RTOS не видел никогда чтобы сообщения сами удалялись в сервисах RTOS.

Я подозреваю, что это вызвало бы создания моря таймеров и потерю производительности.

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

Чтобы сообщение было отправлено и жило бы своей жизнью (само бы удалилось без ведома основного приложения) это нонсенс, который точно приведет рано или поздно к повреждению крыши у программиста. biggrin.gif
Слесарь
Цитата(AlexandrY @ May 27 2013, 11:12) *
Хоть я и не улавливаю тут полностью вашу идею, но скажу что в RTOS не видел никогда чтобы сообщения сами удалялись в сервисах RTOS.

Я подозреваю, что это вызвало бы создания моря таймеров и потерю производительности.

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

Чтобы сообщение было отправлено и жило бы своей жизнью (само бы удалилось без ведома основного приложения) это нонсенс, который точно приведет рано или поздно к повреждению крыши у программиста. biggrin.gif

Да. Вы не поняли суть идеи и не посмотрели представленный код.
Сообщения подчищает отправитель после того как сообщения гарантированно побывали во всех модулях ПО.

В моей системе задачи не останавливаются при ожидании сообщений, задачи не задерживают процессор если сообщения не поступали, отдают процессор для других очередных задач.
AlexandrY
Цитата(Слесарь @ May 27 2013, 17:26) *
Да. Вы не поняли суть идеи и не посмотрели представленный код.
Сообщения подчищает отправитель после того как сообщения гарантированно побывали во всех модулях ПО.

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


Тогда все в порядке. biggrin.gif

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

И здесь я вас поздравлю. Вы готовы к использованию RTOS! 08.gif
Слесарь
Цитата(AlexandrY @ May 27 2013, 17:42) *
задача не тупо должна отдавать управление следующей по списку, которой управление тоже может быть в данный момент не нужно, а передавать управление строго туда где оно необходимо в силу назревших событий или поступивших сообщений.

В двоичных системах управление передается всем задачам поочередно, и только сами задачи знают назрела ли необходимость что-либо исполнять. никакого централизованного знатока нет. например в PC для каждой задачи выделяется около 45 мкс времени, если задача не уложилась в этот срок с выполнением своей работы, управление отбирается принудительно внешним управляющим модулем(диспетчером), если успела, то возвращает управление самостоятельно раньше срока.
Не знаю откуда вы взяли что процессорное время раздается как-то иначе, не последовательно для каждой из задач.

Цитата(AlexandrY @ May 27 2013, 17:42) *
И здесь я вас поздравлю. Вы готовы к использованию RTOS! 08.gif

Нет. Еще не готов. У самого как-то пока не плохо получается управляться с ресурсами процессора. Не забывайте что ОС стали популярны только тогда, когда стали выполнять всю черновую работу программ, например диски и файлы, что не актуально в среде МК.
-SANYCH-
Цитата
Нет. Еще не готов. У самого как-то пока не плохо получается управляться с ресурсами процессора. Не забывайте что ОС стали популярны только тогда, когда стали выполнять всю черновую работу программ, например диски и файлы, что не актуально в среде МК.



RTOS для контроллера нужна в первую очередь для параллельного выполнения задач. Если Вам нужно в каком то модуле ожидать данные длительное время (на пример пока датчик температуры производит измерение. Для примера ds18b20 измеряет около 0.7 секунд) то остальные задачи будут параллельно выполняться а не ждать пока отработает этот модуль. Конечно это можно реализовать с помощью цифровых автоматов и каждый раз когда вы заходите в этот модуль проверять не прошло ли время ожидания, но такой способ усложняет код и увеличивает время написания программы.
RTOS не нужна в двух случаях:
1) - это когда у контроллера очень мало ресурсов (например мало оперативной памяти)
2) когда на контроллере выполняется всего лишь одна задача.
andrewlekar
Во-первых, Вам нужно избавиться от суперлупа и воткнуть ртос. Это увеличит производительность Вашего процессора без изменения логики работы.
Во-вторых, код с отправкой сообщений интересный. Мне нравится.
В-третьих, я для себя тоже сделал многопоточную очередь сообщений - для того, чтобы перетаскивать обработку данных из контекста одного потока в контекст другого. Сразу скажу, на си это выглядит довольно криво, съедает избыточную память и в большинстве случаев очередь как таковая не требуется - создаётся очередь из одного элемента. Для того, чтобы не было переполнения при отсутствии приёмника сообщений, я сделал ограничение размера в конструкторе очереди. Для того, чтобы не было фрагментации памяти, память под очередь выделяется статически на этапе компиляции.

Да, кстати, аналогичный Вашему код есть и у меня в проекте. По сути это защищенная от многопоточного доступа переменная. У нас вместо SendMessage и GetMessage используется WriteRegister и ReadRegister.
demiurg_spb
2TC: скачайте книгу Кубенского А.А. "Структуры и алгоритмы обработки данных: объктно-ориентированный подход и реализация на С+" в ней весьма неплохо разжёван диспетчер сообщений...
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.