Встроенный модуль хранения данных C - PullRequest
8 голосов
/ 25 января 2011

Я нахожусь в процессе разработки встроенного модуля C для хранения данных.Он будет включен в файлы / модули, которые хотят получить доступ к этим «общим» системным данным.Несколько задач объединяют десятки входных данных (GPIO, CAN, данные I2C / SPI / SSP и т. Д.) И сохраняют эти значения с помощью API.Затем другие задачи могут безопасно обращаться к данным через API.Система представляет собой встроенное приложение с ОСРВ, поэтому мьютексы используются для защиты данных.Эти идеи будут использоваться независимо от реализации

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

Быстрое изложение требований этого модуля:

В идеале, было быодин интерфейс, который может получить доступ к переменным (один получить, один набор). Я хотел бы вернуть разные типы переменных (числа с плавающей запятой, целые числа и т. Д.).Это означает, что макросы, вероятно, необходимы. Меня не беспокоит пространство кода, но это всегда вызывает беспокойство Быстрые операции получения / установки абсолютно важны (что означает сохранение в строках, поскольку отсутствует xml / json) Нет необходимости добавлять новые переменныево время выполнения.Все статически определяется при загрузке

Вопрос в том, как бы вы разработали что-то подобное?Перечисления, структуры, методы доступа, макросы и т. Д.?Я здесь не ищу код, я просто обсуждаю общие идеи дизайна.Если в Интернете есть решение, которое решает такие проблемы, возможно, достаточно просто ссылки.

Ответы [ 5 ]

5 голосов
/ 26 января 2011

Я был в этой ситуации пару раз сам.Каждый раз, когда я заканчивал «кататься самостоятельно», я определенно не страдаю от синдрома «Не изобретено здесь» (NIH).Иногда пространство, время выполнения обработки или требования к надежности / восстановлению делают этот путь наименее болезненным.

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

Есть ли C ++ на столе?Встроенные функции, шаблоны и некоторые библиотеки Boost могут быть полезны здесь.Но я предполагаю, что это просто C.

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

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

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

Одна вещь, которую я скажу, это идея получения / установки по тегу (например, может быть список перечислений, таких как CONFIG_DATA, ADDRESS_DATA,и т. д.) это огромный шаг вперед от прямой адресации данных (например, «дайте мне 256 байтов по адресу ox42000). Я видел, что многие магазины испытывают сильную боль, когда вся схема физической адресации, наконец, выходит из строя, и им нужно-фактор / редизайн. Постарайтесь отделить «что» от «как» - клиенты не должны знать или заботиться о том, где хранятся вещи, насколько они велики и т. д. (Вы, возможно, уже знаете все это,извините, если так, я просто вижу это все время ...)

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

2 голосов
/ 13 февраля 2011

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

  • Для более сложных данных с большим количеством зависимостей и ограничений я обнаружил, что модуль более высокого уровня обычно предпочтительнее, даже если он идет за счет скорости. Он избавляет вас от головной боли, а его Обычно это мой опыт, если только у вас нет жестких ограничений. Дело в том, чтобы хранить большую часть ваших «статических» данных, которые не сильно меняются, в этом типе модуля, и извлекать выгоду из простоты использования и более «сложных» функций (например, проверка данных, ссылочная целостность, запросы , Сделки, привилегии и т. Д.). Однако, если скорость имеет решающее значение в некоторых областях, вы можете вообще не использовать ее в этом модуле или реализовать простую схему «зеркалирования» (где вы можете, например, периодически синхронизировать назад и вперед между вашими легкими зеркалами и основной конфигурационной БД на какая-то праздная задача). Имея некоторый опыт решения этой проблемы на встраиваемых системах, я обнаружил, что работать с sqlite проще всего. Просматривая ваши комментарии, я могу засвидетельствовать, что портирование не так уж сложно, если у вас есть стандартная библиотека C, поддерживаемая для вашей платформы. Примерно через неделю я перенес систему на архитектуру PowerPC, и у нас был чистый собственный проприетарный код. В основном это был вопрос определения уровня абстракции ОС и плавного перехода оттуда. Я настоятельно рекомендую sqlite для этого .
  • Если вышеупомянутое все еще слишком тяжело (либо с точки зрения производительности, либо, возможно, это просто перебор), вы можете получить большинство важных преимуществ от использования базовой системы ключ-значение . Идея похожа на то, что упоминалось здесь ранее: определите ваши ключи с помощью enum, сохраните ваши данные в таблице значений одинакового размера (например, 64 бита), и пусть каждая переменная находится в одной ячейке. Я считаю, что это работает для более простых проектов, в которых не так много типов записей конфигурации, в то время как вы можете сохранять гибкость, а систему по-прежнему легко разрабатывать и использовать. Несколько указателей для этого:
    • Разделите ваши перечисления на группы или категории , каждая со своим собственным диапазоном значений. Схема, которая мне нравится, состоит в том, чтобы определить сами группы в одном перечислении и использовать эти значения для запуска первого значения каждой из групп (EntryInGroupID = (groupid << 16) | serial_entry_id).
    • Легко реализовать " таблицы ", используя эту схему (где у вас есть более одной записи для каждой потребности или конфигурации, например, таблицы маршрутизации). Вы можете обобщить дизайн, сделав что-нибудь «таблицей», и если вы хотите определить одно значение, просто сделайте его таблицей с одной записью. Во встроенных системах я бы порекомендовал распределить все заранее, в соответствии с максимальным количеством записей, возможных для этой таблицы, и «используя» ее часть. Таким образом, вы охватили наихудший случай и ведете себя детерминистически.
    • Еще одна вещь, которую я нашел очень полезной, - это использование Hooks для всего. Эти ловушки могут быть реализованы с использованием указателей на функции (для каждого поведения / политики / операции), которые задаются через структуру «дескриптор», которая используется для определения ввода данных. Обычно ваши записи будут использовать функции перехвата «по умолчанию» для чтения / записи (например, прямой доступ к памяти или доступ к файлу) и обнулять другие возможные перехватчики (например, «проверять», «значение по умолчанию», «уведомлять об изменениях»). " и тому подобное). Время от времени, когда у вас есть более сложный случай, вы можете воспользоваться системой хуков, которую вы внедрили, с небольшими затратами на разработку. Эта система хуков по своей сути может предоставить вам «уровень аппаратной абстракции» для хранения самих значений и может с той же легкостью получать доступ к данным из памяти, регистров или файлов. Средство регистрации / отмены регистрации «уведомлять об изменениях» также может действительно помочь в уведомлении задач об изменениях конфигурации, и его не так сложно добавить.
  • Для нужд ультра-реального времени, я бы порекомендовал вам сохранить простоту и сохранить ваши данные в структурах . Это в значительной степени самый легкий способ, которым вы можете пойти, и он обычно отвечает всем вашим потребностям. Однако в этой системе трудно справиться с параллелизмом, поэтому вам придется либо обернуть весь доступ мьютексами, как вы предлагали, либо просто сохранить зеркало для каждой «задачи». Если это действительно ультра-в реальном времени, я бы пошел на подход зеркалирования с периодической синхронизацией по всей системе). Как эта система, так и предложенная выше, позволяют очень легко сериализовать ваши данные (так как они всегда хранятся в двоичном формате), что упростит процесс и просто упростит процесс (все, что вам нужно для сериализации - это memcpy бросок или два). Кроме того, время от времени может помочь использование объединений, и некоторые оптимизаторы справляются с этим хорошо, однако это действительно зависит от архитектуры и набора инструментов, с которым вы работаете.
  • Если вы действительно беспокоитесь о необходимости обновления данных для новых схем, вы можете рассмотреть « модуль обновления », который может быть настроен с несколькими схемами и будет использоваться для преобразования старые форматы в новый при вызове. Вы можете использовать методы генерации кода из XML-схем для генерации самой базы данных (например, в структуры и перечисления) и использовать инструменты XML для выполнения «кода преобразования», написав XSLT или просто простые Python-подобные скрипты. Возможно, вы захотите выполнить преобразование вне цели (например, на хосте), используя более высокоуровневый сценарий, а не вводить эту логику в саму цель.
  • Для Тип данных Безопасность Вы можете использовать множество шаблонов и метапрограммирования. Несмотря на то, что его сложно поддерживать как кодировщик фреймворка, он облегчает работу пользователей системы. Если код правильно (что сложно!), Это может сэкономить вам много циклов и не налагать много кода (с встраиванием, явной реализацией шаблона и большой осторожностью). Если ваш компилятор не так хорош (как это обычно бывает с компиляторами встроенных платформ), просто используйте макросы-обертки для преобразования.
  • По поводу проблемы параллелизма , я обычно прибегаю к одному из следующих методов, в зависимости от общего дизайна системы
    • Как вы и предлагали, оберните все это мьютексом и надейтесь на лучшее. Вы можете получить больше точности, добавив блокировку для каждой таблицы или даже для каждой записи, но у этого есть свой собственный набор проблем, и я бы порекомендовал просто сохранять его простым и придерживаться глобального мьютекса. Если у вас есть репликации для жесткого материала в реальном времени, это, вероятно, не повредит вашей производительности, поскольку вы выполняете синхронизацию только время от времени.
    • Создайте свою базу данных как « одиночное задание » и оберните весь доступ к ней через сообщения (MQ, сокеты, что угодно). Это работает лучше для данных некритического пути (опять же) и является простым решением. Если не слишком много одновременных обращений, это может быть путь. Обратите внимание, что если вы находитесь в том же диапазоне памяти и имеете общую память, производительность операций чтения / записи не так уж высока (поскольку вы не копируете данные), и если вы правильно играете с приоритетами своей задачи, это может быть простое решение, которое удовлетворит большинство ваших потребностей (полностью исключая проблему одновременного доступа).

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

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

Надеюсь, что ответ не был 'т перебор: -)

1 голос
/ 28 января 2011

Я обычно обращаюсь к простому словарному API с использованием int в качестве ключа и значения фиксированного размера.Это выполняется быстро, использует очень небольшой объем ОЗУ программы и имеет предсказуемое использование ОЗУ данных.Другими словами, API самого низкого уровня выглядит следующим образом:

void data_set(uint16 key, uint32 value);
uint32 data_get(uint16 key);

Ключи становятся списком констант:

#define KEY_BOGOMIPS 1
#define KEY_NERDS_PER_HOUR 2

При обработке типов данных вы обрабатываете разные типы данных.Отстой, но вы можете написать макросы, чтобы сделать код немного чище:

#define data_get_float(key) (float)data_get(key)

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

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

struct data_item_t {
    uint16 key;
    uint32 value;
}

struct data_item_t items[NUM_ITEMS];

и перебираю.Для меня это было достаточно быстро даже на очень маленьких (8-битных) микроконтроллерах, хотя оно может не подойти вам, если у вас много элементов.

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

0 голосов
/ 17 августа 2018

Я мог бы подумать о некоторых концепциях смеси модулей AUTOSAR, таких как NVM или даже Com.Для большинства из них фактический тип не имеет значения для работы этого модуля, это всего лишь несколько байтов.

NvM имеет статически сконфигурированную конфигурацию, генерируемую инструментом в соответствии с определением ввода.Каждому блоку присваивается идентификатор, который также настраивается (например, typedef uint16 NvM_BlockIdType).Конфигурация также содержит размер данных, возможный CRC (8,16,32), некоторые сведения, например, используемые с NvM_ReadAll или NvM_WriteAll (или нет), возможные обратные вызовы init или блоки init для инициализации RamBlock, тип блока NvM как собственный, избыточныйили DataSet ...

Блок обрабатывается NVM обычно с одной или двумя очередями (Стандартная очередь заданий и Очередь немедленных заданий).Со стороны приложения, это просто вызов:

Std_ReturnType NvM_ReadBlock(NvM_BlockIdType BlockId, uint8* data);
Std_ReturnType NvM_WriteBlock(NvM_BlockIdType BlockId, uint8* data);

 Std_ReturnType NvM_ReadAll(void);
 Std_ReturnType NvM_WriteAll(void);

 Std_ReturnType NvM_GetErrorStatus(NvM_BlockIdType BlockId, NvM_RequestResultType* res);

Обычно Std_ReturnType возвращает E_OK при принятом запросе, E_NOT_OK при некоторой ошибке, например, ID не найден, NvM не работает ...

Относительно некоторой обработки сигналов, таких как флаги или подобные сигналы, которые не являются примитивными типами, такими как uint8, uint16 ... но, возможно, как uint11, uint4 .. Com фактически получает IPDU и сохраняет их в буферах.Для передачи также копируется IPDU для передачи.На более высоких уровнях у вас есть Com_SendSignal (идентификатор uint16, uint8 * data) или Com_ReceiveSignal (идентификатор uint16, uint8 * data).

В некоторых реализациях просто создается большой буфер размером всех IPDU.Затем они имеют конфигурацию SignalGroup и Signal по Signal-ID и SignalGroup-ID, которая сохраняет смещение в массиве как индекс, плюс начальный бит и размер битов, чтобы окончательно упаковать / распаковать данные сигнала от / к указателю на данные, передаваемые в функции.

Аналогично Com в отношении упаковки / распаковки, можно увидеть в преобразователе SomeIP.

0 голосов
/ 25 апреля 2012

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

...