Обслуживание прерываний
В операционной системе 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 — данный вектор прерывания является совместно используемым (разделяемым) |
IN KAFFINITY ProcessorEnableMask | Установить набор процессоров, которые могут получать сигналы прерывания |
IN BOOLEAN doFloatingSave | Если TRUE — сохранять состояние регистров сопроцессора (FPU). Обычно используется FALSE |
Возвращаемое значение |
• STATUS_SUCCESS • STATUS_INVALID_PARAMETER • STATUS_UNSUFFUCIENT_RESOURCES |
BOOLEAN ISR | IRQL == DIRQL |
Параметры | Процедура драйвера, предоставляемая им для обслуживания прерывания |
IN PKINTERRUPT *pInterruptObject | Объект прерывания, "генерирующий" прерывания |
IN VOID pServiceContext | Контекстный аргумент, указанный при регистрации в IoConnectInterrupt |
Возвращаемое значение |
• TRUE — прерывание было обслужено ISR • FALSE — прерывание не обслуживается |
Параметр 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. Даже в том случае, если выполнены еще не все действия по инициализации драйвера.
В общих чертах, процедура обслуживания прерываний должна придерживаться следующего плана:
Таблица 8.12. Прототип вызова IoRequestDpc
VOID IoRequestDpc | IRQL == DIRQL |
Параметры | Помещает DPC вызов в очередь |
IN PDEVICE_OBJECT pDevObject | Объект устройства, для которого зарегистрирована DPC процедура |
IN PIRP pIrp | Указатель на интересующий IRP пакет |
IN VOID pServiceContext | Контекстный аргумент |
Возвращаемое значение | void |
Обычно рабочие процедуры драйвера получают указатель на объект устройства и указатель на адресованный этому объекту IRP пакет через заголовок при вызове. Но прототип ISR процедур этого не предусматривает. Как же тогда сделать из нее вызов IoRequestDpc, чтобы запланировать DPC процедуру?
Данное затруднение решается, если при регистрации ISR процедуры вызовом IoConnectInterrupt
в качестве контекстного аргумента pServiceContext ввести указатель на структуру расширения структуры (извините за неблагозвучные повторы) данного объекта устройства, то есть указатель на DEVICE_EXTENSION. При условии заблаговременного сохранения там указателя на объект устройства (например, как это было сделано в DriverEntry примера главы 3) процедура ISR драйвера не будет испытывать затруднения с тем, откуда ей взять данный указатель.
Остается вопрос, что такое pIrp и где его найти?
Возможны два варианта. Во-первых, для обработки прерывания действительно требуется текущий обрабатываемый драйвером IRP пакет, и тогда его можно найти, как pDeviceObject->CurrentIrp, но только для пакета из системной очереди, 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->InterruptStateReg)); if( intrStatus!=. . . ) return FALSE; // Это чужое прерывание
// Некоторые действия, например, изменение состояния // регистров устройства, чтобы оно знало: о нем помнят. . . . // Планируем вызов DPC процедуры, например, без IRP пакета: IoRequestDpc( pDevExt->DeviceObject, NULL, pDevExt);
return TRUE; // Прерывание обработано }
Детальное рассмотрение вопросов обработки прерываний и применения DPC процедур выполнено в главе 11, "Обработка аппаратных прерываний".