Идентификаторы задач (Task Identifiers)
Необходимо уметь идентифицировать каждую задачу в системе. Это требование важно и для других объектов ядра, но в задачах есть некоторые нюансы, которые соответствуют теме данной статьи.
Разработчики ОСРВ используют разные подходы к идентификации задач, но можно выделить четыре общие стратегии:
- Задача идентифицируется с помощью указателя (pointer) на свой «блок управления» (“control block”). Указатели всегда уникальны, а также удобны в использовании, поскольку доступ к блоку управления требуется при многих вызовах API. Это подразумевает, что все данные о задаче хранятся в оперативной памяти (RAM), что может быть неэффективно. Указатель обычно занимает около 32 бит памяти.
- Задача может быть определена с помощью произвольного «порядкового числа» (index number). Это значение может пригодиться при предоставлении доступа к записям в определенных таблицах. Такой идентификатор может занимать восемь или меньше бит памяти, в зависимости от ограничений по количеству задач, которые поддерживаются ОСРВ.
- Некоторые ОСРВ разрешают только одну задачу на каждый уровень приоритета и, следовательно, используют приоритет для уникальной идентификации задачи. Это означает, что приоритет задачи не может быть изменен. Этот подход является разновидностью предыдущего подхода.
- Задачи могут иметь имена, которые являются символьными строками (character string). Это может быть полезно для отладки, но вряд ли будет эффективным средством уникальной идентификации задачи. ОСРВ, которые поддерживают именование задач, как правило, имеют дополнительный идентификатор (например, указатель), который используется вызовами API и т. д. Для большинства встраиваемых систем текстовые имена — это накладные расходы; хороший отладчик позволяет называть их локально на хосте.
Предыдущие статьи серии:
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.
Переключение контекста
Переключение контекста — процедура передачи управления от одной задачи к другой. Эту тему стоит изучить поближе, поскольку то, как работает переключение контекста, является фундаментальным принципом работы ОСРВ.
Что такое задача?
Мы знаем, что задача — это квазинезависимая программа, которая делит процессорное время с рядом других задач под управлением ОСРВ. Но необходимо подумать о том, что действительно характеризует задачу.
Набор регистров
Задача — это, в конечном счете, уникальный набор значений регистра процессора. Они либо загружаются в регистры процессора (то есть задача является текущей), либо хранятся где-то до запланированного времени выполнения. В идеальном мире у центрального процессора было бы несколько наборов регистров, и каждый мог быть назначен для отдельной задачи. Подобное было реализовано для особых случаев. Много лет назад в серии TI 9900 от компании Texas Instruments было множество наборов регистров для каждого задания, но они были реализованы в основной памяти, что ограничивало производительность. Архитектура SPARC (раньше применялась в десктопных системах Unix) поддерживает множество наборов регистров в «кольцах доступа» (ring structure), но количество наборов все равно ограничено.
Внутренние данные
У задачи, вероятно, будет свой собственный стек, размер которого может быть задан отдельно для каждой задачи или может быть глобальным параметром для всех задач в системе. Это, наряду с регистрами, обеспечивает хранение данных конкретных задач. Могут быть другие области памяти для хранения данных, предназначенных для конкретной задачи.
Ресурсы общего пользования
Практически любые ресурсы могут быть разделены между задачами. Код может быть общим: либо определенные функции, либо целиком код задачи. Необходимо убедиться, что код является реентерабельным, в первую очередь, не должны использоваться статические переменные (указанные как статические или просто вне функции). Будьте осторожны со стандартными библиотечными модулями, которые не предназначены для встроенного использования; в них обычно много нереентерабельных функций.
Также возможно совместное использование данных, но необходимо обеспечить тщательный контроль доступа к ним. В идеале, только одна задача является «владельцем» данных в любой момент времени.
Как сохранить контекст
Когда задача перепланируется (то есть перестает быть текущей), ее набор регистров необходимо где-то сохранить. Есть как минимум две возможности:
- Регистры могут храниться в специальной таблице для задач. Могут быть частью блока управления задачей (англ. Task Control Block, TCB). Размер является предсказуемым и постоянным значением (для конкретной архитектуры ЦП).
- Регистры могут быть помещены в стек задачи. Это требует распределения достаточного дополнительного объема стека и обеспечения хранения указателя (возможно, в TCB).
Выбор механизма зависит от особенностей конкретной ОСРВ и от целевого процессора. Некоторые (обычно 32-битные) устройства могут эффективно обращаться к стеку; остальные (например, 8-битные) могут быть более оптимальны при работе с таблицами.
Динамическое создание задач
Основной аспект архитектуры ОСРВ заключается в том, что ОСРВ является либо «статической», либо «динамической».
При использовании статической ОСРВ все определяется во время сборки приложения, в частности, количество задач в системе. Это логичный выход для встраиваемых приложений, которые обычно имеют ограниченную функциональность.
Динамическая ОСРВ запускает одну задачу (которая может быть специализированной «основной» задачей), а также создает и удаляет другие задачи по мере необходимости. Это позволяет системе адаптироваться к изменяющимся требованиям и является более близким аналогом десктопной системы, которая ведет себя именно таким образом. Статический/динамический вид также применяется к другим объектам ядра.
Требование к динамическому созданию задач
Эта возможность входит в большинство коммерческих ОСРВ. Однако только небольшая часть приложений действительно нуждается в динамическом режиме работы. Очень часто система запускается, создает все необходимые задачи (и другие объекты), а затем никогда больше ничего не создает и не уничтожает во время работы кода приложения. Возможность создания динамических задач стало формальностью. Один поставщик внедрил ее, все остальные последовали его примеру.
Примечательно, что стандарт OSEK / VDX требует статической архитектуры, даже при том, что это может относиться к довольно сложным приложениям. Результатом этих требований является невозможность реализовать OSEK / VDX с помощью враппера (wrapper), промежуточного слоя поверх обычной (динамической) ОСРВ.
Подводные камни динамического создания задач
Существует несколько проблем, связанных с динамическим режимом работы, которые могут вызывать беспокойство.
Во-первых, усложняется система, а это означает, что для структур данных, описывающих задачи (TCB), необходима дополнительная информация. Как правило, они реализованы в виде двунаправленных списков, что ведет к издержкам, связанным с объемом памяти.
Все данные, описывающие задачу, должны храниться в ОЗУ. Это неэффективно, так как большая часть из них может быть просто постоянными элементами данных, скопированными из ПЗУ. Кроме того, на процессорах более низкого уровня (микроконтроллерах) может не доставать оперативной памяти.
Вероятно, наибольшее беспокойство вызывает возможность непредсказуемого дефицита ресурсов, что может приводить к невозможности создания новых объектов. Поскольку суть системы реального времени заключается в ее предсказуемости, это неприемлемо. Таким образом, необходимо проявлять осторожность при использовании создания динамических задач (и других объектов).
Прерывания
Вполне возможно, что встраиваемая система реального времени может быть реализована без использования прерываний, но это нетипично.
Прерывания и ядро
Когда используется ОСРВ, обработчик прерываний (ISR) делается как можно легче, чтобы «красть» минимальное количество процессорного времени у запланированных задач. Часто устройство может просто обслуживаться, а любая требуемая задача будет поставлена в очередь для обработки. Помимо этого, трудно говорить в общем о прерываниях и их взаимодействии с ядрами, просто потому что они сильно варьируются. С одной стороны, разработчик ОСРВ может сделать так, что прерывания вообще не будут относиться к ядру, и программисту придется следить за тем, чтобы не слишком загружать планировщик задач, используя много процессорного времени в ISR. С другой стороны, ОСРВ может полностью контролировать всю подсистему прерываний. Ни один из описанных подходов не является правильным или неправильным, они просто разные.
Сохранение контекста
ISR всегда нужно сохранять «контекст», чтобы прерываемый код не подвергался воздействию вычислений ISR. В системе, реализованной без ОСРВ, это просто вопрос сохранения любых регистров, используемых ISR (обычно в стеке), и их восстановления перед возвратом. Некоторые процессоры имеют выделенный стек ISR, другие просто используют тот же стек, что и код приложения.
Когда используется ОСРВ, подход может быть точно таким же. Таким же образом стек, используемый ISR, может быть «заимствован» из текущей задачи, или это может быть другой стек, выделенный для прерываний. Некоторые ядра реализуют эту возможность, даже если сам процессор не поддерживает стек прерываний. Ситуация усложняется, если ISR делает вызов API, который влияет на планировщик задач. Это может привести к тому, что прерывание вернется к другой задаче от той, которая была запущена, когда произошло прерывание.
Прерывания и планировщик
Существует несколько обстоятельств, при которых код выполнения ISR может произвести возврат к другой задаче:
- ISR может присвоить более высокий приоритет уже выполненной задаче, а не текущей, если используется планировщик задач по приоритетам.
- ISR может приостановить текущую задачу.
- С помощью планировщика временных интервалов (англ. Time-Slice, TS) обработчик прерываний системного таймера будет управлять временными промежутками и может при необходимости вызвать планировщика.
Тактовый таймер (Tick Clock)
Во встраиваемых системах часто встречается применение периодического «тактового таймера» (кванта времени). В некоторых ОСРВ он является обязательным компонентом. Как правило, наличие тактового таймера является опциональным, а его отсутствие просто исключает доступность определенных служб. Обработчик прерываний таймера обычно предоставляет четыре функциональные возможности:
- Если используется планировщик временных интервалов, обработчик прерываний таймера будет управлять счетчиком времени и планировать новую задачу каждый раз, когда время истекает.
- Обеспечивает поддержку системного времени. В основном это 32-битная переменная, которая увеличивается с помощью таймера и может быть предустановлена или запрошена задачами.
- Если ОСРВ предоставляет приложениям работу с таймерами, то она будет поддерживаться обработчиком прерываний таймера, который будет отвечать за истечение срока и перепланирование.
- Если ОСРВ поддерживает таймауты в блокирующих вызовах API или задачи могут находиться в состоянии «сна», то эти временные промежутки будут поддерживаться обработчиком прерываний таймера.
Когда мы работали над нашей собственной операционной системой реального времени ОСРВ МАКС (ранее уже публиковал статьи о ней), наша команда «наткнулась» на блог Колина Уоллса (Colin Walls), эксперта в области микроэлектроники и встроенного ПО компании Mentor Graphics. Статьи показались интересными, переводили их для себя, но, чтобы не «писать в стол» решили опубликовать. Надеюсь, они будут вам также полезны. Если так, то дальше планируем опубликовать все переведенные статьи серии.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: blogs.mentor.com/colinwalls, e-mail: colin_walls@mentor.com
Читайте первую, вторую и третью статьи цикла, опубликованные ранее.