Все современные микропроцессоры и микроконтроллеры содержат какие-либо механизмы прерывания. Эти механизмы необходимы для обеспечения «отзывчивости», требуемой многими приложениями. Безусловно, отзывчивость и предсказуемость являются основной целью при использовании ОСРВ, но при этом они противостоят друг другу. Использование прерываний может нарушить целостность реального времени ОС. Эта проблема и ее решение были затронуты в одной из предыдущих статей (#4 и #6). В этой статье мы рассмотрим стратегию обработки прерываний, используемую в Nucleus SE. Во всех случаях прерывания НЕ управляются Nucleus SE: они обрабатываются при возникновении согласно приоритету и векторам обычным способом. Время их выполнения просто «крадется» из доступного времени в коде основного приложения и планировщика. Очевидно, из этого следует, что все обработчики прерывания должны быть простыми, короткими и быстрыми.
![](https://habrastorage.org/r/w780q1/webt/xf/fd/p4/xffdp4n9qbgy9yoqppflrg0a6as.jpeg)
Nucleus SE предлагает два способа обработки прерываний: «родной» или «штатный» (Native), при котором прерывания не представляют из себя ничего особенного и в какой-то степени имеют ограниченное взаимодействие с ОС (как минимум при использовании планировщика приоритетов), и «управляемый» (Managed), при котором из обработчика прерывания можно обратиться к гораздо большему числу доступных вызовов API.
При помощи макросов входа/выхода обработчик прерывания в Nucleus SE может использоваться как в стандартном, так и в управляемом варианте.
Штатные прерывания Nucleus SE являются стандартным обработчиком прерывания, их можно считать «неуправляемыми». Обычно они используются, когда прерывание может возникать с высокой частотой и требует обработки при низком использовании вычислительных ресурсов. Такой обработчик скорее всего написан на С, так как многие современные встраиваемые компиляторы поддерживают разработку обработчиков прерываний при помощи ключевого слова interrupt. Сохраняется только та контекстная информация, которую компилятор посчитает необходимой. Это приводит к значительным ограничениям в том, что могут делать стандартные обработчики прерывания, в чем мы скоро убедимся.
Чтобы сформировать штатный обработчик прерывания в Nucleus SE достаточно просто написать обычный обработчик прерывания, включая вызов макроса NUSE_NISR_Enter() в начале и вызов NUSE_NISR_Exit() в конце. Эти макросы определены в файле nuse_types.h и присваивают глобальной переменной NUSE_Task_State значение NUSE_NISR_CONTEXT.
Если вам необходима более высокая гибкость операций, выполняемых обработчиком прерывания, управляемые прерывания Nucleus SE могут стать подходящим решением. Ключевое отличие от стандартного прерывания — сохранение контекста. Вместо того, чтобы позволить компилятору сохранить в стеке несколько регистров, управляемое прерывание сохраняет весь контекст задачи (в собственном контекстном блоке) на входе. Затем контекст текущей задачи восстанавливается из контекстного блока на выходе. Это обеспечивает возможность изменения текущей задачи работой кода обработчика прерывания, что возможно при использовании планировщика приоритетов (priority scheduler). Полное описание сохранения и восстановления контекста в Nucleus SE приводилось в одной из предыдущих статей (#10).
Очевидно, что полное сохранение контекста влечет за собой повышение использования вычислительных ресурсов по сравнению с сохранением в стеке нескольких регистров, которое происходит при стандартном прерывании. Такую цену необходимо заплатить за дополнительную гибкость, и именно она является причиной, по которой предоставляется выбор подхода обработки прерывания.
Управляемое прерывание строится при помощи макроса NUSE_MANAGED_ISR(), описанного в nuse_types.h. Этот макрос создает функцию, которая содержит следующие действия:
Макрос принимает два параметра: имя прерывания, используемое в качестве имени функции для создаваемого обработчика, и имя функции, в которой содержится пользовательская логика обработчика прерывания.
Набор функций API, которые могут быть вызваны из стандартного или управляемого обработчика прерывания, зависит от того, какой планировщик используется. В общих чертах, использование планировщика приоритетов обеспечивает множество возможностей обращения к планировщику через вызов функции API, что представляет сложность при использовании стандартного обработчика прерывания.
Вызовы API стандартного обработчика прерывания при использовании планировщика приоритетов
При использовании планировщика приоритетов разрешено ограниченное количество вызовов функций API из стандартного обработчика прерывания. Это ограничение является результатом гибкости API Nucleus SE: многие вызовы могут привести к тому, что задача будет готова и планировщик не сможет быть вызван стандартным обработчиком прерывания (так как контекст задачи не сохранен). Отключение блокировки задач обеспечит еще большую гибкость.
Всегда разрешены следующие вызовы API:
Однако полезным из них является только NUSE_Signals_Send(), так как он предоставляет удобный способ обозначить задаче, что требуется выполнить какое-то действие.
Если блокировка отключена, то есть задачи не могут переводиться в готовое состояние многими вызовами API, становятся доступны дополнительные вызовы API:
Некоторые вызовы API всегда недоступны стандартным обработчикам прерывания, так как они неизбежно потребуют работы планировщика:
Вызовы API управляемого обработчика прерывания или стандартного обработчика прерывания при использовании любого планировщика, кроме планировщика приоритетов
Гораздо больше функций API может быть вызвано из обработчика прерывания при использовании планировщиков Run to Completion, Round Robin или Time Slice. Если используется планировщик приоритетов, управляемые обработчики прерывания имеют похожий набор функций. Это вызвано тем, что разрешены вызовы, которые могут привести к планированию другой задачи. Эта возможность обеспечивается кодом NUSE_Reschedule(), который обнаруживает контекст вызова в обработчике прерывания и подавляет смену контекста (позволяя ему произойти в конце обработчика прерывания). Полный разбор работы планировщика был приведен в одной из предыдущих статей (#9).
Ключевым требованием является то, что вызовы API внутри обработчика прерывания не должны привести к приостановке текущей задачи, например, ожидающей освобождения ресурса.
Другими словами, такие вызовы должны выполняться при параметре приостановки NUSE_NO_SUSPEND.
С учетом этого могут быть использованы следующие вызовы API:
Некоторые вызовы запрещены всегда, так как они напрямую относятся к текущей задаче:
Обработчик прерывания Real Time Clock (RTC) является единственным полным обработчиком прерывания в Nucleus SE. Помимо того, что он предоставляет весь необходимый функционал для управления временем в Nucleus SE, он также служит примером написания управляемого обработчика прерывания.
Функции, предоставляемые обработчиком прерывания RTC, были перечислены в одной из предыдущей статей, которая касалась широкой темы системного времени в Nucleus SE (#27). Описываемый функционал опционален в зависимости от конфигурации приложения.
Далее мы рассмотрим четыре основных области функционала обработчика прерывания RTC.
Таймеры (Timers)
Если сконфигурированы таймеры приложения, обработчик прерывания уходит в цикл для обработки каждого таймера за счет уменьшения значения его счетчика на 1. Если таймер заканчивает счет (т.е. счетчик достигает 0), возможны два действия:
Таймеры приложения были подробно описаны в предыдущей статье (#28).
Системный таймер (System Clock)
Если сконфигурирован системный таймер, значение NUSE_Tick_Clock просто увеличивается на 1. Более подробную информацию можно найти в статье #28.
Приостановка задачи (Task Sleep)
Если включена поддержка приостановки задач (т.е. вызов API NUSE_Task_Sleep() сконфигурирован), счетчик таймаута каждой задачи (значение в NUSE_Task_Timeout_Counter[]) проверяется, и если оно не равно нулю, уменьшается на 1. Если оно достигает нуля, соответствующая задача возобновляется.
Планирование квантования времени (Time Slice Scheduling)
Если используется планировщик квантования времени (Time Slice), счетчик планировщика (NUSE_Time_Slice_Ticks) декрементируется. Если он достигнет нуля, планировщик вызывается. Вызов NUSE_Reschedule() отвечает за сброс счетчика.
Необходимо объяснить, почему обработчик прерывания RTC является управляемым, так как при определенных обстоятельствах пользователь может решить переписать его как стандартное прерывание, чтобы снизить использование вычислительных ресурсов. Например, если используется только одна системная временная функция (т.е. нет таймеров приложения, нет приостановки задач и нет планировщика Time Slice), штатное прерывание полностью подойдет. Управляемое прерывание требуется в следующих случаях:
Так как реализация прерываний Nucleus SE сильно отличается от Nucleus RTOS, не стоит ожидать совместимости в этом плане. Nucleus RTOS имеет схему прерываний стандартные/низкоуровневые/высокоуровневые, которая немного напоминает схему стандартные/управляемые прерывания в Nucleus SE.
Низкоуровневые обработчики прерывания
Низкоуровневый обработчик прерывания (Low-Level Interrupt Service Routin, LISR) выполняется так же, как и обычный обработчик, включая использование текущего стека. Nucleus RTOS сохраняет контекст до вызова низкоуровневого обработчика прерывания и восстанавливает контекст после завершения работы обработчика. Следовательно, низкоуровневый обработчик прерывания может быть написан на С и может вызывать другие обработчики на С. Однако низкоуровневому обработчику доступны лишь несколько служб Nucleus RTOS. Если обработка прерывания требует дополнительных служб Nucleus RTOS, необходимо активировать высокоуровневый обработчик прерывания. Nucleus RTOS поддерживает использование нескольких низкоуровневых обработчиков прерывания.
Высокоуровневый обработчик прерывания
Высокоуровневые обработчики прерывания (High-Level Interrupt Service Routin, HISR) создаются и удаляются динамически. Каждый высокоуровневый обработчик имеет собственное пространство стека и собственный блок управления. Память выделяется приложением. И, конечно, высокоуровневый обработчик прерывания должен быть создан, прежде чем его сможет активировать низкоуровневый обработчик прерывания.
Так как высокоуровневый обработчик прерывания имеет собственный стек и блок управления, его можно временно заблокировать, если он попытается обратиться к структуре данных Nucleus RTOS, которая в данный момент используется.
Существует три уровня приоритета доступных высокоуровневому обработчику прерывания. Если высокоуровневый обработчик с более высоким приоритетом активируется во время работы обработчика с более низким приоритетом, обработчик с низким приоритетом будет выполняться по мере выполнения задачи. Высокоуровневые обработчики прерывания с одинаковым приоритетом выполняются в порядке их активации. Все активированные высокоуровневые обработчики прерывания должны быть завершены перед продолжением планировки задач в нормальном режиме.
Nucleus RTOS имеет несколько вызовов API для поддержки прерываний. Ни один из них не реализован в Nucleus SE.
Для стандартных прерываний вызовы API предоставляют следующие функции:
Для низкоуровневых прерываний:
Для высокоуровневых прерываний:
Глобальное управление прерыванием
Этот вызов активирует или деактивирует прерывания независимо от задачи. Следовательно, прерывание, деактивированное этим вызовом, останется таковым, пока не будет активировано повторным использованием этого вызова.
Прототип служебного вызова:
Параметры:
new_level – новый уровень прерываний для системы. Всегда может принимать значения NU_DISABLE_INTERRUPTS (деактивирует все прерывания) и NU_ENABLE_INTERRUPTS (активирует все прерывания). В зависимости от архитектуры могут быть доступны другие значения.
Возвращаемое значение:
Этот служебный вызов возвращает предыдущий уровень активированных прерываний.
Локальное управление прерыванием
Этот служебный вызов позволяет активировать или деактивировать прерывания в зависимости от задачи. Этот вызов меняет регистр статуса на указанное значение. Регистр статуса будет возвращен в значение, заданное последним вызовом NU_Control_Interrupts() при следующем изменении контекста.
Прототип служебного вызова:
Параметры:
new_level – новый уровень прерываний для текущей задачи. Всегда может принимать значения NU_DISABLE_INTERRUPTS (деактивирует все прерывания) и NU_ENABLE_INTERRUPTS (активирует все прерывания). В зависимости от архитектуры могут быть доступны другие значения.
Возвращаемое значение:
Этот служебный вызов возвращает предыдущий уровень активированных прерываний.
Установка вектора прерывания
Этот служебный вызов заменяет вектор прерывания, указанный вектором, управляемым обработчиком прерывания.
Прототип служебного вызова:
Параметры:
vector – вектор прерывания, для которого будет зарегистрировано прерывание;
new – обработчик прерывания, записываемый для вектора.
Возвращаемое значение:
Этот служебный вызов возвращает указатель на обработчик прерывания, ранее зарегистрированный для вектора прерывания.
Регистрация низкоуровневого прерывания
Этот служебный вызов связывает функцию низкоуровневого обработчика прерывания с вектором прерывания. Системный контекст автоматически сохраняется перед вызовом указанного низкоуровневого обработчика прерывания и восстанавливается после завершения работы обработчика прерывания.
Прототип служебного вызова:
Параметры:
vector – вектор прерывания, для которого будет зарегистрировано прерывание;
lisr_entry – функция, которая будет зарегистрирована для вектора, значение NU_NULL очистит вектор;
old_lisr – функция, ранее зарегистрированная для указанного вектора.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_VECTOR – некорректный вектор;
NU_NOT_REGISTERED – в данный момент вектор не зарегистрирован, так как в lisr_entry была указана де-регистрация;
NO_MORE_LISRS – было достигнуто максимальное количество зарегистрированных низкоуровневых обработчиков прерывания.
Создание высокоуровневого обработчика прерывания
Этот служебный вызов создает высокоуровневый обработчик прерывания.
Прототип служебного вызова:
Параметры:
hisr – указатель на предоставленный пользователем блок управления высокоуровневым обработчиком прерывания;
name – указатель на 7-символьное имя для высокоуровневого обработчика прерывания с терминирующим нулём;
hisr_entry – точка входа функции высокоуровневого обработчика прерывания;
priority – существует три приоритета для высокоуровневых обработчиков прерывания (0-2); приоритет 0 – самый высокий;
stack_pointer – указатель на область стека высокоуровневого обработчика прерывания;
stack_size – количество байтов в стеке высокоуровневого обработчика прерывания.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – нулевой указатель на блок управления высокоуровневым обработчиком прерывания (NULL) или блок управления уже используется;
NU_INVALID_ENTRY – нулевой указатель на точку входа высокоуровневого обработчика прерывания (NULL);
NU_INVALID_PRIORITY – некорректный приоритет высокоуровневого обработчика прерывания;
NU_INVALID_MEMORY – некорректный указатель на стек;
NU_INVALID_SIZE – слишком маленький размер стека.
Удаление высокоуровневого обработчика прерывания
Этот служебный вызов удаляет ранее созданный высокоуровневый обработчик прерывания.
Прототип служебного вызова:
Параметры:
hisr – указатель на блок управления высокоуровневого обработчика прерывания, предоставленный пользователем.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – некорректный указатель на высокоуровневый обработчик прерывания.
Активация высокоуровневого обработчика прерывания
Этот служебный вызов активирует высокоуровневый обработчик прерываний. Если указанный высокоуровневый обработчик прерываний на данный момент находится на исполнении, запрос на активацию не выполняется до тех пор, пока обработчик не прекратит работу. Высокоуровневый обработчик прерывания запускается на исполнение один раз для каждого запроса на активацию.
Прототип служебного вызова:
Параметры:
hisr – указатель на блок управления высокоуровневого обработчика прерывания.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – некорректный указатель на блок управления высокоуровневого обработчика прерывания.
Получение количества высокоуровневых обработчиков прерывания в системе
Этот служебный вызов возвращает количество установленных высокоуровневых обработчиков прерывания. Все созданные высокоуровневые обработчики прерывания считаются установленными. Удаленные высокоуровневые обработчики прерываний не считаются установленными.
Прототип служебного вызова:
Параметры:
Отсутствуют.
Возвращаемое значение:
Этот служебный вызов возвращает количество установленных высокоуровневых обработчиков прерываний в системе
Получение указателей на блоки управления высокоуровневых обработчиков прерывания
Этот служебный вызов формирует последовательный список указателей на все установленные в системе высокоуровневые обработчики прерывания.
Прототип служебного вызова:
Параметры:
pointer_list – указатель на массив указателей NU_HISR; этот массив будет заполнен указателями на установленные в системе высокоуровневые обработчики прерывания;
maximum_pointers – максимальное количество указателей NU_HISR, которые могут быть помещены в массив; обычно оно равно размеру массива pointer_list.
Возвращаемое значение:
Этот служебный вызов возвращает количество активных высокоуровневых обработчиков прерывания в системе.
Получение указателя на текущий высокоуровневый обработчик прерывания
Этот служебный вызов возвращает указатель на выполняемый в данный момент высокоуровневый обработчик прерывания.
Прототип служебного вызова:
Параметры:
Отсутствуют.
Возвращаемое значение:
Этот служебный вызов возвращает указатель на блок управления выполняемого в данный момент высокоуровневого обработчика прерывания. Если эту функцию вызывает не высокоуровневый обработчик прерывания, возвращается NU_NULL.
Получение информации о высокоуровневом обработчике прерываний
Этот служебный вызов возвращает различную информацию об указанном высокоуровневом обработчике прерывания.
Прототип служебного вызова:
Параметры:
hisr – указатель на высокоуровневый обработчик прерывания;
name – указатель на 8-символьную область для имени высокоуровневого обработчика прерывания, включая терминирующий ноль;
scheduled_count – указатель на переменную для общего количества раз, когда этот высокоуровневый обработчик прерывания был запланирован;
priority – указатель на переменную для хранения приоритета высокоуровневого обработчика прерывания;
stack_base – указатель на указатель для хранения оригинального указателя на стек; это тот же указатель, который был передан при создании высокоуровневого обработчика прерывания;
stack_size – указатель на переменную для хранения общего размера стека высокоуровневого обработчика прерывания;
minimum_stack – указатель на переменную для хранения минимального объёма доступного пространства стека, обнаруженного при выполнении высокоуровневого обработчика прерывания.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – некорректный указатель на высокоуровневый обработчик прерывания.
Служебные вызов API из низкоуровневых обработчиков прерывания
Низкоуровневый обработчик прерывания может использовать только следующие функции Nucleus RTOS:
Служебные вызовы API из высокоуровневых обработчиков прерывания
Высокоуровневые обработчики прерывания имеют доступ к большинству функций Nucleus RTOS, за исключением функций для само-приостановки, так как высокоуровневый обработчик прерывания не может приостановить функцию Nucleus RTOS, параметр всегда должен иметь значение NU_NO_SUSPEND.
В следующей статье из цикла будут рассмотрены инициализация и процедуры запуска Nucleus SE.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он — инженер в области встроенного ПО в Mentor Embedded (подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина, e-mail: colin_walls@mentor.com.
![](https://habrastorage.org/webt/xf/fd/p4/xffdp4n9qbgy9yoqppflrg0a6as.jpeg)
Предыдущие статьи серии:
Статья #28. Программные таймеры
Статья #27. Системное время
Статья #26. Каналы: вспомогательные службы и структуры данных
Статья #25. Каналы передачи данных: введение и основные службы
Статья #24. Очереди: вспомогательные службы и структуры данных
Статья #23. Очереди: введение и базовые службы
Статья #22. Почтовые ящики: вспомогательные службы и структуры данных
Статья #21. Почтовые ящики: введение и базовые службы
Статья #20. Семафоры: вспомогательные службы и структуры данных
Статья #19. Семафоры: введение и базовые службы
Статья #18. Группы флагов событий: вспомогательные службы и структуры данных
Статья #17. Группы флагов событий: введение и базовые службы
Статья #16. Сигналы
Статья #15. Разделы памяти: службы и структуры данных
Статья #14. Разделы памяти: введение и базовые службы
Статья #13. Структуры данных задач и неподдерживаемые вызовы API
Статья #12. Службы для работы с задачами
Статья #11. Задачи: конфигурация и введение в API
Статья #10. Планировщик: дополнительные возможности и сохранение контекста
Статья #9. Планировщик: реализация
Статья #8. Nucleus SE: внутреннее устройство и развертывание
Статья #7. Nucleus SE: введение
Статья #6. Другие сервисы ОСРВ
Статья #5. Взаимодействие между задачами и синхронизация
Статья #4. Задачи, переключение контекста и прерывания
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.
Статья #27. Системное время
Статья #26. Каналы: вспомогательные службы и структуры данных
Статья #25. Каналы передачи данных: введение и основные службы
Статья #24. Очереди: вспомогательные службы и структуры данных
Статья #23. Очереди: введение и базовые службы
Статья #22. Почтовые ящики: вспомогательные службы и структуры данных
Статья #21. Почтовые ящики: введение и базовые службы
Статья #20. Семафоры: вспомогательные службы и структуры данных
Статья #19. Семафоры: введение и базовые службы
Статья #18. Группы флагов событий: вспомогательные службы и структуры данных
Статья #17. Группы флагов событий: введение и базовые службы
Статья #16. Сигналы
Статья #15. Разделы памяти: службы и структуры данных
Статья #14. Разделы памяти: введение и базовые службы
Статья #13. Структуры данных задач и неподдерживаемые вызовы API
Статья #12. Службы для работы с задачами
Статья #11. Задачи: конфигурация и введение в API
Статья #10. Планировщик: дополнительные возможности и сохранение контекста
Статья #9. Планировщик: реализация
Статья #8. Nucleus SE: внутреннее устройство и развертывание
Статья #7. Nucleus SE: введение
Статья #6. Другие сервисы ОСРВ
Статья #5. Взаимодействие между задачами и синхронизация
Статья #4. Задачи, переключение контекста и прерывания
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.
Штатные и управляемые прерывания
Nucleus SE предлагает два способа обработки прерываний: «родной» или «штатный» (Native), при котором прерывания не представляют из себя ничего особенного и в какой-то степени имеют ограниченное взаимодействие с ОС (как минимум при использовании планировщика приоритетов), и «управляемый» (Managed), при котором из обработчика прерывания можно обратиться к гораздо большему числу доступных вызовов API.
При помощи макросов входа/выхода обработчик прерывания в Nucleus SE может использоваться как в стандартном, так и в управляемом варианте.
Штатные прерывания
Штатные прерывания Nucleus SE являются стандартным обработчиком прерывания, их можно считать «неуправляемыми». Обычно они используются, когда прерывание может возникать с высокой частотой и требует обработки при низком использовании вычислительных ресурсов. Такой обработчик скорее всего написан на С, так как многие современные встраиваемые компиляторы поддерживают разработку обработчиков прерываний при помощи ключевого слова interrupt. Сохраняется только та контекстная информация, которую компилятор посчитает необходимой. Это приводит к значительным ограничениям в том, что могут делать стандартные обработчики прерывания, в чем мы скоро убедимся.
Чтобы сформировать штатный обработчик прерывания в Nucleus SE достаточно просто написать обычный обработчик прерывания, включая вызов макроса NUSE_NISR_Enter() в начале и вызов NUSE_NISR_Exit() в конце. Эти макросы определены в файле nuse_types.h и присваивают глобальной переменной NUSE_Task_State значение NUSE_NISR_CONTEXT.
Управляемые прерывания
Если вам необходима более высокая гибкость операций, выполняемых обработчиком прерывания, управляемые прерывания Nucleus SE могут стать подходящим решением. Ключевое отличие от стандартного прерывания — сохранение контекста. Вместо того, чтобы позволить компилятору сохранить в стеке несколько регистров, управляемое прерывание сохраняет весь контекст задачи (в собственном контекстном блоке) на входе. Затем контекст текущей задачи восстанавливается из контекстного блока на выходе. Это обеспечивает возможность изменения текущей задачи работой кода обработчика прерывания, что возможно при использовании планировщика приоритетов (priority scheduler). Полное описание сохранения и восстановления контекста в Nucleus SE приводилось в одной из предыдущих статей (#10).
Очевидно, что полное сохранение контекста влечет за собой повышение использования вычислительных ресурсов по сравнению с сохранением в стеке нескольких регистров, которое происходит при стандартном прерывании. Такую цену необходимо заплатить за дополнительную гибкость, и именно она является причиной, по которой предоставляется выбор подхода обработки прерывания.
Управляемое прерывание строится при помощи макроса NUSE_MANAGED_ISR(), описанного в nuse_types.h. Этот макрос создает функцию, которая содержит следующие действия:
- сохранение контекста задачи;
- присвоение NUSE_Task_State значения NUSE_MISR_CONTEXT;
- предоставленный пользователем код функции обработчика прерывания;
- восстановление NUSE_Task_State в предшествующее состояние;
- восстановление контекста задачи.
Макрос принимает два параметра: имя прерывания, используемое в качестве имени функции для создаваемого обработчика, и имя функции, в которой содержится пользовательская логика обработчика прерывания.
Вызовы API из обработчика прерывания
Набор функций API, которые могут быть вызваны из стандартного или управляемого обработчика прерывания, зависит от того, какой планировщик используется. В общих чертах, использование планировщика приоритетов обеспечивает множество возможностей обращения к планировщику через вызов функции API, что представляет сложность при использовании стандартного обработчика прерывания.
Вызовы API стандартного обработчика прерывания при использовании планировщика приоритетов
При использовании планировщика приоритетов разрешено ограниченное количество вызовов функций API из стандартного обработчика прерывания. Это ограничение является результатом гибкости API Nucleus SE: многие вызовы могут привести к тому, что задача будет готова и планировщик не сможет быть вызван стандартным обработчиком прерывания (так как контекст задачи не сохранен). Отключение блокировки задач обеспечит еще большую гибкость.
Всегда разрешены следующие вызовы API:
NUSE_Task_Current()
NUSE_Task_Check_Stack()
NUSE_Task_Information()
NUSE_Task_Count()
NUSE_Partition_Pool_Information()
NUSE_Partition_Pool_Count()
NUSE_Mailbox_Information()
NUSE_Mailbox_Count()
NUSE_Queue_Information()
NUSE_Queue_Count()
NUSE_Pipe_Information()
NUSE_Pipe_Count()
NUSE_Semaphore_Information()
NUSE_Semaphore_Count()
NUSE_Event_Group_Information()
NUSE_Event_Group_Count()
NUSE_Signals_Send()
NUSE_Timer_Control()
NUSE_Timer_Get_Remaining()
NUSE_Timer_Reset()
NUSE_Timer_Information()
NUSE_Timer_Count()
NUSE_Clock_Set()
NUSE_Clock_Retrieve()
NUSE_Release_Information()
Однако полезным из них является только NUSE_Signals_Send(), так как он предоставляет удобный способ обозначить задаче, что требуется выполнить какое-то действие.
Если блокировка отключена, то есть задачи не могут переводиться в готовое состояние многими вызовами API, становятся доступны дополнительные вызовы API:
NUSE_Partition_Allocate()
NUSE_Partition_Deallocate()
NUSE_Mailbox_Send()
NUSE_Mailbox_Receive()
NUSE_Mailbox_Reset()
NUSE_Queue_Send()
NUSE_Queue_Receive()
NUSE_Queue_Jam()
NUSE_Queue_Reset()
NUSE_Pipe_Send()
NUSE_Pipe_Receive()
NUSE_Pipe_Jam()
NUSE_Pipe_Reset()
NUSE_Semaphore_Obtain()
NUSE_Semaphore_Release()
NUSE_Semaphore_Reset()
NUSE_Event_Group_Set()
NUSE_Event_Group_Retrieve()
Некоторые вызовы API всегда недоступны стандартным обработчикам прерывания, так как они неизбежно потребуют работы планировщика:
NUSE_Task_Suspend()
NUSE_Task_Resume()
NUSE_Task_Sleep()
NUSE_Task_Relinquish()
NUSE_Task_Reset()
NUSE_Signals_Receive()
Вызовы API управляемого обработчика прерывания или стандартного обработчика прерывания при использовании любого планировщика, кроме планировщика приоритетов
Гораздо больше функций API может быть вызвано из обработчика прерывания при использовании планировщиков Run to Completion, Round Robin или Time Slice. Если используется планировщик приоритетов, управляемые обработчики прерывания имеют похожий набор функций. Это вызвано тем, что разрешены вызовы, которые могут привести к планированию другой задачи. Эта возможность обеспечивается кодом NUSE_Reschedule(), который обнаруживает контекст вызова в обработчике прерывания и подавляет смену контекста (позволяя ему произойти в конце обработчика прерывания). Полный разбор работы планировщика был приведен в одной из предыдущих статей (#9).
Ключевым требованием является то, что вызовы API внутри обработчика прерывания не должны привести к приостановке текущей задачи, например, ожидающей освобождения ресурса.
Другими словами, такие вызовы должны выполняться при параметре приостановки NUSE_NO_SUSPEND.
С учетом этого могут быть использованы следующие вызовы API:
NUSE_Task_Current()
NUSE_Task_Check_Stack()
NUSE_Task_Information()
NUSE_Task_Count()
NUSE_Task_Suspend()
NUSE_Task_Resume()
NUSE_Task_Reset()
NUSE_Partition_Allocate()
NUSE_Partition_Deallocate()
NUSE_Partition_Pool_Information()
NUSE_Partition_Pool_Count()
NUSE_Mailbox_Send()
NUSE_Mailbox_Receive()
NUSE_Mailbox_Reset()
NUSE_Mailbox_Information()
NUSE_Mailbox_Count()
NUSE_Queue_Send()
NUSE_Queue_Receive()
NUSE_Queue_Jam()
NUSE_Queue_Reset()
NUSE_Queue_Information()
NUSE_Queue_Count()
NUSE_Pipe_Send()
NUSE_Pipe_Receive()
NUSE_Pipe_Jam()
NUSE_Pipe_Reset()
NUSE_Pipe_Information()
NUSE_Pipe_Count()
NUSE_Semaphore_Obtain()
NUSE_Semaphore_Release()
NUSE_Semaphore_Reset()
NUSE_Semaphore_Information()
NUSE_Semaphore_Count()
NUSE_Event_Group_Set()
NUSE_Event_Group_Retrieve()
NUSE_Event_Group_Information()
NUSE_Event_Group_Count()
NUSE_Signals_Send()
NUSE_Timer_Control()
NUSE_Timer_Get_Remaining()
NUSE_Timer_Reset()
NUSE_Timer_Information()
NUSE_Timer_Count()
NUSE_Clock_Set()
NUSE_Clock_Retrieve()
NUSE_Release_Information()
Некоторые вызовы запрещены всегда, так как они напрямую относятся к текущей задаче:
NUSE_Task_Relinquish()
NUSE_Signals_Receive()
NUSE_Task_Sleep()
Обработчик прерывания Real Time Clock
Обработчик прерывания Real Time Clock (RTC) является единственным полным обработчиком прерывания в Nucleus SE. Помимо того, что он предоставляет весь необходимый функционал для управления временем в Nucleus SE, он также служит примером написания управляемого обработчика прерывания.
Операции обработчика прерывания RTC
Функции, предоставляемые обработчиком прерывания RTC, были перечислены в одной из предыдущей статей, которая касалась широкой темы системного времени в Nucleus SE (#27). Описываемый функционал опционален в зависимости от конфигурации приложения.
Ниже приведен полный код обработчика прерывания RTC.
#if NUSE_TIMER_NUMBER != 0
{
U8 timer;
for (timer=0; timer<NUSE_TIMER_NUMBER; timer++)
{
if (NUSE_Timer_Status[timer])
{
if (--NUSE_Timer_Value[timer] == 0)
{
NUSE_Timer_Expirations_Counter[timer]++;
#if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT ||
NUSE_INCLUDE_EVERYTHING
if (NUSE_Timer_Expiration_Routine_Address[timer]
!= NULL)
{
((PF1)NUSE_Timer_Expiration_Routine_Address[timer])
NUSE_Timer_Expiration_Routine_Parameter[timer]);
}
#endif
/* reschedule? */
if (NUSE_Timer_Reschedule_Time[timer] != 0)
{ /* yes: set up time */
NUSE_Timer_Value[timer] =
NUSE_Timer_Reschedule_Time[timer];
}
else
{ /* no: disable */
NUSE_Timer_Status[timer] = FALSE;
}
}
}
}
}
#endif
#if NUSE_SYSTEM_TIME_SUPPORT || NUSE_INCLUDE_EVERYTHING
NUSE_Tick_Clock++;
#endif
#if NUSE_TASK_SLEEP || NUSE_INCLUDE_EVERYTHING
{
U8 task;
for (task=0; task<NUSE_TASK_NUMBER; task++)
{
if (NUSE_Task_Timeout_Counter[task] != 0)
{
NUSE_Task_Timeout_Counter[task]--;
if (NUSE_Task_Timeout_Counter[task] == 0)
{
NUSE_Wake_Task(task);
}
}
}
}
#endif
#if NUSE_SCHEDULER_TYPE == NUSE_TIME_SLICE_SCHEDULER
if (--NUSE_Time_Slice_Ticks == 0)
{
NUSE_Reschedule();
}
#endif
Далее мы рассмотрим четыре основных области функционала обработчика прерывания RTC.
Таймеры (Timers)
Если сконфигурированы таймеры приложения, обработчик прерывания уходит в цикл для обработки каждого таймера за счет уменьшения значения его счетчика на 1. Если таймер заканчивает счет (т.е. счетчик достигает 0), возможны два действия:
- если обработчик завершения таймера сконфигурирован а таймер имеет корректный (не NULL) указатель на функцию (в NUSE_Timer_Expiration_Routine_Address[]), обработчик выполняется, принимая параметр из NUSE_Timer_Expiration_Routine_Parameter[];
- если таймер настроен на инициализацию после завершения (т.е NUSE_Timer_Reschedule_Time[] имеет ненулевое значение), таймер перезагружается с этим значением.
Таймеры приложения были подробно описаны в предыдущей статье (#28).
Системный таймер (System Clock)
Если сконфигурирован системный таймер, значение NUSE_Tick_Clock просто увеличивается на 1. Более подробную информацию можно найти в статье #28.
Приостановка задачи (Task Sleep)
Если включена поддержка приостановки задач (т.е. вызов API NUSE_Task_Sleep() сконфигурирован), счетчик таймаута каждой задачи (значение в NUSE_Task_Timeout_Counter[]) проверяется, и если оно не равно нулю, уменьшается на 1. Если оно достигает нуля, соответствующая задача возобновляется.
Планирование квантования времени (Time Slice Scheduling)
Если используется планировщик квантования времени (Time Slice), счетчик планировщика (NUSE_Time_Slice_Ticks) декрементируется. Если он достигнет нуля, планировщик вызывается. Вызов NUSE_Reschedule() отвечает за сброс счетчика.
Управляемое прерывание
Необходимо объяснить, почему обработчик прерывания RTC является управляемым, так как при определенных обстоятельствах пользователь может решить переписать его как стандартное прерывание, чтобы снизить использование вычислительных ресурсов. Например, если используется только одна системная временная функция (т.е. нет таймеров приложения, нет приостановки задач и нет планировщика Time Slice), штатное прерывание полностью подойдет. Управляемое прерывание требуется в следующих случаях:
- если используются таймеры и сконфигурированы обработчики их завершения, так как эти обработчики могут выполнять вызовы API (из контекста прерывания), что вызовет новое планирование. Они имеют те же ограничения, что и вызовы API, выполняемые из обработчиков прерывания (см. ранее в этой статье);
- если используется планировщик приоритетов, завершение приостановки задачи может потребовать пробуждения задачи с более высоким приоритетом;
- если используется планировщик Time Slice, он обязательно будет вызван из обработчика прерывания RTC, следовательно, управляемое прерывание обязательно.
Совместимость с Nucleus RTOS
Так как реализация прерываний Nucleus SE сильно отличается от Nucleus RTOS, не стоит ожидать совместимости в этом плане. Nucleus RTOS имеет схему прерываний стандартные/низкоуровневые/высокоуровневые, которая немного напоминает схему стандартные/управляемые прерывания в Nucleus SE.
Низкоуровневые и высокоуровневые обработчики прерывания
Низкоуровневые обработчики прерывания
Низкоуровневый обработчик прерывания (Low-Level Interrupt Service Routin, LISR) выполняется так же, как и обычный обработчик, включая использование текущего стека. Nucleus RTOS сохраняет контекст до вызова низкоуровневого обработчика прерывания и восстанавливает контекст после завершения работы обработчика. Следовательно, низкоуровневый обработчик прерывания может быть написан на С и может вызывать другие обработчики на С. Однако низкоуровневому обработчику доступны лишь несколько служб Nucleus RTOS. Если обработка прерывания требует дополнительных служб Nucleus RTOS, необходимо активировать высокоуровневый обработчик прерывания. Nucleus RTOS поддерживает использование нескольких низкоуровневых обработчиков прерывания.
Высокоуровневый обработчик прерывания
Высокоуровневые обработчики прерывания (High-Level Interrupt Service Routin, HISR) создаются и удаляются динамически. Каждый высокоуровневый обработчик имеет собственное пространство стека и собственный блок управления. Память выделяется приложением. И, конечно, высокоуровневый обработчик прерывания должен быть создан, прежде чем его сможет активировать низкоуровневый обработчик прерывания.
Так как высокоуровневый обработчик прерывания имеет собственный стек и блок управления, его можно временно заблокировать, если он попытается обратиться к структуре данных Nucleus RTOS, которая в данный момент используется.
Существует три уровня приоритета доступных высокоуровневому обработчику прерывания. Если высокоуровневый обработчик с более высоким приоритетом активируется во время работы обработчика с более низким приоритетом, обработчик с низким приоритетом будет выполняться по мере выполнения задачи. Высокоуровневые обработчики прерывания с одинаковым приоритетом выполняются в порядке их активации. Все активированные высокоуровневые обработчики прерывания должны быть завершены перед продолжением планировки задач в нормальном режиме.
Служебные вызовы Nucleus RTOS API для прерывания
Nucleus RTOS имеет несколько вызовов API для поддержки прерываний. Ни один из них не реализован в Nucleus SE.
Для стандартных прерываний вызовы API предоставляют следующие функции:
- управление (активирование/деактивирование) прерыванием (локально и глобально);
- установка вектора прерывания.
Для низкоуровневых прерываний:
- регистрация низкоуровневого обработчика прерываний в ядре.
Для высокоуровневых прерываний:
- создание/удаление высокоуровневых прерываний;
- активация высокоуровневого прерывания;
- получение количества высокоуровневых прерываний в приложении (на данный момент);
- получение указателей на блоки управления всех высокоуровневых прерываний;
- получение указателей на блоки управления текущего высокоуровневого прерывания;
- получение информации о высокоуровневом прерывании.
Глобальное управление прерыванием
Этот вызов активирует или деактивирует прерывания независимо от задачи. Следовательно, прерывание, деактивированное этим вызовом, останется таковым, пока не будет активировано повторным использованием этого вызова.
Прототип служебного вызова:
INT NU_Control_Interrupts (INT new_level);
Параметры:
new_level – новый уровень прерываний для системы. Всегда может принимать значения NU_DISABLE_INTERRUPTS (деактивирует все прерывания) и NU_ENABLE_INTERRUPTS (активирует все прерывания). В зависимости от архитектуры могут быть доступны другие значения.
Возвращаемое значение:
Этот служебный вызов возвращает предыдущий уровень активированных прерываний.
Локальное управление прерыванием
Этот служебный вызов позволяет активировать или деактивировать прерывания в зависимости от задачи. Этот вызов меняет регистр статуса на указанное значение. Регистр статуса будет возвращен в значение, заданное последним вызовом NU_Control_Interrupts() при следующем изменении контекста.
Прототип служебного вызова:
INT NU_Local_Control_Interrupts (INT new_level);
Параметры:
new_level – новый уровень прерываний для текущей задачи. Всегда может принимать значения NU_DISABLE_INTERRUPTS (деактивирует все прерывания) и NU_ENABLE_INTERRUPTS (активирует все прерывания). В зависимости от архитектуры могут быть доступны другие значения.
Возвращаемое значение:
Этот служебный вызов возвращает предыдущий уровень активированных прерываний.
Установка вектора прерывания
Этот служебный вызов заменяет вектор прерывания, указанный вектором, управляемым обработчиком прерывания.
Прототип служебного вызова:
VOID *NU_Setup_Vector (INT vector, VOID *new);
Параметры:
vector – вектор прерывания, для которого будет зарегистрировано прерывание;
new – обработчик прерывания, записываемый для вектора.
Возвращаемое значение:
Этот служебный вызов возвращает указатель на обработчик прерывания, ранее зарегистрированный для вектора прерывания.
Регистрация низкоуровневого прерывания
Этот служебный вызов связывает функцию низкоуровневого обработчика прерывания с вектором прерывания. Системный контекст автоматически сохраняется перед вызовом указанного низкоуровневого обработчика прерывания и восстанавливается после завершения работы обработчика прерывания.
Прототип служебного вызова:
STATUS NU_Register_LISR (INT vector, VOID (*lisr_entry) (INT), VOID (**old_lisr) (INT);
Параметры:
vector – вектор прерывания, для которого будет зарегистрировано прерывание;
lisr_entry – функция, которая будет зарегистрирована для вектора, значение NU_NULL очистит вектор;
old_lisr – функция, ранее зарегистрированная для указанного вектора.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_VECTOR – некорректный вектор;
NU_NOT_REGISTERED – в данный момент вектор не зарегистрирован, так как в lisr_entry была указана де-регистрация;
NO_MORE_LISRS – было достигнуто максимальное количество зарегистрированных низкоуровневых обработчиков прерывания.
Создание высокоуровневого обработчика прерывания
Этот служебный вызов создает высокоуровневый обработчик прерывания.
Прототип служебного вызова:
STATUS NU_Create_HISR (NU_HISR *hisr, CHAR *name, VOID (*hisr_entry) (VOID), OPTION priority, VOID *stack_pointer, UNSIGNED stack_size);
Параметры:
hisr – указатель на предоставленный пользователем блок управления высокоуровневым обработчиком прерывания;
name – указатель на 7-символьное имя для высокоуровневого обработчика прерывания с терминирующим нулём;
hisr_entry – точка входа функции высокоуровневого обработчика прерывания;
priority – существует три приоритета для высокоуровневых обработчиков прерывания (0-2); приоритет 0 – самый высокий;
stack_pointer – указатель на область стека высокоуровневого обработчика прерывания;
stack_size – количество байтов в стеке высокоуровневого обработчика прерывания.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – нулевой указатель на блок управления высокоуровневым обработчиком прерывания (NULL) или блок управления уже используется;
NU_INVALID_ENTRY – нулевой указатель на точку входа высокоуровневого обработчика прерывания (NULL);
NU_INVALID_PRIORITY – некорректный приоритет высокоуровневого обработчика прерывания;
NU_INVALID_MEMORY – некорректный указатель на стек;
NU_INVALID_SIZE – слишком маленький размер стека.
Удаление высокоуровневого обработчика прерывания
Этот служебный вызов удаляет ранее созданный высокоуровневый обработчик прерывания.
Прототип служебного вызова:
STATUS NU_Delete_HISR (NU_HISR *hisr);
Параметры:
hisr – указатель на блок управления высокоуровневого обработчика прерывания, предоставленный пользователем.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – некорректный указатель на высокоуровневый обработчик прерывания.
Активация высокоуровневого обработчика прерывания
Этот служебный вызов активирует высокоуровневый обработчик прерываний. Если указанный высокоуровневый обработчик прерываний на данный момент находится на исполнении, запрос на активацию не выполняется до тех пор, пока обработчик не прекратит работу. Высокоуровневый обработчик прерывания запускается на исполнение один раз для каждого запроса на активацию.
Прототип служебного вызова:
STATUS NU_Activate_HISR (NU_HISR *hisr);
Параметры:
hisr – указатель на блок управления высокоуровневого обработчика прерывания.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – некорректный указатель на блок управления высокоуровневого обработчика прерывания.
Получение количества высокоуровневых обработчиков прерывания в системе
Этот служебный вызов возвращает количество установленных высокоуровневых обработчиков прерывания. Все созданные высокоуровневые обработчики прерывания считаются установленными. Удаленные высокоуровневые обработчики прерываний не считаются установленными.
Прототип служебного вызова:
UNSIGNED NU_Established_HISRs(VOID);
Параметры:
Отсутствуют.
Возвращаемое значение:
Этот служебный вызов возвращает количество установленных высокоуровневых обработчиков прерываний в системе
Получение указателей на блоки управления высокоуровневых обработчиков прерывания
Этот служебный вызов формирует последовательный список указателей на все установленные в системе высокоуровневые обработчики прерывания.
Прототип служебного вызова:
UNSIGNED NU_HISR_Pointers(NU_HISR **pointer_list, UNSIGNED maximum_pointers);
Параметры:
pointer_list – указатель на массив указателей NU_HISR; этот массив будет заполнен указателями на установленные в системе высокоуровневые обработчики прерывания;
maximum_pointers – максимальное количество указателей NU_HISR, которые могут быть помещены в массив; обычно оно равно размеру массива pointer_list.
Возвращаемое значение:
Этот служебный вызов возвращает количество активных высокоуровневых обработчиков прерывания в системе.
Получение указателя на текущий высокоуровневый обработчик прерывания
Этот служебный вызов возвращает указатель на выполняемый в данный момент высокоуровневый обработчик прерывания.
Прототип служебного вызова:
NU_HISR *NU_Current_HISR_Pointer(VOID);
Параметры:
Отсутствуют.
Возвращаемое значение:
Этот служебный вызов возвращает указатель на блок управления выполняемого в данный момент высокоуровневого обработчика прерывания. Если эту функцию вызывает не высокоуровневый обработчик прерывания, возвращается NU_NULL.
Получение информации о высокоуровневом обработчике прерываний
Этот служебный вызов возвращает различную информацию об указанном высокоуровневом обработчике прерывания.
Прототип служебного вызова:
STATUS NU_HISR_Information(NU_HISR *hisr, char *name, UNSIGNED *scheduled_count, DATA_ELEMENT *priority, VOID **stack_base, UNSIGNED *stack_size, UNSIGNED *minimum_stack);
Параметры:
hisr – указатель на высокоуровневый обработчик прерывания;
name – указатель на 8-символьную область для имени высокоуровневого обработчика прерывания, включая терминирующий ноль;
scheduled_count – указатель на переменную для общего количества раз, когда этот высокоуровневый обработчик прерывания был запланирован;
priority – указатель на переменную для хранения приоритета высокоуровневого обработчика прерывания;
stack_base – указатель на указатель для хранения оригинального указателя на стек; это тот же указатель, который был передан при создании высокоуровневого обработчика прерывания;
stack_size – указатель на переменную для хранения общего размера стека высокоуровневого обработчика прерывания;
minimum_stack – указатель на переменную для хранения минимального объёма доступного пространства стека, обнаруженного при выполнении высокоуровневого обработчика прерывания.
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_HISR – некорректный указатель на высокоуровневый обработчик прерывания.
Вызовы API из обработчиков прерывания
Служебные вызов API из низкоуровневых обработчиков прерывания
Низкоуровневый обработчик прерывания может использовать только следующие функции Nucleus RTOS:
NU_Activate_HISR()
NU_Local_Control_Interrupts()
NU_Current_HISR_Pointer()
NU_Current_Task_Pointer()
NU_Retrieve_Clock()
Служебные вызовы API из высокоуровневых обработчиков прерывания
Высокоуровневые обработчики прерывания имеют доступ к большинству функций Nucleus RTOS, за исключением функций для само-приостановки, так как высокоуровневый обработчик прерывания не может приостановить функцию Nucleus RTOS, параметр всегда должен иметь значение NU_NO_SUSPEND.
В следующей статье из цикла будут рассмотрены инициализация и процедуры запуска Nucleus SE.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он — инженер в области встроенного ПО в Mentor Embedded (подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина, e-mail: colin_walls@mentor.com.