ОСРВ MULTEX-ARM
Руководство программиста
Ядро операционной системы

Многозадачность и межзадачное взаимодействие

См. также
Описание методов работы с задачами и примеры кода в файле tasklib.h.

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

Ядро MULTEX-ARM позволяет достаточно легко создавать такие системы и обеспечивает гибкое взаимодействие параллельно выполняющихся процессов с минимальными накладными расходами. Многозадачная среда MULTEX-ARM позволяет представлять прикладной проект в виде набора независимых задач и обеспечить кажущуюся одновременность их выполнения. В зависимости от общих требований прикладной проект может быть построен следующими способами (либо их комбинацией):

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

Недостатком режима карусельного планирования является зависимость эффективного быстродействия (а значит и времени выполнения для отдельной задачи) от комбинации состояний остальных задач системы. Однако, MULTEX-ARM позволяет динамически изменять режим работы ядра – включать или выключать карусельный режим, а также менять квант времени исполнения задачи, либо полностью блокировать переключение задач на время выполнения критичной ко времени секции процедуры. Кроме того, имеется возможность динамической смены приоритета любой задачи. Благодаря этому в среде MULTEX-ARM возможно эффективное построение самых разнообразных прикладных проектов.

Любая С-подпрограмма в среде MULTEX-ARM может быть запущена как отдельная задача со своим собственным контекстом и стеком. Базовые средства MULTEX-ARM позволяют создавать, приостанавливать, возобновлять, задерживать и уничтожать задачи. Задачи могут инициировать внутренние события, на которые будут реагировать другие задачи. Каждая задача MULTEX-ARM может находиться в одном из следующих состояний:

  • Выполняемая задача IN WORK — та задача, которая выполняется процессором в данный момент времени.
  • Активная задача ACTIVE — задача, готовая к выполнению, но ожидающая своей очереди, так как в данный момент выполняется другая задача с таким же или более высоким приоритетом.
  • Задержанная задача DELAYED — задача, выполнение которой отложено на заданное время.
  • Приостановленная задача PEND — задача, ждущая у семафора.
  • Остановленная задача SUSPEND — задача, переведенная в пассивное состояние командой taskSuspend().

Ядро MULTEX-ARM манипулирует задачами с помощью специальных двусвязных циклических динамических списков – каруселей. При инициализации ядра создаются две таких структуры – карусель активных задач и карусель задержанных задач. Каждая задача системы помещается в одну из них, за исключением выполняемой задачи, которая до очередного переключения не отнесена ни к одной из каруселей. Существует еще одна, скрытая задача, которая выполняет пустой "вечный цикл" и служит только для того, чтобы выполняться тогда, когда все остальные задачи приостановлены. В эту задачу вырождается процедура запуска ядра MULTEX-ARM после завершения выполнения функции инициализации kernelInit(). Процесс переключения с задачи на задачу в MULTEX-ARM сводится к следующим шагам:

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

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

Управление прерываниями

См. также
Описание методов управления прерываниями в файле intlib.h.

Прерывания играют важнейшую роль в системах реального времени. Аппаратное прерывание, вызванное поступлением электрического импульса на один из специализированных входов контроллера является сигналом к немедленным действиям, которые должна выполнить система в ответ на это событие. Ядро MULTEX-ARM позволяет обеспечить незамедлительное выполнение при возникновении прерывания пользовательской процедуры, подключенной к этому прерыванию. Такая процедура называется обработчиком прерывания. Все действия по сохранению / восстановлению контекста прерываемой задачи берет на себя операционная система. Ядро MULTEX-ARM настраивает контроллер прерываний таким образом, чтобы отличать аппаратные прерывания от исключений процессора и особых случаев, которые могут возникать при выполнении программы.

Приоритеты прерываний и задач

При планировании распределения вычислительных ресурсов в проекте следует учитывать иерархию выполнения аппаратных прерываний и пользовательских задач и настраивать их приоритеты соответствующим образом. В MULTEX-ARM существует два вида приоритетов. Их описание дано ниже.

Приоритеты прерываний

В ARM процессорах со встроенным контроллером прерываний GIC реализована поддержка групп и приоритетов прерываний. Прерывания объединяются в группы внутри которых распределяются приоритеты обработки прерываний. Прерывания более приоритетной группы могут вытеснять (прерывать) обработку менее приоритетных групп. Приоритет внутри группы позволяет получить преимущество при возникновении нескольких прерываний одновременно.

Такая политика обработки прерываний поддержана в MULTEX-ARM. Для использования доступно 4 группы аппаратных прерываний и 8 уровней приоритетов. Макросы, описывающие порядок распределения прерываний по группам и назначения приоритетов, собраны в отдельную группу. Основной группой для всех прерываний является INTERRUPT_GROUP_MAJOR. Большинство прерываний используемых системой включены именно сюда. В эту же группу рекомендуется добавлять пользовательские обработчики прерываний, подключаемые с помощью interruptConnect(). Более приоритетной группой является INTERRUPT_GROUP_SYSTEM. В неё обычно входит всего одно прерывание — Системный таймер. В группу с наивысшим приоритетом INTERRUPT_GROUP_TIME_CRITICAL рекомендуется помещать прерывания, обеспечивающие мгновенную реакцию на событие, либо строго задающие период следования импульсов на внешних линиях. Обработчики таких прерываний должны выполняться максимально быстро. Например, задействовав TIMER_2 и аппаратный модуль ШИМ в импульсном режиме можно получить сигнал с переменной скважностью и жёстко заданным периодом. Вид такого сигнала с периодом 1,5 мкс приведён на верхнем графике осциллограммы. На нижнем графике показан тестовый импульс сгенерированный системным таймером. В данном случае высокоприоритетное прерывание, запускающее каждый импульс ШИМ, 3 раза прерывает обработку системного таймера. Наивысший приоритет в данному случае позволяет точно выдержать заданную частоту следования импульсов.

Вложенные прерывания

Приоритеты задач

Выделением процессорного времени для каждой задачи занимается Системный таймер реализованный на аппаратном TIMER_1. На каждом тике таймера принимается решение какая из задач будет выполняться в настоящий момент. Каждый раз предпочтение отдаётся наиболее приоритетной из активных задач. В то же время не следует путать приоритеты задач и приоритеты прерываний. Во время исполнения даже самой приоритетной задачи может возникнуть аппаратное прерывание, обработчик которого прервёт текущую задачу.

Таймеры

См. также
Описание методов работы с таймерами в файле timer-arm.h.

В зависимости от модели процессора в нём может быть реализовано от 3 до 6 аппаратных таймеров. В MULTEX-ARM принято распределение таймеров, описанное соответствующей группе макросов. Как правило таймеры TIMER_0 и TIMER_1 заняты системой. Остальные таймеры могут быть задействованы под конкретный проект.

Системный таймер

См. также
Описание методов работы с системным таймером в файле timer.h.

Системный таймер (TIMER_1) используется ядром MULTEX-ARM для обеспечения синхронизации внутренних функций, регистрации таймаутов и организации режима карусельного планирования. Частота системного таймера устанавливается при инициализации ядра равной 1000Гц. Однако она может быть изменена пользовательской задачей на любое целое значение из диапазона 20Гц-1000 Гц. Так как все временные интервалы (таймауты или задержки) задаются в тиках таймера, то при использовании констант они будут изменяться при перестройке частоты таймера. Рекомендуется в значение задержки включать функцию опроса частоты таймера, для получения задержек, независящих от настройки таймера.

Сигналы

См. также
Описание методов работы с сигналами в файле signal.h.

Сигналы — это ограниченная форма межзадачного взаимодействия. Сигналом называется асинхронное уведомление, отосланное задаче для того, чтобы сообщить ей о каком-то событии.

В библиотеках ядра MULTEX-ARM имеются средства для поддержки сигналов, совместимых с UNIX-подобными операционными системами. Стандарт определяет всего шесть сигналов, которые могут быть обработаны. Все они описаны в группе Стандартные для Си сигналы файла signal.h.

Кроме того, есть сигналы, которые не могут быть обработаны, пойманы или игнорированы, такие как SIGKILL и SIGSTOP.

Семафоры

См. также
Описание методов работы семафорами и примеры кода в файле semlib.h.

Семафоры в MULTEX-ARM – это основной механизм синхронизации задач в реальном времени и организации взаимоисключающего доступа задач к общим ресурсам.

Предупреждения
Семафоры это механизм именно межзадачного взаимодействия. Не следует использовать их для синхронизации задач с аппаратными прерываниями!

Ядро MULTEX-ARM поддерживает три типа семафоров:

  • Двоичный семафор – служит для синхронизации задач или организации взаимоисключающего доступа.
  • Целочисленный семафор – служит для защиты множественных ресурсов.
  • Семафор взаимного исключения – служит для наследования приоритета, защиты от удаления и использования рекурсии.

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

Очереди сообщений

См. также
Описание методов работы с очередями сообщений и примеры кода в файле msgQLib.h.

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

Предупреждения
Очереди сообщений это механизм именно межзадачного взаимодействия. Не следует использовать их для обмена данных между задачами и обработчиками аппаратных прерываниями! Для этого служит другой механизм, описанный в ringbuffer.h.

Передаваемые через очереди сообщений данные должны быть объединены в общую группу – сообщение. При этом структура сообщения может быть произвольной, достаточно только, чтобы одно сообщение занимало непрерывный участок памяти, имело предсказуемый размер и его структура была известна как передающей, так и принимающей задаче. Очередь автоматически буферизует заданное при ее создании количество сообщений, поэтому, если принимающая задача вовремя не опросит очередь, то сообщение не будет потеряно. Если задача пытается получить данные из пустой очереди, то она может быть задержана до появления в очереди сообщения. Впрочем, задача может ждать сообщение заданное время или вовсе не ждать. Таким образом, очередь сообщений выполняет не только функции транспортировки данных, но и функции синхронизации задач в реальном времени. И помещать сообщения в очередь, и извлекать их из нее может одновременно несколько задач. Внутренние механизмы очередей обеспечивают корректный множественный доступ к ее данным. Для ссылок на очередь из разных задач используется идентификатор очереди MSG_Q_ID, возвращаемый при ее создании. Помещать сообщения в очередь задача может двумя способами:

Таким образом, обеспечивается возможность передачи двух типов сообщений – обычных и внеочередных. Задачи, ожидающие получения сообщений из очереди, могут получать данные в порядке их обращений к очереди, или в соответствии с их приоритетами. Режим задается при создании очереди опциями MSG_Q_FIFO и MSG_Q_PRIORITY соответственно.

См. также
Описание заголовочных файлов в группе Ядро MULTEX-ARM.

Базовая система ввода / вывода

Ядро MULTEX-ARM поддерживает драйверы символьных (последовательного доступа) и блочных устройств ввода / вывода. Используя простые соглашения, пользователь может создавать свои драйверы символьных устройств и подключать их в систему самостоятельно. При этом устройство становится доступным как по его дескриптору – небольшому целому числу, так и по символьному имени, задаваемому устройству при регистрации его драйвера в системе. Устройства с дескрипторами 0, 1 и 2 являются стандартными устройствами ввода, вывода и вывода ошибок (STDIN, STDOUT и STDERR). Каждая задача в своем блоке управления TCB имеет соответствующие поля – s_in, s_out и s_err. При создании корневой задачи в эти поля заносятся значения STDIN, STDOUT и STDERR. Функции scanf() , printf() и printerr() осуществляют ввод-вывод на эти устройства. В таблице дескрипторов для стандартных устройств указываются ссылки на их адреса, поэтому возможно перенаправление ввода / вывода как глобально, для всех задач, так и отдельно, для конкретной задачи. В базовой системе ввода / вывода MULTEX-ARM используются две таблицы:

  • sysDrvTable – таблица для занесения параметров драйверов устройств ввода / вывода.
  • sysFdTable – таблица для занесения параметров открытых файлов.

Эти таблицы создаются функцией kernelInit(), причем размеры таблиц задаются следующим образом: максимальное число драйверов в системе — 100, а максимальное число открытых файлов — 256.

См. также
Описание методов работы с базовой системой ввода / вывода см. в файле iolib.h.

Блочные устройства и файловые системы

В ядре MULTEX-ARM поддерживается работа с блочными устройствами, такими как SD карты, USB флэш накопители, SATA устройства. Эти устройства оперируют данными, как блоками фиксированной длины, отсюда и название. Драйверы таких устройств должны иметь как минимум две обязательные функции:

  • Функцию чтения из устройства заданного количества блоков в буфер в оперативной памяти.
  • Функцию записи на устройство из буфера заданного количества блоков.

При создании блочного устройства его драйвер заносит указатели на эти функции в специальную структуру BLK_DEV. Так, например, при создании устройства типа MMC в ядре MULTEX-ARM выполняются следующие действия:

Dev = malloc(sizeof(BLK_DEV)); // Выделение памяти под блок управления
memset(Dev,0,sizeof(BLK_DEV)); // Очистка выделенной области памяти
Dev→bd_signature=BD_STD_SIGNATURE; // Установка правильной сигнатуры
Dev→bd_volume=0x80; // Значение для жесткого диска
Dev→bd_startBlk=0; // Читаем блоки с начала ММС
Dev→bd_blkTotal=2; // Для начала достаточно 2 блоков, потом скорр.
Dev->bd_blkRd = mmcRdBlk; // Процедура чтения
Dev->bd_blkWrt = mmcWrBlk; // Процедура записи
Dev->volConfig = mmc; // Данные для драйвера
res = mmc_init(mmc); // Аппаратная настройка ММС
#define BD_STD_SIGNATURE
Definition: iolib.h:268
void * malloc(size_t size)
void * memset(void *s, int c, size_t n)
Definition: iolib.h:274

Далее, для того, чтобы получить доступ к файлам, хранящимся на устройстве, требуется создать еще одну специальную структуру — файловую систему. В ядре MULTEX-ARM создается такая структура — файловая система MSDOS FAT12 / FAT16 / FAT32. Файловые системы создаются вызовом процедуры iosDrvInstall() точно так же, как создаются символьные устройства, описанные ранее. Дескриптор файловой системы FAT12 / FAT16 / FAT32 уже создан ядром при запуске и находится в глобальной переменной DosDriver.

Чтобы привязать конкретное блочное устройство к файловой системе в MULTEX-ARM достаточно вызвать функцию монтирования mountVolume().

Диспетчер памяти

См. также
Описание методов управления памятью см. в файле memlib.h.

Ядро MULTEX-ARM включает комплекс процедур, обеспечивающих работу с динамически выделяемыми блоками памяти (Heap-memory). Этот комплекс, называемый диспетчером динамической памяти, позволяет задачам ядра и пользовательским задачам выделять для собственных нужд блоки памяти требуемого размера и освобождать их по мере надобности. Функции диспетчера памяти используются ядром MULTEX-ARM при создании задач, семафоров и очередей.

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

Для обеспечения защиты от множественного доступа к диспетчеру памяти принято наиболее простое и эффективное решение:

  • Так как диспетчер памяти не может быть защищен семафорами взаимоисключений (сама работа с семафорами нуждается в функциях диспетчера памяти), то на время цикла выделения/высвобождения блока памяти процессор закрывается, что гарантирует непрерываемость операции.

Работа с встроенной КЭШ-памятью процессора

См. также
Описание методов работы с КЭШ-памятью см. в файле cache.h.

Встроенная КЭШ-память процессора обеспечивает существенное повышение быстродействия вычислительной системы. Ядро MULTEX при запуске размещает в памяти таблицу виртуализации памяти и активирует MMU (модуль управления памятью) в соответствии с этой таблицей. При этом для каждой страницы размером в память может быть настроена как некэшируемая, кэшируемая с записью, или кэшируемая с отложенной записью. Для этого в составе BSP для конкретного модуля назначается специальная структура SYS_MEM_MAP, описывающая то, как нужно настраивать память. Так как MULTEX работает с использованием кэш, то в некоторых случаях возможно рассогласование между содержимым памяти и данными в кэш. Это бывает, например, когда какой-либо блок заносит данные в память прямым доступом.

Нелокальные переходы

См. также
Описание функций нелокальных переходов в файлах setjmp.h и stdlib.h.

В ядре MULTEX-ARM содержится библиотека стандартной поддержки нелокальных переходов setjmp. Библиотека объявляет тип данных jmp_buf, который является массивом и который может использоваться для сохранения и восстановления контекста выполнения программы. Для установки точки сохранения служит функция setjmp(). Вернуться в точку сохранения можно с помощью функции longjmp(). Для сохранения и восстановления контекста при переключении между задачами служат функции setexit() и exit().

Работа с ini–файлами

См. также
Описание методов работы с ini-файлами в файле inifiles.h.

Часто пользователю приходится конфигурировать конкретную систему, целевое ПО, работающее в среде MULTEX-ARM, производя его настройку с помощью каких-либо переменных. Эти переменные определяют общее поведение системы. Их значение не должно теряться при отключении питания, а напротив — при включении питания и запуске системы конфигурационные переменные должны принимать те значения, которые были заданы при конфигурировании. С другой стороны, при проведении программных настроек пользователь должен иметь возможность изменять значения каких-либо конфигурационных переменных таким образом, чтобы после повторных включений системы вновь присвоенные значения не терялись. Для этих целей в MULTEX-ARM разработан единый механизм хранения (и модификации при необходимости) данных в специальных ini–файлах, по аналогии с тем, как это было сделано в ранних версиях ОС Windows. Ini-файл - это обыкновенный текстовый файл, который может быть подготовлен как средствами MULTEX-ARM, так и обыкновенным текстовым редактором, а затем записан на диск, доступный операционной системе. Для работы с такими файлами разработана специальная библиотека, входящая в состав ядра MULTEX-ARM.

Эта библиотека позволяет пользователю отыскивать в ini-файле значения переменных по их именам. При этом значения могут представляться как в виде текстовых строк, так и в десятичном виде, либо в виде булевых значений (типа да-нет). Кроме этого, ряд функций позволяет записывать новые значения в этот файл по имени переменной. После закрытия файла на диске MULTEX-ARM будет создан новый ini-файл со всеми внесенными изменениями и дополнениями. Старый ini-файл будет при этом удален.

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

Работа с межпроцессорными каналами.

PLL – распределение тактовых частот

См. также
Описание методов работы с PLL в файле pll.h.

Опорная частота 24 МГц, получаемая с помощью внутренней схемы генерации и стабилизируемая с помощью внешнего кварцевого резонатора, повышается внутри процессора и далее распределяется по внутренним шинам и аппаратным модулям. Как именно происходит распределение частот зависит от конфигурации конкретного процессора. Управление умножением и делением частот и распределение их между шинами и модулями осуществляется с помощью регистров конфигурации PLL.

Функции по настройке PLL регистров полностью берёт на себя операционная система. Однако при проектировании новых драйверов устройств, а так же для проверки правильной работы уже подключенных аппаратных модулей имеет смысл воспользоваться функциями, описанными в файле pll.h. Для вывода в консоль таблицы используемых тактовых частот можно воспользоваться функцией pll(). Для получения значения частоты конкретного аппаратного модуля или шины в программе можно воспользоваться функциями pllGet(), pllAhbGet() и им подобными. Следует учесть, что драйверы устройств могут изменять частоты общих шин, поэтому для получения актуальных значений следует перед использованием get-функций вызвать функцию сбора данных pllUpdate().