База полезных знаний

Объекты прерываний

Ядро предоставляет переносимый механизм — управляющий объект ядра, называемый объектом прерывания, который позволяет драйверам устройств регистрировать процедуры обработки прерываний (ISR) для своих устройств.

Объект прерывания содержит всю информацию, необходимую ядру для того, чтобы связать ISR устройства с конкретным уровнем прерывания, включая адрес ISR, IRQL-уровень, на котором прерывается устройство, и запись в таблице диспетчеризации прерываний ядра (IDT), с которой нужно сопоставить ISR. При инициализации объекта прерывания в нем сохраняются несколько инструкций на языке ассемблера, так называемый код диспетчеризации, копируемый из шаблона обработки прерывания, KiInterruptTemplate. Этот код выполняется при возникновении прерывания.

Этот резидентный код объекта прерывания вызывает настоящий диспетчер прерывания, которым обычно является либо процедура KiInterruptDispatch, либо процедура KiChainedDispatch, передавая ему указатель на объект прерывания.

KiInterruptDispatch является процедурой, используемой для векторов прерывания, для которых зарегистрирован только один объект прерывания. KiChainedDispatch является процедурой для векторов, совместно используемых несколькими объектами прерываний. Объект прерывания содержит информацию, которая нужна этой второй процедуре диспетчера для обнаружения и правильного вызова ISR-процедуры, предоставляемой драйвером устройства.

Объект прерывания также хранит в себе IRQL, связанный с прерыванием, чтобы процедура KiInterruptDispatch или KiChainedDispatch перед вызовом ISR смогла поднять IRQL на правильный уровень, а затем, после возвращения управления из ISR, соответствующим образом понизить IRQL.

Этот процесс, выполняемый в два этапа, нужен потому, что при начальной диспетчеризации способов передачи указателя (или какого-нибудь другого аргумента на этот случай) объекту прерывания не существует, поскольку начальная диспетчеризация осуществляется оборудованием. На мультипроцессорной системе ядро выделяет и инициализирует объект прерывания для каждого центрального процессора, позволяя локальному APIC этого процессора принимать конкретное прерывание.

На Windows-системах x64 ядро оптимизирует диспетчер прерываний, используя специальные процедуры, сохраняющие циклы процессора, пропуская ненужные функции, например, функцию KiInterruptDispatchNoLock, используемую для прерываний, не имеющих связанную с ними и управляемую ядром спин-блокировку (обычно используется драйверами, требующими синхронизации с их ISR-процедурами), и функцию KiInterruptDispatchNoEOI.

Функция KiInterruptDispatchNoEOI используется для прерываний, запрограммировавших APIC в режим автоматического завершения прерывания — «Auto-End-of-Interrupt» (Auto-EOI), — поскольку контроллер прерываний пошлет сигнал EOI автоматически, ядру не нужно выполнять дополнительный код для самостоятельного осуществления EOI. И наконец, конкретно для прерывания профилирования производительности (performance/profiling) используется обработчик KiInterruptDispatchLBControl, поддерживающий регистр управления последним условным переходом Last Branch Control MSR, доступный на современных центральных процессорах.

Этот регистр позволяет ядру отслеживать или сохранять инструкцию условного перехода при трассировке; при прерывании эта информация будет потеряна, поскольку она не сохраняется в контексте регистра обычного прерывания, поэтому для его сохранения должен быть добавлен специальный код. Эта функция используется, к примеру, прерываниями производительности и профилирования HAL, в то время как другие процедуры прерываний HAL пользуются «неблокируемым» кодом диспетчеризации, поскольку HAL не требует от ядра синхронизации с его ISR.

Еще один обработчик прерывания ядра KiFloatingDispatch используется для прерываний, требующих сохранения состояния в формате числа с плавающей точкой. В отличие от кода режима ядра, которому обычно не разрешается использовать операции с плавающей точкой (MMX, SSE, 3DNow!), поскольку относящиеся к ним регистры не будут сохраняться при переключениях контекста, ISR-процедуры могут нуждаться в использовании этих регистров (например, ISR видеокарты, выполняющей быструю операцию рисования).

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

На рисунке показана типовая схема управления прерываниями, связанными с объектами прерываний.

Эксперимент: Изучение внутреннего устройства прерываний.

Используя отладчик ядра, можно просмотреть детали объекта прерывания, включая его IRQL, адрес ISR и специализированный код диспетчеризации прерывания. Сначала нужно запустить команду !idt и обнаружить запись, включающую ссылку на I8042KeyboardInterruptService, ISR-процедуру для PS2-клавиатуры:

81: fffffa80045bae10 i8042prt!I8042KeyboardInterruptService (KINTERRUPT fffffa80045bad80)

Для просмотра содержимого объекта прерывания, связанного с данным прерыванием, нужно запустить команду dt nt!_kinterrupt с адресом, следующим за KINTERRUPT:

lkd> dt nt!_KINTERRUPT fffffa80045bad80

+0x000 Type : 22

+0x002 Size : 160

+0x008 InterruptListEntry : _LIST_ENTRY [ 0x00000000'00000000 - 0x0 ]

+0x018 ServiceRoutine : 0xfffff880'0356ca04 unsigned char

i8042prt!I8042KeyboardInterruptService+0

+0x020 MessageServiceRoutine : (null)

+0x028 MessageIndex : 0

+0x030 ServiceContext : 0xfffffa80'02c839f0

+0x038 SpinLock : 0

+0x040 TickCount : 0

+0x048 ActualLock : 0xfffffa80'02c83b50 -> 0

+0x050 DispatchAddress : 0xfffff800'01a7db90 void nt!KiInterruptDispatch+0

+0x058 Vector : 0x81

+0x05c Irql : 0x8 ''

+0x05d SynchronizeIrql : 0x9 ''

+0x05e FloatingSave : 0 ''

+0x05f Connected : 0x1 ''

+0x060 Number : 0

+0x064 ShareVector : 0 ''

+0x065 Pad : [3] ""

+0x068 Mode : 1 ( Latched )

+0x06c Polarity : 0 ( InterruptPolarityUnknown )

+0x070 ServiceCount : 0

+0x074 DispatchCount : 0

+0x078 Rsvd1 : 0

+0x080 TrapFrame : 0xfffff800'0185ab00 _KTRAP_FRAME

+0x088 Reserved : (null)

+0x090 DispatchCode : [4] 0x8d485550

В данном примере IRQL, присваиваемый Windows прерыванию, равен 8. Хотя прямого отображения вектора прерывания на IRQ нет, Windows отслеживает это преобразование при управлении ресурсами устройства через механизм так называемых арбитров. Для каждого типа ресурсов арбитр поддерживает связь между виртуальным использованием ресурсов (например, вектором прерывания) и физическими ресурсами (например, линией прерывания).

Таким образом можно запросить либо корневой IRQ-арбитр (на системах без ACPI), либо ACPI IRQ-арбитр и получить это отображение.

Для получения информации об ACPI IRQ-арбитре нужно воспользоваться командой !apciirqarb:

lkd> !acpiirqarb

Processor 0 (0, 0):

Device Object: 0000000000000000

Current IDT Allocation:

...

0000000000000081 - 0000000000000081 D fffffa80029b4c20 (i8042prt)

A:0000000000000000 IRQ:0

...

Если используется система без ACPI, можно воспользоваться командой !arbiter 4 (Цифра 4 заставит отладчик показать только IRQ-арбитры):

lkd> !arbiter 4

DEVNODE fffffa80027c6d90 (HTREE\ROOT\0)

Interrupt Arbiter "RootIRQ" at fffff80001c82500

Allocated ranges:

0000000000000081 - 0000000000000081 Owner fffffa80029b4c20 (i8042prt)

В обоих случаях вам будет предоставлен владелец вектора по типу объекта устройства. Затем вы можете воспользоваться командой !devobj, чтобы получить в этом примере информацию об устройстве i8042prt (которое соответствует драйверу PS/2):

lkd> !devobj fffffa80029b4c20

Device object (fffffa80029b4c20) is for:

00000061 \Driver\ACPI DriverObject fffffa8002888e70

Current Irp 00000000 RefCount 1 Type 00000032 Flags 00003040

Dacl fffff9a100096a41 DevExt fffffa800299f740 DevObjExt fffffa80029b4d70 DevNode

fffffa80029b54b0

Объект устройства связан с узлом устройства, хранящим все физические ресурсы этого устройства.

Теперь с помощью команды !devnode можно вывести дамп этих ресурсов и воспользоваться ключом 6, чтобы запросить информацию о ресурсах:

lkd> !devnode fffffa80029b54b0 6

DevNode 0xfffffa80029b54b0 for PDO 0xfffffa80029b4c20

Parent 0xfffffa800299b390 Sibling 0xfffffa80029b5230 Child 0000000000

InstancePath is "ACPI\PNP0303\4&17aa870d&0"

ServiceName is "i8042prt"

...

CmResourceList at 0xfffff8a00185bf40 Version 1.1 Interface 0xf Bus #0

Entry 0 - Port (0x1) Device Exclusive (0x1)

Flags (0x11) - PORT_MEMORY PORT_IO 16_BIT_DECODE

Range starts at 0x60 for 0x1 bytes

Entry 1 - Port (0x1) Device Exclusive (0x1)

Flags (0x11) - PORT_MEMORY PORT_IO 16_BIT_DECODE

Range starts at 0x64 for 0x1 bytes

Entry 2 - Port (0x1) Device Exclusive (0x1)

Flags (0x11) - PORT_MEMORY PORT_IO 16_BIT_DECODE

Range starts at 0x62 for 0x1 bytes

Entry 3 - Port (0x1) Device Exclusive (0x1)

Flags (0x11) - PORT_MEMORY PORT_IO 16_BIT_DECODE

Range starts at 0x66 for 0x1 bytes

Entry 4 - Interrupt (0x2) Device Exclusive (0x1)

Flags (0x01) - LATCHED

Level 0x1, Vector 0x1, Group 0, Affinity 0xffffffff

Узел устройства сообщает, что у данного устройства есть перечень ресурсов с четырьмя записями, одна из которых является записью прерывания, соответствующего IRQ 1. (Номера уровня и вектора представляют вектор IRQ, а не вектор прерывания.) IRQ 1 является традиционным номером PC/ATIRQ, связанным с клавиатурой PS/2, следовательно, это вполне ожидаемое значение. (У USB-клавиатуры будет другое прерывание.)

На ACPI-системах эту информацию можно получить более легким путем, прочитав расширенный вывод представленной ранее команды !acpiirqarb.

В качестве части своего вывода эта команда показывает таблицу отображения IRQ на IDT:

Interrupt Controller (Inputs: 0x0-0x17 Dev: 0000000000000000):

(00)Cur:IDT-a1 Ref-1 edg hi Pos:IDT-00 Ref-0 edg hi

(01)Cur:IDT-81 Ref-1 edg hi Pos:IDT-00 Ref-0 edg hi

(02)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(03)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(04)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(05)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(06)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(07)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(08)Cur:IDT-71 Ref-1 edg hi Pos:IDT-00 Ref-0 edg hi

(09)Cur:IDT-b1 Ref-1 lev hi Pos:IDT-00 Ref-0 edg hi

(0a)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(0b)Cur:IDT-00 Ref-0 edg hi Pos:IDT-00 Ref-0 edg hi

(0c)Cur:IDT-91 Ref-1 edg hi Pos:IDT-00 Ref-0 edg hi

(0d)Cur:IDT-61 Ref-1 edg hi Pos:IDT-00 Ref-0 edg hi

(0e)Cur:IDT-82 Ref-1 edg hi Pos:IDT-00 Ref-0 edg hi

(0f)Cur:IDT-72 Ref-1 edg hi Pos:IDT-00 Ref-0 edg hi

(10)Cur:IDT-51 Ref-3 lev low Pos:IDT-00 Ref-0 edg hi

(11)Cur:IDT-b2 Ref-1 lev low Pos:IDT-00 Ref-0 edg hi

(12)Cur:IDT-a2 Ref-5 lev low Pos:IDT-00 Ref-0 edg hi

(13)Cur:IDT-92 Ref-1 lev low Pos:IDT-00 Ref-0 edg hi

(14)Cur:IDT-62 Ref-2 lev low Pos:IDT-00 Ref-0 edg hi

(15)Cur:IDT-a3 Ref-2 lev low Pos:IDT-00 Ref-0 edg hi

(16)Cur:IDT-b3 Ref-1 lev low Pos:IDT-00 Ref-0 edg hi

(17)Cur:IDT-52 Ref-1 lev low Pos:IDT-00 Ref-0 edg hi

Как и ожидалось, IRQ 1 связан с IDT-записью 0x81.

Адрес ISR-процедуры для объекта прерывания хранится в поле ServiceRoutine(которое команда !idt показывает в своем выводе), а код прерывания, выполняемый при его возникновении, хранится в массиве DispatchCodeв конце объекта прерывания. Хранящийся там код прерывания запрограммирован на создание фрейма системного прерывания в стеке и на последующий вызов функции, чей адрес хранится в поле DispatchAddress(KiInterruptDispatchв данном примере), с передачей этой функции указателя на объект прерывания.

Exit mobile version