Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Драйверы, управляющие собственными очередями IRP, могут использовать спинлок, предоставленный драйвером, вместо системного спинлока для отмены, чтобы синхронизировать доступ к очередям. Вы можете повысить производительность, избегая использования отменяющего спинлока, за исключением случаев, когда это абсолютно необходимо. Поскольку в системе есть только одна спин-блокировка отмены, драйвер в некоторых случаях может ждать, пока эта спин-блокировка станет доступной. Использование спин-блокировки, предоставленной драйвером, устраняет эту потенциальную задержку и делает спин-блокировку отмены доступной для диспетчера ввода-вывода и других драйверов. Хотя система по-прежнему приобретает блокировку spin lock при вызове процедуры Cancel драйвера, драйвер может использовать собственную блокировку spin lock для защиты очереди IRP.
Даже если драйвер не помещает в очередь ожидающие IRP, но сохраняет владение каким-то другим способом, этот драйвер должен задать подпрограмму Отмены для IRP и использовать спин-блокировку для защиты указателя IRP. Например, предположим, что драйвер помечает IRP как ожидающий выполнения, а затем передает указатель на IRP в подпрограмму IoTimer в качестве контекста. Драйвер должен задать подпрограмму Отмена, которая отменяет таймер, и использовать одну и ту же спин-блокировку как в подпрограмме Отмена, так и в обратном вызове таймера при доступе к IRP.
Любой драйвер, который ставит в очередь собственные IRP и использует собственную спин-блокировку, должен выполнить следующее:
Создайте спин-блокировку для защиты очереди.
Устанавливайте и удаляйте подпрограмму Отмены только при удержании этого спинлока.
Если подпрограмма отмены запускается, пока драйвер извлекает IRP, разрешите процедуре Отмены завершить IRP.
Получите блокировку, которая защищает очередь в процедуре Отмена.
Чтобы создать спиновую блокировку, драйвер вызывает KeInitializeSpinLock. В следующем примере драйвер сохраняет спиновую блокировку в структуре DEVICE_CONTEXT вместе с созданной им очередью.
typedef struct {
LIST_ENTRYirpQueue;
KSPIN_LOCK irpQueueSpinLock;
...
} DEVICE_CONTEXT;
VOID InitDeviceContext(DEVICE_CONTEXT *deviceContext)
{
InitializeListHead(&deviceContext->irpQueue);
KeInitializeSpinLock(&deviceContext->irpQueueSpinLock);
}
Чтобы поставить IRP в очередь, драйвер получает блокировку на спин, вызывает InsertTailList, а затем помечает IRP как ожидающий, как показано в следующем примере.
NTSTATUS QueueIrp(DEVICE_CONTEXT *deviceContext, PIRP Irp)
{
PDRIVER_CANCEL oldCancelRoutine;
KIRQL oldIrql;
NTSTATUS status;
KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);
// Queue the IRP and call IoMarkIrpPending to indicate
// that the IRP may complete on a different thread.
// N.B. It is okay to call these inside the spin lock
// because they are macros, not functions.
IoMarkIrpPending(Irp);
InsertTailList(&deviceContext->irpQueue, &Irp->Tail.Overlay.ListEntry);
// Must set a Cancel routine before checking the Cancel flag.
oldCancelRoutine = IoSetCancelRoutine(Irp, IrpCancelRoutine);
ASSERT(oldCancelRoutine == NULL);
if (Irp->Cancel) {
// The IRP was canceled. Check whether our cancel routine was called.
oldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
if (oldCancelRoutine) {
// The cancel routine was NOT called.
// So dequeue the IRP now and complete it after releasing the spin lock.
RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
// Drop the lock before completing the request.
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_PENDING;
} else {
// The Cancel routine WAS called.
// As soon as we drop our spin lock, it will dequeue and complete the IRP.
// So leave the IRP in the queue and otherwise do not touch it.
// Return pending since we are not completing the IRP here.
}
}
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
// Because the driver called IoMarkIrpPending while it held the IRP,
// it must return STATUS_PENDING from its dispatch routine.
return STATUS_PENDING;
}
Как показано в примере, драйвер удерживает свою спин-блокировку, когда устанавливает и очищает подпрограмму Cancel. Пример подпрограммы очереди содержит два вызова IoSetCancelRoutine.
Первый вызов задает подпрограмму Cancel для IRP. Однако, поскольку IRP, возможно, было отменено во время выполнения подпрограммы очереди, драйвер должен проверить член Cancel IRP.
Если Cancel установлено, то была сделана заявка на отмену, и драйвер должен выполнить повторный вызов IoSetCancelRoutine, чтобы проверить, была ли вызвана ранее заданная процедура Cancel.
Если IRP был отменен, но подпрограмма Cancel еще не была вызвана, то текущая подпрограмма извлекает IRP из очереди и завершает его со статусом STATUS_CANCELLED.
Если IRP отменен и подпрограмма отмены уже вызвана, текущее возвращаемое значение помечает ожидание IRP и возвращает STATUS_PENDING. Процедура отмены завершает IRP.
В следующем примере показано, как удалить IRP из ранее созданной очереди:
PIRP DequeueIrp(DEVICE_CONTEXT *deviceContext)
{
KIRQL oldIrql;
PIRP nextIrp = NULL;
KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);
while (!nextIrp && !IsListEmpty(&deviceContext->irpQueue)) {
PDRIVER_CANCEL oldCancelRoutine;
PLIST_ENTRY listEntry = RemoveHeadList(&deviceContext->irpQueue);
// Get the next IRP off the queue.
nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
// Clear the IRP's cancel routine.
oldCancelRoutine = IoSetCancelRoutine(nextIrp, NULL);
// IoCancelIrp() could have just been called on this IRP. What interests us
// is not whether IoCancelIrp() was called (nextIrp->Cancel flag set), but
// whether IoCancelIrp() called (or is about to call) our Cancel routine.
// For that, check the result of the test-and-set macro IoSetCancelRoutine.
if (oldCancelRoutine) {
// Cancel routine not called for this IRP. Return this IRP.
ASSERT(oldCancelRoutine == IrpCancelRoutine);
} else {
// This IRP was just canceled and the cancel routine was (or will be)
// called. The Cancel routine will complete this IRP as soon as we
// drop the spin lock, so do not do anything with the IRP.
// Also, the Cancel routine will try to dequeue the IRP, so make
// the IRP's ListEntry point to itself.
ASSERT(nextIrp->Cancel);
InitializeListHead(&nextIrp->Tail.Overlay.ListEntry);
nextIrp = NULL;
}
}
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
return nextIrp;
}
В примере драйвер приобретает связанный спинлок перед тем, как получить доступ к очереди. Удерживая спин-блокировку, проверяется, что очередь не пуста, и извлекается следующий IRP из очереди. Затем он вызывает IoSetCancelRoutine, чтобы сбросить процедуру Отмены для IRP. Так как IRP может быть отменен, пока драйвер извлекает IRP из очереди и сбрасывает серию Отмена, драйвер должен проверить значение, возвращаемое IoSetCancelRoutine. Если IoSetCancelRoutine возвращает NULL, это указывает на то, что функция Cancel либо уже была вызвана, либо будет вызвана в ближайшее время, и тогда функция dequeuing позволяет функции Cancel завершить IRP. Затем он освобождает блокировку, которая защищает очередь, и завершает выполнение функции.
Обратите внимание на использование InitializeListHead в предыдущей подпрограмме. Драйвер может повторно отправить IRP, чтобы подпрограмма отмены могла отменить ее, но проще вызвать InitializeListHead, которая повторно инициализирует поле ListEntry IRP, чтобы он указывает на сам IRP. Использование указателя самостоятельного ссылки важно, так как структура списка может измениться, прежде чем подпрограмма отмены получает блокировку спина. И если структура списка изменяется, возможно, делая исходное значение ListEntry недопустимым, функция Cancel может повредить список при извлечении IRP из очереди. Но если ListEntry указывает на сам IRP, то подпрограмма отмены всегда будет использовать правильный IRP.
Подпрограмма отмены , в свою очередь, просто делает следующее:
VOID IrpCancelRoutine(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DEVICE_CONTEXT *deviceContext = DeviceObject->DeviceExtension;
KIRQL oldIrql;
// Release the global cancel spin lock.
// Do this while not holding any other spin locks so that we exit at the right IRQL.
IoReleaseCancelSpinLock(Irp->CancelIrql);
// Dequeue and complete the IRP.
// The enqueue and dequeue functions synchronize properly so that if this cancel routine is called,
// the dequeue is safe and only the cancel routine will complete the IRP. Hold the spin lock for the IRP
// queue while we do this.
KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);
RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
// Complete the IRP. This is a call outside the driver, so all spin locks must be released by this point.
Irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return;
}
Диспетчер операций ввода-вывода всегда получает глобальную блокировку спина отмены перед вызовом подпрограммы Отмены , поэтому первая задача подпрограммы отмены — освободить эту блокировку спина. Затем он получает блокировку спина, которая защищает очередь драйвера irPs, удаляет текущий IRP из очереди, освобождает его спин-блокировку, завершает IRP с STATUS_CANCELLED и не повышает приоритет и возвращает.
Дополнительные сведения об отмене спин-блокировок см. в техническом документе «Логика отмены в драйверах Windows».