Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 21 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
21
Dung lượng
137,02 KB
Nội dung
Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) Bởi: Khoa CNTT ĐHSP KT Hưng Yên Just as happens with people in real life, programs sometimes change their mind about the I/O requests they’ve asked you to perform for them We’re not talking about simple fickleness here Applications might terminate after issuing requests that will take a long time to complete, leaving requests outstanding Such an occurrence is especially likely in the WDM world, where the insertion of new hardware might require you to stall requests while the Configuration Manager rebalances resources or where you might be told at any moment to power down your device To cancel a request in kernel mode, someone calls IoCancelIrp The operating system automatically calls IoCancelIrp for every IRP that belongs to a thread that’s terminating with requests still outstanding A user-mode application can call CancelIo to cancel all outstanding asynchronous operations issued by a given thread on a file handle IoCancelIrp would like to simply complete the IRP it’s given with STATUS_CANCELLED, but there’s a hitch: IoCancelIrp doesn’t know where you have salted away pointers to the IRP, and it doesn’t know for sure whether you’re currently processing the IRP So it relies on a cancel routine you provide to most of the work of cancelling an IRP It turns out that a call to IoCancelIrp is more of a suggestion than a demand It would be nice if every IRP that somebody tried to cancel really got completed with STATUS_CANCELLED But it’s OK if a driver wants to go ahead and finish the IRP normally if that can be done relatively quickly You should provide a way to cancel I/O requests that might spend significant time waiting in a queue between a dispatch routine and a StartIo routine How long is significant is a matter for your own sound judgment; my advice is to err on the side of providing for cancellation because it’s not that hard to and makes your driver fit better into the operating system If It Weren’t for Multitasking… An intricate synchronization problem is associated with cancelling IRPs Before I explain the problem and the solution, I want to describe the way cancellation would 1/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) work in a world where there was no multitasking and no concern with multiprocessor computers In that utopia, several pieces of the I/O Manager would fit together with your StartIo routine and with a cancel routine you’d provide, as follows: • When you queue an IRP, you set the CancelRoutine pointer in the IRP to the address of your cancel routine When you dequeue the IRP, you set CancelRoutine to NULL • IoCancelIrp unconditionally sets the Cancel flag in the IRP Then it checks to see whether the CancelRoutine pointer in the IRP is NULL While the IRP is in your queue, CancelRoutine will be non-NULL In this case, IoCancelIrp calls your cancel routine Your cancel routine removes the IRP from the queue where it currently resides and completes the IRP with STATUS_CANCELLED • Once you dequeue the IRP, IoCancelIrp finds the CancelRoutine pointer set to NULL, so it doesn’t call your cancel routine You process the IRP to completion with reasonable promptness (a concept that calls for engineering judgment), and it doesn’t matter to anyone that you didn’t actually cancel the IRP Synchronizing Cancellation Unfortunately for us as programmers, we write code for a multiprocessing, multitasking environment in which effects can sometimes appear to precede causes There are many possible race conditions between the queue insertion, queue removal, and cancel routines in the naive scenario I just described For example, what would happen if IoCancelIrp called your cancel routine to cancel an IRP that happened to be at the head of your queue? If you were simultaneously removing an IRP from the queue on another CPU, you can see that your cancel routine would probably conflict with your queue removal logic But this is just the simplest of the possible races In earlier times, driver programmers dealt with the cancel races by using a global spin lock—the cancel spin lock Because you shouldn’t use this spin lock for synchronization in your own driver, I’ve explained it briefly in the sidebar Read the sidebar for its historical perspective, but don’t plan to use this lock Here is a sketch of IoCancelIrp You need to know this to correctly write IRP-handling code (This isn’t a copy of the Windows XP source code—it’s an abridged excerpt.) BOOLEAN IoCancelIrp(PIRP Irp) { IoAcquireCancelSpinLock(&Irp->CancelIrql); 2/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) Irp->Cancel = TRUE; PDRIVER_CANCEL CancelRoutine = IoSetCancelRoutine(Irp, NULL); if (CancelRoutine) { PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); (*CancelRoutine)(stack->DeviceObject, Irp); return TRUE; } else { IoReleaseCancelSpinLock(Irp->CancelIrql); return FALSE; } } IoCancelIrp first acquires the global cancel spin lock As you know if you read the sidebar earlier, lots of old drivers contend for the use of this lock in their normal IRP-handling path New drivers hold this lock only briefly while handling the cancellation of an IRP Setting the Cancel flag to TRUE alerts any interested party that IoCancelIrp has been called for this IRP IoSetCancelRoutine performs an interlocked exchange to simultaneously retrieve the existing CancelRoutine pointer and set the field to NULL in one atomic operation IoCancelIrp calls the cancel routine, if there is one, without first releasing the global cancel spin lock The cancel routine must release the lock! Note also that the device object argument to the cancel routine comes from the current stack location, where IoCallDriver is supposed to have left it If there is no cancel routine, IoCancelIrp itself releases the global cancel spin lock Good idea, huh? 3/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) • Could someone call IoCancelIrp twice? The thing to think about is that the Cancel flag might be set in an IRP because of some number of primeval calls to IoCancelIrp and that someone might call IoCancelIrp one more time (getting a little impatient, are we?) while StartPacket is active This wouldn’t matter because our first test of the Cancel flag occurs after we install our cancel pointer We would find the flag set to TRUE in this hypothetical situation and would therefore execute the second call to IoSetCancelRoutine Either IoCancelIrp or we win the race to reset the cancel pointer to NULL, and whoever wins ends up completing the IRP The residue from the primeval calls is simply irrelevant Cancelling IRPs You Create or Handle Sometimes you’ll want to cancel an IRP that you’ve created or passed to another driver Great care is required to avoid an obscure, low-probability problem Just for the sake of illustration, suppose you want to impose an overall 5-second timeout on a synchronous I/O operation If the time period elapses, you want to cancel the operation Here is some naive code that, you might suppose, would execute this plan: SomeFunction() { KEVENT event; IO_STATUS_BLOCK iosb; KeInitializeEvent(&event, ); PIRP Irp = IoBuildSynchronousFsdRequest( , &event, &iosb); NTSTATUS status = IoCallDriver(DeviceObject, Irp); if (status == STATUS_PENDING) { LARGE_INTEGER timeout; timeout.QuadPart = -5 * 10000000; if (KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout) == STATUS_TIMEOUT) 4/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) { IoCancelIrp(Irp); // [...]... _DEVICE_EXTENSION { PIRP TheIrp; ULONG CancelFlag; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) OnComplete, (PVOID) pdx, TRUE, TRUE, TRUE); 13/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) pdx->CancelFlag... Tail.Overlay.ListEntry); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PLIST_ENTRY current = next; next = next->Flink; if (fop && stack->FileObject != fop) continue; if (!IoSetCancelRoutine(Irp, NULL )) continue; RemoveEntryList(current); InsertTailList(&cancellist, current); } 18/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) KeReleaseSpinLock(&pdq->lock, oldirql); while (!IsListEmpty(&cancellist )) . .. DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; KEVENT event; KeInitializeEvent(&event, NotificationEvent, FALSE); IoSetCompletionRoutine(Irp, OnComplete, (PVOID) &event, TRUE, TRUE, TRUE); NTSTATUS status = IoCallDriver( ); 11/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) if (status == STATUS_PENDING) { LARGE_INTEGER timeout; timeout.QuadPart... PeekNextIrp(PIO_CSQ csq, PIRP Irp, PVOID PeekContext) { PDEVICE_EXTENSION pdx = GET_DEVICE_EXTENSION(csq); PLIST_ENTRY next = Irp ? Irp->Tail.Overlay.ListEntry.Flink : pdx->IrpQueueAnchor.Flink; 20/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) while (next != &pdx->IrpQueueAnchor) { PIRP NextIrp = CONTAINING_RECORD(next, IRP, Tail.Overlay.ListEntry); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(NextIrp);... Irp; IoMarkIrpPending(Irp); IoCallDriver(pdx->LowerDeviceObject, Irp); return STATUS_PENDING; } VOID CancelTheIrp(PDEVICE_EXTENSION pdx) { PIRP Irp = (PIRP) InterlockedExchangePointer( (PVOID *) &pdx->TheIrp, NULL); if (Irp) { IoCancelIrp(Irp); if (InterlockedExchange(&pdx->CancelFlag, 1)) IoCompleteRequest(Irp, IO_ NO_INCREMENT); } } NTSTATUS OnComplete(PDEVICE_OBJECT fdo, PIRP Irp, PDEVICE_EXTENSION... sample in the DDK): NTSTATUS DispatchCleanup(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT fop = stack->FileObject; PIRP qirp; while ((qirp = IoCsqRemoveNextIrp(&pdx->csq, fop )) ) CompleteRequest(qirp, STATUS_CANCELLED, 0); return CompleteRequest(Irp, STATUS_SUCCESS, 0); } Implement... &timeout) == STATUS_TIMEOUT) { IoCancelIrp(Irp); KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); } } status = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_ NO_INCREMENT); return status; } NTSTATUS OnComplete(PDEVICE_OBJECT junk, PIRP Irp, PVOID pev) { if (Irp->PendingReturned) KeSetEvent((PKEVENT) pev, IO_ NO_INCREMENT, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; } 12/21 Hủy bỏ yêu. .. STATUS_SUCCESS CleanupRequests contains a wealth of detail: VOID CleanupRequests(PDEVQUEUE pdq, PFILE_OBJECT fop, 17/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) NTSTATUS status) { LIST_ENTRY cancellist; InitializeListHead(&cancellist); KIRQL oldirql; KeAcquireSpinLock(&pdq->lock, &oldirql); PLIST_ENTRY first = &pdq->head; PLIST_ENTRY next; for (next = first->Flink; next != first; ) { PIRP Irp = CONTAINING_RECORD(next,... PIRP Irp, PDEVICE_EXTENSION pdx) { if (InterlockedExchangePointer((PVOID *) &pdx->TheIrp, NULL) ││ InterlockedExchange(&pdx->CancelFlag, 1)) 14/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) return STATUS_SUCCESS; return STATUS_MORE_PROCESSING_REQUIRED; } This code is similar to the code I showed earlier for cancelling your own asynchronous IRP Here, however, allowing IoCompleteRequest to finish completing... simple: NTSTATUS DispatchCleanup(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT fop = stack->FileObject; CleanupRequests(&pdx->dqReadWrite, fop, STATUS_CANCELLED); return CompleteRequest(Irp, STATUS_SUCCESS, 0); } CleanupRequests will remove all IRPs from the queue that belong to ... fdo->DeviceExtension; IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) OnComplete, (PVOID) pdx, TRUE, TRUE, TRUE); 13/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests. .. excerpt .) BOOLEAN IoCancelIrp(PIRP Irp) { IoAcquireCancelSpinLock(&Irp->CancelIrql); 2/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) Irp->Cancel = TRUE; PDRIVER_CANCEL CancelRoutine = IoSetCancelRoutine(Irp,... PDEVICE_EXTENSION pdx) { if (InterlockedExchangePointer((PVOID *) &pdx->TheIrp, NULL) ││ InterlockedExchange(&pdx->CancelFlag, 1)) 14/21 Hủy bỏ yêu cầu vàora (Cancelling IO Requests ) return STATUS_SUCCESS;