Управление памятью
При создании задач, очередей, мьютексов или других «объектов» FreeRTOS использует оперативную память. Объекты чаще всего создаются динамически в куче, т.е. во время выполнения программы. В разделе о стандартной библиотеке мы уже упоминали о проблемах, связанных с функциями динамического выделения и освобождения памяти malloc()
и free()
. В ОС атомарность (непрерывность) функций из стандартной библиотеки не обеспечивается, т.е. во время выделения или освобождения памяти планировщик с легкостью может переключить задачу. Другими словами, стандартные функции malloc()
/ free()
(которые к тому же не всегда доступны во встраиваемых системах, а если и присутствуют, то занимают много места) не подходят, так как не являются потокобезопасными. FreeRTOS предоставляет несколько реализаций кучи (файлы heap_1.c
, heap_2.c
, heap_3.c
, heap_4.c
, heap_5.c
) через pvPortMalloc()
/ vPortFree()
. Каждая реализация имеет свои достоинства и недостатки. Устройство памяти может сильно различаться от одной платформы к другой, поэтому данная функциональность вынесена в слой платформозависимого кода.
Самая простая куча, heap_1.c
, позволяет выделить память, но не освободить ее. Такой механизм удобен, когда все задачи, очереди и т.д. создаются перед запуском планировщика и в дальнейшем остаются в памяти навсегда. В критических системах строго рекомендуется использовать именно эту реализацию. Изображение ниже иллюстрирует поэтапное заполнение памяти.

Реализация из heap_2.c
позволяет не только выделить, но и освободить память. При этом высвобожденный участок не объединяется (в отличие от heap_4.c
) с соседним свободным блоком, т.е. присутствует проблема фрагментации памяти. Однако такой проблемы не будет, если выделенные и освобожденные блоки всегда имеют одинаковый размер (стек задачи).
Алгоритм устроен таким образом, что он всегда выбирает участок наименьшей длины, удовлетворяющий запрашиваемому размеру данных. Допустим, у нас имеются участки на 15, 25 и 130 байт. При вызове vPortMalloc(20)
алгоритм выберет второй, т.е. область памяти минимальной длины, в который помещается запрашиваемое количество байт. Оставшиеся 5 байт не будут задействованы и будут доступны для дальнейшей аллокации. Пример ниже иллюстрирует проблему фрагментации (стек и TCB — это разные сущности!)

heap_2.c
оставлена в FreeRTOS для обратной совместимости. Использовать ее в новых создаваемых проектах не рекомендуется (лучше заменить на heap_4.c
).
Реализация из heap_3.c
оборачивает стандартные функции malloc()
и free()
, создавая критические секции, т.е. приостанавливая планировщик на время работы с памятью.
heap_4.c
работает схожим образом с heap_2.c
, с тем отличием, что: а) берет первый же попавшийся блок, который подходит по размеру; б) объединяет (англ. coalescences) при освобождении участки (для уменьшения фрагментации). Допустим, куча содержит три блока по 5, 300, 50 байт соответственно. При вызове pvPortMalloc(20)
алгоритм выберет второй блок.

A. Было создано три задачи. Большой кусок памяти остался свободным сверху. B. Вторая задача была удалена, участки памяти из-под TCB и стека были освобождены и объединены. Большой кусок памяти сверху остался нетронутым. C. При создании очереди алгоритм находит первый подходящий участок — место из-под задачи номер два. Очередь создается на ее месте, оставляя свободную память между собой и задачей номер три. D. Какой-то из задач потребовалась память, и она запросила ее из кучи через pvPortMalloc()
. Запрашиваемый участок меньше первого свободного, поэтому алгоритм помещает данные в начало свободного участка. E. Память из-под очереди высвобождается, таким образом в куче образуются три свободных участка. F. После освобождения памяти от пользовательских данных два свободных участка, их окружающих, и область из-под этих данных объединяются в один большой участок.
Данная реализация не является детерминированной, но она быстрее, чем функции из стандартной библиотеки.
Алгоритм heap_5.c
идентичен heap_4.c
, разница заключается лишь в том, что в качестве массива кучи могут быть использованы разные участки ОЗУ. Изображение ниже демонстрирует такую возможность.

Допускается в одном проекте использовать сразу несколько реализаций кучи: так, в stm32f405 имеется 192 килобайта оперативной памяти, однако 64 из нее является CCM (сокр. core coupled memory), которая располагается в другой области. В этом случае удобно использовать, например, heap3.c
для основной памяти и heap4.c
для CCM.
Выполнение кода из данной области невозможно.
Размер кучи (по сути это просто массив) задается через configTOTAL_HEAP_SIZE
(для heap_1.c
, heap_2
, и heap_4.c
) в файле конфигурации. Получить объем доступной памяти в куче (для всех реализаций, кроме heap_3.c
) можно через функцию xPortGetFreeHeapSize()
.