Распределение оперативной памяти
Схема распределения оперативной памяти
Приведённая схема распределения ОЗУ является стандартной для набора инструментов GCC, используемого в системе сборки ОСРВ MULTEX-ARM. Сама операционная система привносит в распределение памяти некоторые особенности.
При распределении памяти система сборки использует 3 базовых адреса:
- DRAM_START – начальный адрес оперативной памяти;
- MEM_POOP_END – конечный адрес оперативной памяти. Он же является конечным адресом пула динамической памяти (отсюда и название);
- MX_TEXT – начальный адрес основной программы. Именно по этому адресу передаёт управление загрузчик.
Внизу схемы приведены значения базовых адресов по умолчанию для ОЗУ размером 1 Гб. При использовании другого объёма памяти эти значения можно изменить в Makefile проекта. В данной статье рассматривается только такая адресация, как самая распространённая.
Справа от начального адреса MX_TEXT располагаются сегменты памяти, определяемые линковщиком GCC при сборке итогового бинарного файла. Данные в этих сегментах располагаются в порядке увеличения адресов. Сегменты памяти в порядке возрастания адресов:
- Text – исполняемый бинарный код;
- Rodata – сегмент констант;
- Data – сегмент инициализированных данных;
- BSS – сегмент неинициализированных данных.
Слева начального адреса MX_TEXT располагаются 3 стека. Обратите внимание, что стеки заполняются в ходе выполнения программы начиная от начальных адресов в сторону уменьшения памяти:
- IRQ stack – стек прерываний;
- SVS stack – стек режима супервизора;
- FIQ stack – и стек быстрых прерываний.
Распределением адресов стеков занимается операционная система. Наибольшее место выделяется под стек режима супервизора, так как он является основным для работы системы. Под стек быстрых прерываний FIQ места практически не выделяется, так как в системе используются только обычные прерывания IRQ.
В начале памяти выделяется место под таблицу кэширования ОЗУ. Здесь место так же выделяет операционная система. Таблица заполняется один раз при старте программы и далее не изменяется.
В проектах с аппаратной поддержкой мультимедиа может быть выделен дополнительный блок памяти под нужды аппаратного кодера/декодера видео Cedrus. Такой блок размещается в не используемой памяти между стеками FIQ и IRQ. Адрес размещения блока и его размер жёстко прописаны в библиотеке lib_sunxi и не могут быть изменены. Если аппаратное кодирование/декодирование видео в проекте не используется, то память по этим адресам может быть использована (см. раздел “Сдвиг адреса начала программы“).
Большую часть ОЗУ занимает пул динамической памяти. За выделение и освобождение памяти здесь полностью отвечает операционная система. Заполнение динамической памяти ведётся от старшего адреса MEM_POOP_END в сторону убывания. В пуле динамической памяти выделяется место под стек для каждой запускаемой задачи. Размер стека указывается при старте задачи.
Адреса сегментов памяти
Значения адресов большинства сегментов памяти доступны в программе. Так адреса начала и конца оперативной памяти передаются компилятору в качестве дефайна при сборке каждого объектного файла и их можно получить по именам DRAM_START и MEM_POOP_END.
Начальный адрес программы MX_TEXT, а так же адреса сегментов переменных доступны в итоговом бинарном файле в качестве констант. Их имена можно посмотреть в файле build.map, создаваемом при каждой сборке проекта.
Адреса стеков в программе не доступны, но они вычисляются из основных значений DRAM_START и MX_TEXT. Как именно это делается всегда можно посмотреть в файле startup.s, поставляемого в составе операционной системы.
Адрес же начала пула динамической памяти постоянно изменяется в ходе выполнения программы. И для вычисления размера свободной памяти следует использовать функцию memAvail().
Пример получения адресов сегментов памяти можно посмотреть в тестовом проекте “Пример размещения данных в ОЗУ”. Проект размещён на страничке MULTEX-ARM в разделе “Примеры”. Ссылка на раздел:
Подробное описание данного примера можно посмотреть в видео, размещённом по адресу:
https://rutube.ru/video/ababe5b8fce444aef59c9dd5148fbf70/?r=wd
Выделение памяти внутри функций
Каждой задаче при запуске в ОСРВ MULTEX-ARM выделяется свой стек. Размер этого стека по умолчанию всего 65 Кб. А значит объявлять внутри функций большие массивы крайне не рекомендуется. Иначе это может привести к переполнению стека и потере данных. Для размещения больших массивов нужно пользоваться динамической памятью и использовать функции malloc() и free() для выделения и освобождения памяти. С другой стороны, стек работает быстрее, так как переменные стека сгруппированы вместе и кеширование работает на них оптимально. А значит, небольшие массивы всё-таки выгоднее объявлять внутри функции, чтобы они оказались в стеке задачи.
Осталось определиться, что считать большим массивом, а что нет. Рекомендуется провести такую границу в районе 1 Кб, то есть примерно на уровне 1-2% от размера стека задачи по умолчанию. Всё что меньше 1 Кб – можно просто объявлять прямо в функции. Всё, что больше 1 Кб – нужно выделять в динамической памяти с помощью malloc(). Размер в 1 Кб хорошо соотносится как с размером стека, так и с размером кэша процессора.
Но бывают случаи, когда даже при таком подходе стека всё равно не хватает. Например при многократном вызове рекурсии. Функции, потребляющие большой объём оперативной памяти, следует запускать в отдельной задаче с увеличенным стеком. Для увеличения стека при запуске задачи нужно указать требуемый размер в функции taskSpawn().
Сдвиг адреса начала программы
Под динамическую память отводится вся свободная память от конца сегмента BSS до конца памяти MEM_POOP_END. Объём свободной в данный момент памяти можно узнать с помощью функции memAvail(). Если вызвать эту функцию в пустом проекте, запущенном на машине с ОЗУ 1 Гб, то будет выведено значение немногим более 900 Мб. То есть свободно 900 Мб из 1 Гб физически установленного на плате. При этом ядро операционной системы вместе с задачами пользователя, как известно, занимает примерно 1 Мб.
Оставшаяся часть оперативной памяти (~100 Мб) выделяется под таблицу кеширования, аппаратный кодер Cedrus и стеки. В проектах, где видео кодер не используется, вся отводимая кодеру память достаётся стеку SVS. Этим стеком пользуется только корневая процедура операционной системы и для нормальной работы ей такой большой стек не нужен. Если же проект не использует работу с видео, но ему нужна дополнительная память, например, для вычислений, то освободившееся место можно присоединить к пулу динамической памяти. Для этого следует сдвинуть адрес начала основной программы MX_TEXT влево. Тогда линковщик при сборке проекта пересчитает адреса сегментов и пул динамической памяти увеличится. А перераспределением адресов стеков займётся уже сама операционная система при запуске.
Будем исходить из того, что на SVS стек достаточно выделить столько же места, сколько и на стек прерываний IRQ – 16 Кб. Значит на два стека и таблицу кэширования достаточно размера 48 Кб. Стек быстрых прерываний остаётся маленьким, как и был. Аа значит, адрес начала программы можно сдвинуть на значение 0x4000c000.
Для изменения адреса начала программы нужно:
- изменить значение MX_TEXT в мэйкфайле проекта и пересобрать проект;
- сообщить загрузчику, с какого адреса следует запускать собранную программу. Для этого необходимо пересобрать скрипт boot.scr с указанием нового адреса и заменить имеющийся скрипт на диске целевой машины. Этот скрипт является принадлежностью загрузчика u-boot и как его собирать можно найти в сети.
Пример Makefile для сборки скрипта boot.scr:
default:
@clear
mkimage -C none -A arm -T script -d boot.cmd boot.scr
Пример исходного текста файла boot.cmd, используемого для сборки скрипта:
setenv bootargs bootdelay=0
echo Multex-ARM kernel loading ...
fatload mmc 0 0x4000c000 /multex.bin
echo
echo *** Start Multex-ARM kernel ***
go 0x4000c000
После сборки полученный скрипт загрузчика boot.scr вместе с пересобранным бинарным файлом multex.bin следует разместить на загрузочном диске целевой машины. Удобнее всего положить скрипт рядом с собранным бинарным файлом в папку out проекта и далее воспользоваться одной из команд системы сборки MULTEX-ARM:
- make install – скопирует все файлы из папки out на uSd диск, вставленный в кардридер инструментальной машины;
- make rinstall – скопирует все файлы из папки out на диск удалённой инструментальной машины по протоколу FTP (машина должна быть запущена и видна по сети, на ней должен быть включен FTP-сервер).
После перезагрузки целевой машины команда memAvail() вернёт уже большее значение – порядка 1Гб на простом проекте.