В предыдущих статьях мы обсуждали функциональность ядра с точки зрения выполняемых задач и взаимодействия между ними. В этой статье мы рассмотрим, что еще может делать ядро, что в значительной степени проявляется в ряде других доступных вызовов API. Мы также ответим на вопрос, что превращает ядро в операционную систему?
Помимо планирования задач и взаимодействия между ними, ОСРВ будет включать функциональность (вызовы API) для управления задачами различными способами. Рассмотрим некоторые возможностей.
В «динамической» ОСРВ есть вызовы функций, позволяющие создавать задачи (и другие объекты ОСРВ), когда они требуются. Такие вызовы включают широкий диапазон параметров, которые определяют задачу, например, точка входа, размер стека и приоритет. Соответствующий API-вызов удаления задачи позволяет освобождать ресурсы после того, как задача завершена.
В «статической» ОСРВ определяющие параметры задачи настраиваются в своего рода конфигурационном файле во время сборки.
Приостановка и возобновление задачи
Как мы увидели, большинство ОСРВ имеют концепцию «приостановленного» состояния задач. Этого можно добиться различными способами. Один из них – явный вызов API-функции «Приостановить задачу». Она может быть вызвана самой собой или другой задачей. Соответствующий вызов «Возобновить задачу» позволяет задаче снова стать в очередь на планирование.
Состояние сна задачи
Для системы реального времени контроль времени является важным требованием и может принимать различные формы. Простой вид – возможность задачи «засыпать», то есть задача приостанавливается на определенный период времени. Когда время истекает, задача «просыпается» и снова ставится в очередь на планирование. Для этой цели обычно будет доступен вызов API. Конечно, эта функциональная возможность зависит от доступности таймера.
При использовании планировщика Round Robin («карусель») задача может отказаться от управления процессором ради следующей задачи в цепочке. Для этого будет доступна API-функция «Освобождение задачи». Задача не приостанавливается, она будет доступна для планирования, когда наступит ее очередь. При использовании планировщика Time slice («временных интервалов») возможна ситуация, когда задача может освободить часть своего временного интервала, если у неё нет важной работы для немедленного выполнения. Освобождение задачи не имеет никакого логического значения при работе планировщиков Run to completion («выполнение до завершения») или Priority («планирование по приоритетам»).
В предыдущей статье мы выяснили, что помимо состояний «Ready» («Готова к продолжению») или «Suspended» («Приостановлена»), ОСРВ может поддерживать и другие состояния задач. Задача может быть «Finished», что означает, что ее основная функция просто вышла: не требуется специальный API-вызов. Задача может быть «Terminated», что означает, что она недоступна для планирования и должна быть сброшена, чтобы снова стать доступной для запуска, см. «Сброс задачи» ниже. Для этого требуется специальный API-вызов. Доступность этих дополнительных состояний задач, используемая терминология и их точные определения будут отличаться в зависимости от ОСРВ.
Многие ОСРВ предлагают вызов API-функции «Сброс задачи», которая позволяет вернуть задачу в исходное состояние. Она может находиться в приостановленном состоянии и потребовать выполнения функции «Возобновить задачу», чтобы стать в очередь на планирование.
В «динамической» ОСРВ API-вызовы могут быть доступны для настройки нескольких параметров задачи во время выполнения. Примеры включают приоритет и продолжительность временного интервала.
Во многих приложениях важно, чтобы программа могла динамически захватывать некоторую память, когда это требуется, и освобождать ее, когда она больше не нужна. Это же происходит во встроенном программном обеспечении. Однако обычные подходы подвержены проблемам, которые в десктопных приложениях маловероятны или неудобны, но для встроенной системы они могут быть катастрофическими. Тем не менее есть способы внедрить подобные сервисы, даже в статичную ОСРВ.
В десктопной программе на языке C функция может вызывать malloc(), указывая, сколько требуется памяти, и получать обратно указатель на область хранения. Воспользовавшись памятью, она может быть освобождена при вызове free(). Память выделяется из области, которая называется «heap». Проблема данного подхода заключается в том, что с нескоординированной последовательностью вызовов этих функций область «heap» может легко стать фрагментированной, и тогда распределение памяти будет терпеть неудачу, даже если доступно достаточно памяти, т.к. смежные области недостаточно велики. В некоторых системах (например, Java и Visual Basic) для осуществления дефрагментации используются сложные схемы «сборки мусора». Проблема в том, что эти схемы могут привести к значительным непредсказуемым задержкам во времени исполнения и необходимости использовать косвенные указатели (что не работает в языке C).
Если malloc() и free() были реализованы реентерабельным способом (обычно не так) и используются задачами ОСРВ, фрагментация будет происходить очень быстро, а системный сбой будет почти неизбежен. В C ++ существуют операторы new и delete, которые в целом выполняют те же функции, что и malloc(), и free(). Они подвержены тем же ограничениям и проблемам.
Чтобы обеспечить систему реального времени динамически доступной памятью, можно использовать блочный подход к управлению памятью. Такие блоки обычно называют «разделами» (partitions); разделы могут быть выделены из «пула разделов».
Пул разделов содержит определенное количество блоков, каждый из которых имеет одинаковый размер. Количество и размер блоков в разделе определяются при создании пула разделов. Это может быть динамически, если сама система это допускает, или статически во время сборки. Как правило, приложение может включать несколько пулов разделов, предлагающих блоки разных размеров.
Если задаче необходима память, она вызывает API, запрашивающий блок из определенного пула. Если этот вызов будет успешным, задача получит указатель на выделенный блок. Если вызов не удается, т.к. в указанном пуле нет доступных разделов, задача может получить ответ об ошибке. В качестве альтернативы задача может быть заблокирована (приостановлена), пока другая задача не освободит блок в разделе.
Обычно задача просто передает указатель на блок памяти в любой код, который использует данный блок. Это приводит к возникновению проблемы, когда блок больше не нужен. Если код имеет только указатель на блок, как он может сообщить ОСРВ через вызов API, из какого пула разделов он хочет освободить память? Ответ в том, что большинство ОСРВ поддерживают дополнительные данные в выделенном блоке (как правило, отрицательное смещение от указателя), которые предоставляют требуемую информацию. Таким образом, для вызова API для освобождения блока требуется только его адрес.
В следующей статье будет больше информации о разделах памяти.
Функциональность, связанная с использованием и контролем времени, скорее всего, будет доступна в ОС реального времени. Возможности будут отличаться в зависимости от ОСРВ, но мы рассмотрим общедоступные. В любом случае таймер реального времени — это обязательный элемент для функционирования любого из этих сервисов.
Простое системное время, или «тактовый таймер», доступно почти всегда. Это просто счетчик (как правило, 32 бита), который увеличивается с помощью процедуры обслуживания прерываний в режиме реального времени и может устанавливаться и считываться через вызовы API.
Обычно ОСРВ разрешает блокирующие вызовы API, то есть вызывающая задача приостанавливается (блокируется), пока запрашиваемый сервис не будет предоставлен. Обычно эта блокировка является неопределенной, но некоторые ОСРВ предлагают тайм-аут, за время выполнения которого вызов возвращается, когда истекает время ожидания, если сервис продолжает оставаться недоступным. Таймауты вызовов API поддерживаются не всеми ОСРВ.
Обычно задачи имеют возможность приостанавливать себя на фиксированный период времени. Это обсуждалось ранее в разделе «Управление задачами».
Чтобы программные задачи выполняли функции отсчета времени, большинство ОСРВ предлагают объекты таймера. Это независимые таймеры, обновляемые обработчиком прерываний таймера реального времени, которые могут контролироваться вызовами API. Такие вызовы настраивают, контролируют и отслеживают работу таймера. Как правило, они могут быть установлены для однократного срабатывания или автоматического перезапуска. Также обычно поддерживается подпрограмма истечения срока действия, функция, которая выполняется каждый раз, когда таймер завершает цикл. В следующей статье будет больше информации о программных таймерах и описание их реализации.
Степень, в которой ОСРВ связаны с прерываниями и вводом/выводом, очень различна. Аналогично, некоторые ОСРВ имеют очень четкую структуру для драйверов устройств, что может добавить проблем при выборе конкретного продукта.
Примером такого вызова API является процедура пробуждения задачи с более высоким приоритетом, чем та, которая был запущена, когда произошло прерывание.
Некоторые ОСРВ полностью контролируют все прерывания. Доступна серия вызовов API, позволяющих «регистрировать» ISR программ. Такой подход позволяет планировщику точно определять, когда разрешены прерывания, и облегчает использование большинства вызовов API из ISR.
Например, в Nucleus RTOS реализована концепция «низкоприоритетных» и «высокоприоритетных» обработчиков прерываний, которая обеспечивает надежное управление прерываниями без лишних накладных расходов (то есть увеличения задержки прерывания).
Другие ОСРВ могут использовать по отношению к прерываниям автоматический режим «hands off», который предоставляет разработчикам больше возможностей для обеспечения гарантии, что обработчики прерываний правильно работают. Как правило, дополнительные префикс (пролог) и суффикс (эпилог) ISR предоставляются для защиты вызовов API, сделанных в ней. Nucleus SE применяет легковесные средства для обработки прерываний, которые будут описаны в следующей статье.
Большинство ОСРВ определяют структуру драйвера устройства. Детали могут отличаться в зависимости от ОСРВ, но драйвер обычно состоит из двух взаимодействующих компонентов: встроенного кода (вызовы API) и ISR. Обычно для управления и регистрации драйверов будут доступны другие вызовы API.
В настоящее время большинство ОСРВ на рынке не заботятся о вводе/выводе более высокого уровня, но некоторые из них определяют поток ввода/вывода, который в основном устанавливает соединение между соответствующими драйверами устройств и стандартными функциями языка C, такими как printf(). Исторически сложилось так, что ОСРВ часто поддерживали «консоль», интерфейс пользователя к ОСРВ через последовательный канал. В основном это применялось для диагностики и отладки. Использование современных отладчиков, которые поддерживают отладку приложений с ОСРВ, устраняет потребность в таких объектах.
Обычно от ОСРВ требуется максимальная производительность при минимальном объеме занимаемой памяти. Следовательно, проверка целостности не является самой приоритетной задачей. При помощи современных технологий отладки, учитывающих особенности ОСРВ, большую часть проверок можно выполнить вне самой ОСРВ.
Вызовы API могут иметь множество сложных параметров. Это может привести к возникновению ошибок. Многие ОСРВ предоставляют проверку параметров среды выполнения с возвратом кода ошибки в случае некорректного параметра. Так как для этого необходим дополнительный код, а сами проверки негативно влияют на производительность, проверку параметров лучше выполнять во время сборки или конфигурирования.
Для большинства типов планировщика (кроме Run to Completion) каждая задача имеет собственный стек, размер которого определяется индивидуально. В некоторых ОСРВ ядро имеет отдельный стек, в других стек задач «берется в займы» во время вызова API. Очевидно, целостность стека важна для общей надежности системы. Следовательно, ОСРВ часто предлагают инструменты для проверки целостности стеков во время выполнения. Существует несколько вариантов:
Несмотря на то, что данная функция напрямую не поддерживается в ОСРВ, под проверку целостности всей системы может быть выделена задача приложения. Такая задача может отвечать за сброс сторожевого таймера. Задача может принимать периодические входные данные (например, параметры сигнала) от каждой критической задачи. Сброс сторожевого таймера (который не даст системе перезагрузиться) будет выполнен только после того, как придут данные от всех задач.
ОСРВ – это нечто большее, чем просто ядро, на котором мы были до сих пор сосредоточены. Этим настольная операционная система значительно отличается от встраиваемой ОСРВ. Обычно в настольной ОС все дополнительные компоненты идут в комплекте или могут быть установлены (все настольные ПК имеют графический пользовательский интерфейс, и лишь немногие из них не имеют доступа к сети). Настольный ПК не имеет настоящих ограничений в ресурсах: в наличии всегда есть свободная память, пространство на жестком диске и неиспользуемый ресурс центрального процессора. В мире встраиваемых систем с ограниченными ресурсами такие дополнительные компоненты как видеокарты, сетевые компоненты и файловые системы могут быть необходимы, но они должны быть отключаемыми и масштабируемыми для минимизации занимаемого объема памяти.
Большинство встраиваемых систем так или иначе имеют отношение к сетям. Таким образом, вполне ожидаемо, что существует значительный интерес к сетевым решениям для встраиваемых систем, благодаря которому на рынке имеется большое количество продуктов.
TCP/IP – стандартный протокол, широко используется и является очевидным выбором для множества приложений. Обычно TCP/IP используется для протокола Ethernet (IEEE802.3), который в среднем обеспечивает скорость 10 Мб/с. Сегодня и 100 Мб/с довольно распространены, а на подходе 1 Гб/с. Кроме того, TCP/IP можно использовать и для других протоколов. Например, протокол PPP (Point-to-Point Protocol) – реализация TCP/IP для последовательной передачи данных, которая была адаптирована под широкополосные Интернет-соединения.
До недавнего времени использовалась версия v4 протокола IP (IPv4). Однако она устаревает, так как заканчиваются свободные адреса. Решением является IPv6, значительно увеличивающий количество возможных адресов и предоставляющий более эффективные инструменты для сопровождения и безопасности. IPv6 широко доступен и используется в оборудовании множества стран, а также военными системами во всем мире.
Альтернативой является протокол пользовательских дейтаграмм (англ. User Datagram Protocol, UDP). Этот протокол используется для достижения максимальной производительности. UDP не обеспечивает такую же надежность и последовательность как TCP, но обладает небольшим весом и высокой эффективностью.
USB – «универсальная последовательная шина» (Universal Serial Bus), широко используется в устройствах для подключения к настольными компьютерам. Обеспечивает очень простой в использовании интерфейс типа «plug & play», который скрывает за собой довольно сложное программное обеспечение. Встраиваемое устройство, которое должно подключаться к ПК, должно быть реализовано в виде USB-функции, что требует определенного набора программных компонентов. Если устройство должно управлять другими устройствами, подключенными через USB (как обычный ПК), ему необходим комплект программных средств типа «хост».
IEEE1394 – другой стандарт последовательных интерфейсов, который используется для быстрой передачи большого объема данных между устройствами (например, для передачи видеоданных), также известен как FireWire и i.Link.
Беспроводные протоколы – удобство и распространенность различных беспроводных технологий среди потребителей привело к высокому спросу на беспроводные возможности во встраиваемых устройствах. Wi-Fi (набор стандартов IEEE802.11) обеспечивает полный набор сетевых возможностей, позволяя реализовывать как одноранговые, так и инфраструктурные топологии на достаточном расстоянии. Интерес к безопасности данных в таких сетях растет, а значит, это должно отразиться на программном обеспечении. Другие радиотехнологии, в частности, Bluetooth и ZigBee, предоставляют двухточечную беспроводную связь малой дальности.
Так как сетевые возможности имеют высокий спрос, существует множество поставщиков, предлагающих свои решения. Перед покупателями встает проблема проверки качества доступных продуктов. В отличие от ядра ОСРВ, полная проверка функционала и производительности стека протокола является непростой задачей. К счастью, для проверки протоколов доступны наборы инструментов (пусть и по значительной цене), а потенциальный покупатель может узнать у поставщика, каким набором они пользовались при проверке.
Графический интерфейс становится все более распространенным среди встраиваемых устройств. Он может представлять из себя очень простой, маленький монохроматический LCD (как на старых телефонах, MP3-плеерах, сигнализациях и т.д.). С другой стороны, ресивер цифрового телевидения может иметь собственный HDTV экран с высоким разрешением. Такой экран требует программной поддержки, которая полностью встроена в ядро ОСРВ.
Так как экран обычно имеет какое-либо устройство ввода, поддержка таких устройств часто включена в графический пакет. Такой пакет может поддерживать указательные устройства (например, мышь), тачскрины, кнопочные панели и полноценные клавиатуры.
Графику можно использовать различными способами. Она может просто обеспечивать вывод информации (например, как электронное табло). Либо дисплей может быть частью графического интерфейса пользователя вместе с меню, окнами, иконками и подобными элементами. В любом случае, требуется довольно специфичный набор программного обеспечения, а графический пакет, поставляемый вместе с ОСРВ, должен обеспечить необходимую гибкость без значительного увеличения объема занимаемой памяти.
Когда встраиваемое приложение должно хранить и обрабатывать значительные объемы данных, очевидно, что имеет смысл организовать эти данные в какую-нибудь файловую систему. Данные могут находиться в RAM, во встроенной флеш-памяти, на флешке, обычном жестком диске или на оптическом диске (CD-ROM или DVD-ROM). Опять же, такая возможность должна иметь полностью встроенную в ОСРВ программную поддержку. Файловая система должна быть тщательно разработана для соответствия требованиям реентерабельности многозадачной системы.
Соответствие стандартам особенно важно для файловых систем. Например, использование формата дисков, совместимого с MS-DOS, позволяет разработчикам использовать хорошо зарекомендовавшую себя архитектуру и предлагает полноценный обмен данными с настольными системами.