Подтвердить что ты не робот

Модели Loose-связи для программирования встроенных систем

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

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

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

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

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

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

[изменить]

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

4b9b3361

Ответ 1

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

#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

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

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

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

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

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

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

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

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

Ответ 4

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

  • Отдельная логика и выполнение:

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

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

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

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

  • Выросший на Amiga, мне трудно забыть об этом. ROM-совместимая операционная система, но легко расширяемая в ОЗУ загружаемыми библиотеками. Тяжелое использование указателя, передающего, сделанное как для жесткого кода, так и для быстрых сообщений.

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

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

Ответ 5

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

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

  • Наши обезьяны-козыри не будут их перечитывать и не хотят учиться.
  • Библиотеки С++ значительно больше (хотя мы можем обойти это.)
  • Для таких действительно маленьких устройств с ОСРВ обычно это не требуется. Для больших устройств, где мы запускаем встроенный Linux, было бы неплохо.

НТН.

Ответ 6

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

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

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

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

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

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

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

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

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

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

Теперь vtables может быть вычислен и сгенерирован как .h файл С#defines программой-генератором C, которая является исполняемым "инструментом сборки".

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

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

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

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

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