When an application sends an I/O request, the Windows I/O manager package it as an IRP and pass it down the stack of driver objects. If a driver in the stack can handle the request, it completes the action and returns the IRP up to stack back to the I/O manager. If a driver cannot handle the request, it does what it can and pass it down the stack to the next driver. When the IRP reaches the bottom of the stack, the request has to be completed there as there is no further way to go,
When the IRP is created, the I/O manager also allocates additional spaces for each of the drivers in the stack. The space holds an array of structures (IO_STACK_LOCATION) and each driver is assigned one of them.
The array index start at 1 and is assigned to the driver at the bottom of the stack. As such, the access starts from the end of the array and goes down, reminiscent of a stack. Each structure contains the major and minor function to be invoked by I/O manager, parameters and also pointer to the device object. It also contains the CompletionRoutine field which points to the call back routine from the driver above in the stack.
When a driver's dispatch routine receives an IRP, it retrieves the parameter from its IO_STACK_LOCATION using the API call IoGetCurrentIrpSTackLocation(). When the dispatch routine plans to forward the IRP to the next level, it will
(1) set up the IO_STACK_LOCATION for the next driver
(2) register a completion routine (optional)
(3) send off the IRP to the next driver down
(4) return a status code (NTSTATUS)
If the current IO_STACK_LOCATION is not used by the current driver, it can be reused for the next driver. The current drive uses IoSkipCurrentIrpStackLocation(IN PIRP irp) which decrements the current IO Stack location pointer by 1. So when I/O manager dispatch the next driver, it will be incremented by 1 and thus reuse the same location.
IoCopyCurrentIrpStackLocation copies the content of the IO_STACK_LOCATION entry to the next one except the pointer to the completion routine.
IoSetCompletionRoutine registers a completion routine. The call can specify the conditions- success, error or cancel - for invoking the completion routines.
IoCallDriver() calls the next driver and passes along the IRP. The first parameter is the DEVICE_OBJECT of the next driver. The dispatch routine needs to obtain the reference by itself and there is no standard way to do so.
The I/O manager initiates the completion process when one of the driver calls its IoCompleteRequest() API function. The status and information field in the IRP's IO_STATUS_BLOCK structure is initialized. The second argument to IoCompleteRequest is always set to IO_NO_INCREMENT. Starting from the current IO Stack Location, I/O manager will call the completion routines registered. It moves up the stack entry by entry. It skip the level if the completion routine is set to NULL. It keeps on moving up unless the routine returns STATUS_MORE_PROCESSING_REQUIRED status, until it reaches the top of the stack.
No comments:
Post a Comment