Опубликован: 24.11.2024 | Доступ: свободный | Студентов: 1 / 0 | Длительность: 03:11:00
Лекция 4:

FreeRTOS

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >

Компоненты FreeRTOS и их применение

Ключевые компоненты FreeRTOS

Ниже перечислены ключевые компоненты FreeRTOS:

  • управление памятью;
  • задачи;
  • очереди, семафоры и мьютексы;
  • прямые уведомления о задачах;
  • буферы потоков и сообщений;
  • таймеры.

Мы опишем их более подробно в последующих разделах.

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

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

Управление памятью: методы распределения памяти

Управление памятью осуществляется FreeRTOS для её эффективного использования. Поддерживается как статическое, так и динамическое распределение. Обе схемы имеют свои преимущества и недостатки, в зависимости от приложения. Разработчик приложения выбирает подходящую схему управления памятью в зависимости от требований. Одно и то же приложение может содержать задачи как динамического, так и статического распределения.

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

Схема распределения памяти во FreeRTOS по умолчанию динамическая. В случаях, когда разработчику приложения требуется больший контроль над управлением памятью для определенных компонентов приложения, он может выбрать статическое распределение памяти для этих задач. FreeRTOS предоставляет отдельный набор функций для компонентов, которым требуется статическое распределение памяти. Эти функции доступны, когда configSUPPORT_STATIC_ALLOCATION имеет значение 1 в файле настроек конфигурации.

Ниже перечислены некоторые преимущества использования динамического распределения памяти (адаптировано из руководства пользователя FreeRTOS):

  • при создании объекта требуется меньше параметров функции;
  • выделение памяти происходит автоматически, в рамках функций API RTOS;
  • разработчику приложения не нужно заботиться о выделении памяти;
  • оперативная память, используемая объектом RTOS, может быть повторно использована при удалении объекта, что потенциально уменьшает максимальный объем оперативной памяти приложения;
  • функции API RTOS позволяют возвращать информацию об использовании кучи, что позволяет оптимизировать её размер.

Ниже перечислены некоторые преимущества использования статического распределения памяти (адаптировано из руководства пользователя FreeRTOS):

  • объекты RTOS могут быть размещены в определенных местах памяти;
  • максимальный объем оперативной памяти может быть определен во время компоновки, а не во время выполнения;
  • разработчику приложения не нужно заботиться о корректной обработке сбоев при выделении памяти;
  • это позволяет использовать RTOS в приложениях, которые просто не допускают динамического распределения памяти (хотя FreeRTOS включает схемы распределения, которые могут преодолеть большинство возражений).
Управление памятью
Управление памятью: схемы управления памятью во FreeRTOS

FreeRTOS определяет пять схем управления памятью. Они содержатся в отдельных файлах: heap_1.c, heap_2.c, heap_3.c, heap_4.c и heap_5.c, которые находятся в каталоге Source/Portable/MemMang. Пользователи могут добавлять свои собственные реализации по мере необходимости, но хотя бы одна из этих реализаций должна быть включена при компиляции исходного текста FreeRTOS в приложении.

Ниже приводится простое описание каждой из этих реализаций.

  • heap_1.c

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

  • heap_2.c

    heap_2 использует алгоритм наибольшего соответсвия (best-fit) для выделения памяти, и пространство, которое больше не используется, освобождается для дальнейшего использования. Он не объединяет свободные места в один блок перед перераспределением. Эта схема может быть использована, когда во время выполнения приложения происходит многократное удаление и создание задач или других компонентов RTOS. Не рекомендуется использовать эту схему, если освобождаемые и перераспределяемые блоки памяти имеют произвольный размер, так как это может привести к фрагментации памяти. Кроме того, распределение не является детерминированным, но оно более эффективно, чем реализация malloc в языке Си.

  • heap_3.c

    heap_3 - это простая, потокобезопасная обертка вокруг стандартных функций malloc() и free() языка Си. Эта схема требует, чтобы компоновщик настроил кучу, а библиотека компилятора предоставила функции malloc() и free(). Она не является детерминированной и может привести к увеличению размера кода ядра.

  • heap_4.c

    heap_4 использует "первый подходящий" алгоритм (first-fit) для выделения памяти. В отличие от heap_2, он объединяет соседние свободные пространства в более крупный блок, а затем выделяет память; он включает алгоритм коалесценции, который поддерживает эту возможность. Эта схема может быть использована в системах, требующих многократного создания и удаления задач и других компонентов. Поскольку эта реализация объединяет области памяти в более крупные блоки памяти, она с меньшей вероятностью приведет к неправильной фрагментации памяти.

  • heap_5.c

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

Более подробные объяснения и примеры использования вышеперечисленных схем можно найти в руководстве FreeRTOS.

Задачи

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

Планировщик (scheduler) RTOS отвечает за контроль над тем, какая задача должна быть выполнена в любой момент времени. В одноядерных системах только одна задача может быть активна в приложении в любой момент времени. Поэтому планировщик также отвечает за безопасное включение и выключение каждой задачи, а также за сохранение состояния, чтобы при повторном включении каждая задача возвращалась в свое предшествующее состояние. Это достигается планировщиком FreeRTOS за счёт ведения индивидуального стека для каждой задачи.

Задачи: состояния

Задача может находиться в одном из следующих четырёх состояний:

  • Готова к выполнению.

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

  • Выполняется.

    В этом состоянии задача выполняется на процессоре. Если система имеет только одно ядро, то в каждый момент времени может выполняться только одна задача.

  • Заблокирована.

    В этом состоянии задача не готова к выполнению, так как ожидает входных данных от внешних источников, других задач или временных событий (например, события таймера или задержки). Для каждой заблокированной задачи существует тайм-аут, по достижении которого задача переходит из заблокированного состояния в состояние готовности. После истечения тайм-аута задаче не нужно ждать наступления события, которое её блокировало.

  • Приостановлена.

    Приостановленные задачи не могут автоматически выйти из этого состояния, так как для них не установлен тайм-аут. Они должны быть явно выведены из этого состояния приложением с помощью операции возобновления (resume).

Задачи: приоритеты

Задачам можно назначать приоритеты по мере необходимости. FreeRTOS позволяет пользователю определить переменное количество уровней приоритетов. Уровни начинаются с 0, а максимальный уровень определяется в файле FreeRTOSConfig.h. Это максимальное значение должно быть разумным, чтобы минимизировать использование оперативной памяти.

Планирование задач осуществляется планировщиком. Планировщик гарантирует, что задачи в состоянии готовности с более высоким приоритетом будут выполняться перед задачами с более низким приоритетом, которые также находятся в состоянии готовности. FreeRTOS может быть настроена на выполнение задач с одинаковым приоритетом в режиме "нарезания времени" (time slicing), для чего в конфигурационном файле задается параметр configUSE_TIME_SLICING. Разделение между задачами с равным приоритетом осуществляется с помощью схемы арбитража round-robin.

Задачи: реализация

Задачи создаются с помощью функции xTaskCreate() или xTaskCreateStatic() и могут быть удалены с помощью функции xTaskDelete().

Параметры могут быть переданы в задачу для дальнейшей обработки с помощью указателя параметров.

Примерная структура реализации задачи представлена ниже.

Шаг 1: Создание двух задач

xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL, mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
xTaskCreate( prvQueueSendTask, "Tx", configMINIMAL_STACK_SIZE * 2U, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL ); 

Шаг 2: Определение задачи 1 (prvQueueReceiveTask)

static void prvQueueReceiveTask( void *pvParameters )
{
     unsigned long ulReceivedValue;
     const unsigned long ulExpectedValue = 100UL;
     const char * const pcMessage1 = "Blink1";
     const char * const pcMessage2 = "Blink2";
     const char * const pcFailMessage = "Unexpected value received\r\n";

     int f = 1;

     /* Remove compiler warning about unused parameter. */
     ( void ) pvParameters;

     for( ;; )
     {....
Очереди

Очереди - основной механизм межзадачного взаимодействия. Задачи могут использовать их для обмена информацией друг с другом. Очереди реализованы как потокобезопасные FIFO (first in first out). Задачи добавляют информацию в конец очереди, а другие задачи, которым нужны данные из очереди, забирают их из начала и обрабатывают. При необходимости задачи также могут перемещать данные не в конец очереди, а в её начало.

FreeRTOS использует метод очереди путем копирования, где данные, отправленные в очередь, копируются в неё. Этот метод обеспечивает простую, но мощную реализацию. Данные могут передаваться через очередь в одном из следующих форматов:

  • необработанные данные;
  • ссылка на данные через указатели (когда данные, которые необходимо разделить, имеют значительный размер).

FreeRTOS отвечает за выделение памяти для очереди и за хранение данных по мере необходимости.

Очереди: доступ из нескольких задач

В очередях может быть несколько задач, которые пишут в них или читают из них. Обычно в очередь записывают несколько задач, и реже из нее читают несколько задач.

Очереди: механизм блокировки и разблокировки

Когда задача пытается читать из пустой очереди, она переходит в состояние "заблокирована", пока либо данные не станут доступны в очереди, либо не будет достигнут тайм-аут блокировки.

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

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

Если несколько задач блокируются на одной и той же очереди, то задача с наивысшим приоритетом будет разблокирована первой.

Очереди: реализация

В следующем примере показано, как можно реализовать и использовать очереди между двумя задачами.

Шаг 1. Создание очереди

/* Create the queue. */

xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );

Шаг 2: Использование очереди

/* Send a value to the queue, causing the task receiving this data from
the queue to unblock and toggle the LED. 0 is used as the block time so
that the sending operation will not block; it shouldn't need to block, as
the queue should always be empty at this point in the code. */

xQueueSend( xQueue, &ulValueToSend, 0U );
< Лекция 3 || Лекция 4: 1234 || Лекция 5 >