Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Ovladače, které spravují vlastní fronty IRP, mohou k synchronizaci přístupu k frontám použít spin lock poskytnutý ovladačem, místo systémového zrušovacího spin locku. Výkon můžete zlepšit tím, že se vyhnete používání "cancel spin lock", kromě případů, kdy je to nezbytně nutné. Vzhledem k tomu, že systém má pouze jeden zámek typu "cancel spinlock", může ovladač někdy muset počkat, až bude tento zámek k dispozici. Použití spinového zámku dodaného ovladačem eliminuje toto možné zpoždění a zpřístupní zámek zrušení otáčení pro správce vstupně-výstupních operací a další ovladače. I když systém stále získává zamykací spinlock při volání rutiny Zrušit ovladače, může ovladač použít svůj vlastní spinlock k ochraně své fronty IRPs.
I když ovladač nezařadí čekající IRP do fronty, ale zachová vlastnictví jiným způsobem, musí tento ovladač nastavit rutinu Cancel pro IRP a použít spinlock k ochraně ukazatele IRP. Předpokládejme například, že ovladač označuje čekající "IRP" a pak předá ukazatel "IRP" jako kontext rutině "IoTimer" . Ovladač musí nastavit rutinu Zrušit, která časovač zruší, a při přístupu k IRP musí použít stejný spin lock v rutině Zrušit i ve zpětném volání časovače.
Každý ovladač, který zařadí do fronty své vlastní IRP a používá vlastní spinlock, musí provést následující:
Vytvořte spinlock pro ochranu fronty.
Rutinu Zrušit nastavujte a vymazávejte pouze při držení tohoto spinlocku.
Pokud se rutina Cancel spustí, zatímco ovladač odstraňuje IRP ze seznamu, umožněte rutině Cancel dokončit zpracování IRP.
Získejte zámek, který chrání frontu v rutině Zrušit.
Chcete-li vytvořit zámek otáčení, ovladač volá KeInitializeSpinLock. V následujícím příkladu ovladač uloží otáčkový zámek do struktury DEVICE_CONTEXT spolu s frontou, kterou vytvořil.
typedef struct {
LIST_ENTRYirpQueue;
KSPIN_LOCK irpQueueSpinLock;
...
} DEVICE_CONTEXT;
VOID InitDeviceContext(DEVICE_CONTEXT *deviceContext)
{
InitializeListHead(&deviceContext->irpQueue);
KeInitializeSpinLock(&deviceContext->irpQueueSpinLock);
}
Ovladač zařadí do fronty IRP, získá otáčkový zámek, zavolá InsertTailLista pak označí IRP jako čekající, jak ukazuje následující příklad:
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;
}
Jak ukazuje příklad, ovladač uchovává zámek otáčení, zatímco nastavuje a vymaže Zrušit rutinu. Ukázková rutina řízení front obsahuje dvě volání IoSetCancelRoutine.
První volání nastaví rutinu Zrušit pro IRP. Vzhledem k tomu, že se IRP mohl během spuštění rutiny řízení front zrušit, musí ovladač zkontrolovat člena Cancel protokolu IRP.
Pokud je nastavena Cancel, bylo požadováno zrušení a ovladač musí provést druhé volání IoSetCancelRoutine, aby zjistil, jestli byla volána dříve nastavená rutina Cancel.
Pokud byl IRP zrušen, ale rutina Zrušit ještě nebyla volána, aktuální rutina vyřadí IRP z fronty a dokončí ji se stavem STATUS_CANCELLED.
Pokud byl IRP zrušen a rutina Zrušit již byla volána, pak aktuální návrat označuje čekající IRP a vrací STATUS_PENDING. Rutina Zrušit dokončí IRP.
Následující příklad ukazuje, jak z fronty, kterou jste vytvořili dříve, odebrat 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;
}
V tomto příkladu ovladač získá přidružený spinový zámek před přístupem k frontě. Během držení otáčkového zámku zkontroluje, že fronta není prázdná, a poté získá další IRP z fronty. Potom volá IoSetCancelRoutine k resetování rutiny Cancel pro IRP. Vzhledem k tomu, že IRP může být zrušen, zatímco ovladač dequeueuje IRP a resetuje Cancel routinu, musí ovladač zkontrolovat hodnotu vrácenou IoSetCancelRoutine. Pokud IoSetCancelRoutine vrátí NULL, což značí, že rutina Zrušit buď byla, nebo bude brzy volána, pak procedura vyřazení z fronty umožní, aby rutina Zrušit dokončila IRP. Potom uvolní zámek, který chrání frontu, a vrátí se.
Všimněte si použití InitializeListHead v předchozí rutině. Ovladač by mohl znovu zařadit IRP do fronty, aby ho rutina Cancel mohla vyjmout, ale je jednodušší volat InitializeListHead, která znovu inicializuje pole ListEntry tak, aby odkazovalo na samotný IRP. Použití samo-odkazujícího ukazatele je důležité, protože struktura seznamu se může změnit dříve, než procedura Cancel získá spinlock. A pokud se struktura seznamu změní, může být původní hodnota ListEntry neplatná, může rutina Zrušit poškodit seznam při vyřazení z fronty protokolu IRP. Pokud ale ListEntry odkazuje na samotnou hodnotu protokolu IRP, bude rutina Cancel vždy používat správnou hodnotu IRP.
Rutina Zrušit v podstatě činí následující:
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;
}
Správce vstupně-výstupních operací vždy získá globální spinlock zrušení před voláním rutiny Zrušit, takže prvním úkolem rutiny Zrušit je uvolnit tento spinlock. Potom získá zámek číselníku, který chrání frontu přístupových hodnot IRP ovladače, odebere aktuální IRP z fronty, uvolní zámek číselníku, dokončí IRP s STATUS_CANCELLED a bez zvýšení priority a vrátí se.
Další informace o zrušení spinlocků najdete v odborném dokumentu Cancel Logic in Windows Drivers.