Pull to refresh

Вся правда об ОСРВ. Статья #14. Разделы памяти: введение и базовые службы

Reading time 7 min
Views 1.8K
Original author: Colin Walls


Разделы памяти упоминались ранее в одной из предыдущих статей (#6), где производилось сравнение со стандартной функцией языка C malloc(). Раздел (partition) – это область памяти, получаемая из пула разделов (пула памяти). Разделение памяти предоставляет гибкий способ надежного и детерминированного выделения и освобождения памяти.

Предыдущие статьи серии:
Статья #13. Структуры данных задач и неподдерживаемые вызовы API
Статья #12. Службы для работы с задачами
Статья #11. Задачи: конфигурация и введение в API
Статья #10. Планировщик: дополнительные возможности и сохранение контекста
Статья #9. Планировщик: реализация
Статья #8. Nucleus SE: внутреннее устройство и развертывание
Статья #7. Nucleus SE: введение
Статья #6. Другие сервисы ОСРВ
Статья #5. Взаимодействие между задачами и синхронизация
Статья #4. Задачи, переключение контекста и прерывания
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.

Использование разделов


В Nucleus SE пулы разделов настраиваются во время создания. В одном приложении может быть до 16 пулов разделов. Если они не настроены, структуры данных и вызовы служб, относящиеся к этим пулам, не будут включены в приложение.

Пул разделов – это область памяти, разделенная на определенное количество блоков фиксированного размера. Разработчик полностью контролирует размер и количество разделов в каждом пуле. Задачи могут запрашивать выделенные разделы памяти и получать указатель на область хранения и не должны записывать данные вне выделенного раздела. Раздел может быть освобожден любой задачей при передаче указателя функции API. Запрос на выделение раздела, когда свободных разделов нет, может привести к ошибке или приостановке запроса, в зависимости от выбранных параметров вызова API и конфигурации Nucleus SE.

Настройка разделов памяти


Количество пулов разделов


Как и в большинстве объектов Nucleus SE, конфигурация пулов разделов в основном осуществляется с помощью директивы #define в nuse_config.h. Главным параметром является NUSE_PARTITION_POOL_NUMBER, который определяет, сколько пулов разделов определено в приложении. Значение по умолчанию – 0 (т. е. пулы разделов не используются), разработчик может установить любое значение от 0 до 16. Иное значение приведет к ошибке компиляции, которая выявлена при проверке в nuse_config_check.h (он включается в nuse_config.c, и, следовательно, компилируется вместе с этим модулем), что приводит к компиляции директивы #error.

Выбор ненулевого значения является приоритетным способом активирования пулов разделов. Это приводит к определению структур данных и назначению им соответствующего размера. Структуры данных в ПЗУ должны быть инициализированы подходящими значениями, описывающими каждый пул разделов. Более подробно о структурах данных будет в следующей статье. Такой выбор также активирует настройки API.

Активация вызовов API


Каждая функция API (служебный вызов) в Nucleus SE активируется директивой #define в nuse_config.h. Для пулов разделов к ним относятся:

NUSE_PARTITION_ALLOCATE
NUSE_PARTITION_DEALLOCATE
NUSE_PARTITION_POOL_INFORMATION
NUSE_PARTITION_POOL_COUNT

По умолчанию, им всем присвоено значение FALSE, отключая, таким образом, каждый служебный вызов и препятствуя включению кода реализации. Для настройки пулов разделов в приложении нужно выбрать необходимые вызовы API и присвоить соответствующим директивам значение TRUE.

Ниже представлена выдержка из файла nuse_config.h по умолчанию:



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

Служебные вызовы пулов разделов


Nucleus RTOS поддерживает семь служебных вызовов, относящихся к пулам разделов, которые предоставляют следующий функционал:

Описание функционала Nucleus RTOS Nucleus SE
Выделение раздела NU_Allocate_Partition() NUSE_Partition_Allocate()
Освобождение раздела NU_Deallocate_Partition() NUSE_Partition_Deallocate()
Предоставление информации
о конкретном пуле разделов
NU_Partition_Pool_Information() NUSE_Partition_Pool_Information()
Возвращение значения количества (на данный момент) настроенных
пулов разделов в приложении
NU_Established_Partition_Pools() NUSE_Partition_Pool_Count()
Добавление (создание) нового пула разделов в приложение NU_Create_Partition_Pool() Не реализовано.
Изменение (удаление) пула разделов из приложения NU_Delete_Partition_Pool() Не реализовано.
Возвращение указателей на все пулы разделов, существующие в приложении на данный момент NU_Partition_Pool_Pointers() Не реализовано.

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

Стоит отметить, что ни в Nucleus RTOS, ни в Nucleus SE не реализована функция перезагрузки. Это сделано намеренно. Очень часто одна задача выделяет раздел и передает указатель другой задаче (которая может позже освободить его). Если перезагружать пул разделов, все разделы будут помечены как неиспользуемые, однако, механизм, позволяющий отслеживать и уведомлять все задачи, которые могут использовать разделы, отсутствует.

Службы выделения и освобождения разделов


Фундаментальные операции с пулами разделов — выделение разделов в пуле (т. е. пометка раздела как используемого и возвращение его адреса) и освобождение раздела (т. е. раздел помечается как неиспользуемый). Nucleus RTOS и Nucleus SE предоставляют два базовых вызова API для этих операций, описание которых приведено ниже.

Выделение раздела


Вызов API Nucleus RTOS для выделения раздела очень гибкий, что позволяет разработчикам приостанавливать задачи на неопределенный период времени или без таймаута, если операция не может быть завершена немедленно, например, когда вы пытаетесь выделить раздел из пула, в котором уже все разделы распределены. Nucleus SE предоставляет такую же службу, только приостановка задач в ней опциональна, а таймаут не реализован.

Вызов API Nucleus RTOS для выделения раздела


Прототип вызова:

STATUS NU_Allocate_Partition (NU_PARTITION_POOL *pool, VOID **return_pointer, UNSIGNED suspend);

Возвращаемое значение:

NU_SUCCESS – вызов был успешно завершен;
NU_NO_PARTITION – нет доступных разделов;
NU_INVALID_POOL – некорректный указатель пула разделов;
NU_INVALID_POINTER – передан нулевой указатель на возвращаемые данные (NULL);
NU_INVALID_SUSPEND – попытка приостановить задачу была произведена из не связанного с задачей потока;
NU_TIMEOUT – нет доступных разделов, даже после приостановки на указанный период ожидания;
NU_POOL_DELETED – пул разделов был удален, когда задача была приостановлена.

Вызов API Nucleus SE для выделения раздела


Этот вызов API поддерживает основной функционал Nucleus RTOS API.

Прототип вызова:

STATUS NUSE_Partition_Allocate (NUSE_PARTITION_POOL pool, ADDR *return_pointer, U8 suspend);

Параметры:

pool – индекс (ID) используемого пула разделов;
return_pointer – указатель на переменную типа ADDR, которая принимает адрес выделяемого раздела;
suspend – параметр для приостановки задачи, может принимать значения NUSE_NO_SUSPEND или NUSE_SUSPEND.

Возвращаемое значение:

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_NO_PARTITION – нет доступных разделов;
NUSE_INVALID_POOL – некорректный индекс пула разделов;
NUSE_INVALID_POINTER – передан нулевой указатель на возвращаемые данные (NULL);
NUSE_INVALID_SUSPEND —попытка приостановить задачу была произведена из не связанного с задачей потока или при отключенных API-функциях блокировки.

Реализация выделения раздела в Nucleus SE


Код функции API NUSE_Partition_Allocate выбирается при помощи условной компиляции после проверки параметров, в зависимости от того, активирован API-вызов блокировки (приостановки задач) или нет. Ниже мы отдельно рассмотрим эти два варианта.

Если блокирующие вызовы дезактивированы, вызов API довольно прост:



Сначала проверяется доступность свободных разделов. Если таких разделов нет, возвращается ошибка (NUSE_NO_PARTITION). Затем идет перебор разделов, в ходе которого первые байты проверяются на наличие нулевых значений (что говорит о том, что раздел не используется). При нахождении такого раздела, ему присваивается флаг «используется», включающий в себя индекс пула раздела (см. «Освобождение раздела» ниже), и возвращается указатель на следующий байт (начало области реальных данных). Пояснения о структурах данных пулов разделов будут представлены в следующей статье в разделе «Структуры данных».

Если активирована блокировка, код для этого вызова API становится немного сложнее:



Код заключен в цикл do…while, которые продолжает работу, пока параметр приостановки имеет значение NUSE_SUSPEND.

Если нет доступных разделов и параметр приостановки имеет значение NUSE_NO_SUSPEND, вызов API прекращает работу и возвращает значение NUSE_NO_PARTITION. Если параметру приостановки было присвоено значение NUSE_SUSPEND, задача приостанавливается. При возврате (например, когда задача возобновляется), возвращаемое значение NUSE_SUCCESS говорит о том, что задача была возобновлена, поскольку раздел памяти был освобожден и код возвращается в начало цикла. Так как функции API для перезагрузки пулов разделов нет, задачи не могут быть возобновлены по другим причинам, но для стабильности блокировки других типов объектов процесс проверки NUSE_Task_Blocking_Return[] сохраняется.

Освобождение раздела


Освобождение раздела в Nucleus RTOS и Nucleus SE делает его вновь доступным. Перед освобождением не производится проверка, используется ли данный раздел какой-либо задачей или нет, за это отвечает прикладной программист. Для освобождения раздела требуется только указатель на область данных.

Вызов API Nucleus RTOS для освобождения раздела


Прототип вызова:

STATUS NU_Deallocate_Partition(VOID *partition);

Параметры:

partition – указатель на область данных (возвращаемый функцией NU_Allocate_Partition()) освобождаемого раздела;

Возвращаемое значение:

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_POINTER – указатель раздела нулевой (NULL), или не указывает на допустимый используемый раздел.

Вызов API Nucleus SE для освобождения раздела


Этот вызов API поддерживает основной функционал Nucleus RTOS API.

Прототип вызова:

STATUS NUSE_Partition_Deallocate(ADDR partition);

Параметры:

partition – указатель на область данных (возвращаемый функцией NUSE_Partition_Allocate()) освобождаемого раздела

Возвращаемое значение:

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_POINTER – указатель раздела нулевой (NULL), или не указывает на допустимый используемый раздела

Реализация


Вместо реализации при помощи блокирующих и неблокирующих функций API функция NUSE_Partition_Deallocate() просто содержит условно скомпилированную секцию, которая отвечает за разблокировку задач. Этот код реализует само освобождение разделов:



Сначала индекс раздела извлекается из байта его состояния. Затем, состояние раздела меняется на «неиспользуемый», счетчик используемых разделов уменьшается, и функция сообщает об успешном завершении операции.

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



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

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

Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он — инженер в области встроенного ПО в Mentor Embedded (подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании.
Tags:
Hubs:
+8
Comments 0
Comments Leave a comment

Articles