Продолжение серии статей Колина Уоллса об ОСРВ, посвященное задачам, переключению контекста и прерывания.
Необходимо уметь идентифицировать каждую задачу в системе. Это требование важно и для других объектов ядра, но в задачах есть некоторые нюансы, которые соответствуют теме данной статьи.
Переключение контекста — процедура передачи управления от одной задачи к другой. Эту тему стоит изучить поближе, поскольку то, как работает переключение контекста, является фундаментальным принципом работы ОСРВ.
Мы знаем, что задача — это квазинезависимая программа, которая делит процессорное время с рядом других задач под управлением ОСРВ. Но необходимо подумать о том, что действительно характеризует задачу.
Задача — это, в конечном счете, уникальный набор значений регистра процессора. Они либо загружаются в регистры процессора (то есть задача является текущей), либо хранятся где-то до запланированного времени выполнения. В идеальном мире у центрального процессора было бы несколько наборов регистров, и каждый мог быть назначен для отдельной задачи. Подобное было реализовано для особых случаев. Много лет назад в серии TI 9900 от компании Texas Instruments было множество наборов регистров для каждого задания, но они были реализованы в основной памяти, что ограничивало производительность. Архитектура SPARC (раньше применялась в десктопных системах Unix) поддерживает множество наборов регистров в «кольцах доступа» (ring structure), но количество наборов все равно ограничено.
У задачи, вероятно, будет свой собственный стек, размер которого может быть задан отдельно для каждой задачи или может быть глобальным параметром для всех задач в системе. Это, наряду с регистрами, обеспечивает хранение данных конкретных задач. Могут быть другие области памяти для хранения данных, предназначенных для конкретной задачи.
Практически любые ресурсы могут быть разделены между задачами. Код может быть общим: либо определенные функции, либо целиком код задачи. Необходимо убедиться, что код является реентерабельным, в первую очередь, не должны использоваться статические переменные (указанные как статические или просто вне функции). Будьте осторожны со стандартными библиотечными модулями, которые не предназначены для встроенного использования; в них обычно много нереентерабельных функций.
Также возможно совместное использование данных, но необходимо обеспечить тщательный контроль доступа к ним. В идеале, только одна задача является «владельцем» данных в любой момент времени.
Когда задача перепланируется (то есть перестает быть текущей), ее набор регистров необходимо где-то сохранить. Есть как минимум две возможности:
Выбор механизма зависит от особенностей конкретной ОСРВ и от целевого процессора. Некоторые (обычно 32-битные) устройства могут эффективно обращаться к стеку; остальные (например, 8-битные) могут быть более оптимальны при работе с таблицами.
Динамическое создание задач
Основной аспект архитектуры ОСРВ заключается в том, что ОСРВ является либо «статической», либо «динамической».
При использовании статической ОСРВ все определяется во время сборки приложения, в частности, количество задач в системе. Это логичный выход для встраиваемых приложений, которые обычно имеют ограниченную функциональность.
Динамическая ОСРВ запускает одну задачу (которая может быть специализированной «основной» задачей), а также создает и удаляет другие задачи по мере необходимости. Это позволяет системе адаптироваться к изменяющимся требованиям и является более близким аналогом десктопной системы, которая ведет себя именно таким образом. Статический/динамический вид также применяется к другим объектам ядра.
Требование к динамическому созданию задач
Эта возможность входит в большинство коммерческих ОСРВ. Однако только небольшая часть приложений действительно нуждается в динамическом режиме работы. Очень часто система запускается, создает все необходимые задачи (и другие объекты), а затем никогда больше ничего не создает и не уничтожает во время работы кода приложения. Возможность создания динамических задач стало формальностью. Один поставщик внедрил ее, все остальные последовали его примеру.
Примечательно, что стандарт OSEK / VDX требует статической архитектуры, даже при том, что это может относиться к довольно сложным приложениям. Результатом этих требований является невозможность реализовать OSEK / VDX с помощью враппера (wrapper), промежуточного слоя поверх обычной (динамической) ОСРВ.
Существует несколько проблем, связанных с динамическим режимом работы, которые могут вызывать беспокойство.
Во-первых, усложняется система, а это означает, что для структур данных, описывающих задачи (TCB), необходима дополнительная информация. Как правило, они реализованы в виде двунаправленных списков, что ведет к издержкам, связанным с объемом памяти. Все данные, описывающие задачу, должны храниться в ОЗУ. Это неэффективно, так как большая часть из них может быть просто постоянными элементами данных, скопированными из ПЗУ. Кроме того, на процессорах более низкого уровня (микроконтроллерах) может не доставать оперативной памяти.
Вероятно, наибольшее беспокойство вызывает возможность непредсказуемого дефицита ресурсов, что может приводить к невозможности создания новых объектов. Поскольку суть системы реального времени заключается в ее предсказуемости, это неприемлемо. Таким образом, необходимо проявлять осторожность при использовании создания динамических задач (и других объектов).
Вполне возможно, что встраиваемая система реального времени может быть реализована без использования прерываний, но это нетипично.
Когда используется ОСРВ, обработчик прерываний (ISR) делается как можно легче, чтобы «красть» минимальное количество процессорного времени у запланированных задач. Часто устройство может просто обслуживаться, а любая требуемая задача будет поставлена в очередь для обработки. Помимо этого, трудно говорить в общем о прерываниях и их взаимодействии с ядрами, просто потому что они сильно варьируются. С одной стороны, разработчик ОСРВ может сделать так, что прерывания вообще не будут относиться к ядру, и программисту придется следить за тем, чтобы не слишком загружать планировщик задач, используя много процессорного времени в ISR. С другой стороны, ОСРВ может полностью контролировать всю подсистему прерываний. Ни один из описанных подходов не является правильным или неправильным, они просто разные.
ISR всегда нужно сохранять «контекст», чтобы прерываемый код не подвергался воздействию вычислений ISR. В системе, реализованной без ОСРВ, это просто вопрос сохранения любых регистров, используемых ISR (обычно в стеке), и их восстановления перед возвратом. Некоторые процессоры имеют выделенный стек ISR, другие просто используют тот же стек, что и код приложения.
Когда используется ОСРВ, подход может быть точно таким же. Таким же образом стек, используемый ISR, может быть «заимствован» из текущей задачи, или это может быть другой стек, выделенный для прерываний. Некоторые ядра реализуют эту возможность, даже если сам процессор не поддерживает стек прерываний. Ситуация усложняется, если ISR делает вызов API, который влияет на планировщик задач. Это может привести к тому, что прерывание вернется к другой задаче от той, которая была запущена, когда произошло прерывание.
Существует несколько обстоятельств, при которых код выполнения ISR может произвести возврат к другой задаче:
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: http://blogs.mentor.com/colinwalls, e-mail: colin_walls@mentor.com