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

Запуск FreeRTOS

627

Инженеры ST определили специальный API-интерфейс для статического выделения объектов FreeRTOS, таких как потоки, семафоры и т. д. Этот API-интерфейс немного отличается от официального API-интерфейса CMSIS-RTOS. Функции, отличающиеся в двух моделях выделения памяти, перечислены в таблице 3.

Таблица 3: Функции, отличающиеся в двух моделях выделения памяти

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

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

 

 

osThreadDef()

osThreadStaticDef()

osMutexDef()

osMutexStaticDef()

osSemaphoreDef()

osSemaphoreStaticDef()

osMessageQDef()

osMessageQStaticDef()

osTimerDef()

osTimerStaticDef()

23.4.2.1.Выделение памяти потоку idle при использовании модели статического выделения памяти

Благодаря динамическому выделению FreeRTOS полностью заботится о выделении памяти потоку idle (вместе с его стеком и TCB). Вместо этого, при использовании статического выделения за правильное выделение памяти потоку idle отвечаем мы, как и с любым другим потоком.

Будучи потоком idle, автоматически выделяемым ядром во время его инициализации, FreeRTOS предоставляет универсальный способ выделения необходимого пространства памяти для потока idle. Функция:

void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t \ **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize);

вызывается FreeRTOS перед запуском потока idle. Эта процедура должна использоваться для выделения TCB и стека потока idle и для передачи указателя на эти области памяти ядру. При использовании CubeMX для генерации проекта с FreeRTOS и выборе статической модели выделения памяти файл src/freertos.c уже содержит реализацию для функ-

ции vApplicationGetIdleTaskMemory().

23.4.3. Пулы памяти

Спецификация CMSIS-RTOS предоставляет понятие пулов памяти, и уровень, разработанный ST поверх ОС FreeRTOS, реализует их28. Пулы памяти (Memory pools) – это блоки фиксированного размера с динамически выделенной памятью, реализованные так, чтобы они были потокобезопасными. Это позволяет получить к ним доступ как из потоков, так и из ISR. Пулы памяти реализованы ST с использованием процедур pvPortMalloc() и vPortFree(), и, следовательно, действующее выделение памяти зависит от одного из аллокаторов heap_x.c. Пулы памяти являются необязательной функцией, которую можно включить, установив для макроса osFeature_Pool значение 1 в файле cmsis_os.h.

Пул памяти определен следующей структурой Си:

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

Запуск FreeRTOS

 

628

typedef struct os_pool_def {

 

uint32_t

pool_sz;

/* Количество элементов в пуле

*/

uint32_t

item_sz;

/* Размер каждого элемента

*/

void

*pool;

/* Тип объектов в пуле

*/

} osPoolDef_t;

Как и для описанных ранее определений потоков, пул памяти можно легко определить с помощью макроса osPoolDef(). Пул эффективно создается при помощи функции:

osPoolId osPoolCreate(const osPoolDef_t *pool_def);

Спецификации CMSIS-RTOS определяют функцию:

void *osPoolAlloc(osPoolId pool_id);

для извлечения одного блока памяти из пула, размер которого равен параметру item_sz структуры osPoolDef_t. Если в пуле больше нет свободного места, функция возвращает NULL. Чтобы освободить блок в пуле, мы используем функцию:

osStatus osPoolFree(osPoolId pool_id, void *block);

Спецификации CMSIS-RTOS также определяют функцию:

void *osPoolCAlloc(osPoolId pool_id);

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

Следующий псевдокод показывает, как легко использовать пулы памяти.

1 #include "cmsis_os.h"

2

3typedef struct {

4uint8_t Buf[32];

5uint8_t Idx;

6} MEM_BLOCK;

7

8 osPoolDef (MemPool, 8, MEM_BLOCK);

9

10void AllocMemoryPoolBlock (void) {

11osPoolId MemPool_Id;

12MEM_BLOCK *addr;

13

14MemPool_Id = osPoolCreate (osPool (MemPool));

15if (MemPool_Id != NULL) {

16// выделение блока памяти

17addr = (MEM_BLOCK *)osPoolAlloc (MemPool_Id);

19if (addr != NULL) {

20// блок памяти был выделен

21}

Запуск FreeRTOS

629

22}

23}

В строке 8 определяется новый пул, который содержит восемь элементов, каждый из которых имеет размер, равный sizeof(MEM_BLOCK) (размер автоматически высчитывается макросом). Затем пул эффективно создается в строке 14, и один из восьми блоков извлекается из пула в строке 17 с помощью процедуры osPoolAlloc().

23.4.4. Обнаружение переполнения стека

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

К сожалению, дать конкретный ответ непросто, потому что это зависит от довольно длинного списка аспектов, которые нужно иметь в виду. Прежде всего, размер стека зависит от того, насколько глубок стек вызовов, то есть от количества функций, вызываемых нашим потоком, и от места, занимаемого каждым из них. Это пространство, по существу, состоит из локальных переменных и переданных параметров. Другими важными факторами являются архитектура процессора, используемый компилятор и выбранный уровень оптимизации.

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

FreeRTOS предлагает два способа обнаружения переполнения стека. Первый состоит в использовании функции:

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );

которая возвращает количество «неиспользуемых» слов стека потока. Например, предположим, что поток определен со стеком из 100 слов (то есть 400 Байт в STM32). Предположим, что в худшем случае поток использует 90 слов своего стека. Тогда

uxTaskGetStackHighWaterMark() возвратит значение 10.

Тип TaskHandle_t параметра xTask является не чем иным, как osThreadId, возвращаемым

функцией osThreadCreate(), и если мы вызываем uxTaskGetStackHighWaterMark() из того

же потока, мы можем передать NULL.

Эта функция доступна только если:

макрос configCHECK_FOR_STACK_OVERFLOW определен со значением больше 0, или

configUSE_TRACE_FACILITY определен со значением больше 0, или

INCLUDE_uxTaskGetStackHighWaterMark определен со значением больше 1.

Все они должны быть явно определены в файле FreeRTOSConfig.h.

Запуск FreeRTOS

630

Рисунок 12: Как FreeRTOS заполняет стек фиксированным значением (0xA5) для обнаружения переполнения стека

Как uxTaskGetStackHighWaterMark() узнает, сколько стека было использовано? В этой функции нет никакой магии. Когда один из вышеупомянутых макросов определен, FreeRTOS заполняет стек потока «магическим» числом (определенным макросом tskSTACK_FILL_BYTE в файле task.c), как показано на рисунке 12. Это «водяной знак», используемый для получения количества свободных ячеек памяти (то есть количество ячеек в конце стека потока, в котором все еще содержится это значение). Это один из наиболее эффективных методов, используемых для обнаружения переполнения буфера.

Функцию uxTaskGetStackHighWaterMark() можно также использовать для проверки эффективного использования стека потока и, следовательно, уменьшения его размера, если тратится слишком много места.

FreeRTOS предлагает два дополнительных метода для обнаружения переполнения стека. Оба они заключаются в установке макроса configCHECK_FOR_STACK_OVERFLOW в файле FreeRTOSConfig.h. Если мы установим его в 1, то каждый раз, когда поток заканчивает выполнение, FreeRTOS проверяет значение текущего указателя стека: если он выше, чем вершина стека потока, то, вероятно, произошло переполнение стека. В этом случае функция обратного вызова:

void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName);

вызывается автоматически. Определив эту функцию в нашем приложении, мы можем обнаружить переполнение стека и отладить его. Например, во время сеанса отладки мы можем поместить в него программную точку останова:

void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName) { asm("BKPT #0"); /* Если обнаруживается переполнение стека, отладчик останавливает

выполнение микропрограммы здесь */

}

Этот метод быстрый, но он может пропустить переполнение стека, которое происходит в середине переключения контекста. Таким образом, сконфигурировав макрос config-

CHECK_FOR_STACK_OVERFLOW в 2, FreeRTOS применит тот же метод функции uxTaskGetStack-

HighWaterMark(), то есть заполнит стек значением «водяного знака» и вызовет vApplicationStackOverflowHook, если последние 20 Байт стека будут отличаться от их ожидаемого значения. Поскольку FreeRTOS выполняет эту проверку при каждом переключении