Миро Самек. Построение простых систем на ARM-контроллерах с использованием инструментов GNU (перевод)


Часть 4

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


4.1 Опции компилятора для языка "Си"

Опции компилятора для языка "Си" определяются в файле "Makefile", в каталоге "c_blinky" проекта. "Makefile" задаёт различные опции для сборки отладочного и выходного вариантов проекта и позволяет использование набора инструкций "ARM" или "THUMB" для каждого модуля по отдельности.

 
Листинг 4.1 Опции компилятора для языка "Си" для отладочной (a) и выходной (b) конфигураций

            ARM_CORE = arm7tdmi

            ifeq (rel, $(CONF))     # Release configuration ..................

(b)         BINDIR = rel
            CCFLAGS =   -c \
(1b)                    -mcpu=$(ARM_CORE) \
(2b)                    -mthumb-interwork \
(5b)                    -Os \
(3b)                    -mlong-calls \
(4b)                    -ffunction-sections \
                        -Wall \
(6b)                    -DNDBEBUG \
                        -o$@

            ...

            else                    # default Debug configuration ............

(a)         BINDIR = dbg
            CCFLAGS =   -g -c \
(1a)                    -mcpu=$(ARM_CORE) \
(2a)                    -mthumb-interwork \
(5a)                    -O \
(3a)                    -mlong-calls \
(4a)                    -ffunction-sections \
                        -Wall \
                        -o$@
										

Приведённый код демонстрирует наиболее важные опции компилятора, как-то:

(1) "-mcpu" уточняет модель целевого процессора ARM и используется компилятором GCC для выбора конкретного набора инструкций при генерации объектного кода. В примере переменной "ARM_CORE" присвоено значение "arm7tdmi".

(2) "-mthumb-interwork" позволяет свободно смешивать "ARM" и "THUMB" код.

(3) "-mlong-calls" сообщает компилятору, что вызовы функций должны производиться через предварительную запись адреса функции в регистр и вызов через этот регистр (инструкцией "BX"), что позволяет использовать код, расположенный в любом месте 32-битного адресного пространства. Это иногда требуется при передаче управления между кодом в областях "ROM" и "RAM".

Примечание. Потребность в длинных вызовах зависит от конфигурации памяти конкретной модели процессора. Например, семейство AT91SAM7 фирмы Atmel не требует использования длинных вызовов, так как вся физическая память расположена ниже 25-битной границы адресов. С другой стороны, семейство LPC2xxx фирмы NXP требует использования длинных вызовов, потому что ROM располагается по адресу 0x0000000, а RAM - 0x40000000. Использование длинных вызовов безопасно при любой конфигурации памяти.

(4) "-ffunction-sections" предписывает компилятору располагать каждую функцию в индивидуальной кодовой секции выходного файла. Имя функции обуславливает имя секции. Например, функция "Blinky_shift()" помещается в секцию ".text.Blinky_shift", позволяя выбирать наиболее подходящее местоположение (скажем, в RAM) именно этой секции.

(5) "-O" выбирает уровень оптимизации. Для окончательного варианта установлен более высокий уровень оптимизации (5b).

(6) Выходная версия определяет макрос "NDEBUG".


4.2 Опции компилятора для C++

Опции компилятора для "C++" задаются в файле "Makefile" в подкаталоге "cpp_blinky" проекта. "Makefile" содержит различные опции для сборки выходной и отладочной версии и позволяет использование набора инструкций "ARM" или "THUMB" для каждого модуля отдельно.

"Makefile" для "C++" использует как уже обсуждавшиеся для языка "Си" опции, так и две дополнительные для управления особенностями "C++":

 
Листинг 4.2 Опции компилятора для проекта на языке C++

            CCFLAGS =   -c \
                        -mcpu=$(ARM_CORE) \
                        -mthumb-interwork -Os \
(1)                     -fno-rtti \
(2)                     -fno-exceptions \
                        -mlong-calls \
                        -ffunction-sections \
                        -Wall \
                        -DNDBEBUG \
                        -o$@
										

(1) "-fno-rtti" запрещает генерацию информации о классах, содержащих виртуальные функции. Эта информация используется для идентификации типов ("dynamic_cast" и "typeid") времени выполнения.

Запрет RTTI экономит несколько килобайт программного кода из библиотеки "C++" времени выполнения (предполагается, что проект не содержит кода, использующего RTTI). Отметим, что оператор "dynamic_cast" может по-прежнему использоваться для приведения типов, не нуждающихся в информации о типах времени выполнения, то есть для приведения к "void *" или к бесконфликтным базовым классам.

(2) "-fno-exceptions" предотвращает генерацию дополнительного кода, нужного для обработки исключений. Запрет обработки исключений экономит несколько килобайт кода из библиотеки C++ времени выполнения (предполагается, что вы не компонуете проект со сторонним кодом, использующим обработку исключений).


4.3 Сокращение избыточного кода в C++

Опции компилятора, управляющие особенностями C++, тесно примыкают к проблеме умньшения объёма кода в C++. В любом случае, запрета RTTI и обработки исключений на уровне компилятора недостаточно для полного исключения ситуации, в которой компоновщик GNU может добавить в проект несколько десятков килобайт библиотечного кода.

Это происходит потому, что стандартные операторы "new" и "delete" используют исключения и, соответственно, нуждаются в библиотеках, их обрабатывающих. (Эти операторы используются в статическом инициализационном коде конструкторов/деструкторов и компонуются даже если вы не используете динамическую область памяти в своём приложении.)

Лишние 50KB кода неприемлимы для большинства небольших ARM-процессоров. Чтобы их убрать следует переопределить свои собственные, не использующие исключений версии операторов "new" и "delete", что и сделано в файле "mini_cpp.cpp" из каталога "cpp_blinky".

 
Листинг 4.3 Модуль "mini_cpp.cpp" с модифицированными функциями "new()", "delete()" и "__aeabi_atexit()"

      #include <stdlib.h>           // for prototypes of malloc() and free()

(1)   void *operator new(size_t size) throw() {
            return malloc(size);
      }

      void operator delete(void *p) throw() {
(2)         free(p);
      }

      extern "C" int __aeabi_atexit(void *object,
                                    void (*destructor)(void *),
                                    void *dso_handle) {
(3)         return 0;
      }
										

Фрагмент исходного кода в листинге 4.3 показывает минимальную обвязку, позволяющую полностью исключить библиотечный код обработки исключений. Далее следуют некоторые пояснения:

(1) Стандартная версия оператора "new" использует исключение "std::bad_alloc". Новая минимальная реализация вместо исключений использует стандартную функцию "malloc()".

(2) Минимальная реализация использует стандартную функцию "free()".

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

И, наконец, если вы не используете динамическую область памяти, а так следует поступать в надёжном, понятном (deterministic) приложении, вы можете сократить C++ код ещё больше. Модуль "no_heap.cpp" содержит пустые определения функций "malloc()" и "free()":

 

      extern "C" void *malloc(size_t) {
          return (void *)0;
      }

      extern "C" void free(void *) {
      }
										

ПредпросмотрAttachmentSize
bare_metal_arm_systems_html.zip327.73 КБ
blinky_files.zip173.94 КБ