Можливості виділення пам'яті для модульної розробки прошивки в С


16

модульні підходи загалом досить зручні (портативні та чисті), тому я намагаюся програмувати модулі настільки незалежно від будь-яких інших модулів, наскільки це можливо. Більшість моїх підходів базується на структурі, яка описує сам модуль. Функція ініціалізації задає основні параметри, після чого оброблювач (покажчик на дескриптивну структуру) передається будь-якій функції в модулі.

Наразі мені цікаво, яким може бути найкращий підхід до розподілу пам'яті для структури, що описує модуль. Якщо можливо, я хотів би наступного:

  • Непрозора структура, тому структура може бути змінена лише за допомогою наданих функцій інтерфейсу
  • Кілька примірників
  • пам'ять, виділена лінкером

Я бачу такі можливості, що всі суперечать одній із моїх цілей:

глобальна декларація

декілька екземплярів, нанесених на лінкер, але структура непрозора

(#includes)
module_struct module;

void main(){
   module_init(&module);
}

малок

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

в module.h:

typedef module_struct Module;

у module.c функція init, malloc та вказівник повернення на виділену пам'ять

module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;

в main.c

(#includes)
Module *module;

void main(){
    module = module_init();
}

декларація в модулі

непрозора структура, виділена лінкером, лише заздалегідь визначена кількість примірників

зберігайте всю структуру та пам'ять всередині модуля і ніколи не піддавайте обробник або struct.

(#includes)

void main(){
    module_init(_no_param_or_index_if_multiple_instances_possible_);
}

Чи є можливість комбінувати їх якось для непрозорої структури, лінкера замість розподілу купи та декількох / будь-якої кількості екземплярів?

рішення

як запропоновано в деяких відповідях нижче, я думаю, що найкращим способом є:

  1. резервного місця для MODULE_MAX_INSTANCE_COUNT модулів у вихідному файлі модулів
  2. не визначайте MODULE_MAX_INSTANCE_COUNT у самому модулі
  3. додати #ifndef MODULE_MAX_INSTANCE_COUNT #error у файл заголовка модулів, щоб переконатися, що користувач модулів знає про це обмеження та визначає максимальну кількість бажаних екземплярів для програми
  4. при ініціалізації екземпляра повертайте або адресу пам'яті (* void) описової структури, або індекс модулів (все, що вам більше подобається)

12
Більшість вбудованих FW дизайнерів уникають динамічного розподілу, щоб зберегти використання пам'яті детерміновано та просто. Особливо, якщо це голий метал і не має основної ОС для управління пам'яттю.
Євген Ш.

Саме тому я хочу, щоб лінкер здійснював розподіли.
Л.

4
Я не зовсім впевнений, що я розумію ... Як у вас може бути пам'ять, виділена лінкером, якщо у вас є динамічна кількість примірників? Це здається мені досить ортогональним.
jcaron

Чому б не дозволити лінкеру виділити один великий пул пам’яті та зробити власні виділення з цього, що також дає перевагу алокатора з нульовим режимом. Ви можете зробити об'єкт пулу статичним у файлі за допомогою функції розподілу, щоб він був приватним. У деякому моєму коді я виконую всі виділення в різних процедурах init, потім після цього роздруковую, скільки було виділено, тому в остаточній компіляції виробництва я встановив пул саме такого розміру.
Лі Даніел Крокер

2
Якщо це рішення часу компіляції, ви можете просто визначити число у вашому Makefile або еквіваленті, і все готово. Число не було б у джерелі модуля, але було б конкретним додатком, і ви просто використовуєте номер екземпляра як параметр.
jcaron

Відповіді:


4

Чи є можливість об'єднати їх якось для анонімної структури, лінкера замість розподілу купи та декількох / будь-якої кількості екземплярів?

Звичайно, є. Спершу, однак, визнайте, що "будь-яке число" екземплярів має бути зафіксованим або принаймні встановленим верхнім числом під час компіляції. Це необхідна умова для статичного розподілу примірників (те, що ви називаєте "розподіл лінкера"). Ви можете змінити число без зміни джерела, оголосивши макрос, який його визначає.

Тоді вихідний файл, що містить фактичну структуру декларації та всі пов'язані з нею функції, також оголошує масив примірників із внутрішнім зв’язком. Він забезпечує або масив із зовнішнім зв'язком покажчиків на екземпляри, або функцію доступу до різних покажчиків за індексом. Варіант функції трохи більш модульний:

module.c

#include <module.h>

// 4 instances by default; can be overridden at compile time
#ifndef NUM_MODULE_INSTANCES
#define NUM_MODULE_INSTANCES 4
#endif

struct module {
    int demo;
};

// has internal linkage, so is not directly visible from other files:
static struct module instances[NUM_MODULE_INSTANCES];

// module functions

struct module *module_init(unsigned index) {
    instances[index].demo = 42;
    return &instances[index];
}

Я думаю, ви вже знайомі з тим, як тоді заголовок оголошує структуру неповним типом і оголошує всі функції (написані вказівниками на цей тип). Наприклад:

модуль.h

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

Тепер struct moduleнепрозорий відмінні переклад одиниць module.c, * і ви можете отримати доступ і використовувати до кількості примірників , визначених під час компіляції без динамічного розподілу.


* Якщо, звичайно, ви не скопіюєте його визначення. Справа в тому, що module.hцього не робити.


Я думаю, що це дивна конструкція передавати індекс поза класом. Коли я реалізую такі пули пам’яті, я дозволяю індексу бути приватним лічильником, збільшуючи на 1 для кожного виділеного екземпляра. Поки ви не досягнете "NUM_MODULE_INSTANCES", де конструктор поверне помилку пам'яті.
Лундін

Це справедливий момент, @Lundin. Цей аспект конструкції передбачає, що індекси мають притаманне значення, що може бути, а може і не бути насправді. Це є випадком, хоча і тривіальним так, для вихідного справи ФПА в. Таке значення, якщо воно існує, може бути додатково підтримане наданням засобів для отримання покажчика екземпляра без ініціалізації.
Джон Боллінгер

Таким чином, ви в основному залишаєте пам'ять для n модулів, незалежно від того, скільки буде використано, і повертаєте покажчик на наступний невикористаний елемент, якщо програма його ініціалізує. Я думаю, це могло б спрацювати.
Л.

@ L.Heinrichs Так, оскільки вбудовані системи мають детермінований характер. Немає такого поняття, як "нескінченна кількість об'єктів" чи "невідома кількість об'єктів". Об'єкти часто теж одиночні (драйвери апаратного забезпечення), тому часто немає необхідності в пулі пам'яті, оскільки буде існувати лише один єдиний екземпляр об'єкта.
Лундін

Я згоден у більшості випадків. Питання мало також теоретичний інтерес. Але я міг би використовувати сотні 1-провідних датчиків температури, якщо є достатня кількість вводу-виводу (як єдиний приклад, який я можу придумати зараз).
Л.

22

Я програмую невеликі мікроконтролери на C ++, які досягають саме того, що ви хочете.

Те, що ви називаєте модулем, - це клас C ++, він може містити дані (доступні зовні чи ні) та функції (аналогічно). Конструктор (виділена функція) ініціалізує його. Конструктор може приймати параметри часу виконання або (мій улюблений) параметри часу збирання (шаблон). Функції класу неявно отримують змінну класу як перший параметр. (Або, часто мої переваги, клас може виступати як прихований сингтон, тому всі дані доступні без цього накладного режиму).

Об'єкт класу може бути глобальним (таким чином, ви знаєте в час зв’язку, що все підходить), або локальним стеком, імовірно, в основному. (Мені не подобається глобальний C ++ через невизначений порядок глобальної ініціалізації, тому я віддаю перевагу локальному стеку).

Моїм кращим стилем програмування є те, що модулі - це статичні класи, а їх (статична) конфігурація - за параметрами шаблону. Це дозволяє уникнути майже всіх перекриттів та забезпечує оптимізацію. Поєднайте це з інструментом, який обчислює розмір стека, і ви можете спати без побоювань :)

Моя розмова про такий спосіб кодування в C ++: Об'єкти? Ні, дякую!

Багато вбудованих / мікроконтролерів програмістів, схоже, не люблять C ++, тому що вони думають, що це змусить їх використовувати всі C ++. Це абсолютно не потрібно, і це було б дуже поганою ідеєю. (Напевно, ви також не використовуєте всіх C! Подумайте, купа, плаваюча точка, setjmp / longjmp, printf, ...)


У коментарі Адам Хаун згадує RAII та ініціалізацію. IMO RAII має більше спільного з деконструкцією, але його суть є дійсною: глобальні об'єкти будуть побудовані до вашого основного запуску, тому вони можуть працювати над недійсними припущеннями (як, наприклад, основна тактова частота, яка буде змінена латероном). Це ще одна причина НЕ використовувати об'єкти, ініціалізовані глобальним кодом. (Я використовую сценарій зв'язання, який вийде з ладу, коли у мене є об'єкти, ініціалізовані глобальним кодом.) IMO такі "об'єкти" повинні бути явно створені та передані навколо. Сюди входить об’єкт 'очікування', що об'єкт ', який забезпечує функцію wait (). У моїй установці це "об'єкт", який встановлює тактову частоту чіпа.

Якщо говорити про RAII: це ще одна особливість C ++, яка є дуже корисною для малих вбудованих систем, хоча не з тієї причини (делокація пам'яті), яку вона найбільше застосовує у великих системах (малі вбудовані системи в основному не використовують динамічну операцію з пам'яттю). Подумайте про блокування ресурсу: ви можете зробити заблокований ресурс об’єктом обгортки, а обмежити доступ до ресурсу можливо лише за допомогою блокуючої обгортки. Коли обгортка виходить із сфери застосування, ресурс розблокується. Це запобігає доступу без блокування та робить набагато більш ймовірним забуття розблокування. з деякою (шаблоною) магією це може бути нульовим накладним.


У початковому запитанні не було згадки про С, звідси і моя С ++ - орієнтована відповідь. Якщо це дійсно повинно бути C….

Ви можете скористатися макро-хитрістю: оголосити свої студії публічно, щоб вони мали тип і могли бути розподілені у всьому світі, але маніпулюйте назви своїх компонентів поза зручністю, якщо інший макрос не визначений інакше, що стосується файлу .c вашого модуля. Для додаткової безпеки ви можете використовувати час компіляції для керування.

Або мати публічну версію вашої структури, яка не має нічого корисного, і мати приватну версію (з корисними даними) лише у вашому файлі .c, і стверджувати, що вони однакового розміру. Дещо хитрості файлів може зробити це автоматизованим.


@Lundins коментує поганих (вбудованих) програмістів:

  • Тип програміста, який ви описуєте, мабуть, створить безлад на будь-якій мові. Один очевидний спосіб - макроси (присутні в C і C ++).

  • Інструменти можуть допомогти певною мірою. Для моїх студентів я маю на меті побудований сценарій, який визначає відсутність виключень, no-rtti та дає помилку в посиланнях, коли використовується купа або глобалізовані коди, ініціалізовані кодом. І він вказує попередження = помилка і включає майже всі попередження.

  • Я рекомендую використовувати шаблони, але метапрограмування з конспектером та концепціями все менше і менше потрібно.

  • "розгублені програмісти Arduino" Я дуже хотів би замінити стиль програмування Arduino (проводка, реплікація коду в бібліотеках) на сучасний підхід C ++, який може бути легшим, безпечнішим та створювати швидший і менший код. Якби я мав час і сили….


Дякую за цю відповідь! Використання C ++ - це варіант, але ми використовуємо C у своїй компанії (про що я чітко не згадував). Я оновив питання, щоб повідомити людям, що я говорю про C.
L. Heinrichs,

Чому ви використовуєте (лише) C? Можливо, це дає вам змогу переконати їх хоча б розглянути C ++ ... Те, що ви хочете, є істотним (невелика частина) C ++, реалізованим у C.
Wouter van Ooijen

Те, що я роблю в своєму першому «реальному» вбудованому хобі-проекті, - ініціалізація простого за замовчуванням у конструкторі та використання окремого методу Init для відповідних класів. Ще одна перевага полягає в тому, що я можу передавати покажчики на заглушки для тестування одиниць.
Мішель Кейзерс

2
@Michel для хобі-проекту ви вільні у виборі мови? Візьміть C ++!
Wouter van Ooijen

4
І хоча справді цілком можливо написати хороші програми на C ++ для вбудованих, проблема полягає в тому, що дещо> 50% усіх програмістів вбудованих систем там є шахрайства, плутаних програмістів на ПК, любителів Arduino тощо. Такі люди просто не можуть використовувати чистий підмножина C ++, навіть якщо ви поясните це їх обличчю. Дайте їм C ++, і перш ніж ви це дізнаєтесь, вони використовуватимуть всю STL, метапрограмування шаблонів, обробку винятків, множинне успадкування тощо. І результат, звичайно, повний сміття. Це, на жаль, те, як закінчуються деякі 8 з 10 вбудованих проектів C ++.
Лундін

7

Я вважаю, що FreeRTOS (можливо, інша ОС?) Робить щось подібне до того, що ви шукаєте, визначаючи 2 різні версії структури.
"Справжній", який використовується внутрішньо функціями ОС, і "підроблений", який має той самий розмір, як і "справжній", але всередині не має корисних членів (лише купа int dummy1та подібних).
За межами коду ОС піддається лише "фальшива" структура, яка використовується для виділення пам'яті статичним екземплярам структури.
Внутрішньо, коли викликаються функції в ОС, їм передається адреса зовнішньої "підробленої" структури як ручка, і вона потім набирається як вказівник на "справжню" структуру, щоб функції ОС могли робити все, що їм потрібно робити.


Хороша ідея, я думаю, я міг би використовувати --- #define BUILD_BUG_ON (умова) ((недійсна) sizeof (char [1 - 2 * !! (умова)])) --- BUILD_BUG_ON (sizeof (real_struct)! = Sizeof ( fake_struct)) ----
Л.

2

Анонімна структура, тому структура може бути змінена лише за допомогою наданих функцій інтерфейсу

На мою думку, це безглуздо. Ви можете розмістити коментар там, але немає сенсу намагатися приховати його далі.

C ніколи не забезпечить таку високу ізоляцію, навіть якщо для структури немає декларації, її буде легко випадково перезаписати, наприклад, помилковою memcpy () або переповненням буфера.

Натомість просто дайте ім'я структури та довіряйте іншим людям також писати хороший код. Це також полегшить налагодження, коли структура має ім'я, яке ви можете використовувати для позначення.


2

Чисті питання щодо SW краще задавати на /programming/ .

Концепція з викриттям структури неповного типу абоненту, як ви описуєте, часто називається "непрозорим типом" або "непрозорими вказівниками" - анонімна структура означає щось інше цілком.

Проблема з цим полягає в тому, що абонент не зможе виділити екземпляри об'єкта, а лише покажчики на нього. На ПК ви б використовували mallocвсередині об'єкта "конструктор", але malloc - це неприйняття вбудованих систем.

Отже, те, що ви робите вбудованим, - це забезпечити пул пам’яті. У вас обмежена кількість оперативної пам’яті, тому обмеження кількості об'єктів, які можна створити, зазвичай не є проблемою.

Див. Статичний розподіл непрозорих типів даних на SO.


Оу дякую, що вияснили плутанину з іменами в моєму кінці, неправильно налаштуйте ОП. Я думав перейти до стека переповнення, але вирішив, що хотів би конкретно орієнтуватися на вбудованих програмістів.
Л.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.