Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2754
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Запуск FreeRTOS

622

23.4. Выделение памяти и управление ею

В двух предыдущих примерах мы начали использовать FreeRTOS, не слишком задумываясь о выделении памяти (memory allocation) потокам и другим структурам, используемым ОС. Единственное исключение представлено последним параметром, переданным макросу osThreadDef(), который соответствует размеру стека, зарезервированного для потока. FreeRTOS, однако, не только нуждается в достаточном количестве памяти для выделения потокам, но и также использует дополнительные части SRAM для выделения своим внутренним структурам (список TCB и т. д.). То же самое относится и к другим примитивам синхронизации, которые мы будем изучать позже, таким как семафоры и мьютексы. Откуда именно берется эта память?

Традиционно FreeRTOS реализовывала модель динамического выделения до выпуска 8.x. Это является важным ограничением, поскольку в некоторых областях применения динамическое выделение памяти категорически не рекомендуется или даже явно запрещено. Несмотря на то что, как мы скоро увидим, один из пяти динамических аллокаторов, реализованных FreeRTOS, отвечает большинству требований выделения памяти в этих областях применения, к сожалению, эта характеристика FreeRTOS препятствовала ее использованию при принятии этого ограничения. Начиная с последней версии 9.x, FreeRTOS реализует две модели выделения памяти: полностью статическую и полностью динамическую.

Для активации модели выделения памяти используются два макроса: configSUP-

PORT_STATIC_ALLOCATION и configSUPPORT_DYNAMIC_ALLOCATION. Оба они могут принимать

значения 0 или 1, чтобы отключить/включить соответствующую модель памяти. Важно подчеркнуть, что две модели памяти не являются взаимоисключающими: их можно использовать одновременно в соответствии с потребностями пользователя. Как мы увидим позже, две модели памяти вынуждают использовать отдельные API-интерфейсы.

23.4.1. Модель динамического выделения памяти

FreeRTOS реализует модель динамического выделения памяти, которая использует области SRAM для выделения всех внутренних структур ОС, включая TCB. По сравнению со статической моделью выделения динамическая имеет некоторые немаловажные преимущества:

Выделение памяти происходит автоматически, в рамках API-функций ОСРВ.

Разработчикам не нужно самим заботиться о выделении памяти.

ОЗУ, используемое объектом ОСРВ, может быть повторно использовано, если объект был удален, что потенциально уменьшает максимальный объем ОЗУ приложения.

Предоставляются API-функции ОСРВ для возврата информации об использовании кучи, что позволяет оптимизировать размер кучи.

FreeRTOS предоставляет пять схем динамического выделения памяти, и они могут быть выбраны в соответствии с требованиями приложения.

При создании объекта требуется меньше параметров функции.

Запуск FreeRTOS

623

FreeRTOS не использует классические функции malloc() и free(), предоставляемые библиотекой среды выполнения Си24, потому что:

1.они используют много места в коде, увеличивая размер микропрограммы;

2.они не предназначены для обеспечения потокобезопасности;

3.они не являются детерминированными (время выполнения функции будет отличаться от вызова к вызову).

Таким образом, FreeRTOS предоставляет свою собственную схему динамического выделения для обработки необходимой памяти, но, поскольку существует несколько способов сделать это, каждая обладает своими преимуществами и компромиссами. FreeRTOS спроектирована так, что эта часть абстрагирована от остальной части ядра ОС, и она предоставляет пять различных схем выделения, которые пользователь может выбирать в зависимости от его конкретных потребностей. pvPortMalloc() и vPortFree() являются наиболее важными функциями, реализованными в каждой схеме, и их имя четко говорит о том, что они делают.

Эти пять схем не являются частью ядра FreeRTOS, но они являются частью уровня платформозависимого кода, и они реализованы в пяти файлах с исходным кодом Си, называющихся heap_1.c..heap_5.c и содержащихся в папке portable/MemMang. Скомпилировав один из этих файлов вместе с остальным кодом FreeRTOS, мы автоматически выбираем эту схему размещения для нашего приложения. Более того, мы можем в конечном итоге предоставить свою модель выделения, реализовав этот API-уровень (в худшем случае, нам нужно реализовать 5 функций) в соответствии с нашими конкретными потребностями.

23.4.1.1. heap_1.c

Многие встроенные приложения используют ОСРВ для логического разделения микропрограммы на блоки. Каждый блок имеет свои особенности, и часто он работает независимо от других блоков. Например, предположим, что вы разрабатываете устройство с TFT-дисплеем (возможно, контроллер современной посудомоечной машины). Обычно микропрограмма разделена на несколько потоков, один из которых отвечает за графическое взаимодействие (он обновляет отображение, печатая информацию и показывая потрясающие графические виджеты), а другие потоки отвечают за управление программой стирки (и, таким образом, за обработку датчиков, двигателей, насосов и т. д.). Эти приложения обычно имеют функцию main(), которая порождает потоки (как мы делали в предыдущих примерах), и почти ничего больше не инициализируя ОС, когда она начинает выполняться. Это означает, что аллокатор не должен учитывать какие-либо более сложные проблемы выделения, такие как детерминизм и фрагментация, и его можно упростить.

Аллокатор heap_1.c реализует очень простую версию pvPortMalloc() и не поддерживает vPortFree(). Приложения, которые никогда не удаляют поток или другие объекты ядра, такие как очереди, семафоры и т. д., подходят для использования этой схемы выделения памяти. Те области применения, где использование динамически выделенной памяти не рекомендуется, могут извлечь выгоду из этой схемы выделения, поскольку она предлагает детерминистский подход к управлению памятью, избегая фрагментации (поскольку память никогда не освобождается).

24 С одним заметным исключением в виде аллокатора heap_3.c, как мы скоро увидим.

Запуск FreeRTOS

624

Аллокатор heap_1.c делит статически выделенный массив на маленькие порции, поскольку выполняются вызовы pvPortMalloc(). Это и в самом деле куча FreeRTOS. Общий размер этого массива (выраженный в байтах) определяется макросом configTOTAL_HEAP_SIZE в файле FreeRTOSConfig.h. Единственный компромисс с этой схемой выделения состоит в том, что, будучи целым массивом, выделенным во время компиляции, приложение будет потреблять много SRAM, даже если оно не использует его полностью. Это означает, что программисты должны тщательно выбрать правильное значение для

размера configTOTAL_HEAP_SIZE.

Стоит отметить важный момент. Память программ на Си традиционно разделена на две области: стек и куча. Говорят, что куча динамически растет во время выполнения и растет в противоположном направлении стека. Однако, как видите, аллокатор heap_1.c не имеет ничего общего с кучей всего приложения, так как он использует массив, объявленный как static, который расположен в секции .data, как мы узнали в Главе 20, для хранения объектов, нуждающихся в динамике. Несомненно, это форма динамического выделения, но она не связана с использованием функций malloc() и free(). Это означает, что мы можем безопасно использовать их в нашем приложении, даже если их использование не рекомендуется во встроенных приложениях.

23.4.1.2. heap_2.c

heap_2.c также работает путем деления статически выделенного массива, размер которого задается макросом configTOTAL_HEAP_SIZE. Он использует алгоритм наилучшего соответствия (best-fit algorithm) для выделения памяти и, в отличие от схемы выделения heap_1.c, позволяет освобождать память. Этот алгоритм считается устаревшим и не подходит для новых разработок. heap_4.c – лучшая альтернатива данному аллокатору. По этой причине мы не будем вдаваться в подробности того, как он работает. Если вы заинтересованы в нем, то можете обратиться к официальной документации FreeRTOS25.

23.4.1.3. heap_3.c

heap_3.c использует обычные функции Си malloc() и free() для выделения памяти. Это означает, что параметр configTOTAL_HEAP_SIZE не влияет на управление памятью, поскольку malloc() предназначена для самостоятельного управления кучей. Это означает, что нам нужно соответствующим образом сконфигурировать наши скрипты компоновщика, как показано в Главе 20. Кроме того, учтите, что реализация malloc() отличается от реализации, предоставляемой newlib-nano и обычной newlib. Тем не менее, более универсальная реализация, предоставляемая библиотекой newlib, требует гораздо больше

Flash-памяти.

heap_3.c делает malloc() и free() потокобезопасными, временно приостанавливая работу планировщика FreeRTOS. Для получения дополнительной информации о них обратитесь к официальной документации FreeRTOS26.

23.4.1.4. heap_4.c

heap_4.c работает аналогично heap_1.c и heap_2.c. То есть он использует статически выделенный массив, размер которого задается значением макроса configTOTAL_HEAP_SIZE,

25http://www.freertos.org/a00111.html#heap_2

26http://www.freertos.org/a00111.html#heap_3

Запуск FreeRTOS

625

для хранения объектов, выделенных во время выполнения. Тем не менее, он имеет другой подход при выделении памяти. Фактически, он использует алгоритм первого соответствия (first fit algorithm), который объединяет смежные свободные блоки в один большой блок, снижая риск фрагментации памяти. Этот метод, обычно используемый сборщиком мусора (garbage collector) в языках с динамическим и автоматическим выделением памяти, также называется объединением (coalescing).

К сожалению, такое поведение аллокатора heap_4.c приводит к тому, что он не детерминирован: выделение/освобождение многих небольших объектов вместе с созданием/уничтожением потоков может привести к большой фрагментации, что требует больше вычислительной обработки для упаковки памяти. Более того, нет никакой гарантии, что алгоритм вообще избежит утечек памяти. Однако обычно он быстрее, чем самая стандартная реализация malloc() и free(), особенно тех, которые предоставляются библиотекой newlib-nano.

Подробное объяснение алгоритма heap_4.c выходит за рамки данной книги. Для получения дополнительной информации обратитесь к документации FreeRTOS27.

23.4.1.5. heap_5.c

heap_5.c использует тот же алгоритм, что и у аллокатора heap_4.c, но он позволяет разделить пул памяти между различными несмежными областями памяти. Это особенно полезно для микроконтроллеров STM32, предоставляющих контроллер FSMC, который позволяет прозрачно использовать внешние SDRAM для увеличения всей оперативной памяти. Программист может решить выделить какой-то интенсивно используемый поток во внутреннюю память SRAM (или в CCM-память, если она доступна), а затем использовать внешнюю SDRAM для менее значимых объектов, таких как семафоры и мьютексы.

Определив пользовательский скрипт компоновщика, можно выделить два пула в двух областях памяти, а затем использовать функцию vPortDefineHeapRegions() от FreeRTOS, чтобы определить их как пулы памяти. Однако это продвинутое использование ОС, которое мы не будем здесь подробно описывать. Если вам интересно, вы можете обратиться к превосходной книге создателя FreeRTOS Ричарда Барри «Освоение ядра реального времени FreeRTOS» (Mastering the FreeRTOS Real Time Kernel by Richard Barry).

23.4.1.6.Как использовать malloc() и связанные с ней функции Си с

FreeRTOS

Как было сказано ранее, за исключением схемы выделения heap_3.c, FreeRTOS не использует память кучи Си для ее выделения потокам и другим объектам. Таким образом, вы можете использовать malloc() и free() в своем приложении.

Если вместо этого вы хотите использовать процедуры pvPortMalloc() и vPortFree(), обеспечивая переносимость вашего кода, вы можете просто переопределить malloc() и free() следующим образом:

void *malloc (size_t size) { return pvPortMalloc(size);

}

27 http://www.freertos.org/a00111.html#heap_4