Прерывания не управляются такой ОСРВ, как Nucleus SE: они обрабатываются при возникновении в соответствии с установленными приоритетами. Время их выполнения просто «крадется» из доступного времени в коде основного приложения. Поэтому все обработчики прерывания должны быть простыми, короткими и быстрыми.
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 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 может быть вызвано из обработчика прерывания при использовании планировщиков 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 (RTC) является единственным полным обработчиком прерывания в Nucleus SE. Помимо того, что он предоставляет весь необходимый функционал для управления временем в Nucleus SE, он также служит примером написания управляемого обработчика прерывания.
Функции, предоставляемые обработчиком прерывания RTC, были перечислены в одной из предыдущей статей, которая касалась широкой темы системного времени в Nucleus SE (#27). Описываемый функционал опционален в зависимости от конфигурации приложения.
#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
Если сконфигурирован системный таймер, значение NUSE_Tick_Clock просто увеличивается на 1. Более подробную информацию можно найти в статье #28.
Если включена поддержка приостановки задач (т.е. вызов 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, которая в данный момент используется.
Существует три уровня приоритета доступных высокоуровневому обработчику прерывания. Если высокоуровневый обработчик с более высоким приоритетом активируется во время работы обработчика с более низким приоритетом, обработчик с низким приоритетом будет выполняться по мере выполнения задачи. Высокоуровневые обработчики прерывания с одинаковым приоритетом выполняются в порядке их активации. Все активированные высокоуровневые обработчики прерывания должны быть завершены перед продолжением планировки задач в нормальном режиме.
Этот вызов активирует или деактивирует прерывания независимо от задачи. Следовательно, прерывание, деактивированное этим вызовом, останется таковым, пока не будет активировано повторным использованием этого вызова.
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);
VOID *NU_Setup_Vector (INT vector, VOID *new);
Этот служебный вызов связывает функцию низкоуровневого обработчика прерывания с вектором прерывания. Системный контекст автоматически сохраняется перед вызовом указанного низкоуровневого обработчика прерывания и восстанавливается после завершения работы обработчика прерывания.
STATUS NU_Register_LISR (INT vector, VOID (*lisr_entry) (INT), VOID (**old_lisr) (INT);
STATUS NU_Create_HISR (NU_HISR *hisr, CHAR *name, VOID (*hisr_entry) (VOID),
OPTION priority, VOID *stack_pointer, UNSIGNED stack_size);
STATUS NU_Delete_HISR (NU_HISR *hisr);
Этот служебный вызов активирует высокоуровневый обработчик прерываний. Если указанный высокоуровневый обработчик прерываний на данный момент находится на исполнении, запрос на активацию не выполняется до тех пор, пока обработчик не прекратит работу. Высокоуровневый обработчик прерывания запускается на исполнение один раз для каждого запроса на активацию.
Прототип служебного вызова:
STATUS NU_Activate_HISR (NU_HISR *hisr);
Этот служебный вызов возвращает количество установленных высокоуровневых обработчиков прерывания. Все созданные высокоуровневые обработчики прерывания считаются установленными. Удаленные высокоуровневые обработчики прерываний не считаются установленными.
UNSIGNED NU_Established_HISRs(VOID);
Этот служебный вызов формирует последовательный список указателей на все установленные в системе высокоуровневые обработчики прерывания.
UNSIGNED NU_HISR_Pointers(NU_HISR **pointer_list, UNSIGNED maximum_pointers);
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_Activate_HISR()
NU_Local_Control_Interrupts()
NU_Current_HISR_Pointer()
NU_Current_Task_Pointer()
NU_Retrieve_Clock()
Высокоуровневые обработчики прерывания имеют доступ к большинству функций Nucleus RTOS, за исключением функций для само-приостановки, так как высокоуровневый обработчик прерывания не может приостановить функцию Nucleus RTOS, параметр всегда должен иметь значение NU_NO_SUSPEND.
В следующей статье из цикла будут рассмотрены инициализация и процедуры запуска Nucleus SE.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com