[C++, Системное программирование, Программирование микроконтроллеров] Запуск сложных C++ приложений на микроконтроллерах
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Сегодня никого не удивить возможностью разрабатывать на C++ под микроконтроллеры. Проект mbed полностью ориентирован на этот язык. Ряд других RTOS предоставляют возможности разработки на С++. Это удобно, ведь программисту доступны средства объектно-ориентированного программирования. Вместе с тем, многие RTOS накладывают различные ограничения на использование C++. В данной статье мы рассмотрим внутреннюю организацию C++ и выясним причины этих ограничений.
Сразу хочу отметить, что большинство примеров будут рассмотрены на RTOS Embox. Ведь в ней на микроконтроллерах работают такие сложные C++ проекты как Qt и OpenCV. OpenCV требует полной поддержки С++, которой обычно нет на микроконтроллерах.
Базовый синтаксис
Синтаксис языка C++ реализуется компилятором. Но в рантайм необходимо реализовать несколько базовых сущностей. В компиляторе они включаются в библиотеку поддержки языка libsupc++.a. Наиболее базовой является поддержка конструкторов и деструкторов. Существуют два типа объектов: глобальные и выделяемые с помощью операторов new.
Глобальные конструкторы и деструкторы
Давайте взглянем на то как работает любое C++ приложение. Перед тем как попасть в main(), создаются все глобальные C++ объекты, если они присутствуют в коде. Для этого используется специальная секция .init_array. Еще могут быть секции .init, .preinit_array, .ctors. Для современных компиляторов ARM, чаще всего секции используются в следующем порядке .preinit_array, .init и .init_array. С точки зрения LIBC это обычный массив указателей на функции, который нужно пройти от начала и до конца, вызвав соответствующий элемент массива. После этой процедуры управление передается в main().
Код вызова конструкторов для глобальных объектов из Embox:
void cxx_invoke_constructors(void) {
extern const char _ctors_start, _ctors_end;
typedef void (*ctor_func_t)(void);
ctor_func_t *func = (ctor_func_t *) &_ctors_start;
....
for ( ; func != (ctor_func_t *) &_ctors_end; func++) {
(*func)();
}
}
Давайте теперь посмотрим как устроено завершение C++ приложения, а именно, вызов деструкторов глобальных объектов. Существует два способа.
Начну с наиболее используемого в компиляторах — через __cxa_atexit() (из C++ ABI). Это аналог POSIX функции atexit, то есть вы можете зарегистрировать специальные обработчики, которые будут вызваны в момент завершения программы. Когда при старте приложения происходит вызов глобальных конструкторов, как описано выше, там же есть и сгенерированный компилятором код, который регистрирует обработчики через вызов __cxa_atexit. Задача LIBC здесь сохранить требуемые обработчики и их аргументы и вызвать их в момент завершения приложения.
Другим способом является сохранение указателей на деструкторы в специальных секциях .fini_array и .fini. В компиляторе GCC это может быть достигнуто с помощью флага -fno-use-cxa-atexit. В этом случае во время завершения приложения деструкторы должны быть вызваны в обратном порядке (от старшего адреса к младшему). Этот способ менее распространен, но может быть полезен в микроконтроллерах. Ведь в этом случае на момент сборки приложения можно узнать сколько обработчиков потребуется.
Код вызова деструкторов для глобальных объектов из Embox:
int __cxa_atexit(void (*f)(void *), void *objptr, void *dso) {
if (atexit_func_count >= TABLE_SIZE) {
printf("__cxa_atexit: static destruction table overflow.\n");
return -1;
}
atexit_funcs[atexit_func_count].destructor_func = f;
atexit_funcs[atexit_func_count].obj_ptr = objptr;
atexit_funcs[atexit_func_count].dso_handle = dso;
atexit_func_count++;
return 0;
};
void __cxa_finalize(void *f) {
int i = atexit_func_count;
if (!f) {
while (i--) {
if (atexit_funcs[i].destructor_func) {
(*atexit_funcs[i].destructor_func)(atexit_funcs[i].obj_ptr);
atexit_funcs[i].destructor_func = 0;
}
}
atexit_func_count = 0;
} else {
for ( ; i >= 0; --i) {
if (atexit_funcs[i].destructor_func == f) {
(*atexit_funcs[i].destructor_func)(atexit_funcs[i].obj_ptr);
atexit_funcs[i].destructor_func = 0;
}
}
}
}
void cxx_invoke_destructors(void) {
extern const char _dtors_start, _dtors_end;
typedef void (*dtor_func_t)(void);
dtor_func_t *func = ((dtor_func_t *) &_dtors_end) - 1;
/* There are two possible ways for destructors to be calls:
* 1. Through callbacks registered with __cxa_atexit.
* 2. From .fini_array section. */
/* Handle callbacks registered with __cxa_atexit first, if any.*/
__cxa_finalize(0);
/* Handle .fini_array, if any. Functions are executed in teh reverse order. */
for ( ; func >= (dtor_func_t *) &_dtors_start; func--) {
(*func)();
}
}
Глобальные деструкторы необходимы, чтобы иметь возможность перезапускать C++ приложения. Большинство RTOS для микроконтроллеров предполагает запуск единственного приложения, которое не перезагружается. Старт начинается с пользовательской функции main, единственной в системе. Поэтому в небольших RTOS зачастую глобальные деструкторы пустые, ведь их использование не предполагается.
Код глобальный деструкторов из Zephyr RTOS:
/**
* @brief Register destructor for a global object
*
* @param destructor the global object destructor function
* @param objptr global object pointer
* @param dso Dynamic Shared Object handle for shared libraries
*
* Function does nothing at the moment, assuming the global objects
* do not need to be deleted
*
* @return N/A
*/
int __cxa_atexit(void (*destructor)(void *), void *objptr, void *dso)
{
ARG_UNUSED(destructor);
ARG_UNUSED(objptr);
ARG_UNUSED(dso);
return 0;
}
Операторы new/delete
В компиляторе GCC реализация операторов new/delete находится в библиотеке libsupc++, А их декларации в заголовочном файле .
Можно использовать реализации new/delete из libsupc++.a, но они достаточно простые и могут быть реализованы например, через стандартные malloc/free или аналоги.
Код реализации new/delete для простых объектов Embox:
void* operator new(std::size_t size) throw() {
void *ptr = NULL;
if ((ptr = std::malloc(size)) == 0) {
if (alloc_failure_handler) {
alloc_failure_handler();
}
}
return ptr;
}
void operator delete(void* ptr) throw() {
std::free(ptr);
}
RTTI & exceptions
Если ваше приложение простое, вам может не потребоваться поддержка исключений и динамическая идентификация типов данных (RTTI). В этом случае их можно отключить с помощью флагов компилятора -no-exception -no-rtti.
Но если эта функциональность С++ требуется, ее нужно реализовать. Сделать это куда сложнее чем new/delete.
Хорошая новость заключается в том что эти вещи не зависят от ОС и уже реализованы в кросс-компиляторе в библиотеке libsupc++.a. Соответственно, самый простой способ добавить поддержку это использовать библиотеку libsupc++.a из кросс компилятора. Сами прототипы находятся в заголовочных файлах и .
Для использования исключений из кросс-компилятора есть небольшие требования, которые нужно реализовать при добавлении собственного метода загрузки C++ рантайма. В линкер скрипте должна быть предусмотрена специальная секция .eh_frame. А перед использованием рантайма эта секция должна быть инициализирована с указанием адреса начала секции. В Embox используется следующий код:
void register_eh_frame(void) {
extern const char _eh_frame_begin;
__register_frame((void *)&_eh_frame_begin);
}
Для ARM архитектуры используются другие секции с собственной структурой информации — .ARM.exidx и .ARM.extab. Формат этих секция определяется в стандарте“Exception Handling ABI for the ARM Architecture” — EHABI. .ARM.exidx это таблица индексов, а .ARM.extab это таблица самих элементов требуемых для обработки исключения. Чтобы использовать эти секции для обработки исключений, необходимо включить их в линкер скрипт:
.ARM.exidx : {
__exidx_start = .;
KEEP(*(.ARM.exidx*));
__exidx_end = .;
} SECTION_REGION(text)
.ARM.extab : {
KEEP(*(.ARM.extab*));
} SECTION_REGION(text)
Чтобы GCC мог использовать эти секции для обработки исключений, указывается начало и конец секции .ARM.exidx — __exidx_start и __exidx_end. Эти символы импортируются в libgcc в файле libgcc/unwind-arm-common.inc:
extern __EIT_entry __exidx_start;
extern __EIT_entry __exidx_end;
Более подробно про stack unwind на ARM написано в статье.
Стандартная библиотека языка (libstdc++)
Собственная реализация стандартной библиотеки
В поддержку языка C++ входит не только базовый синтаксис, но и стандартная библиотека языка libstdc++. Ее функциональность, так же как и для синтаксиса, можно разделить на разные уровни. Есть базовые вещи типа работы со строками или C++ обертка setjmp . Они легко реализуются через стандартную библиотеку языка C. А есть более продвинутые вещи, например, Standard Template Library (STL).
Стандартная библиотека из кросс-компилятора
Базовые вещи реализованы в Embox. Если этих вещей достаточно, то можно не подключать внешнюю стандартную библиотеку языка C++. Но если нужна, например, поддержка контейнеров, то самым простым способом является использование библиотеки и заголовочных файлов из кросс-компилятора.
При использовании стандартной библиотеки С++ из кросс-компилятора существует особенность. Взглянем на стандартный arm-none-eabi-gcc:
$ arm-none-eabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-none-eabi-gcc
COLLECT_LTO_WRAPPER=/home/alexander/apt/gcc-arm-none-eabi-9-2020-q2-update/bin/../lib/gcc/arm-none-eabi/9.3.1/lto-wrapper
Target: arm-none-eabi
Configured with: *** --with-gnu-as --with-gnu-ld --with-newlib ***
Thread model: single
gcc version 9.3.1 20200408 (release) (GNU Arm Embedded Toolchain 9-2020-q2-update)
Он собран с поддержкой --with-newlib.Newlib реализация стандартной библиотеки языка C. В Embox используется собственная реализация стандартной библиотеки. Для этого есть причина, минимизация накладных расходов. И следовательно для стандартной библиотеки С можно задать требуемые параметры, как и для других частей системы.
Так как стандартные библиотеки C отличаются, то для поддержки рантайма нужно реализовать слой совместимости. Приведу пример реализации из Embox одной из необходимых но неочевидных вещей для поддержки стандартной библиотеки из кросс-компилятора
struct _reent {
int _errno; /* local copy of errno */
/* FILE is a big struct and may change over time. To try to achieve binary
compatibility with future versions, put stdin,stdout,stderr here.
These are pointers into member __sf defined below. */
FILE *_stdin, *_stdout, *_stderr;
};
struct _reent global_newlib_reent;
void *_impure_ptr = &global_newlib_reent;
static int reent_init(void) {
global_newlib_reent._stdin = stdin;
global_newlib_reent._stdout = stdout;
global_newlib_reent._stderr = stderr;
return 0;
}
Все части и их реализации необходимые для использования libstdc++ кросс-компилятора можно посмотреть в Embox в папке ‘third-party/lib/toolchain/newlib_compat/’
Расширенная поддержка стандартной библиотеки std::thread и std::mutex
Стандартная библиотека C++ в компиляторе может иметь разный уровень поддержки. Давайте еще раз взглянем на вывод:
$ arm-none-eabi-gcc -v
***
Thread model: single
gcc version 9.3.1 20200408 (release) (GNU Arm Embedded Toolchain 9-2020-q2-update)
Модель потоков “Thread model: single”. Когда GCC собран с этой опцией, убирается вся поддержка потоков из STL (например std::thread и std::mutex). И, например, со сборкой такого сложного С++ приложение как OpenCV возникнут проблемы. Иначе говоря, для сборки приложений, которые требуют подобную функциональность, недостаточно этой версии библиотеки.
Решением, которые мы применяем в Embox, является сборка собственного компилятора ради стандартной библиотеки с многопоточной моделью. В случае Embox модель потоков используется posix “Thread model: posix”. В этом случае std::thread и std::mutex реализуются через стандартные pthread_* и pthread_mutex_*. При этом также отпадает необходимость подключать слой совместимости с newlib.
Конфигурация Embox
Хотя пересборка компилятора и является самым надежным и обеспечивает максимально полное и совместимое решение, но при этом оно занимает достаточно много времени и может потребовать дополнительных ресурсов, которых не так много в микроконтроллере. Поэтому данный метод не целесообразно использовать везде.
В Embox для оптимизации затрат на поддержку введены несколько абстрактных классов (интерфейсов) различные реализации которых можно задать.
- embox.lib.libsupcxx — определяет какой метод для поддержки базового синтаксиса языка нужно использовать.
- embox.lib.libstdcxx — определяет какую реализацию стандартной библиотеки нужно использовать
Есть три варианта libsupcxx:
- embox.lib.cxx.libsupcxx_standalone — базовая реализация в составе Embox.
- third_party.lib.libsupcxx_toolchain — использовать библиотеку поддержки языка из кросс-компилятора
- third_party.gcc.tlibsupcxx — полная сборка библиотеки из исходников
Минимальный вариант может работать даже без стандартной библиотеки С++. В Embox есть реализация базирующаяся на простейших функциях из стандартной библиотеки языка С. Если этой функциональности не хватает, можно задать три варианта libstdcxx.
- third_party.STLport.libstlportg — стандартная библиотека вслкючающая STL на основе проекта STLport. Не требует пересборки gcc. Но проект давно не поддерживается
- third_party.lib.libstdcxx_toolchain — стандартная библиотека из кросс-компилятора
- third_party.gcc.libstdcxx — полная сборка библиотеки из исходников
Если есть желание у нас на wiki описано как можно собрать и запустить Qt или OpenCV на STM32F7. Весь код естественно свободный.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование микроконтроллеров, Компьютерное железо, DIY или Сделай сам] Raspberry Pi Pico на МК RP2040: начало и первые шаги. Что есть поесть за $4
- [Программирование, C++, Учебный процесс в IT, Карьера в IT-индустрии] C++ в Практикуме. Как обучить студентов плюсам, не отпугивая
- [Программирование, C++, Работа с 3D-графикой, Разработка игр, CGI (графика)] Vulkan. Руководство разработчика. Window surface (перевод)
- [Системное программирование, Rust, WebAssembly] Ржавеем дальше. Как появился Rust и можно ли на нём WEB?
- [C++, C, Программирование микроконтроллеров] Реализация многозадачности на функциональных очередях (без RTOS)
- [Системное администрирование, Системное программирование, DevOps] Функции Terraform (перевод)
- [C++, Разработка под Arduino] Разработка средств измерения температуры
- [Системное администрирование, Системное программирование, DevOps] Режим высокой доступности HashiCorp Vault (HA) (перевод)
- [Информационная безопасность, C++, C, Разработка для интернета вещей] Espressif IoT Development Framework: 71 выстрел в ногу
- [Информационная безопасность, C++, C, Разработка для интернета вещей] Espressif IoT Development Framework: 71 Shots in the Foot
Теги для поиска: #_c++, #_sistemnoe_programmirovanie (Системное программирование), #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_embox, #_c++, #_opencv, #_qt, #_mcu, #_mcus, #_blog_kompanii_embox (
Блог компании Embox
), #_c++, #_sistemnoe_programmirovanie (
Системное программирование
), #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:46
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Сегодня никого не удивить возможностью разрабатывать на C++ под микроконтроллеры. Проект mbed полностью ориентирован на этот язык. Ряд других RTOS предоставляют возможности разработки на С++. Это удобно, ведь программисту доступны средства объектно-ориентированного программирования. Вместе с тем, многие RTOS накладывают различные ограничения на использование C++. В данной статье мы рассмотрим внутреннюю организацию C++ и выясним причины этих ограничений. Сразу хочу отметить, что большинство примеров будут рассмотрены на RTOS Embox. Ведь в ней на микроконтроллерах работают такие сложные C++ проекты как Qt и OpenCV. OpenCV требует полной поддержки С++, которой обычно нет на микроконтроллерах. Базовый синтаксис Синтаксис языка C++ реализуется компилятором. Но в рантайм необходимо реализовать несколько базовых сущностей. В компиляторе они включаются в библиотеку поддержки языка libsupc++.a. Наиболее базовой является поддержка конструкторов и деструкторов. Существуют два типа объектов: глобальные и выделяемые с помощью операторов new. Глобальные конструкторы и деструкторы Давайте взглянем на то как работает любое C++ приложение. Перед тем как попасть в main(), создаются все глобальные C++ объекты, если они присутствуют в коде. Для этого используется специальная секция .init_array. Еще могут быть секции .init, .preinit_array, .ctors. Для современных компиляторов ARM, чаще всего секции используются в следующем порядке .preinit_array, .init и .init_array. С точки зрения LIBC это обычный массив указателей на функции, который нужно пройти от начала и до конца, вызвав соответствующий элемент массива. После этой процедуры управление передается в main(). Код вызова конструкторов для глобальных объектов из Embox: void cxx_invoke_constructors(void) {
extern const char _ctors_start, _ctors_end; typedef void (*ctor_func_t)(void); ctor_func_t *func = (ctor_func_t *) &_ctors_start; .... for ( ; func != (ctor_func_t *) &_ctors_end; func++) { (*func)(); } } Давайте теперь посмотрим как устроено завершение C++ приложения, а именно, вызов деструкторов глобальных объектов. Существует два способа. Начну с наиболее используемого в компиляторах — через __cxa_atexit() (из C++ ABI). Это аналог POSIX функции atexit, то есть вы можете зарегистрировать специальные обработчики, которые будут вызваны в момент завершения программы. Когда при старте приложения происходит вызов глобальных конструкторов, как описано выше, там же есть и сгенерированный компилятором код, который регистрирует обработчики через вызов __cxa_atexit. Задача LIBC здесь сохранить требуемые обработчики и их аргументы и вызвать их в момент завершения приложения. Другим способом является сохранение указателей на деструкторы в специальных секциях .fini_array и .fini. В компиляторе GCC это может быть достигнуто с помощью флага -fno-use-cxa-atexit. В этом случае во время завершения приложения деструкторы должны быть вызваны в обратном порядке (от старшего адреса к младшему). Этот способ менее распространен, но может быть полезен в микроконтроллерах. Ведь в этом случае на момент сборки приложения можно узнать сколько обработчиков потребуется. Код вызова деструкторов для глобальных объектов из Embox: int __cxa_atexit(void (*f)(void *), void *objptr, void *dso) {
if (atexit_func_count >= TABLE_SIZE) { printf("__cxa_atexit: static destruction table overflow.\n"); return -1; } atexit_funcs[atexit_func_count].destructor_func = f; atexit_funcs[atexit_func_count].obj_ptr = objptr; atexit_funcs[atexit_func_count].dso_handle = dso; atexit_func_count++; return 0; }; void __cxa_finalize(void *f) { int i = atexit_func_count; if (!f) { while (i--) { if (atexit_funcs[i].destructor_func) { (*atexit_funcs[i].destructor_func)(atexit_funcs[i].obj_ptr); atexit_funcs[i].destructor_func = 0; } } atexit_func_count = 0; } else { for ( ; i >= 0; --i) { if (atexit_funcs[i].destructor_func == f) { (*atexit_funcs[i].destructor_func)(atexit_funcs[i].obj_ptr); atexit_funcs[i].destructor_func = 0; } } } } void cxx_invoke_destructors(void) { extern const char _dtors_start, _dtors_end; typedef void (*dtor_func_t)(void); dtor_func_t *func = ((dtor_func_t *) &_dtors_end) - 1; /* There are two possible ways for destructors to be calls: * 1. Through callbacks registered with __cxa_atexit. * 2. From .fini_array section. */ /* Handle callbacks registered with __cxa_atexit first, if any.*/ __cxa_finalize(0); /* Handle .fini_array, if any. Functions are executed in teh reverse order. */ for ( ; func >= (dtor_func_t *) &_dtors_start; func--) { (*func)(); } } Глобальные деструкторы необходимы, чтобы иметь возможность перезапускать C++ приложения. Большинство RTOS для микроконтроллеров предполагает запуск единственного приложения, которое не перезагружается. Старт начинается с пользовательской функции main, единственной в системе. Поэтому в небольших RTOS зачастую глобальные деструкторы пустые, ведь их использование не предполагается. Код глобальный деструкторов из Zephyr RTOS: /**
* @brief Register destructor for a global object * * @param destructor the global object destructor function * @param objptr global object pointer * @param dso Dynamic Shared Object handle for shared libraries * * Function does nothing at the moment, assuming the global objects * do not need to be deleted * * @return N/A */ int __cxa_atexit(void (*destructor)(void *), void *objptr, void *dso) { ARG_UNUSED(destructor); ARG_UNUSED(objptr); ARG_UNUSED(dso); return 0; } Операторы new/delete В компиляторе GCC реализация операторов new/delete находится в библиотеке libsupc++, А их декларации в заголовочном файле . Можно использовать реализации new/delete из libsupc++.a, но они достаточно простые и могут быть реализованы например, через стандартные malloc/free или аналоги. Код реализации new/delete для простых объектов Embox: void* operator new(std::size_t size) throw() {
void *ptr = NULL; if ((ptr = std::malloc(size)) == 0) { if (alloc_failure_handler) { alloc_failure_handler(); } } return ptr; } void operator delete(void* ptr) throw() { std::free(ptr); } RTTI & exceptions Если ваше приложение простое, вам может не потребоваться поддержка исключений и динамическая идентификация типов данных (RTTI). В этом случае их можно отключить с помощью флагов компилятора -no-exception -no-rtti. Но если эта функциональность С++ требуется, ее нужно реализовать. Сделать это куда сложнее чем new/delete. Хорошая новость заключается в том что эти вещи не зависят от ОС и уже реализованы в кросс-компиляторе в библиотеке libsupc++.a. Соответственно, самый простой способ добавить поддержку это использовать библиотеку libsupc++.a из кросс компилятора. Сами прототипы находятся в заголовочных файлах и . Для использования исключений из кросс-компилятора есть небольшие требования, которые нужно реализовать при добавлении собственного метода загрузки C++ рантайма. В линкер скрипте должна быть предусмотрена специальная секция .eh_frame. А перед использованием рантайма эта секция должна быть инициализирована с указанием адреса начала секции. В Embox используется следующий код: void register_eh_frame(void) {
extern const char _eh_frame_begin; __register_frame((void *)&_eh_frame_begin); } Для ARM архитектуры используются другие секции с собственной структурой информации — .ARM.exidx и .ARM.extab. Формат этих секция определяется в стандарте“Exception Handling ABI for the ARM Architecture” — EHABI. .ARM.exidx это таблица индексов, а .ARM.extab это таблица самих элементов требуемых для обработки исключения. Чтобы использовать эти секции для обработки исключений, необходимо включить их в линкер скрипт: .ARM.exidx : {
__exidx_start = .; KEEP(*(.ARM.exidx*)); __exidx_end = .; } SECTION_REGION(text) .ARM.extab : { KEEP(*(.ARM.extab*)); } SECTION_REGION(text) Чтобы GCC мог использовать эти секции для обработки исключений, указывается начало и конец секции .ARM.exidx — __exidx_start и __exidx_end. Эти символы импортируются в libgcc в файле libgcc/unwind-arm-common.inc: extern __EIT_entry __exidx_start;
extern __EIT_entry __exidx_end; Более подробно про stack unwind на ARM написано в статье. Стандартная библиотека языка (libstdc++) Собственная реализация стандартной библиотеки В поддержку языка C++ входит не только базовый синтаксис, но и стандартная библиотека языка libstdc++. Ее функциональность, так же как и для синтаксиса, можно разделить на разные уровни. Есть базовые вещи типа работы со строками или C++ обертка setjmp . Они легко реализуются через стандартную библиотеку языка C. А есть более продвинутые вещи, например, Standard Template Library (STL). Стандартная библиотека из кросс-компилятора Базовые вещи реализованы в Embox. Если этих вещей достаточно, то можно не подключать внешнюю стандартную библиотеку языка C++. Но если нужна, например, поддержка контейнеров, то самым простым способом является использование библиотеки и заголовочных файлов из кросс-компилятора. При использовании стандартной библиотеки С++ из кросс-компилятора существует особенность. Взглянем на стандартный arm-none-eabi-gcc: $ arm-none-eabi-gcc -v
Using built-in specs. COLLECT_GCC=arm-none-eabi-gcc COLLECT_LTO_WRAPPER=/home/alexander/apt/gcc-arm-none-eabi-9-2020-q2-update/bin/../lib/gcc/arm-none-eabi/9.3.1/lto-wrapper Target: arm-none-eabi Configured with: *** --with-gnu-as --with-gnu-ld --with-newlib *** Thread model: single gcc version 9.3.1 20200408 (release) (GNU Arm Embedded Toolchain 9-2020-q2-update) Он собран с поддержкой --with-newlib.Newlib реализация стандартной библиотеки языка C. В Embox используется собственная реализация стандартной библиотеки. Для этого есть причина, минимизация накладных расходов. И следовательно для стандартной библиотеки С можно задать требуемые параметры, как и для других частей системы. Так как стандартные библиотеки C отличаются, то для поддержки рантайма нужно реализовать слой совместимости. Приведу пример реализации из Embox одной из необходимых но неочевидных вещей для поддержки стандартной библиотеки из кросс-компилятора struct _reent {
int _errno; /* local copy of errno */ /* FILE is a big struct and may change over time. To try to achieve binary compatibility with future versions, put stdin,stdout,stderr here. These are pointers into member __sf defined below. */ FILE *_stdin, *_stdout, *_stderr; }; struct _reent global_newlib_reent; void *_impure_ptr = &global_newlib_reent; static int reent_init(void) { global_newlib_reent._stdin = stdin; global_newlib_reent._stdout = stdout; global_newlib_reent._stderr = stderr; return 0; } Все части и их реализации необходимые для использования libstdc++ кросс-компилятора можно посмотреть в Embox в папке ‘third-party/lib/toolchain/newlib_compat/’ Расширенная поддержка стандартной библиотеки std::thread и std::mutex Стандартная библиотека C++ в компиляторе может иметь разный уровень поддержки. Давайте еще раз взглянем на вывод: $ arm-none-eabi-gcc -v
*** Thread model: single gcc version 9.3.1 20200408 (release) (GNU Arm Embedded Toolchain 9-2020-q2-update) Модель потоков “Thread model: single”. Когда GCC собран с этой опцией, убирается вся поддержка потоков из STL (например std::thread и std::mutex). И, например, со сборкой такого сложного С++ приложение как OpenCV возникнут проблемы. Иначе говоря, для сборки приложений, которые требуют подобную функциональность, недостаточно этой версии библиотеки. Решением, которые мы применяем в Embox, является сборка собственного компилятора ради стандартной библиотеки с многопоточной моделью. В случае Embox модель потоков используется posix “Thread model: posix”. В этом случае std::thread и std::mutex реализуются через стандартные pthread_* и pthread_mutex_*. При этом также отпадает необходимость подключать слой совместимости с newlib. Конфигурация Embox Хотя пересборка компилятора и является самым надежным и обеспечивает максимально полное и совместимое решение, но при этом оно занимает достаточно много времени и может потребовать дополнительных ресурсов, которых не так много в микроконтроллере. Поэтому данный метод не целесообразно использовать везде. В Embox для оптимизации затрат на поддержку введены несколько абстрактных классов (интерфейсов) различные реализации которых можно задать.
Есть три варианта libsupcxx:
Минимальный вариант может работать даже без стандартной библиотеки С++. В Embox есть реализация базирующаяся на простейших функциях из стандартной библиотеки языка С. Если этой функциональности не хватает, можно задать три варианта libstdcxx.
Если есть желание у нас на wiki описано как можно собрать и запустить Qt или OpenCV на STM32F7. Весь код естественно свободный. =========== Источник: habr.com =========== Похожие новости:
Блог компании Embox ), #_c++, #_sistemnoe_programmirovanie ( Системное программирование ), #_programmirovanie_mikrokontrollerov ( Программирование микроконтроллеров ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:46
Часовой пояс: UTC + 5