Использование спиновых блокировок: пример
Минимизация времени, в течение которой драйвер удерживает спин-блокировки, может значительно повысить производительность драйвера и системы в целом. Например, рассмотрим следующий рисунок, на котором показано, как блокировка прерывания защищает данные, относящиеся к конкретному устройству, которые должны совместно использоваться между ISR и подпрограммами StartIo и DpcForIsr на компьютере SMP.
Хотя ISR драйвера выполняется в DIRQL на одном процессоре, его подпрограмма StartIo выполняется в DISPATCH_LEVEL на втором процессоре. Обработчик прерываний ядра содержит объект InterruptSpinLock для isR драйвера, который обращается к защищенным данным, зависящим от устройства, таким как состояние или указатели на регистры устройств (SynchronizeContext), в расширении устройства драйвера. Подпрограмма StartIo , которая готова к доступу к SynchronizeContext, вызывает KeSynchronizeExecution, передав указатель на связанные объекты прерывания, общий syncContext и подпрограмму SynchCritSection драйвера (AccessDevice на предыдущем рисунке).
До тех пор, пока ISR не возвращается, тем самым освобождая драйвер InterruptSpinLock, KeSynchronizeExecutionвращается на втором процессоре, не позволяя AccessDevice прикоснуться к SynchronizeContext. Однако KeSynchronizeExecution также вызывает IRQL на втором процессоре в SynchronizeIrql объектов прерывания, тем самым предотвращая прерывание другого устройства на этом процессоре, чтобы AccessDevice можно было запустить в DIRQL сразу после возврата ISR. Тем не менее, более высокие прерывания DIRQL для других устройств, прерывания часов и прерывания отказа питания по-прежнему могут возникать на любом процессоре.
Когда ISR помещает в очередь DpcForIsr драйвера и возвращает его, AccessDevice запускается на втором процессоре в SynchronizeIrql связанных объектов прерывания и обращается к SynchronizeContext. Между тем DpcForIsr запускается на другом процессоре в DISPATCH_LEVEL IRQL. DpcForIsr также готов к доступу к SynchronizeContext, поэтому он вызывает KeSynchronizeExecution, используя те же параметры, что и подпрограмма StartIo на шаге 1.
Когда KeSynchronizeExecution получает спиновую блокировку и запускает AccessDevice от имени подпрограммы StartIo , предоставляемой драйвером подпрограмме синхронизации AccessDevice предоставляется монопольный доступ к SynchronizeContext. Так как AccessDevice выполняется в SynchronizeIrql, ISR драйвера не может получить блокировку спина и получить доступ к той же области, пока блокировка спина не будет снята, даже если устройство прерывается на другом процессоре во время выполнения AccessDevice.
AccessDevice возвращается, освобождая спиновую блокировку. Подпрограмма StartIo возобновляется при DISPATCH_LEVEL на втором процессоре. KeSynchronizeExecution теперь запускает AccessDevice на третьем процессоре, чтобы получить доступ к SynchronizeContext от имени DpcForIsr. Однако если прерывание устройства произошло до DpcForIsr с именем KeSynchronizeExecution на шаге 2, ISR может запуститься на другом процессоре, прежде чем KeSynchronizeExecution сможет получить спиновую блокировку и запустить AccessDevice на третьем процессоре.
Как показано на предыдущем рисунке, в то время как подпрограмма, работающая на одном процессоре, содержит спин-блокировку, каждая другая процедура, пытающаяся получить такую блокировку спина, не получает никакой работы. Каждая процедура, пытающаяся получить уже удерживаемую спиновую блокировку, просто вращается на своем текущем процессоре, пока держатель не отпустит спин-блокировку. При освобождении спин-блокировки ее может получить только одна подпрограмма; любая другая подпрограмма, пытающаяся в настоящее время получить ту же блокировку спина, будет продолжать вращаться.
Держатель любой блокировки спина выполняется на поднятом irQL: либо в DISPATCH_LEVEL для исполнительной спиновой блокировки, либо в DIRQL для блокировки прерывания спина. Вызовы KeAcquireSpinLock и KeAcquireInStackQueuedSpinLock выполняются в DISPATCH_LEVEL, пока они не вызовут KeReleaseSpinLock или KeReleaseInStackQueuedSpinLock , чтобы освободить блокировку. Вызывающие объекты KeSynchronizeExecution автоматически вызывают IRQL на текущем процессоре в SynchronizeIrql объектов прерывания, пока предоставленная вызывающей стороной подпрограмма SynchCritSection не завершит работу и KeSynchronizeExecution не вернет управление. Дополнительные сведения см. в разделе Подпрограммы поддержки вызовов, использующие спиновые блокировки.
Помните следующее об использовании спин-блокировок:
Весь код, который выполняется на более низком irQL, не может выполнить какую-либо работу с набором процессоров, занятых держателем спин-блокировки и другими подпрограммами, пытающимися получить ту же спиновую блокировку.
Следовательно, минимизация времени, в течение которой драйвер удерживает спиновые блокировки, приводит к значительно более высокой производительности драйвера и значительно повышает общую производительность системы.
Как показано на предыдущем рисунке, обработчик прерываний ядра выполняет подпрограммы, выполняемые в том же irQL на многопроцессорном компьютере, в порядке первой очереди. Ядро также выполняет следующие действия:
Когда подпрограмма драйвера вызывает KeSynchronizeExecution, ядро вызывает выполнение подпрограммы SynchCritSection драйвера на том же процессоре, из которого выполнялся вызов KeSynchronizeExecution (см. шаги 1 и 3).
Когда ISR драйвера помещает в очередь свой DpcForIsr, ядро запускает DPC на первом доступном процессоре, на котором IRQL меньше DISPATCH_LEVEL. Это не обязательно тот же процессор, из которого был выполнен вызов IoRequestDpc (см. шаг 2).
Операции ввода-вывода, управляемые прерыванием, могут быть сериализованы на однопроцессорном компьютере, но те же операции могут быть действительно асинхронными на компьютере SMP. Как показано на предыдущем рисунке, ISR драйвера может работать на процессоре CPU4 на компьютере SMP до того, как DpcForIsr начнет обработку IRP, для которого ISR уже обработал прерывание устройства на ЦП1.
Иными словами, не следует предполагать, что блокировка прерывания может защитить данные операции, сохраняемые ISR при выполнении на одном процессоре, от перезаписи ISR, когда прерывание устройства происходит на другом процессоре до выполнения подпрограммы DpcForIsr или CustomDpc .
Хотя драйвер может попытаться сериализовать все операции ввода-вывода, управляемые прерыванием, для сохранения данных, собранных ISR, этот драйвер не будет работать гораздо быстрее на компьютере SMP, чем на компьютере с однопроцессором. Чтобы обеспечить максимально возможную производительность драйвера, сохраняя при этом переносимость на однопроцессорных и многопроцессорных платформах, драйверы должны использовать некоторые другие методы для сохранения данных операций, полученных ISR для последующей обработки DpcForIsr.
Например, ISR может сохранять данные, относящиеся к конкретной операции, в IRP, который он передает DpcForIsr. Усовершенствование этого метода заключается в реализации DpcForIsr , который обращается к числу дополнений ISR, обрабатывает количество irps счетчика с помощью данных, предоставленных ISR, и сбрасывает счетчик до нуля перед возвратом. Конечно, счетчик должен быть защищен блокировкой прерывания драйвера, так как его ISR и SynchCritSection будут поддерживать его значение динамически.