![]() |
ОСРВ MULTEX-ARM
Полное описание
|
Задача в операционной системе это последовательность команд, выполняющая логически завершённую часть общей работы программы. Задача оформляется в виде отдельной процедуры и обладает собственными ресурсами.
Многозадачность — свойство ОС распределять вычислительные ресурсы между несколькими задачами. Задачи получают время на исполнение в соответствии с назначенными им приоритетами. Выполнение задач происходит квазипараллельно и синхронизируется с внешними событиями, такими как поступление данных от устройства ввода/вывода, срабатывание таймера, аппаратное прерывание от внешнего источника, готовность внешней системы к получению данных и т.д. Такие внешние события могут возникать независимо друг от друга в произвольные моменты времени.
Межзадачное взаимодействие и связь задач с обработчиками прерываний в MULTEX-ARM осуществляется с помощью специальных средств синхронизации — семафоров и очередей сообщений.
Любая С-подпрограмма в среде MULTEX-ARM может быть запущена как отдельная задача со своим собственным контекстом и стеком. Базовые средства MULTEX-ARM позволяют создавать, приостанавливать, возобновлять, задерживать и уничтожать задачи. Задачи могут инициировать внутренние события, на которые будут реагировать другие задачи. Каждая задача MULTEX-ARM может находиться в одном из следующих состояний:
Ядро MULTEX-ARM манипулирует задачами с помощью специальных двусвязных циклических динамических списков – каруселей. При инициализации ядра создаются две таких структуры – карусель активных задач и карусель задержанных задач. Каждая задача системы помещается в одну из них, за исключением выполняемой задачи, которая до очередного переключения не отнесена ни к одной из каруселей. Существует еще одна, скрытая задача, которая выполняет пустой "вечный цикл" и служит только для того, чтобы выполняться тогда, когда все остальные задачи приостановлены. В эту задачу вырождается процедура запуска ядра MULTEX-ARM после завершения выполнения функции инициализации kernelInit(). Процесс переключения с задачи на задачу в MULTEX-ARM сводится к следующим шагам:
Настройка приоритетов аппаратных прерываний в комплексе с назначением приоритетов выполняемых задач позволяет реализовать достаточно гибкую и эффективную многозадачную среду на программно-аппаратном уровне. Так, чистое время переключения задач сравнимо с временем вызова "пустой" процедуры языка С. Однако, следует иметь в виду, что большое количество задач в системе может привести к увеличению накладных расходов на манипуляции с каруселями.
Прерывания играют важнейшую роль в системах реального времени. Аппаратное прерывание, вызванное поступлением электрического импульса на один из специализированных входов контроллера является сигналом к немедленным действиям, которые должна выполнить система в ответ на это событие. Ядро 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. На каждом тике таймера принимается решение какая из задач будет выполняться в настоящий момент. Каждый раз предпочтение отдаётся наиболее приоритетной из активных задач. В то же время не следует путать приоритеты задач и приоритеты прерываний. Во время исполнения даже самой приоритетной задачи может возникнуть аппаратное прерывание, обработчик которого прервёт текущую задачу.
В зависимости от модели процессора в нём может быть реализовано от 3 до 6 аппаратных таймеров. В MULTEX-ARM принято распределение таймеров, описанное соответствующей группе макросов. Как правило таймеры TIMER_0 и TIMER_1 заняты системой. Остальные таймеры могут быть задействованы под конкретный проект.
Системный таймер (TIMER_1) используется ядром MULTEX-ARM для обеспечения синхронизации внутренних функций, регистрации таймаутов и организации режима карусельного планирования. Частота системного таймера устанавливается при инициализации ядра равной 1000Гц. Однако она может быть изменена пользовательской задачей на любое целое значение из диапазона 20Гц-1000 Гц. Так как все временные интервалы (таймауты или задержки) задаются в тиках таймера, то при использовании констант они будут изменяться при перестройке частоты таймера. Рекомендуется в значение задержки включать функцию опроса частоты таймера, для получения задержек, независящих от настройки таймера.
Сигналы — это ограниченная форма межзадачного взаимодействия. Сигналом называется асинхронное уведомление, отосланное задаче для того, чтобы сообщить ей о каком-то событии.
В библиотеках ядра MULTEX-ARM имеются средства для поддержки сигналов, совместимых с UNIX-подобными операционными системами. Стандарт Cи определяет всего шесть сигналов, которые могут быть обработаны. Все они описаны в группе Стандартные для Си сигналы файла signal.h.
Кроме того, есть сигналы, которые не могут быть обработаны, пойманы или игнорированы, такие как SIGKILL и SIGSTOP.
Семафоры в MULTEX-ARM – это основной механизм синхронизации задач в реальном времени и организации взаимоисключающего доступа задач к общим ресурсам. Выполнение задачи может быть синхронизовано с помощью семафора как с другой задачей, так и с обработчиком прерывания.
Ядро MULTEX-ARM поддерживает три типа семафоров:
Для семафоров MULTEX-ARM реализован единый универсальный интерфейс управления. Функции управления семафорами могут применяться к семафору любого типа, различаются только функции создания семафоров. Функция создания семафора возвращает идентификатор семафора, который используется для ссылок на этот семафор из других функций. При создании семафора указывается тип очереди, в которую будут выстраиваться задачи, ждущие у этого семафора. Очередь может быть двух типов: в порядке приоритета задач (опция SEM_Q_PRIORITY) или в порядке поступления (SEM_Q_FIFO).
Очереди сообщений – удобный механизм межзадачного взаимодействия. С помощью очередей сообщений задачи могут обмениваться данными между собой и с обработчиками прерываний, не заботясь о синхронизации моментов выдачи и получения данных.
Передаваемые через очереди сообщений данные должны быть объединены в общую группу – сообщение. При этом структура сообщения может быть произвольной, достаточно только, чтобы одно сообщение занимало непрерывный участок памяти, имело предсказуемый размер и его структура была известна как передающей, так и принимающей задаче. Очередь автоматически буферизует заданное при ее создании количество сообщений, поэтому, если принимающая задача вовремя не опросит очередь, то сообщение не будет потеряно. Если задача пытается получить данные из пустой очереди, то она может быть задержана до появления в очереди сообщения. Впрочем, задача может ждать сообщение заданное время или вовсе не ждать. Таким образом, очередь сообщений выполняет не только функции транспортировки данных, но и функции синхронизации задач в реальном времени. И помещать сообщения в очередь, и извлекать их из нее может одновременно несколько задач. Внутренние механизмы очередей обеспечивают корректный множественный доступ к ее данным. Для ссылок на очередь из разных задач используется идентификатор очереди MSG_Q_ID, возвращаемый при ее создании. Помещать сообщения в очередь задача может двумя способами:
Таким образом, обеспечивается возможность передачи двух типов сообщений – обычных и внеочередных. Задачи, ожидающие получения сообщений из очереди, могут получать данные в порядке их обращений к очереди, или в соответствии с их приоритетами. Режим задается при создании очереди опциями MSG_Q_FIFO и MSG_Q_PRIORITY соответственно.
Потоком в MULTEX-ARM принято называть последовательность команд выполняемую одним ядром процессора в течение некоторого времени.
Многопоточность — свойство ОС предоставлять задаче несколько физических ядер процессора для параллельного выполнения независимых частей программы.
Работа всех задач операционной системы выполняется на первом ядре процессора. Здесь же выполняется большая часть задач пользователя. Для некоторых моделей процессоров первое ядро является единственным. Однако, в многоядерных процессорах отдельные процедуры прикладной программы могут быть выполнены на дополнительных ядрах процессора. Это позволяет увеличить вычислительные мощности, предоставляемые программе.
Процедуры, выполняемые на отдельном ядре, выполняются параллельно с прочими задачами MULTEX-ARM. Это делает их похожими на обычные задачи. Тем не менее, применение таких процедур имеет ряд отличий:
Использование дополнительных ядер позволяет разбить задачу вычисления на несколько параллельных потоков. При этом каждый поток вычисления будет выполняться на отдельном ядре процессора. Такой подход позволяет в разы сократить общее время обработки всех имеющихся наборов данных.
Процедура запущенная в отдельном потоке (на отдельном ядре) не прерывается другими задачами. Это относится даже к задачам с приоритетом выше задачи запустившей отдельный поток. Таким свойством стоит пользоваться и выносить процедуры вычислений из низкоприоритетных задач в отдельные потоки.
Ниже приведён пример запуска некой функции calcFunction() в двух потоках. Сначала запускается обработка одного из наборов данных на дополнительном ядре. Далее первое ядро занимается обработкой другого набора данных. Для синхронизации окончания работы двух потоков используется функция ожидания waitCore2Proc(). После окончания ожидания можно использовать результаты обработки обоих потоков.
Ядро MULTEX-ARM поддерживает драйверы символьных (последовательного доступа) и блочных устройств ввода / вывода. Используя простые соглашения, пользователь может создавать свои драйверы символьных устройств и подключать их в систему самостоятельно. При этом устройство становится доступным как по его дескриптору – небольшому целому числу, так и по символьному имени, задаваемому устройству при регистрации его драйвера в системе. Устройства с дескрипторами 0, 1 и 2 являются стандартными устройствами ввода, вывода и вывода ошибок (STDIN, STDOUT и STDERR). Каждая задача в своем блоке управления TCB имеет соответствующие поля – s_in, s_out и s_err. При создании корневой задачи в эти поля заносятся значения STDIN, STDOUT и STDERR. Функции scanf() , printf() и printerr() осуществляют ввод-вывод на эти устройства. В таблице дескрипторов для стандартных устройств указываются ссылки на их адреса, поэтому возможно перенаправление ввода / вывода как глобально, для всех задач, так и отдельно, для конкретной задачи. В базовой системе ввода / вывода MULTEX-ARM используются две таблицы:
Эти таблицы создаются функцией kernelInit(), причем размеры таблиц задаются следующим образом: максимальное число драйверов в системе — 100, а максимальное число открытых файлов — 256.
В ядре MULTEX-ARM поддерживается работа с блочными устройствами, такими как SD карты, USB флэш накопители, SATA устройства. Эти устройства оперируют данными, как блоками фиксированной длины, отсюда и название. Драйверы таких устройств должны иметь как минимум две обязательные функции:
При создании блочного устройства его драйвер заносит указатели на эти функции в специальную структуру BLK_DEV. Так, например, при создании устройства типа MMC в ядре MULTEX-ARM выполняются следующие действия:
Далее, для того, чтобы получить доступ к файлам, хранящимся на устройстве, требуется создать еще одну специальную структуру — файловую систему. В ядре MULTEX-ARM создается такая структура — файловая система MSDOS FAT12 / FAT16 / FAT32. Файловые системы создаются вызовом процедуры iosDrvInstall() точно так же, как создаются символьные устройства, описанные ранее. Дескриптор файловой системы FAT12 / FAT16 / FAT32 уже создан ядром при запуске и доступна на чтение через метод dosDriverId().
Чтобы привязать конкретное блочное устройство к файловой системе в MULTEX-ARM достаточно вызвать функцию монтирования mountTypedVolume(). Для упрощения подключения определённых типов дисков созданы специальные функции:
Для получения метки тома, полученной при подключении можно воспользоваться функциями mmcLabelGet() и sataLabelGet(). Посмотреть список всех подключенных устройств можно в консоли с помощью функции iosDevShow(). Всеми зарегистрированными в системе дисками можно пользоваться с помощью функций open(), read() и write(). Ниже приведён пример кода работы с диском SATA:
Ядро MULTEX-ARM включает комплекс процедур, обеспечивающих работу с динамически выделяемыми блоками памяти (Heap-memory). Этот комплекс, называемый диспетчером динамической памяти, позволяет задачам ядра и пользовательским задачам выделять для собственных нужд блоки памяти требуемого размера и освобождать их по мере надобности. Функции диспетчера памяти используются ядром MULTEX-ARM при создании задач, семафоров и очередей.
Особенностью реализации диспетчера памяти в MULTEX-ARM является принцип выделения блоков памяти от старших адресов к младшим. При этом наибольший свободный сегмент памяти всегда оказывается первым в цепочке. Это очень важно для повышения быстродействия системы. Диспетчер обеспечивает защиту от сегментации памяти – при высвобождении блоков производится слияние смежных свободных блоков.
Для обеспечения защиты от множественного доступа к диспетчеру памяти принято наиболее простое и эффективное решение:
Встроенная КЭШ-память процессора обеспечивает существенное повышение быстродействия вычислительной системы. Ядро MULTEX при запуске размещает в памяти таблицу виртуализации памяти и активирует MMU (модуль управления памятью) в соответствии с этой таблицей. При этом для каждой страницы размером в 1М память может быть настроена как некэшируемая, кэшируемая с записью, или кэшируемая с отложенной записью. Для этого в составе BSP для конкретного модуля назначается специальная структура SYS_MEM_MAP, описывающая то, как нужно настраивать память. Так как MULTEX работает с использованием кэш, то в некоторых случаях возможно рассогласование между содержимым памяти и данными в кэш. Это бывает, например, когда какой-либо блок заносит данные в память прямым доступом.
В ядре MULTEX-ARM содержится библиотека стандартной поддержки нелокальных переходов setjmp. Библиотека объявляет тип данных jmp_buf, который является массивом и который может использоваться для сохранения и восстановления контекста выполнения программы. Для установки точки сохранения служит функция setjmp(). Вернуться в точку сохранения можно с помощью функции longjmp(). Для сохранения и восстановления контекста при переключении между задачами служат функции setexit() и exit().
Часто пользователю приходится конфигурировать конкретную систему, целевое ПО, работающее в среде MULTEX-ARM, производя его настройку с помощью каких-либо переменных. Эти переменные определяют общее поведение системы. Их значение не должно теряться при отключении питания, а напротив — при включении питания и запуске системы конфигурационные переменные должны принимать те значения, которые были заданы при конфигурировании. С другой стороны, при проведении программных настроек пользователь должен иметь возможность изменять значения каких-либо конфигурационных переменных таким образом, чтобы после повторных включений системы вновь присвоенные значения не терялись. Для этих целей в MULTEX-ARM разработан единый механизм хранения (и модификации при необходимости) данных в специальных ini–файлах, по аналогии с тем, как это было сделано в ранних версиях ОС Windows. Ini-файл - это обыкновенный текстовый файл, который может быть подготовлен как средствами MULTEX-ARM, так и обыкновенным текстовым редактором, а затем записан на диск, доступный операционной системе. Для работы с такими файлами разработана специальная библиотека, входящая в состав ядра MULTEX-ARM.
Эта библиотека позволяет пользователю отыскивать в ini-файле значения переменных по их именам. При этом значения могут представляться как в виде текстовых строк, так и в десятичном виде, либо в виде булевых значений (типа да-нет). Кроме этого, ряд функций позволяет записывать новые значения в этот файл по имени переменной. После закрытия файла на диске MULTEX-ARM будет создан новый ini-файл со всеми внесенными изменениями и дополнениями. Старый ini-файл будет при этом удален.
В ini-файле переменные сгруппированы в секции. Каждая секция также имеет уникальное имя. Для того, чтобы отыскать, или записать требуемое значение, необходимо задать имя секции и имя переменной. При чтении также задается значение по умолчанию, а при записи — то значение, которое требуется присвоить.
Опорная частота 24 МГц, получаемая с помощью внутренней схемы генерации и стабилизируемая с помощью внешнего кварцевого резонатора, повышается внутри процессора и далее распределяется по внутренним шинам и аппаратным модулям. Как именно происходит распределение частот зависит от конфигурации конкретного процессора. Управление умножением и делением частот и распределение их между шинами и модулями осуществляется с помощью регистров конфигурации PLL.
Функции по настройке PLL регистров полностью берёт на себя операционная система. Однако при проектировании новых драйверов устройств, а так же для проверки правильной работы уже подключенных аппаратных модулей имеет смысл воспользоваться функциями, описанными в файле pll.h. Для вывода в консоль таблицы используемых тактовых частот можно воспользоваться функцией pll(). Для получения значения частоты конкретного аппаратного модуля или шины в программе можно воспользоваться функциями pllGet(), pllAhbGet() и им подобными. Следует учесть, что драйверы устройств могут изменять частоты общих шин, поэтому для получения актуальных значений следует перед использованием get-функций вызвать функцию сбора данных pllUpdate().