Размещение динамических векторов во внешней оперативной памяти

В настоящее время я работаю над большим собственным проектом на микроконтроллере STM32F7 cortex-m7 на C ++ с использованием GCC. Мне нужно хранить большой массив во внешней SDRAM (16 МБ), содержащий векторы структур нот (по 12 байт). У меня уже есть рабочий FMC и созданный пользовательский регион RAM

/* Specify the memory areas */
MEMORY
{
RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 512K
FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 1024K
SDRAM (xrw)     : ORIGIN = 0xC0000000, LENGTH = 16M
}

/* Define output sections */
SECTIONS
{
  /* Section créée pour l'allocation dans la SDRAM externe*/
  .fmc :
  {
    . = ALIGN(8);
    *(.fmc)        
    *(.fmc*)
    . = ALIGN(8);
  } >SDRAM

мой массив объявлен так:

std::vector<SequencerNoteEvent> NotesVectorArray[kMaxPpqn] __attribute__((section(".fmc")));

Пока все нормально. Я создал массив векторов во внешней оперативной памяти. Как я могу продолжить создание элементов вектора

NotesVectorArray[position].push_back(note);

происходит в той же внешней оперативной памяти динамически? В настоящее время я могу объявлять только статические данные с помощью __attribute__(section)

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

Кажется возможным иметь несколько куч. Где находится связь между scatter-файлом и эффективным распределением памяти?

Заранее спасибо,

Бен


person zelo zelatus    schedule 06.03.2019    source источник
comment
Вам нужно будет написать собственный распределитель вместо использования по умолчанию std::allocator. how i should replace it См. Второй аргумент шаблона std::vector.   -  person eerorika    schedule 06.03.2019
comment
Для этого вам нужно будет использовать распределитель. К сожалению, я не знаком с архитектурой STM32F7, поэтому я не смогу рассказать вам, как выделить память во внешнем ОЗУ.   -  person SergeyA    schedule 06.03.2019
comment
Как бы вы выделить блок памяти (альтернативу malloc ()) для такой внешней памяти?   -  person Michael Veksler    schedule 06.03.2019
comment
Возможно, это немного наивно, но я подумал о повторном использовании malloc, но указав на другую часть памяти. Мой внешний плунжер фактически является частью моего MCU.   -  person zelo zelatus    schedule 06.03.2019


Ответы (1)


Я столкнулся с той же проблемой и получил рабочее решение, переопределив operator new. Преимущество этого перед использованием настраиваемого распределителя заключается в том, что все динамические выделения будут автоматически находиться в SDRAM, поэтому вы можете использовать std :: function или любой контейнер STL, для которого требуется куча, без необходимости каждый раз передавать настраиваемый аргумент распределителя.

Я сделал следующее:

  1. Удалите стандартную библиотеку, добавив -nostdlib к флагам компиляции. Я также добавил это во флаги компоновщика. Также удалите и --specs=... из флагов компилятора и компоновщика. Бонус: вы сэкономите ~ 60-80 КБ кода!

  2. Напишите свою замену для operator new, operator delete. Я создал новый файл с именем new.cpp и добавил следующее: (взято из https://arobenko.gitbooks.io/bare_metal_cpp/content/compiler_output/dyn_mem.html)

//new.cpp
#include <cstdlib>
#include <new>

void *operator new(size_t size) noexcept { return malloc(size); }
void operator delete(void *p) noexcept { free(p); }
void *operator new[](size_t size) noexcept { return operator new(size); }
void operator delete[](void *p) noexcept { operator delete(p); }
void *operator new(size_t size, std::nothrow_t) noexcept { return operator new(size); }
void operator delete(void *p, std::nothrow_t) noexcept { operator delete(p); }
void *operator new[](size_t size, std::nothrow_t) noexcept { return operator new(size); }
void operator delete[](void *p, std::nothrow_t) noexcept { operator delete(p); }
  1. Определите в сценарии компоновщика переменную, которая соответствует наименьшему адресу SDRAM (началу кучи). В этом нет строгой необходимости, поскольку вы можете использовать константу (0xC0000000) в своем коде на шаге 4, но это упрощает хранение адреса SDRAM в одном месте. Просто добавьте одну строку: PROVIDE( _fmc_start = . );
//linker script

 [snip]

  /* Section créée pour l'allocation dans la SDRAM externe*/
  .fmc :
  {
    . = ALIGN(8);
    PROVIDE( _fmc_start = . );
    *(.fmc)     
    *(.fmc*)
    . = ALIGN(8);
  } >SDRAM

  1. Новая реализация new будет напрямую вызывать malloc(), который вызовет _sbrk(), когда ему понадобится блок памяти в куче. Таким образом, вы должны определить _sbrk(), чтобы отслеживать указатель кучи, и просто запустить указатель с наименьшего адреса SDRAM, который вы только что определили в сценарии компоновщика. Вот что я сделал:
//sbrk.cpp, or append this to new.cpp
#include <cstdlib>

extern "C" size_t _sbrk(int incr)
{
    extern char _fmc_start; // Defined by the linker
    static char *heap_end;
    char *prev_heap_end;

    if (heap_end == 0)
        heap_end = &_fmc_start;
    prev_heap_end = heap_end;
    //Optionally can check for out-of-memory error here
    heap_end += incr;
    return (size_t)prev_heap_end;
}
  1. На этом этапе, если вы попытаетесь скомпилировать, вы, вероятно, получите много ошибок компоновщика. Точные ошибки будут зависеть от того, какие части стандартной библиотеки использует ваш проект, помимо новых и удаленных. В моем случае я использовал std::function, и компилятор пожаловался, что у него нет __throw_bad_function_call(). Вероятно, вы также увидите ошибку для таких вещей, как _init(), _fini() и __errno. Основная стратегия - создать собственную пустую заглушку функции или объявление переменной для каждого из них. Стоит выполнить быстрый поиск каждого из них, чтобы увидеть, для какой цели они служат. Возможно, вы обнаружите, что ваш проект или библиотека, которую вы включаете, используют некоторые функции, о которых вы не знали! Когда вы создаете заглушку, вам необходимо правильно сопоставить сигнатуру функции, поэтому для этого также потребуется поиск в Интернете. Многие заглушки предназначены для обработки ошибок / исключений, поэтому вы можете решить, как обрабатывать их в теле функции (или игнорировать ошибки).

Например, вот некоторая информация о (устаревших, но обязательных) функциях _init() и _fini(): https://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html

Вот некоторая информация о __dso_handle: https://lists.debian.org/debian-gcc/2003/07/msg00057.html

__cxa_pure_virtual (): Какова цель __cxa_pure_virtual?

Вот что требовалось моему проекту для работы:

//syscalls.cpp
extern "C" {
void _init(void) {}
void _fini(void) {}
int __errno;
void *__dso_handle = (void *)&__dso_handle;
}
namespace std
{
//These are error handlers. They could alternatively restart, or log an error
void __throw_bad_function_call() { while (1); }
void __cxa_pure_virtual() { while (1); }
} // namespace std

После всего этого он наконец-то скомпилируется, и если вы воспользуетесь отладчиком, вы увидите, что адреса ваших векторов начинаются с 0xC0000000!

person Dan Green    schedule 09.10.2020