[Перевод] Вся правда об ОСРВ. Статья #15. Разделы памяти: службы и структуры данных

3twdaubgcbrqlkg0al5lttnqs-y.jpeg

В этой статье мы продолжаем рассматривать разделы памяти ОСРВ.

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

Получение информации о пуле разделов


Этот служебный вызов позволяет получить частичную информацию о пуле разделов. Реализация Nucleus SE отличается от Nucleus RTOS тем, что в ней возвращается меньше информации, поскольку присваивание объектам имен и запросы на приостановку не поддерживаются, и приостановка задачи невозможна.

Вызов получения информации о пуле разделов в Nucleus RTOS


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

STATUS NU_Partition_Pool_Information (NU_PARTITION_POOL *pool, CHAR *name, VOID **start_address, UNSIGNED *pool_size, UNSIGNED *partition_size, UNSIGNED *available, UNSIGNED *allocated, OPTION *suspend_type, UNSIGNED *tasks_waiting, NU_TASK **first_task);

Параметры:

pool — указатель на пул разделов, о котором запрошена информация;
name — указатель на 8-символьную область назначения для имени пула разделов; включает место для терминирующего нуля;
start_address — указатель на переменную, которая получает указатель на начало области данных пула разделов;
pool_size — указатель на переменную, которая получает размер пула разделов (в байтах);
partition_size — указатель на переменную, которая получает размер разделов в данном пуле;
available — указатель на переменную, которая получает число доступных в данный момент разделов в данном пуле;
allocated — указатель на переменную, которая получает число используемых в данный момент разделов в данном пуле;
suspend_type — указатель на переменную для получения типа приостановки задачи; допустимые типы приостановки: NU_FIFO и NU_PRIORITY;
tasks_waiting — указатель на переменную, которая получает количество приостановленных задач в данном пуле разделов;
first_task — указатель на указатель задачи, в котором размещен указатель первой приостановленной задачи.

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

NU_SUCCESS — вызов выполнен успешно;
NU_INVALID_POOL — некорректный указатель на пул разделов.

Вызов получения информации о пуле разделов в Nucleus SE


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

STATUS NUSE_Partition_Pool_Information (NUSE_PARTITION_POOL pool, ADDR *start_address, U32 *pool_size, U16 *partition_size, U8 *available, U8 *allocated, U8 *tasks_waiting, NUSE_TASK *first_task)

Параметры:

pool — индекс пула разделов, о котором запрошена информация;
start_address — указатель на переменную, которая получает указатель на начало области данных пула разделов;
pool_size — указатель на переменную, которая получает размер пула разделов (в байтах);
partition_size — указатель на переменную, которая получает размер разделов в данном пуле;
available — указатель на переменную, которая получает число доступных в данный момент разделов в данном пуле;
allocated — указатель на переменную, которая получает число используемых в данный момент разделов в данном пуле;
tasks_waiting — указатель на переменную, которая получает количество приостановленных задач в данном пуле разделов (если приостановка задач отключена, возвращается 0);
first_task — указатель на переменную типа NUSE_TASK, которая получает индекс первой приостановленной задачи (если приостановка задач отключена, возвращается 0).

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

NUSE_SUCCESS — вызов выполнен успешно;
NUSE_INVALID_POOL — некорректный индекс пула разделов;
NUSE_INVALID_POINTER — один или несколько переданных указателей некорректны.

Реализация получения информации и пуле разделов в Nucleus SE


Реализация такого вызова API проста в исполнении:

ia82sj807l4yuaio-mvf-ypjivm.jpeg

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

Получение количества пулов разделов


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

Вызов получения количества пулов разделов в Nucleus RTOS


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

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

UNSIGNED NU_Established_Partition_Pools (VOID);

Параметры:

Отсутствуют.

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

Количество созданных в приложении пулов разделов.

Вызов получения количества пулов разделов в Nucleus SE


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

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

U8 NUSE_Partition_Pool_Count (void);

Параметры:

Отсутствуют

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

Количество созданных в приложении пулов разделов.

Реализация


Реализация данного вызова API крайне проста: возвращается значение #define символа NUSE_PARTITION_POOL_NUMBER.

Структуры данных


Как и все другие объекты Nucleus SE, пулы разделов используют массивы структуры и в ПЗУ, и в ОЗУ, число которых зависит от числа пулов, заданного в настройках.

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

Структура данных ядра, размещаемых в ОЗУ


К таким структурам данных относятся:

NUSE_Partition_Pool_Partition_Used[] — массив типа U8, имеющий одну запись для каждого сконфигурированного пула разделов, содержащий счетчик используемых в данный момент пулов;
NUSE_Partition_Pool_Blocking_Count[] — массив типа U8, содержащий счетчик заблокированных задач в каждом пуле разделов. Данный массив существует, если возможна блокировка вызова API.

Такие структуры данных инициализируются нулями с помощью NUSE_Init_ Partition_Pool () при запуске Nucleus SE. Это логично, поскольку делает каждый раздел в каждом пуле неиспользуемым (свободным). В следующей статье будет представлено полное описание процедур запуска в Nucleus SE.

Ниже приведены описания структур данных в файле nuse_init.c.

vi3xsxmbwdzkwgfge9ws_dsvups.jpeg

Пользовательские данные в ОЗУ


Пользователю необходимо выделить область в ОЗУ для хранения данных для каждого пула раздела. Объем области в ОЗУ должен соответствовать объему сконфигурированных разделов (см. «Данные в ПЗУ» ниже) с дополнительным байтом для каждого раздела в пуле. В каждом разделе области данных предшествует один байт состояния.

Данные в ПЗУ


К ним относятся:

NUSE_Partition_Pool_Data_Address[] — массив типа ADDR, с одной записью для каждого сконфигурированного пула разделов, содержащий адрес начала области хранения данных;
NUSE_Partition_Pool_Partition_Number[]— массив типа U8 с одной записью для каждого сконфигурированного пула разделов, содержащий информацию о количестве разделов в пуле;
NUSE_Partition_Pool_Partition_Size[] — массив типа U16 с одной записью для каждого сконфигурированного пула разделов, содержащий размер разделов для пулов.

Такие структуры данных объявляются и инициализируются (статически) в nuse_config.c:

zt67lqadvkdzmuvazbj0mmlhp8a.jpeg

Объем памяти (Data footprint) для пула разделов


Как и для всех объектов ядра в Nucleus SE, объем памяти, необходимой для пулов разделов, предсказуем.

Объем ПЗУ (в байтах) для всех пулов разделов приложения может быть вычислен следующим образом:

NUSE_PARTITION_POOL_NUMBER * (sizeof (ADDR) + 2)

Объем данных ядра в ОЗУ для всех пулов разделов приложения при активированной блокировке вызовов API занимает всего 2 байта на один пул разделов, при не активированной блокировке — 1 байт.

Объем памяти для хранения пользовательских данных в ОЗУ варьируется для каждого пула разделов, хотя, как уже говорилось, для пула с индексом n его можно вычислить как:

NUSE_Partition_Pool_Partition_Number[n] *
(NUSE_Partition_Pool_Partition_Size[n] + 1)

Нереализованные вызовы API


Три вызова API для пулов разделов, реализованные в Nucleus RTOS, не поддерживаются в Nucleus SE.

Создание пула разделов (Create Partition Pool)


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

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

STATUS NU_Create_Partition_Pool (NU_PARTITION_POOL *pool, CHAR *name, VOID *start_address, UNSIGNED pool_size, UNSIGNED partition_size, OPTION suspend_type);

Параметры:

pool — указатель на пользовательский блок управления пулами разделов; используется в качестве дескриптера («handle») пула разделов в других вызовах API;
name — указатель на имя пула разделов, 7-символьную строку с терминирующий нулем;
start_address — задает начальный адрес для области памяти пула разделов;
pool_size — общий объем области памяти в байтах;
partition_size — объем памяти в байтах для каждого раздела в пуле. Сверх этого выделяется дополнительно небольшой объем памяти, связанный с каждым разделом, что реализуется благодаря двум используемым указателям данных.
suspend_type — определяет, как задачи приостанавливаются в пуле разделов; допустимые варианты параметра: NU_FIFO и NU_PRIORITY.

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

NU_SUCCESS — указывает на успешное завершение вызова;
NU_INVALID_POOL — указывает на нулевое значение блока управления пулом разделов (NULL);
NU_INVALID_MEMORY — указывает на нулевое значение области памяти, определяемой start_ address (NULL);
NU_INVALID_SIZE — указывает, что размер раздела либо равен 0, либо больше памяти, выделенной для раздела;
NU_INVALID_SUSPEND — некорректное значение suspend_type.

Удаление пула разделов


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

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

STATUS NU_Delete_Partition_Pool (NU_PARTITION_POOL *pool);

Параметры:

pool — указатель на блок управления пулом разделов;

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

NU_SUCCESS — указывает на успешное завершение вызова;
NU_INVALID_POOL — указывает на некорректное значение указателя пула разделов;

Указатели пула разделов


Этот вызов API строит последовательный список указателей на все пулы разделов в системе. В Nucleus SE в этом нет необходимости, поскольку объекты идентифицируются индексом, а не указателем.

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

UNSIGNED NU_Partition_Pool_Pointers (NU_PARTITION_POOL **pointer_list, UNSIGNED maximum_pointers);

Параметры:

pointer_list — указатель на массив указателей NU_PARTITION_POOL; массив заполняется указателями на сконфигурированные пулы в системе;
maximum_pointers — максимальное количество указателей, которые можно поместить в массив.

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

Количество указателей NU_PARTITION_POOL, помещенных в массив.

Совместимость с Nucleus RTOS


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

Идентификаторы объектов


В Nucleus RTOS все объекты описаны структурами данных (блоками управления), имеющими определенный тип. Указатель на этот блок управления используется как идентификатор для пула разделов. Я решил, что в Nucleus SE требуется другой подход для более эффективного использования памяти. Все объекты ядра описываются несколькими таблицами в ОЗУ и/или ПЗУ. Размеры этих таблиц определяются количеством конфигурируемых типов всех объектов. Идентификатор для конкретного объекта — индекс в этих таблицах. Поэтому я определил NUSE_PARTITION_POOL эквивалентным U8, после чего переменная (не указатель) данного типа служит в качестве идентификатора задачи. С этой небольшой несовместимостью легко разобраться, если код перенесен с или на Nucleus RTOS. Идентификаторы объектов обычно хранятся и передаются без изменений.

Nucleus RTOS также поддерживает присваивание имен пулам разделов. Эти имена используются только при отладке. Я исключил их из Nucleus SE для экономии памяти.

Количество разделов и их объем


В Nucleus RTOS пул разделов конфигурируется с учетом общего объема пула и объема разделов (которые несут с собой еще 2 указателя). Эти параметры определены как UNSIGNED (примерно, 32 бита). В Nucleus SE пул разделов конфигурируется с учетом объема раздела (для которого добавлен дополнительный байт) и общего количества разделов. Эти параметры определены как U16 и U8 соответственно.

Нереализованные вызовы API


Nucleus RTOS поддерживает 7 вызовов для работы с пулами разделов, 3 из которых не реализованы в Nucleus SE. Более подробно об этих вызовах и о причинах их исключения изложено выше.

Следующая статья будет посвящена сигналам.

Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он — инженер в области встроенного ПО в Mentor Embedded (подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина, e-mail: colin_walls@mentor.com.

© Habrahabr.ru