Программирование драйверов Windows

       

Обслуживание прерываний


В операционной системе Windows NT 5 все прерывания изначально обрабатываются ядром. Это сделано для облегчения переносимости операционной системы на разные аппаратные платформы. Ядро обеспечивает диспетчеризацию прерываний между драйверами путем создания и последующего подключения объектов прерывания по вызову IoConnectInterrupt

(см. таблицу 8.10). Этот вызов получает адрес процедуры для обслуживания прерываний (ISR, Interrupt Service Routine) драйвера, и таким образом операционная система связывает указанное аппаратное прерывание с определенным драйвером и принадлежащей ему ISR функцией.

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

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

Диспетчер прерываний режима ядра вызывает ISR процедуру на уровне синхронизации SynchronizeIrql, указанном в вызове IoConnectInterrupt. Обычно, это один из DIRQL уровней. Кроме того, диспетчер прерываний получает владение над объектом спин-блокировки pSpinLock и удерживает ее во время выполнения ISR процедуры, что предохраняет от выполнения ISR процедуры на других процессорах.

При выполнении на столь высоком уровне IRQL существует некоторые вещи, которые процедура ISR не может себе позволить. В дополнении к обычным предостережениям избегать манипуляций со страничной памятью, ISR процедура не должна пытаться получать или освобождать какие-либо системные ресурсы, даже нестраничную память. Если разработчик предполагает сделать системный вызов из ISR процедуры, следует обязательно обратить внимание на уровень, на котором тот может выполняться.
Вполне вероятно, что такие системные вызовы придется перепоручить DPC процедуре, запуск которой вполне может запланировать данная функция обслуживания прерываний.

Таблица 8.10. Прототип функции IoConnectInterrupt

NTSTATUS IoConnectInterrupt IRQL == PASSIVE_LEVEL
Параметры Регистрирует процедуру обслуживания прерывания, предоставляемую драйвером, и "подключает" ее к источнику прерываний
OUT PKINTERRUPT

*pInterruptObject
Адрес указателя, в котором будет возращен указатель на объект прерывания
IN PKSERVICE_ROUTINE

ServiceRoutine
Процедура (функция) драйвера, которая теперь будет обслуживать прерывание
IN PVOID pServiceContext Аргумент, передаваемый в процедуру ISR, обычно рекомендуется приводить здесь указатель на структуру расширения объекта устройства
IN PKSPIN_LOCK pSpinLock Инициализированный объект спин-блокировки
IN ULONG Vector Транслированное значение вектора прерывания
IN KIRQL Irql Значение DIRQL для данного устройства
IN KIRQL SynchronizeIrql Обычно равно значению Irql
IN KINTERRUPT_MODE InterruptMode Аппаратный тип прерывания. Одно из значений:

• LevelSensitive

• Latched
IN BOOLEAN isSharableVector Если TRUE &#8212 данный вектор прерывания является совместно используемым (разделяемым)
IN KAFFINITY ProcessorEnableMask Установить набор процессоров, которые могут получать сигналы прерывания
IN BOOLEAN doFloatingSave Если TRUE &#8212 сохранять состояние регистров сопроцессора (FPU). Обычно используется FALSE
Возвращаемое значение • STATUS_SUCCESS

• STATUS_INVALID_PARAMETER

• STATUS_UNSUFFUCIENT_RESOURCES
Таблица 8.11. Прототип функции драйвера для обслуживания прерываний

BOOLEAN ISR IRQL == DIRQL
Параметры Процедура драйвера, предоставляемая им для обслуживания прерывания
IN PKINTERRUPT *pInterruptObject Объект прерывания, "генерирующий" прерывания
IN VOID pServiceContext Контекстный аргумент, указанный при регистрации в IoConnectInterrupt
Возвращаемое значение • TRUE &#8212 прерывание было обслужено ISR

• FALSE &#8212 прерывание не обслуживается
<


Параметр InterruptMode вызова IoConnectInterrupt интерпретируется операционной системой следующим образом. Когда драйверы подключили свои ISR процедуры к прерыванию, считая его LevelSensitive, операционная система вызывает все подключенные таким образом ISR функции до тех пор, пока одна из них не возвратит TRUE. В противном случае, то есть если указано значение Latched для параметра InterruptMode, то операционная система вызывает все из подключенных таким образом ISR процедур, и эти вызовы повторяются до тех пор, пока все ISR процедуры не возвратят значение FALSE.

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

Использование объектов прерываний дело достаточно хлопотное. Во-первых, если ISR процедура обслуживает более чем одно прерывание или драйвер имеет более чем одну ISR процедуру, должна использоваться спин-блокировка для того, чтобы не возникло недоразумений в использовании контекстного аргумента pServiceContext процедур(ы) ISR.

Во-вторых, в случае, если процедура ISR управляет более чем одним прерыванием (вектором прерывания), следует позаботиться о том, чтобы значение, указанное в качестве SynchronizeIrql было наибольшим значение DIRQL из всех обслуживаемых прерываний.

Наконец, процедура ISR драйвера должна быть готова к работе с момента выполнения вызова IoConnectInterrupt. Даже в том случае, если выполнены еще не все действия по инициализации драйвера.

В общих чертах, процедура обслуживания прерываний должна придерживаться следующего плана:



  • Определить, относится ли поступившее прерывание к данному драйверу. Если не относится &#8212 немедленно возвратить FALSE.


  • Выполнить все операции над устройством, необходимые для того, чтобы подтвердить устройству получение прерывания.


  • Определить, существует ли необходимость в передаче данных и дополнительных действиях, которые могут быть выполнены на низких уровнях IRQL. Если такая работа имеется, то следует запланировать вызов DPC процедуры (предоставляемой драйвером), то есть поставить в очередь DPC-запрос вызовом IoRequestDpc.


  • Возвратить значение TRUE.


  • Таблица 8.12. Прототип вызова IoRequestDpc

    VOID IoRequestDpc IRQL == DIRQL
    Параметры Помещает DPC вызов в очередь
    IN PDEVICE_OBJECT pDevObject Объект устройства, для которого зарегистрирована DPC процедура
    IN PIRP pIrp Указатель на интересующий IRP пакет
    IN VOID pServiceContext Контекстный аргумент
    Возвращаемое значение void
    Если сравнить прототип ISR процедуры и прототип вызова IoRequestDpc, который делается из ISR процедуры для планирования последующего вызова DPC процедуры (для завершения работы над прерыванием), то становится очевидной проблема.

    Обычно рабочие процедуры драйвера получают указатель на объект устройства и указатель на адресованный этому объекту IRP пакет через заголовок при вызове. Но прототип ISR процедур этого не предусматривает. Как же тогда сделать из нее вызов IoRequestDpc, чтобы запланировать DPC процедуру?

    Данное затруднение решается, если при регистрации ISR процедуры вызовом IoConnectInterrupt

    в качестве контекстного аргумента pServiceContext ввести указатель на структуру расширения структуры (извините за неблагозвучные повторы) данного объекта устройства, то есть указатель на DEVICE_EXTENSION. При условии заблаговременного сохранения там указателя на объект устройства (например, как это было сделано в DriverEntry примера главы 3) процедура ISR драйвера не будет испытывать затруднения с тем, откуда ей взять данный указатель.

    Остается вопрос, что такое pIrp и где его найти?



    Возможны два варианта. Во-первых, для обработки прерывания действительно требуется текущий обрабатываемый драйвером IRP пакет, и тогда его можно найти, как pDeviceObject-&#62CurrentIrp, но только для пакета из системной очереди, SystemQueuing. Во-вторых, IRP пакет может и не потребоваться (по логике прерывания и работы драйвера), тогда можно просто указать NULL. Более того, во многих случаях IRP пакет указать невозможно или нежелательно. Поскольку Диспетчер ввода/вывода не анализирует этот параметр, то в нем можно передавать нужные (по логике работы) данные так же, как и в указателе pServiceContext. В результате, ISR процедура может выглядеть следующим образом:

    BOOLEAN OnInterrupt ( PKINTERRUPT pInterruptObject, PVOID pServiceContext ) { PMYDEVICE_EXTENSION pDevExt=(PMYDEVICE_EXTENSION) pServiceContext;

    // Считываем нужный регистр нашего устройства чтобы // определить, действительно ли оно посылало сигнал прерывания ULONG intrStatus = READ_PORT_ULONG((PULONG) (pDevExt-&#62InterruptStateReg)); if( intrStatus!=. . . ) return FALSE; // Это чужое прерывание

    // Некоторые действия, например, изменение состояния // регистров устройства, чтобы оно знало: о нем помнят. . . . // Планируем вызов DPC процедуры, например, без IRP пакета: IoRequestDpc( pDevExt-&#62DeviceObject, NULL, pDevExt);

    return TRUE; // Прерывание обработано }

    Детальное рассмотрение вопросов обработки прерываний и применения DPC процедур выполнено в главе 11, "Обработка аппаратных прерываний".


    Содержание раздела