Шаблоны слабой связи для [закрытого] программирования встроенных систем

Где я могу найти некоторые хорошие, доказанные инструкции или примеры при написании расширяемого, модульного, слабо связанного кода в C (если возможными)?

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

Таким образом, мы ищем способы сделать этот код более удобным в сопровождении и более модульным. Мы не интересуемся кодированием стандартов, а скорее разрабатываем предложения. У нас есть хорошие конвенции кодирования (именование, организовывая код, SVN), таким образом, это не проблема.

Из того, что я видел в сети (я могу быть неправым), это кажется большинством программистов, какая программа исключительно в плоскости C или ассемблер, по крайней мере, в uC/Embedded сообществе, ограничивают от использования чего-либо больше то простое процедурное программирование.

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

Вы знаете о таких ресурсах или имеете подобные предложения, кроме того, от, "почему Вы не переключаетесь на C++ или другой язык программирования"?

[Править]

Большое спасибо за все ответы, у меня не было времени для исследования их все же. Платформа является 16-разрядной (XC166 или подобной), uC, явный hw (никакой RTOS).

19
задан Groo 4 January 2010 в 19:02
поделиться

5 ответов

Мы находимся в похожей ситуации. Для решения этих проблем мы реализовали систему сборки, которая поддерживает несколько реализаций желаемых интерфейсов (реализация которых является функцией целевой компиляции), и избегаем использования возможностей API, которые не входят в состав портативных обёртки. Определение обёртки живёт в .h файле, который # включает - специфический для реализации заголовочный файл. Следующий макет демонстрирует, как мы можем работать с семафорным интерфейсом:

#ifndef __SCHEDULER_H
#define __SCHEDULER_H

/*! \addtogroup semaphore Routines for working with semaphores.
 * @{
 */

/* impl/impl_scheduler.h gets copied into place before any file using
 * this interface gets compiled. */
#include "impl/impl_scheduler.h"

/* semaphore operation return values */
typedef enum _semaphoreErr_e
{
    SEMAPHORE_OK = impl_SEMAPHORE_OK,
    SEMAPHORE_TIMEOUT = impl_SEMAPHORE_TIMEOUT
} semaphoreErr_e;

/*! public data type - clients always use the semaphore_t type. */
typedef impl_semaphore_t semaphore_t;

/*! create a semaphore. */
inline semaphore_t *semaphoreCreate(int InitialValue) {
  return impl_semaphoreCreate(InitialValue);
}
/*! block on a semaphore. */
inline semaphoreErr_e semaphorePend(semaphore_t *Sem, int Timeout) {
  return impl_semaphorePend(Sem, Timeout);
}
/*! Allow another client to take a semaphore. */
inline void semaphorePost(semaphore_t *Sem) {
  impl_semaphorePost(Sem);
}

/*! @} */

#endif

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

.
3
ответ дан 30 November 2019 в 05:20
поделиться

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

Почему мы не переключаемся на С++? С удовольствием. Мы не переключаемся в основном по этим причинам:

  1. Наши кодовые обезьяны не стали бы его грокировать и неохотно учатся.
  2. Библиотеки на Си++ значительно больше (хотя мы можем работать и с этим)
  3. Для таких действительно маленьких RTOS устройств в этом обычно нет необходимости. Для больших устройств, где мы используем встроенный Linux, было бы неплохо.
1
ответ дан 30 November 2019 в 05:20
поделиться

Лучше быть уверенным, что фиксированная компоновка - это то, что вы хотите! Сорвать его и поместить в динамический может оказаться очень сложно!

Я предлагаю следующие проблемы, которые пытается решить любой встроенный фреймворк:

Вычисление смещений в данные

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

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

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

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

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

Предотвращение повреждения указателя функции и максимизация производительности отладки

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

Снова можно вычислить и сгенерировать эти стабильные файлы в виде .h файл с #defines программой на генераторе C, которая является исполняемым "build tool".

Для инициализации использования объекта требуется специальный конструктор макрос для записи в соответствующий vtable id.

Обе эти проблемы, по сути, уже хорошо решены, например, препроцессором objective-C (который будет выводить сырой C), но вы можете сделать это с нуля, если хотите остаться с очень маленьким набором инструментов.

Распределение блоков памяти по ресурсам/задачам в статической структуре памяти

Если вам необходимо поддерживать многопоточность, то, возможно, лучше всего связать недолговечные динамические задачи с конкретными индексами в древовидной структуре (ближайший к выделению объекта в эквивалентной процедурной/OO программе), используя пробную блокировку произвольного индекса, например, атомный инкремент (от нуля с ==1 check) или мьютекс, затем проверить, доступен ли блок, и если да, пометить его как использованный, то разблокировать блок.

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

.
0
ответ дан 30 November 2019 в 05:20
поделиться

Вы можете захотеть взглянуть на стандарт алгоритма xDAIS. Он был разработан для DSP-приложений, но идеи могут быть адаптированы и к низкоресурсным встраиваемым конструкциям.

http://en.wikipedia.org/wiki/XDAIS_algorithms

В двух словах: xDAIS - это соглашение об интерфейсе в стиле ООП, не отличающееся от COM для языка Си. Вы имеете фиксированный набор интерфейсов, который модуль может реализовать через структуру указателей функций.

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

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

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

Стандарт xDAIS определяет очень тощий механизм стиля ООП для наследования. Он очень удобен для отладки и протоколирования. У вас есть алгоритм, который делает забавные вещи? Просто добавьте простую однофайловую обертку вокруг существующего алгоритма и регистрируйте все обращения к UART или около того. Благодаря строго определённому интерфейсу нет никакой догадки, как работает модуль и как передаются параметры.

В прошлом я использовал xDAIS, и он хорошо работает. Привыкание к нему занимает некоторое время, но преимущества архитектуры plug-and-play и простота отладки перевешивают первоначальные усилия.

3
ответ дан 30 November 2019 в 05:20
поделиться

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

  • Разделение логики и выполнения:

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

    Если, например, вы кодируете какое-то встроенное устройство, которое считывает значения, интерпретирует их и что-то изменяет на основе этих показаний, вы можете захотеть полностью отделить «логическую» часть от той части, где вы на самом деле общаетесь с сетью, оборудованием, пользователем или любым другим внешним объектом.

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

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

  • Я рос на Амиге, и мне трудно это забыть. Операционная система с возможностью ПЗУ, но легко расширяемая в ОЗУ с помощью загружаемых библиотек. Интенсивное использование передачи указателя сделано как для жесткого кода, так и для быстрых сообщений.

Читая другой ответ Нильса Пипенбринка, его предложение использовать xDAIS выглядит хорошим (но далеко не единственным) способом реализации этого. Если большая часть вашего кода использует подобное соглашение о сообщениях, скорее всего, ваш код является модульным и обслуживаемым.

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

1
ответ дан 30 November 2019 в 05:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: