Шторм прерываний

Метки:  , , , , , , , , , , , , , , , , ,
Открытая статья. В процессе доработки.

В процессе работы с операционной системой Windows, пользователь может столкнуться с ощутимым снижением производительности рабочей станции. Естественно первое, что приходит на ум, это воспользоваться встроенным диагностическим средством под названием Диспетчер задач, при вызове которого на проблемной станции можно лицезреть следующую картину:

interrupt storm

Очевидно, что по какой то причине в системе вдруг появилась некая, пока еще неопознанная, нагрузка, "съедающая" 20-90% процессорного времени. При всем этом, красный цвет шкалы в окне Загрузка ЦП указывает на нагрузку, вызванную каким-то источником в ядре, иначе кодом, работающим в режиме ядра. Знакомая картина, не правда ли? С одной стороны, похожие проблемы с загрузкой ЦП уже освещались в статье про нагрузку на процессор. Однако, описываемый в данном материале подвид инцидентов стоит выделить в особую категорию, поскольку его отличают ряд отличительных особенностей, о которых у нас сейчас и пойдет речь. Давайте для дальнейшей диагностики используем системную утилиту под названием Монитор ресурсов, который даст нам очень интересный результат:

ISR DPC

Проблема начинает явно обособляться, и у нас четко просматривается виновник возникающей нагрузки: системные прерывания, которые включают в себя отложенные вызовы процедур (DPC) и процедуры обработки прерываний (ISR). С некоторыми оговорками можно говорить о такой явлении, как шторм прерываний.

Аппаратная часть: теория

Ну и коль скоро мы упомянули о шторме прерываний, не лишним было бы рассказать и о самих прерываниях. Архитектура x86-64 спроектирована таким образом, что в ней присутствует механизм под названием прерывания. Собственно из самого понятия "прерывание" становится очевидным, что что-то где-то чего-то прерывает с какой-то определенной целью.

Прерывание - запрос (исходящий от аппаратного или программного обеспечения), сообщающий процессору о наступлении события, требующего специальной обработки (обслуживания).

Прерывания бывают следующих видов:

  • внешние (асинхронные, аппаратные) - события, инициируемые в любой произвольный момент внешними аппаратными устройствами: сигнал таймера, видеокарты, сетевого адаптера или дискового накопителя, нажатие клавиш клавиатуры, движение мыши. Факт возникновения в системе подобного прерывания расценивается как запрос на прерывание текущей работы (Interrupt request, IRQ) - устройства сообщают, что они требуют внимания со стороны операционной системы;
  • внутренние (синхронные) - события, возникающие в самом центральном процессоре, как результат выполнения ряда условий в ходе исполнения машинного кода: деление на ноль, переполнение стека, недопустимый код операции или обращение к недопустимым адресам (областям) памяти;
  • программные (подвид внутренних/синхронных) - события, инициируемые при выполнении на процессоре специальной инструкции в коде программы (int). Программные прерывания, как правило, используются для обращения к функциям встроенного программного обеспечения (UEFI/BIOS, firmware), драйверов и операционной системы.

Если рассматривать аппаратные прерывания, то они были разработаны с целью оптимизировать архитектуру по скорости обработки запросов от устройств, подключенных к общей системной шине. Как вы знаете, устройства компьютера находятся в постоянной работе: на сетевую карту приходят пакеты данных, непрерывно генерируются сигналы от таймера, пользователь время от времени производит нажатие клавиш клавиатуры/мыши, а так же происходит множество других событий. Так вот, дабы процессор не тратил своё драгоценное время на ожидание в циклическом опросе подключенных аппаратных устройств на предмет наличия данных, было решено добавить механизм, позволяющий самим устройствам сообщать/сигнализировать центральному процессору о необходимости прерывания текущей задачи и обработки поступившей информации. При реализации механизма прерываний в определенной степени оптимизируется быстродействие за счет экономии процессорного времени.
Аппаратные прерывания градируются следующим образом:

  • Прерывания, инициируемые сигналом на линии (Line-Based Interrupts, LBI) - базируется на наличии у устройства линии (пина) прерывания, на которой выставляется сигнал о необходимости обработки;
  • Прерывания, инициируемые сообщениями (Message-Signalled Interrupts, MSI[-X]) - базируется на записи сообщения (блока данных определенного формата) в адресное пространство, отображаемое на адреса локального контроллера прерываний (LAPIC) процессора;
Механизмы LBI и MSI для одного и того же устройства являются взаимоисключающими. Таким образом, при включении механизма MSI запрещено использовать вывод INTx# для запроса на обслуживание.

В зависимости от того, какой именно механизм используется для запроса на обслуживание, либо устройство "заземляет" один из своих контактов INT{A/B/C/D}# ("дергает" свою линию прерывания) или же формирует/отсылает сообщение MSI. Сформированный тем или иным образом запрос прерывания обрабатывается определяемыми используемой архитектурой аппаратными средствами, так в архитектуре IBM-PC-совместимых компьютеров используются так называемые контроллеры прерываний (PIC/APIC/LAPIC/IOAPIC), которые физически реализуются на уровне элементной базы (чипсет/процессор) компьютера и обеспечивают обслуживание всех видов запросов на обслуживание (прерываний/сообщений) от аппаратных устройств.
Так же, контроллеры, работающие с традиционной сигнальной схемой (LBI), поддерживают такие режимы как:

  • Режим триггера прерывания, срабатывающего по фронту (запускаемого фронтом) сигнала (edge interrupt trigger mode);
  • Режим триггера прерывания, срабатывающего по уровню (запускаемого уровнем) сигнала (level interrupt trigger mode);

Старые системы с шиной ISA на материнской плате не поддерживали триггера прерываний по уровню, соответственно PC/XT, PC/AT-совместимые системы должны были быть запрограммированы на фронтальный триггер прерываний. На системах с шиной MCA, устройства использовали триггер прерываний, срабатывающий по уровню, соответственно и контроллер прерываний запрограммирован всегда работать в режиме срабатывания по уровню. В современных системах (с шинами EISA/PCI/PCIe) применяется универсальные регистры контроля фронта/уровня (Edge/Level Control Registers, ELCR), контролирующие режим на каждой линии IRQ. Регистры ELCR программируются кодом BIOS/UEFI на этапе начальной инициализации станции, размещаются по адресам 0x4D0 и 0x4D1 в адресном пространстве ввода-вывода (архитектура x86) и являются 8-битными.

Логика работы

Внешние прерывания ввода-вывода в виде сигнала поступают на одну из линий контроллера прерываний (IRQ0-IRQ15), либо в виде сообщения, записываемого в зарезервированную область памяти. Современные контроллеры комбинируют множество источников в единую линию прерывания, соединенную с процессором (INTR), а так же отслеживают факт передачи сообщения по битовым шаблонам (при записи сообщения в память через шину данных). Таким образом контроллер, получая сигнал/сообщение (IRQx/MSI) от подчиненного устройства, в свою очередь прерывает работу процессора. Выбранный процессор (ядро) передает контроллеру запрос прерывания (Interrupt request, IRQ), в ответ на который контроллер прерываний превращает IRQ в номер прерывания, использует этот номер в качестве индекса во внутренней структуре под названием таблица дескрипторов прерываний и возвращает процессору вектор прерывания.

IDT (Таблица векторов прерываний, Interrupt Descriptor Table) - таблица, состоящая из (8/16-байтовых) дескрипторов, определяющих адреса обработчиков и права доступа для аппаратных/программных прерываний, а так же исключений (прерываний, зарезервированных процессором).

В процессорах x86 (судя по названию) таблица заполняется дескрипторами, которые можно (с оговорками) рассматривать в качестве указателей на функции ядра, которые уже должны обеспечивать непосредственную обработку прерываний/исключений. Дескрипторы, в свою очередь, могут быть трех типов: шлюз прерывания, шлюз ловушки и шлюз задачи. В операционной системе Windows существует схожая таблица, которая (не смотря на полную схожесть аббревиатуры) носит название таблицы диспетчеризации прерываний (Interrupt Dispatch Table) и подготавливается ядром на этапе загрузки операционной системы.
Windows отображает аппаратные IRQ-запросы на номера прерываний в IDT и также использует IDT для настройки обработчиков системных прерываний для исключений. Именно на основании данных из IDT процессор производит обработку прерывания, осуществляя передачу управления соответствующей процедуре обработки. Таким образом, операционная система Windows организует диспетчеризацию прерываний.

Диспетчеризация прерываний - механизм передачи процессором управления подпрограмме обработчика прерывания для обслуживания аппаратно-программного события.

Содержимое IDT, включая информацию о том, какие обработчики системных прерываний назначены операционной системой Windows прерываниям (включая исключения и IRQ-запросы), можно посмотреть, используя команду !idt отладчика WinDbg.

Термин "ловушка" (trap) иногда используется как синоним термина "прерывание".

Общие причины

Давайте попробуем собрать распространенные причины возникновения шторма прерываний на аппаратном и программном уровнях:

  1. Устройство не освобождает (не снимает) сигнал прерывания с линии (INTx#) по требованию собственного драйвера.
  2. Драйвер устройства не инициирует запрос на снятие сигнала прерывания (INTx#) с обслуживаемого устройства (EOI?), по причине того, что не определяет момент выставления сигнала прерывания устройством.
  3. Драйвер устройства заявляет о прерывании в случае, когда прерывание не было инициировано обслуживаемым устройством (случается, когда множество устройств используют одно и то же IRQ).
  4. Регистр контроля фронта/уровня (ELCR) содержит некорректное значение.
  5. Устройства, работающие в обеих режимах триггера прерывания (по фронтам и по уровню), разделяют одно и то же IRQ (например, COM-порт и PCI SCSI-контроллер).
  6. Драйвер контроллера прерываний постоянно сообщает о наличии прерывания от устройства, при этом не производя его обработку. В этом случае создается ситуация, когда прерывания от устройств не обслуживаются. Устройство, выставляющее сигнал "прерывание", не обслуживается и вынуждено вновь выставить прерывание, которое, опять же, не обслуживается, и так в цикле.
  7. Некоторые системные драйвера репортуют о том, что подчиненные устройства требуют обработки прерывания, в то время как на самом деле данные устройства не выставляют запрос на прерывание. Подобное случае когда устройства на материнской плате работают некорректно.

В общем случае, если исключить все косвенные факторы влияния, всё сводится к двум основным причинам:

  1. Неисправному (сбоящему) аппаратному устройству;
  2. Проблемному (глючному) драйверу устройства;

Программная часть: обнаружение источника нагрузки

Для нахождения причины дисфункции может быть использован довольно широкий круг инструментария, тем не менее наиболее приспособлены для данной задачи следующие приложения:

  1. Проведение диагностики при помощи утилит комплекта Windows Performance Tools, методика частично описана в статье о том, что грузит процессор;
  2. Проведение локальной/дистанционной отладки ядра при помощи отладчика WinDbg;
  3. Диагностика при помощи утилиты LatencyMon;
  4. Диагностика при помощи утилиты DPC Latency Checker;
  5. Диагностика при помощи утилиты Process Hacker;

Источник: сторонний драйвер

При определении источника шторма прерываний при помощи одного из перечисленных выше средств диагностики, подозрение часто падает на драйвер(а) стороннего производителя, поскольку именно они имеют проблемы со стабильностью кода. Типичный пример выявления драйвера, который ответственен за шторм прерываний при помощи утилиты LatencyMon:

driver interrupt storm

Очевидно что это относительно простой случай. На картинке отчетливо видно имя виновного драйвера, им, в нашем конкретном случае является HECIx64.sys, входящий в комплект драйверов Intel Management Engine Interface. В любом случае, по имени драйвера-источника в Сети Интернет (при помощи поисковика) можно всегда найти полное наименование комплекта драйверов, содержащего в своем составе упоминаемый драйвер. Общая последовательность действий выглядит следующим образом:

  • По имени драйвера определить принадлежность к определенному программному комплексу или аппаратному устройству;
  • Обновить непосредственно драйвер или программный комплекс, включающий в себя (имеющий в своем составе) проблемный драйвер;
  • Проверить работоспособность сопутствующих служб/сервисов, если таковые были установлены вместе с драйвером устройства;
  • Отключить/заменить устройство или изменить аппаратную конфигурацию (настройки BIOS/собственной прошивки) подчиненного устройства;

Эти простые действия, в большинстве случаев, приводят к действенным положительным результатам.

Источник: системный драйвер

Но встречаются и ситуации, в которых диагностика инцидента, связанного со штормом прерываний, значительно усложняется. Это происходит, когда виновником является один из системных драйверов (драйверов, входящих в состав операционной системы).

acpi.sys dpc

Подобного рода проблемы всегда обескураживают технических специалистов, поскольку степень доверия к системным драйвера очень высока (вроде как они хорошо отлажены), возникает ощущение, что сам системный драйвер не может являться причиной шторма прерываний. Другое дело, что в ядре Windows могла бы быть реализована логика по предотвращению шторма прерываний даже в случае некорректного поведения аппаратных устройств, и еще можно было бы реализовать расширенную диагностику по быстрому выявлению сбойного подчиненного устройства. Тем не менее, судя по информации с технических форумов Microsoft, подобное до сих пор не реализовано и проблема стара как мир.

По снимку экрана видно, что в данном случае источником нагрузки на процессор является большое количество вызовов функций системного драйвера acpi.sys. При этом, в некоторых случаях при трассировке в отчетах отмечаются только процедуры верхнего уровня: ACPIInterruptDispatchEventDpcACPIInterruptServiceRoutine, ACPIDevicePowerDPC, ACPIInterruptServiceRoutineDPC, в других случаях удается записать стеки вызовов, которые упираются во взаимодействие кода драйвера acpi.sys с функциями уровня аппаратных абстракций hal.dll, работающими с конфигурационным пространством PCI.

acpi.sys - системный драйвер, обеспечивающий поддержку (со стороны ОС) стандарта ACPI, являющегося интерфейсом к таким функциям аппаратного обеспечения, как: поиск, конфигурирование, управление питанием и мониторинг устройств.

В общем случае, драйвер отвечает за обработку прерываний по событиям, относящимся к Управлению питанием и обработку прерываний от устройств на материнской плате. ACPI это средство, которым BIOS/UEFI сообщает системе, какие устройства имеются в системе, где они расположены, какие используют ресурсы, какие топологии шин подключены, какие возможности по управлению питанием есть в наличии, и так далее.
Стандарт ACPI обеспечивает следующий функционал:

  • обеспечение интерфейса к шине и контроллеру (SMBus);
  • контроль за потреблением энергии устройств: различные состояния/режимы энергопотребления компонентов;
  • мониторинг состояния оборудования: отслеживание температуры, скорости работы вентиляторов и прочих датчиков;
  • автоматическое конфигурирование устройств;
  • обслуживание системных событий;

Драйвер acpi.sys общается с:

  • ACPI-таблицы конфигурации -- блоки определения, описывающие интерфейсы аппаратных средств.
  • ACPI-регистры -- часть описания интерфейсов из ACPI-таблиц, организованная в виде регистров (для доступа к устройствам, не поддерживающим PCI пространство конфигурации).
  • ACPI-BIOS -- часть кода BIOS/UEFI, которая совместима с ACPI-спецификациями. Как правило это код, отвечающий за загрузку, засыпание/пробуждение и перезагрузку станции.

Помимо того, что ACPI выполняет огромную роль в процессе загрузки компьютера, нужно помнить, что ACPI так же участвует в управлении различными аспектами функционирования оборудования на протяжении всего цикла работы. И осуществляется это при помощи так называемых событий общего назначения (ACPI General Purpose Event, GPE).

  • От аппаратных устройств посредством ACPI BIOS поступают события управления питанием (PM Event, Power Management Event), логика в чипсете принимает данные сообщения и выставляет признак события GPE.
  • Старые PCI-устройства и устройства, интегрированные в чипсет, посылают уведомление непосредственно через ACPI.

ACPI-таблица содержит пространства, закрепленные за определенными системными ресурсами, и когда аппаратное обеспечение запускает прерывание ACPI, функции ядра считывают значения из этой области, чтобы увидеть, какие GPE помечены. Код ACPI ASL может использовать идентификатор инициатора (источника) для информирования операционной системы об устройстве, вызвавшем событие.

Отладка производилась в режиме прямой (в момент выполнения) отладки ядра (live kernel debug), поэтому данные могут быть "не целостными", рекомендуется удаленная отладка.

Это все хорошо, однако нам хотелось бы докопаться до причины даже в отсутствии внятных средств диагностики проблемы. Сразу возникает ряд вопросов: через какие механизмы/события/прерывания устройства сигнализируют о состоянии управления питанием? С какими именно аппаратными устройствами работает драйвер acpi.sys? Например, на одной из рабочих машин, при помощи команды !acpiirqarb (показывает структуру ACPI IRQ арбитра, содержащую конфигурацию устройств ввода-вывода на входах контроллера прерываний и таблицу диспетчеризации прерываний (IDT) выбранного процессора) я получил следующий вывод:

Ну, похоже на перечисление аппаратных устройств системы. В идеальном случае, хотелось бы наблюдать однозначное соответствие между номером вектора прерывания в таблице IDT и сопоставленной процедурой обработки, по имени которой можно было бы достоверно определить сопоставленное оборудование. Собственно, в выводе команды присутствуют номера векторов прерываний (записей) в таблице IDT (первые две колонки), которые сопоставлены с именем устройства в системе (6-я колонка). Теперь попробуем найти функцию обработки прерывания и для этого воспользуемся командой !idt -a:

В первой колонке мы видим номер вектора прерывания, во второй адрес обработчика, в третьей присутствует имя функции-обработчика. Как уже говорилось, в таблице IDT присутствуют дескрипторы-указатели на функции ядра, являющиеся обработчиками прерываний устройств. Но на деле, как можно наблюдать по выводу выше, только часть обработчиков (исключения/программные прерывания) в таблице IDT имеют имена, хоть как-то определяющие назначение функции-обработчика. По именам всех остальных элементов таблицы (обработчиков аппаратных прерываний) сложно определить, с каким именно устройством они взаимосвязаны. Все замаскировано под некую функцию-диспетчер KiIsrThunkShadow, которая в своем коде (с разным смешением от начала), содержит 256 шаблонов перенаправления вызова. То есть, непосредственно диспетчеризация прерываний ядром Windows представляет собой многоступенчатый процесс, в ходе которого выполняется некоторое количество подготовительных действий перед передачей управления драйверам, зарегистрировавшим процедуры обработки прерываний (ISR).

Из вывода команды !idt мы получаем номера прерываний и соответствующие имена внутренних функций ядра.

Так, получается, что в стек мы заталкиваем номер вектора прерывания (например 50h в строке 3), после чего функция-диспетчер KiIsrThunkShadow передает управление к подчиненным функциям KxIsrLinkageShadow. Сама же функция KxIsrLinkageShadow представляет собой обертку к KxIsrLinkage/KiIsrLinkage, выполняющие предварительные проверочные/настроечные действия базового адреса стека ядра, значения PML4 (адресное пространство ядра) с инициализацией CR3. Функция KiIsrLinkage выполняет следующие действия:

  • Сохраняет значения используемых регистров в структуре KTRAP_FRAME, созданной в стеке.
  • Проверяет есть ли активная обработка прерывания, ЦП выполнял инструкции в пределах определенного региона функции ExpInterlockedPopEntrySList* и если это так, то сбрасывает указатель исполняемой инструкции (RIP).
  • Проверяет, отключены ли прерывания и если это так, то производит останов системы с кодом TRAP_CAUSE_UNKNOWN.
  • Регистрирует указатель на структуру прерывания, ассоциированную с прерыванием и производит обслуживание прерывания.
  • Восстанавливает значения используемых регистров из структуры KTRAP_FRAME.
  • Производит возврат из процедуры обработки прерывания.

Но главное, на что стоит обратить внимание, так это, что внутри функции KiIsrLinkage каким-то образом определяется адрес обработчика, зарегистрированного тем или иным драйвером устройства. Скорее всего как-то задействована структура KINTERRUPT, однако с кодом я пока не разобрался.

... ДОПОЛНЕНИЕ СЛЕДУЕТ

Общие рекомендации

Ну куда же нам деться от хорошо нам знакомых "общих рекомендаций", называемых, так же, "танцами с бубном":

  1. Проблема связана с Power Management и SATA AHCI драйвером. В случае с материнской платы на чипсете Intel: при наличии установленного Intel Rapid Storage Technolody Driver (RST), откатить драйвер на предыдущую версию. Ссылка для скачивания с официального сайта.
  2. Проблема с режимами работы шины PCI Express. Открываем Панель управления - Электропитание - опция Настройка плана электропитания для текущего плана - Изменить дополнительные параметры питания - ветвь PCI Express - развернуть Управление питанием состояния связи - установить в значение Откл.
    Если нет этой опции, то идем в реестр по пути HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\501a4d13-42af-4429-9fd1-a8218c268e20\ee12f906-d277-404b-b6da-e5fa1a576df5, создаем параметр с именем Attributes типа DWORD и выставляем значение 0.
  3. Проблема с сетевым адаптером. Открываем Диспетчер устройств - раздел Сетевые адаптеры - находим установленную сетевую карту - в свойствах открываем вкладку Управление электропитанием - Отключить опции Разрешить этому устройству выводить компьютер из ждущего режима (Wake-on-LAN (WOL)) и Разрешать вывод компьютера из ждущего режима только с помощью "магического пакета" (Disabling magic packets).

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *