Hàng đợi yêu cầu vàora (queuing IO requests)

12 186 0
Hàng đợi yêu cầu vàora (queuing IO requests)

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Hàng đợi yêu cầu Vàora (Queuing IO Requests) Hàng đợi yêu cầu Vàora (Queuing IO Requests) Bởi: Khoa CNTT ĐHSP KT Hưng Yên Sometimes your driver receives an IRP that it can’t handle right away Rather than reject the IRP by causing it to fail with an error status, your dispatch routine places the IRP on a queue In another part of your driver, you provide logic that removes one IRP from the queue and passes it to a StartIo routine Microsoft Queuing Routines Apart from this sidebar, I’m omitting discussion of the functions IoStartPacket and IoStartNextPacket, which have been part of Windows NT since the beginning These functions implement a queuing model that’s inappropriate for WDM drivers In that model, a device is in one of three states: idle, busy with an empty queue, or busy with a nonempty queue If you call IoStartPacket at a time when the device is idle, it unconditionally sends the IRP to your StartIo routine Unfortunately, many times a WDM driver needs to queue an IRP even though the device is idle These functions also rely heavily on a global spin lock whose overuse has created a serious performance bottleneck Just in case you happen to be working on an old driver that uses these obsolete routines, however, here’s how they work A dispatch routine would queue an IRP like this: NTSTATUS DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp) { IoMarkIrpPending(Irp); IoStartPacket(fdo, Irp, NULL, CancelRoutine); return STATUS_PENDING; } 1/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) Your driver would have a single StartIo routine Your DriverEntry routine would set the DriverStartIo field of the driver object to point to this routine If your StartIo routine completes IRPs, you would also call IoSetStartIoAttributes (in Windows XP or later) to help prevent excessive recursion into StartIo IoStartPacket and IoStartNextPacket call StartIo to process one IRP at a time In other words, StartIo is the place where the I/O manager serializes access to your hardware A DPC routine (see the later discussion of how DPC routines work) would complete the previous IRP and start the next one using this code: VOID DpcForIsr(PKDPC junk, PDEVICE_OBJECT fdo, PIRP Irp, PVOID morejunk) { IoCompleteRequest(Irp, STATUS_NO_INCREMENT); IoStartNextPacket(fdo, TRUE); } To provide for canceling a queued IRP, you would need to write a cancel routine Illustrating that and the cancel logic in StartIo is beyond the scope of this book In addition, you can rely on the CurrentIrp field of a DEVICE_OBJECT to always contain NULL or the address of the IRP most recently sent (by IoStartPacket or IoStartNextPacket) to your StartIo routine Queuing an IRP is conceptually very simple You can provide a list anchor in your device extension, which you initialize in your AddDevice function: typedef struct _DEVICE_EXTENSION { LIST_ENTRY IrpQueue; BOOLEAN DeviceBusy; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS AddDevice( ) { 2/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) InitializeListHead(&pdx->IrpQueue); } Then you can write two naive routines for queuing and dequeuing IRPs: VOID NaiveStartPacket(PDEVICE_EXTENSION pdx, PIRP Irp) { if (pdx->DeviceBusy) InsertTailList(&pdx->IrpQueue, &Irp->Tail.Overlay.ListEntry); else { pdx->DeviceBusy = TRUE; StartIo(pdx->DeviceObject, Irp); } } VOID NaiveStartNextPacket(PDEVICE_EXTENSION pdx, PIRP Irp) { if (IsListEmpty(&pdx->IrpQueue)) pdx->DeviceBusy = FALSE; else { PLIST_ENTRY foo = RemoveHeadList(&pdx->IrpQueue); PIRP Irp = CONTAINING_RECORD(foo, IRP, Tail.Overlay.ListEntry); 3/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) StartIo(pdx->DeviceObject, Irp); } } Then your dispatch routine calls NaiveStartPacket, and your DPC routine calls NaiveStartNextPacket in the manner discussed earlier in connection with the standard model There are many problems with this scheme, which is why I called it naive The most basic problem is that your DPC routine and multiple instances of your dispatch routine could all be simultaneously active on different CPUs They would likely conflict in trying to access the queue and the busy flag You could address that problem by creating a spin lock and using it to guard against the obvious races, as follows: typedef struct _DEVICE_EXTENSION { LIST_ENTRY IrpQueue; KSPIN_LOCK IrpQueueLock; BOOLEAN DeviceBusy; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS AddDevice( ) { InitializeListHead(&pdx->IrpQueue); KeInitializeSpinLock(&pdx->IrpQueueLock); } VOID LessNaiveStartPacket(PDEVICE_EXTENSION pdx, PIRP Irp) { KIRQL oldirql; 4/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) KeAcquireSpinLock(&pdx->IrpQueueLock, &oldirql); if (pdx->DeviceBusy) { InsertTailList(&pdx->IrpQueue, &Irp->Tail.Overlay.ListEntry; KeReleaseSpinLock(&pdx->IrpQueueLock, oldirql); } else { pdx->DeviceBusy = TRUE; KeReleaseSpinLock(&pdx->IrpQueueLock, DISPATCH_LEVEL); StartIo(pdx->DeviceObject, Irp); KeLowerIrql(oldirql); } } VOID LessNaiveStartNextPacket(PDEVICE_EXTENSION pdx, PIRP Irp) { KIRQL oldirql; KeAcquireSpinLock(&pdx->IrpQueueLock, &oldirql); if (IsListEmpty(&pdx->IrpQueue) { pdx->DeviceBusy = FALSE; KeReleaseSpinLock(&pdx->IrpQueueLock, oldirql); 5/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) else { PLIST_ENTRY foo = RemoveHeadList(&pdx->IrpQueue); KeReleaseSpinLock(&pdx->IrpQueueLock, DISPATCH_LEVEL); PIRP Irp = CONTAINING_RECORD(foo, IRP, Tail.Overlay.ListEntry); StartIo(pdx->DeviceObject, Irp); KeLowerIrql(oldirql); } } Incidentally, we always want to call StartIo at a single IRQL Because DPC routines are among the callers of LessNaiveStartNextPacket, and they run at DISPATCH_LEVEL, we pick DISPATCH_LEVEL That means we want to stay at DISPATCH_LEVEL when we release the spin lock (You did remember that these two queue management routines need to be in nonpaged memory because they run at DISPATCH_LEVEL, right?) These queueing routines are actually almost OK, but they have one more defect and a shortcoming The shortcoming is that we need a way to stall a queue for the duration of certain PnP and Power states IRPs accumulate in a stalled queue until someone unstalls the queue, whereupon the queue manager can resume sending IRPs to a StartIo routine The defect in the “less naive” set of routines is that someone could decide to cancel an IRP at essentially any time IRP cancellation complicates IRP queuing logic so much that I’ve devoted the next major section to discussing it Before we get to that, though, let me explain how to use the queuing routines that I crafted to deal with all the problems Using the DEVQUEUE Object To solve a variety of IRP queuing problems, I created a package of subroutines for managing a queue object that I call a DEVQUEUE I’ll show you first the basic usage of 6/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) a DEVQUEUE Later in this chapter, I’ll explain how the major DEVQUEUE service routines work I’ll discuss in later chapters how your PnP and power management code interacts with the DEVQUEUE object or objects you define You define a DEVQUEUE object for each queue of requests you’ll manage in the driver For example, if your device manages reads and writes in a single queue, you define one DEVQUEUE: typedef struct _DEVICE_EXTENSION { DEVQUEUE dqReadWrite; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; On the CD Code for the DEVQUEUE is part of GENERIC.SYS In addition, if you use my WDMWIZ to create a skeleton driver and don’t ask for GENERIC.SYS support, your skeleton project will include the files DEVQUEUE.CPP and DEVQUEUE.H, which fully implement exactly the same object I don’t recommend trying to type this code from the book because the code from the companion content will contain even more features than I can describe in the book I also recommend checking my Web site (www.oneysoft.com) for updates and corrections Figure 5-8 illustrates the IRP processing logic for a typical driver using DEVQUEUE objects Each DEVQUEUE has its own StartIo routine, which you specify when you initialize the object in AddDevice: NTSTATUS AddDevice( ) { PDEVICE_EXTENSION pdx = ; InitializeQueue(&pdx->dqReadWrite, StartIo); } 7/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) IRP flow with a DEVQUEUE and a StartIo routine You can specify a common dispatch function for both IRP_MJ_READ and IRP_MJ_WRITE: NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DriverObject->MajorFunction[IRP_MJ_READ] = DispatchReadWrite; DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchReadWrite; } #pragma PAGEDCODE NTSTATUS DispatchReadWrite(PDEVICE_OBJECT fdo, PIRP Irp) { 8/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) PAGED_CODE(); PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; IoMarkIrpPending(Irp); StartPacket(&pdx->dqReadWrite, fdo, Irp, CancelRoutine); return STATUS_PENDING; } #pragma LOCKEDCODE VOID CancelRoutine(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; CancelRequest(&pdx->dqReadWrite, Irp); } Note that the cancel argument to StartPacket is not optional: you must supply a cancel routine, but you can see how simple that routine will be If you complete IRPs in a DPC routine, you’ll also call StartNextPacket: VOID DpcForIsr(PKPDC junk1, PDEVICE_OBJECT fdo, PIRP junk2, PDEVICE_EXTENSION pdx) { StartNextPacket(&pdx->dqReadWrite, fdo); } 9/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) If you complete IRPs in your StartIo routine, schedule a DPC to make the call to StartNextPacket in order to avoid excessive recursion For example: typedef struct _DEVICE_EXTENSION { KDPC StartNextDpc; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS AddDevice( ) { KeInitializeDpc(&pdx->StartNextDpc, (PKDEFERRED_ROUTINE) StartNextDpcRoutine, pdx); } VOID StartIo( ) { IoCompleteRequest( ); KeInsertQueueDpc(&pdx->StartNextDpc, NULL, NULL); } VOID StartNextDpcRoutine(PKDPC junk1, PDEVICE_EXTENSION pdx, PVOID junk2, PVOID junk3) { StartNextPacket(&pdx->dqReadWrite, pdx->DeviceObject); } In this example, StartIo calls IoCompleteRequest to complete the IRP it has just handled Calling StartNextPacket directly might lead to a recursive call to StartIo After enough recursive calls, we’ll run out of stack To avoid the potential stack overflow, we queue the StartNextDpc DPC object and return Because StartIo runs at DISPATCH_LEVEL, it won’t be possible for the DPC routine to be called before 10/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) StartIo returns Therefore, StartNextDpcRoutine can call StartNextPacket without worrying about recursion typedef struct _DEVICE_EXTENSION { IO_CSQ_IRP_CONTEXT RedContext; IO_CSQ_IRP_CONTEXT BlueContext; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; When you receive a “red” IRP, you specify the context structure in your call to IoCsqInsertIrp: IoCsqInsertIrp(&pdx->IrpQueue, RedIrp, &pdx->RedContext); How to park a “blue” IRP should be pretty obvious When you later decide you want to complete a parked IRP, you write code like this: PIRP RedIrp = IoCsqRemoveIrp(&pdx->IrpQueue, &pdx->RedContext); if (RedIrp) { RedIrp->IoStatus.Status = STATUS_XXX; RedIrp->IoStatus.Information = YYY; IoCompleteRequest(RedIrp, IO_NO_INCREMENT); } IoCsqRemoveIrp will return NULL if the IRP associated with the context structure has already been cancelled Bear in mind the following caveats when using this mechanism: • It’s up to you to make sure that you haven’t previously parked an IRP using a particular context structure IoCsqInsertIrp is a VOID function and therefore has no way to tell you when you violate this rule 11/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) • You mustn’t touch an I/O buffer associated with a parked IRP because the IRP can be cancelled (and the I/O buffer released!) at any time while it’s parked You should remove the IRP from the queue before trying to use a buffer 12/12 [...].. .Hàng đợi yêu cầu Vàora (Queuing IO Requests) StartIo returns Therefore, StartNextDpcRoutine can call StartNextPacket without worrying about recursion typedef struct _DEVICE_EXTENSION { IO_ CSQ_IRP_CONTEXT RedContext; IO_ CSQ_IRP_CONTEXT BlueContext; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; When you receive a “red” IRP, you specify the context structure in your call to IoCsqInsertIrp: IoCsqInsertIrp(&pdx->IrpQueue,... mind the following caveats when using this mechanism: • It’s up to you to make sure that you haven’t previously parked an IRP using a particular context structure IoCsqInsertIrp is a VOID function and therefore has no way to tell you when you violate this rule 11/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) • You mustn’t touch an I/O buffer associated with a parked IRP because the IRP can be cancelled... &pdx->RedContext); How to park a “blue” IRP should be pretty obvious When you later decide you want to complete a parked IRP, you write code like this: PIRP RedIrp = IoCsqRemoveIrp(&pdx->IrpQueue, &pdx->RedContext); if (RedIrp) { RedIrp->IoStatus.Status = STATUS_XXX; RedIrp->IoStatus.Information = YYY; IoCompleteRequest(RedIrp, IO_ NO_INCREMENT); } IoCsqRemoveIrp will return NULL if the IRP associated with ... struct _DEVICE_EXTENSION { LIST_ENTRY IrpQueue; BOOLEAN DeviceBusy; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS AddDevice( ) { 2/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) InitializeListHead(&pdx->IrpQueue);.. .Hàng đợi yêu cầu Vàora (Queuing IO Requests) Your driver would have a single StartIo routine Your DriverEntry routine would set the DriverStartIo field of the driver object... DispatchReadWrite(PDEVICE_OBJECT fdo, PIRP Irp) { 8/12 Hàng đợi yêu cầu Vàora (Queuing IO Requests) PAGED_CODE(); PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; IoMarkIrpPending(Irp); StartPacket(&pdx->dqReadWrite,

Ngày đăng: 31/12/2015, 22:10

Mục lục

    Hàng đợi yêu cầu Vàora (Queuing IO Requests)

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan