Синхронизация кода прерывания
Следующие факторы усложняют код драйвера, который обрабатывает аппаратные прерывания в многопроцессорных системах:
Каждый раз, когда устройство прерывается, оно предоставляет сведения о конкретном прерывании, которые являются нестабильными, так как они могут быть перезаписаны при следующем прерывании устройства.
Устройства прерывают работу с относительно высоким уровнем IRQL, а их подпрограммы обслуживания прерываний (ISR) могут прерывать выполнение другого кода драйвера.
Для прерываний DIRQL ISR должен выполняться в DIRQL, удерживая блокировку спина, поставляемую драйвером, чтобы ISR может предотвратить дополнительные прерывания, сохраняя изменчивые сведения. DIRQL предотвращает прерывание работы текущего процессора, а блокировка спина предотвращает прерывание другим процессором.
IsR должен выполняться быстро, так как устройство не может прерывать работу во время выполнения isR. Длительное выполнение ISR может замедлить работу системы или привести к потере данных.
Подпрограмма ISR и отложенного вызова процедур (DPC) обычно должны обращаться к хранилищу, в которой isr хранит переменные данные устройства. Эти подпрограммы должны синхронизироваться друг с другом, чтобы они не обращались к хранилищу одновременно.
Из-за всех этих факторов при написании кода драйвера, обрабатывающего прерывания, необходимо использовать следующие правила:
Только функция обратного вызова EvtInterruptIsr обращается к данным о переменных прерываниях, например к регистрам устройств, содержащим сведения об прерывании.
Функция обратного вызова EvtInterruptIsr должна переместить переменные данные в определенный драйвером буфер данных прерываний, к которому могут обращаться функция обратного вызова EvtInterruptDpc драйвера, функция обратного вызова EvtInterruptWorkItem или несколько функций обратного вызова EvtDpcFunc .
Если драйвер предоставляет функции обратного вызова EvtInterruptDpc или EvtInterruptWorkItem для своих объектов прерывания, лучше всего хранить данные прерывания в контекстном пространстве объекта прерывания. Функции обратного вызова объекта прерывания могут получить доступ к контексту объекта с помощью получаемого дескриптора объекта.
Если драйвер предоставляет несколько функций обратного вызова EvtDpcFunc для каждой функции обратного вызова EvtInterruptIsr , данные прерываний можно хранить в контекстном пространстве каждого объекта DPC.
Весь код драйвера, который обращается к буферу данных прерываний, должен быть синхронизирован, чтобы доступ к данным одновременно выполнялась только одной подпрограммой.
Для объектов прерывания DIRQL функция обратного вызова EvtInterruptIsr обращается к этому буферу данных по адресу IRQL = DIRQL, удерживая при этом блокировку спина, предоставляемую драйвером объекта прерывания. Поэтому все подпрограммы, которые обращаются к буферу, также должны выполняться в DIRQL при удержании спиновой блокировки. (Как правило, функция обратного вызова EvtInterruptDpc или EvtDpcFunc прерывания является единственной другой подпрограммой, которая должна получить доступ к буферу.)
Все подпрограммы, обращаюющиеся к буферу данных прерываний, за исключением функции обратного вызова EvtInterruptIsr , должны выполнять одно из следующих действий:
- Вызовите WdfInterruptSynchronize , чтобы запланировать функцию обратного вызова EvtInterruptSynchronize , которая обращается к буферу данных прерывания.
- Размещение кода, который обращается к буферу данных прерываний между вызовами WdfInterruptAcquireLock и WdfInterruptReleaseLock.
Оба этих метода позволяют функции EvtInterruptDpc или EvtDpcFunc получать доступ к данным прерывания в DIRQL, удерживая при этом спиновую блокировку прерывания. DIRQL предотвращает прерывание работы текущего процессора, а блокировка спина предотвращает прерывание другим процессором.
Если устройство поддерживает несколько векторов прерываний или сообщений и если вы хотите синхронизировать обработку этих прерываний драйвером, можно назначить одну блокировку спина нескольким объектам прерываний DIRQL. Платформа определяет наивысший DIRQL из набора прерываний и всегда получает спиновую блокировку на этом DIRQL, чтобы синхронизированный код не был прерван никакими векторами прерываний или сообщениями в наборе.
Для объектов прерываний пассивного уровня платформа получает блокировку прерываний пассивного уровня перед вызовом функции обратного вызова EvtInterruptIsr драйвера в IRQL = PASSIVE_LEVEL. В результате все подпрограммы, которые обращаются к буферу, должны либо получить блокировку прерываний, либо внутренне синхронизировать доступ к буферу. Как правило, функция обратного вызова EvtInterruptWorkItem прерывания является единственной другой подпрограммой, которая обращается к буферу. Сведения о получении блокировки прерываний из функции обратного вызова EvtInterruptWorkItem см. в разделе Примечания этой страницы.
Вы также можете синхронизировать обработку драйвера нескольких векторов прерываний, назначив одну блокировку ожидания нескольким объектам прерываний пассивного уровня.
Если часть кода, обрабатывающего прерывания DIRQL, должна выполняться по адресу IRQL = PASSIVE_LEVEL, функция обратного вызова EvtInterruptDpc или EvtDpcFunc может создать один или несколько рабочих элементов , чтобы код выполнялся как функции обратного вызова EvtWorkItem .
Кроме того, в KMDF версии 1.11 и более поздних драйвер может запросить рабочий элемент прерывания, вызвав WdfInterruptQueueWorkItemForIsr. (Напомним, что функция обратного вызова EvtInterruptIsr драйвера может вызывать WdfInterruptQueueWorkItemForIsr или WdfInterruptQueueDpcForIsr, но не оба метода.)
Если важно синхронизировать функции обратного вызова EvtInterruptDpc и EvtDpcFunc драйвера друг с другом и с другими функциями обратного вызова, связанными с устройством, драйвер может задать для элемента AutomaticSerializationзначение TRUE в структуре WDF_INTERRUPT_CONFIG прерывания и в структуре WDF_DPC_CONFIG объекта DPC. Кроме того, драйвер может использовать спиновые блокировки платформы. (Установка для элемента AutomaticSerializationзначения TRUE не синхронизирует функцию обратного вызова EvtInterruptIsr с другими функциями обратного вызова. Используйте WdfInterruptSynchronize или WdfInterruptAcquireLock для синхронизации функции обратного вызова EvtInterruptIsr , как описано ранее в этом разделе.)
Дополнительные сведения о синхронизации подпрограмм драйверов см. в разделе Методы синхронизации для драйверов Framework-Based.