Рассматриваются назначение, способы создания и границы применимости критических потоков реального времени (CRTT)
Содержание статьи:
При проектировании обработчиков прерываний разработчик делает непростой выбор между классической функцией обработки прерываний ( ISR) и потоком. В первом случае на обработчик накладываются функциональные ограничения, поскольку выполнение кода в контексте ядра сопряжено с определенными рисками, во втором случае при обработке прерывания возникают дополнительные задержки (latency). Их природа раскрыта в разделе задержка обработки прерываний и задержка планирования.
Потенциальные риски классического ISR связанны с ошибками в обработчике. В самом худшем случае это чревато падением ядра или плохо диагностируемыми проблемами. Обработка прерываний в потоке гораздо безопаснее, поскольку не сопряжена с общесистемными последствиями, но код в этом случае вынужден бороться за вычислительный ресурс с различными ISR (которые обладают более высоким приоритетом, чем любой поток), вытеснениями при перепланировании и более приоритетными потоками системы.
В случае, если выполнение критически важной работы пересекается с логикой обработки прерываний, начиная с редакции 2024
, разработчикам доступен альтернативный механизм, который называется критическими потоками реального времени (для краткости CRTT от англ. Critical Real-Time Threads). Они являются чем-то средним между ISR и потоком и, в общем случае, сочетают их преимущества. Но, ввиду своей дуалистической природы, использование CRTT все-таки не лишено некоторых рисков.
Достоинства:
Недостатки:
Отличия от реализации обычных потоков:
На следующем скриншоте представлен фрагмент системной трассы, полученной с помощью TraceKev:
Рассмотрим этот вырожденный, но наглядный пример, поясняющий логику реализации CRTT:
Можно заметить, что интересующий поток регулярно вытесняется более приоритетным кодом. Это следует из степени дискретности трассы потока на графике. Из условий эксперимента следует, что это вызвано влиянием IRQ1 и IRQ3, которые маршрутизируются контроллером прерываний на 1-ое процессорное ядро.
Подобное поведение и регулярные вытеснения вполне типичны для любой системы, особенно при высокой нагрузке. В сочетании с вытеснениями от более приоритетных потоков, эти факторы влияют на величину задержки (latency) произвольного потока. По завершении дпнного эксперимента счетчик прерываний будет содержать вполне предсказуемое значение, превышающее ~100 000.
Теперь повторим эксперимент, запустив ядро с опцией -z и обозначив целевой поток в качестве критического потока реального времени с помощью вызова ThreadCtl():
uint8_t isr_prio = 3;ThreadCtl( _NTO_TCTL_ISR_LEVEL_SET, (void *)isr_prio ); /* Поток должен обладать привилегиями суперпользователя */
На следующем скриншоте показан эквивалентный фрагмент системной трассы после внесения вышеуказанных изменений:
Можно выделить следующие существенные отличия:
Почему так происходит?
Для CRTT настраивается автоматическое маскирование и размаскирование прерываний, чей приоритет не выше isr_prio. Оно будет осуществляться каждый раз при переключении контекста. Поскольку в данном сценарии критический поток реального времени может быть вытеснен только ISR, его трасса становится абсолютно непрерывной. А поскольку счетчик прерываний в тесте был привязан именно к маскируемому прерыванию, основания для его увеличения также отсутствуют.
Продолжение обработки прерываний от таймера объясняется достаточно просто: функциональность критического потока реального времени распространяется лишь на процессорное ядро, на котором он выполняется в текущий момент времени. Поскольку в рамках thread affinity для CRTT настроена привязка к 1-му процессорному ядру, то его миграция на иные ядра не осуществляется. Как было оговорено в начале эксперимента, прерывание таймера ассоциировано с 0-ым процессорным ядром и автоматические маскирования его не затрагивают.
![]() | Несмотря на то, что критические потоки реального времени, не могут нарушать целостность ядра ОС (в отличие от классических ISR), их следует применять с соблюдением строгих мер предосторожности и ответственного подхода. Разработчикам настоятельно рекомендуется ознакомиться с настоящим параграфом перед проектированием систем, использующих CRTT. |
Злоупотребление критическими потоками реального времени может оказывать негативное (с точки зрения latency) общесистемное воздействие на все остальные потоки в системе. Сюда можно включить как длительное исполнение CRTT на одном и том же процессорном ядре, так и их массовое порождение. Это вполне очевидно, поскольку наделение потоков дополнительными преимуществами, это происходит за счет ущемления других потребителей этого ресурса. Таким образом, для порождения критических потоков должны существовать веские основания. Особенно если для их выполнения требуется значительная доля процессорного времени.
Применение CRTT, допустимо только при понимании общей логики работы контроллера прерываний и маршрутизации / приоритизации IRQ в конкретной проектируемой системе. Маршрутизация прерываний настраивается в модуле startup-*, причем, по умолчанию все прерывания маршрутизируются на 0-ое процессорное ядро и имеют одинаковый приоритет, кроме прерывания таймера, которое имеет повышенный приоритет, поскольку является источником общесистемного тактирования. Определять и устанавливать конкретные уровни приоритетов IRQ можно установить лишь путем анализа исходного кода модуля startup-*. Без сведений об этих приоритетах невозможно спроектировать предсказуемую систему, соответствующую генеральному дизайну. Появление CRTT, не согласованного с архитектурой системы, приведет лишь к многочисленным и сложно диагностируемым проблемам.
Из предыдущей рекомендации прямо вытекает недопустимость длительного маскирования прерываний таймера средствами CRTT (впрочем, как и любыми иными способами) ― осознанно или из-за недостаточно тщательного анализа конфигурации. Данный шаг может рассматриваться лишь как вмешательство в функционирование ядра ОС.
При длительном маскировании прерываний могут проявляться неочевидные особенности драйверов, которые проектировались без расчета на присутствие CRTT в системе. В общем случае это не является дефектом и требует лишь более тщательного распределения вычислительных ресурсов между потоками и настройки маршрутизации / приоритизации прерываний. Тем не менее, в отдельных случаях действительно может быть выполнена доработка конкретного драйвера.
Ошибки проектирования, которые вызывают конфликт между CRTT и другими важными процессами, могут приводить к нарушению ожидаемых временных характеристик этих процессов. Это не противоречит базовым принципам систем реального времени и устраняется посредством углубленного анализа архитектуры и конфигурации системы на этапе ее проектирования, поскольку лишь разработчик конечной системы определяет относительную важность тех или иных процессов. ОС лишь предоставляет различные механизмы, но не диктует обязательность их применения.
Предыдущий параграф призван обозначить риски, разработчику конечной системы следует сопоставлять с потенциальной пользой от применения CRTT.
![]() | В дальнейшем изложении мы исходим из того, что разработчик системы изучил параграф "Ограничения и управление рисками". |
Рассмотрим несколько вариантов использования CRTT для решения практических задач.
Несмотря на то, что этот способ применения представляется наиболее очевидным, он является далеко не самым востребованным. В тоже время, критические секции в потоке можно организовывать гораздо более простыми методами, применение которых крайне редко требуется масштабировать до общесистемного уровня. Однако, название CRTT произошло именно от этого способа применения.
В данном случае речь идет о создании невытесняемого кода, исполняемого на процессоре. В отличие от обычных примитивов синхронизации, такой код можно глобально синхронизировать как с другими потоками, так и с ISR. Оправданны ли в данном случае издержки – вопрос риторический.
С другой стороны, путем создания критической секции такого рода, можно жестко смещать баланс потребления вычислительных ресурсов в пользу CRTT для повышения производительности отдельных операций. Безусловно, это будет ущемлять как интересы других потребителей (latency), так и общую отзывчивость системы. Однако последнее не является значимым фактором для систем реального времени, что их фундаментально отличает от систем общего назначения.
Несмотря на ограниченную востребованность описанного подхода, с точки зрения архитектурных возможностей ОС, он имеет полное право на существование.
Во многих системах реального времени существуют как задачи (потоки), которые являются системообразующим, так и исполняющие вторичные, чисто утилитарные, функции. Задачи первого класса можно условно отнести к домену реального времени, который подлежит строгому контролю и анализу со стороны разработчика, а вторые должны получать вычислительный ресурс по остаточному принципу.
![]() | Стоит отдельно отметить, что для ядра ОС все потоки являются одноранговыми потоками реального времени, облада.щими разными приоритетами. Наделение их той или иной значимостью появляется лишь на этапе проектирования конечной системы исходя из возлагаемых на них функций. Такой информацией ядро ОС не располагает. |
Среди задач из домена реального времени, очевидно, могут найтись такие, значимость которых для системы является преобладающей. При реализации в качестве критических потоков реального времени появляется архитектурная возможность изолировать их от избыточного влияния остального системного кода и даже устройств периферии. Достигается это лишь обоснованным выбор уровней приоритетов планирования и автоматически маскируемых прерываний.
Развивая идею предыдущего сценария, можно смоделировать ситуацию, при которой эндосистемные сервисы диагностики и превентирования критических ситуаций смогли обнаружить фатальное событие. В этом случае от системы может потребоваться обеспечение управляемости ключевым системообразующим процессом (или процессами, если процессорных ядер больше одного), ради которого она проектировалась. Это прямое воплощение базового требования к системам реального времени – самая важная задача должна исполняться во что бы то ни стало (ОС должна создавать для этого условия).
В этом случае, в качестве отчаянного шага система может породить или заранее подготовить CRTT, который переведет процессорное ядро в режим линейного исполнения кода:
![]() | Следует повторно отметить, что этот шаг приводит к необратимой потере управления со стороны ОС. Однако, поскольку моделируется чрезвычайная ситуация, которую не смогла предотвратить проектируемая система, указанные меры реагирования на неё могут представляться обоснованными и сомасштабными. |
Еще один вариант применения критических потоков реального времени – безопасная обработка IRQ в потоке с минимальным временем отклика. Под временем отклика понимается интервал времени от возникновения внешнего события, до завершения его обработки – он включает как задержку (latency) при начале отработки, так и время реакции на событие. На оба компонента в полной мере влияет выполнение более приоритетных потребителей вычислительного ресурса. Применение CRTT может позитивно сказаться на обоих факторах, составляющих время отклика.
В рассматриваемом случае является желательным, хотя и не обязательным, привязка IRQ и CRTT к одному процессорному ядру. В зависимости от приемлемости накладных расходов, обусловленных межпроцессорными коммуникациями.
Пример минимального CRTT, выполняющего роль безопасного ISR:
#incldue <sys/siginfo.h>#include <sys/neutrino.h>int main( int argc, char** argv ){struct sigevent event;int id,irq = ???;uint8_t level = ???;/* Получение прав на регистрацию ISR */if ( ThreadCtl( _NTO_TCTL_IO, 0 ) == -1 )return (EXIT_FAILURE);/* Установка нового порога срабатывания прерываний */if ( ThreadCtl( _NTO_TCTL_SET_ISR_LEVEL, (void *)level ) == -1 )return (EXIT_FAILURE);/* Регистрация неявного ISR (поскольку мы не хотим иметь даже теоретическую возможность нарушить* работу ядра ОС, приемлемым остается лишь получение уведомлений о прерываниях).** Поскольку это событие только разблокирует соответствующий поток, мы будем просто получать управление* по каждому прерыванию. */SIGEV_INTR_INIT( &event );id = InterruptAttachEvent( irq, &event, 0 );if ( id == -1 )return (EXIT_FAILURE);/* Ожидание и обработка прерываний */while ( 1 ) {InterruptWait( NULL, NULL );/* Код обработки прерывания */.../* Выбор между размаскированием целевого прерывания до или после его фактической обработки зависит* от характера прерывания. */InterruptUnmask( irq, id );}return (EXIT_SUCCESS);}
![]() | Как и в случае с классическими ISR, рекомендуется минимизировать время исполнения критических потоков реального времени, если для этого нет особых оснований. В противном случае возможно снижение интерактивности всей проектируемой системы. Оценивать примелемость этих издержек должен разработчик конечной системы. |
InterruptAttach(), InterruptAttachEvent(), InterruptWait(), struct sigevent, ThreadCtl()
Предыдущий раздел: Обработчики прерываний