[Перевод] Вся правда об ОСРВ. Статья #15. Разделы памяти: службы и структуры данных
В этой статье мы продолжаем рассматривать разделы памяти ОСРВ.
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 проста в исполнении:
Функция возвращает статус пула разделов. Затем, если активирована блокировка вызовов 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.
Пользовательские данные в ОЗУ
Пользователю необходимо выделить область в ОЗУ для хранения данных для каждого пула раздела. Объем области в ОЗУ должен соответствовать объему сконфигурированных разделов (см. «Данные в ПЗУ» ниже) с дополнительным байтом для каждого раздела в пуле. В каждом разделе области данных предшествует один байт состояния.
Данные в ПЗУ
К ним относятся:
NUSE_Partition_Pool_Data_Address[] — массив типа ADDR, с одной записью для каждого сконфигурированного пула разделов, содержащий адрес начала области хранения данных;
NUSE_Partition_Pool_Partition_Number[]— массив типа U8 с одной записью для каждого сконфигурированного пула разделов, содержащий информацию о количестве разделов в пуле;
NUSE_Partition_Pool_Partition_Size[] — массив типа U16 с одной записью для каждого сконфигурированного пула разделов, содержащий размер разделов для пулов.
Такие структуры данных объявляются и инициализируются (статически) в nuse_config.c:
Объем памяти (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.