上一节关于CreateFile函数的调用过程分析中,有一个函数很让我在意,下面代码片段为其核心功能的实现:
NTSTATUS
NTAPI
IopParseDevice(IN PVOID ParseObject,
IN PVOID ObjectType,
IN OUT PACCESS_STATE AccessState,
IN KPROCESSOR_MODE AccessMode,
IN ULONG Attributes,
IN OUT PUNICODE_STRING CompleteName,
IN OUT PUNICODE_STRING RemainingName,
IN OUT PVOID Context,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
OUT PVOID *Object)
{
..................
PIRP Irp;
Irp = IoAllocateIrp(DeviceObject->StackSize, TRUE);
if (!Irp)
{
/* Dereference the device and VPB, then fail */
IopDereferenceDeviceObject(OriginalDeviceObject, FALSE);
if (Vpb) IopDereferenceVpbAndFree(Vpb);
return STATUS_INSUFFICIENT_RESOURCES;
}
Irp->RequestorMode = AccessMode;
Irp->Flags = IRP_CREATE_OPERATION |
IRP_SYNCHRONOUS_API |
IRP_DEFER_IO_COMPLETION;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
Irp->UserIosb = &IoStatusBlock;
Irp->MdlAddress = NULL;
Irp->PendingReturned = FALSE;
Irp->UserEvent = NULL;
Irp->Cancel = FALSE;
Irp->CancelRoutine = NULL;
Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
SecurityContext.SecurityQos = SecurityQos;
SecurityContext.AccessState = AccessState;
SecurityContext.DesiredAccess = AccessState->RemainingDesiredAccess;
SecurityContext.FullCreateOptions = OpenPacket->CreateOptions;
/* Get the I/O Stack location */
StackLoc = (PEXTENDED_IO_STACK_LOCATION)IoGetNextIrpStackLocation(Irp);
StackLoc->Control = 0;
/* Check what kind of file this is */
switch (OpenPacket->CreateFileType)
{
/* Normal file */
case CreateFileTypeNone:
/* Set the major function and EA Length */
StackLoc->MajorFunction = IRP_MJ_CREATE;
StackLoc->Parameters.Create.EaLength = OpenPacket->EaLength;
/* Set the flags */
StackLoc->Flags = (UCHAR)OpenPacket->Options;
StackLoc->Flags |= !(Attributes & OBJ_CASE_INSENSITIVE) ?
SL_CASE_SENSITIVE: 0;
break;
/* Named pipe */
case CreateFileTypeNamedPipe:
/* Set the named pipe MJ and set the parameters */
StackLoc->MajorFunction = IRP_MJ_CREATE_NAMED_PIPE;
StackLoc->Parameters.CreatePipe.Parameters =
OpenPacket->MailslotOrPipeParameters;
break;
/* Mailslot */
case CreateFileTypeMailslot:
/* Set the mailslot MJ and set the parameters */
StackLoc->MajorFunction = IRP_MJ_CREATE_MAILSLOT;
StackLoc->Parameters.CreateMailslot.Parameters =
OpenPacket->MailslotOrPipeParameters;
break;
}
/* Set the common data */
Irp->Overlay.AllocationSize = OpenPacket->AllocationSize;
Irp->AssociatedIrp.SystemBuffer = OpenPacket->EaBuffer;
StackLoc->Parameters.Create.Options = (OpenPacket->Disposition << 24) |
(OpenPacket->CreateOptions &
0xFFFFFF);
StackLoc->Parameters.Create.FileAttributes = OpenPacket->FileAttributes;
StackLoc->Parameters.Create.ShareAccess = OpenPacket->ShareAccess;
StackLoc->Parameters.Create.SecurityContext = &SecurityContext;
/* Check if we really need to create an object */
if (!UseDummyFile)
{
/* Create the actual file object */
InitializeObjectAttributes(&ObjectAttributes,
NULL,
Attributes,
NULL,
NULL);
Status = ObCreateObject(KernelMode,
IoFileObjectType,
&ObjectAttributes,
AccessMode,
NULL,
sizeof(FILE_OBJECT),
0,
0,
(PVOID*)&FileObject);
if (!NT_SUCCESS(Status))
{
/* Create failed, free the IRP */
IoFreeIrp(Irp);
/* Dereference the device and VPB */
IopDereferenceDeviceObject(OriginalDeviceObject, FALSE);
if (Vpb) IopDereferenceVpbAndFree(Vpb);
/* We failed, return status */
OpenPacket->FinalStatus = Status;
return Status;
}
/* Clear the file object */
RtlZeroMemory(FileObject, sizeof(FILE_OBJECT));
/* Check if this is Synch I/O */
if (OpenPacket->CreateOptions &
(FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT))
{
/* Set the synch flag */
FileObject->Flags |= FO_SYNCHRONOUS_IO;
/* Check if it's also alertable */
if (OpenPacket->CreateOptions & FILE_SYNCHRONOUS_IO_ALERT)
{
/* It is, set the alertable flag */
FileObject->Flags |= FO_ALERTABLE_IO;
}
}
/* Check if this is synch I/O */
if (FileObject->Flags & FO_SYNCHRONOUS_IO)
{
/* Initialize the event */
KeInitializeEvent(&FileObject->Lock, SynchronizationEvent, FALSE);
}
/* Check if the caller requested no intermediate buffering */
if (OpenPacket->CreateOptions & FILE_NO_INTERMEDIATE_BUFFERING)
{
/* Set the correct flag for the FSD to read */
FileObject->Flags |= FO_NO_INTERMEDIATE_BUFFERING;
}
/* Check if the caller requested write through support */
if (OpenPacket->CreateOptions & FILE_WRITE_THROUGH)
{
/* Set the correct flag for the FSD to read */
FileObject->Flags |= FO_WRITE_THROUGH;
}
/* Check if the caller says the file will be only read sequentially */
if (OpenPacket->CreateOptions & FILE_SEQUENTIAL_ONLY)
{
/* Set the correct flag for the FSD to read */
FileObject->Flags |= FO_SEQUENTIAL_ONLY;
}
/* Check if the caller believes the file will be only read randomly */
if (OpenPacket->CreateOptions & FILE_RANDOM_ACCESS)
{
/* Set the correct flag for the FSD to read */
FileObject->Flags |= FO_RANDOM_ACCESS;
}
}
else
{
/* Use the dummy object instead */
DummyFileObject = OpenPacket->DummyFileObject;
RtlZeroMemory(DummyFileObject, sizeof(DUMMY_FILE_OBJECT));
/* Set it up */
FileObject = (PFILE_OBJECT)&DummyFileObject->ObjectHeader.Body;
DummyFileObject->ObjectHeader.Type = IoFileObjectType;
DummyFileObject->ObjectHeader.PointerCount = 1;
}
/* Setup the file header */
FileObject->Type = IO_TYPE_FILE;
FileObject->Size = sizeof(FILE_OBJECT);
FileObject->RelatedFileObject = OpenPacket->RelatedFileObject;
FileObject->DeviceObject = OriginalDeviceObject;
/* Check if this is a direct device open */
if (DirectOpen) FileObject->Flags |= FO_DIRECT_DEVICE_OPEN;
/* Check if the caller wants case sensitivity */
if (!(Attributes & OBJ_CASE_INSENSITIVE))
{
/* Tell the driver about it */
FileObject->Flags |= FO_OPENED_CASE_SENSITIVE;
}
/* Now set the file object */
Irp->Tail.Overlay.OriginalFileObject = FileObject;
StackLoc->FileObject = FileObject;
/* Check if the file object has a name */
if (RemainingName->Length)
{
/* Setup the unicode string */
FileObject->FileName.MaximumLength = RemainingName->Length +
sizeof(WCHAR);
FileObject->FileName.Buffer = ExAllocatePoolWithTag(PagedPool,
FileObject->
FileName.
MaximumLength,
TAG_IO_NAME);
if (!FileObject->FileName.Buffer)
{
/* Failed to allocate the name, free the IRP */
IoFreeIrp(Irp);
/* Dereference the device object and VPB */
IopDereferenceDeviceObject(OriginalDeviceObject, FALSE);
if (Vpb) IopDereferenceVpbAndFree(Vpb);
/* Clear the FO and dereference it */
FileObject->DeviceObject = NULL;
if (!UseDummyFile) ObDereferenceObject(FileObject);
/* Fail */
return STATUS_INSUFFICIENT_RESOURCES;
}
}
/* Copy the name */
RtlCopyUnicodeString(&FileObject->FileName, RemainingName);
/* Initialize the File Object event and set the FO */
KeInitializeEvent(&FileObject->Event, NotificationEvent, FALSE);
OpenPacket->FileObject = FileObject;
/* Queue the IRP and call the driver */
IopQueueIrpToThread(Irp);
Status = IoCallDriver(DeviceObject, Irp);
if (Status == STATUS_PENDING)
{
/* Wait for the driver to complete the create */
KeWaitForSingleObject(&FileObject->Event,
Executive,
KernelMode,
FALSE,
NULL);
/* Get the new status */
Status = IoStatusBlock.Status;
}
else
{
/* We'll have to complete it ourselves */
ASSERT(!Irp->PendingReturned);
ASSERT(!Irp->MdlAddress);
/* Completion happens at APC_LEVEL */
KeRaiseIrql(APC_LEVEL, &OldIrql);
/* Get the new I/O Status block ourselves */
IoStatusBlock = Irp->IoStatus;
Status = IoStatusBlock.Status;
/* Manually signal the even, we can't have any waiters */
FileObject->Event.Header.SignalState = 1;
/* Now that we've signaled the events, de-associate the IRP */
IopUnQueueIrpFromThread(Irp);
/* Check if the IRP had an input buffer */
if ((Irp->Flags & IRP_BUFFERED_IO) &&
(Irp->Flags & IRP_DEALLOCATE_BUFFER))
{
/* Free it. A driver might've tacked one on */
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
/* Free the IRP and bring the IRQL back down */
IoFreeIrp(Irp);
KeLowerIrql(OldIrql);
}
/* Copy the I/O Status */
OpenPacket->Information = IoStatusBlock.Information;
/* The driver failed to create the file */
if (!NT_SUCCESS(Status))
{
/* Check if we have a name */
if (FileObject->FileName.Length)
{
/* Free it */
ExFreePoolWithTag(FileObject->FileName.Buffer, TAG_IO_NAME);
FileObject->FileName.Length = 0;
}
/* Clear its device object */
FileObject->DeviceObject = NULL;
/* Save this now because the FO might go away */
OpenCancelled = FileObject->Flags & FO_FILE_OPEN_CANCELLED ?
TRUE : FALSE;
/* Clear the file object in the open packet */
OpenPacket->FileObject = NULL;
/* Dereference the file object */
if (!UseDummyFile) ObDereferenceObject(FileObject);
/* Dereference the device object */
IopDereferenceDeviceObject(OriginalDeviceObject, FALSE);
/* Unless the driver cancelled the open, dereference the VPB */
if (!(OpenCancelled) && (Vpb)) IopDereferenceVpbAndFree(Vpb);
/* Set the status and return */
OpenPacket->FinalStatus = Status;
return Status;
}
...............................
}
这段代码节选自ReactOSV03-15,代码比较长,我节选了其中最重要的一部分。代码的一开头首先初始化一个IRP结构指针,IRP由IRP头部与附加结构IO_STACK_LOCATION组成,参考以下代码:
IopParseDevice->IoAllocateIrp
PIRP NTAPI IoAllocateIrp(IN CCHAR StackSize,IN BOOLEAN ChargeQuota)
{
PIRP Irp = NULL;
USHORT Size = IoSizeOfIrp(StackSize);
PKPRCB Prcb;
UCHAR Flags = 0;
PNPAGED_LOOKASIDE_LIST List = NULL;
PP_NPAGED_LOOKASIDE_NUMBER ListType = LookasideSmallIrpList;
/* Set Charge Quota Flag */
if (ChargeQuota) Flags |= IRP_QUOTA_CHARGED;
/* FIXME: Implement Lookaside Floats */
/* Figure out which Lookaside List to use */
if ((StackSize <= 8) && (ChargeQuota == FALSE))
{
/* Set Fixed Size Flag */
Flags = IRP_ALLOCATED_FIXED_SIZE;
/* See if we should use big list */
if (StackSize != 1)
{
Size = IoSizeOfIrp(8);
ListType = LookasideLargeIrpList;
}
/* Get the PRCB */
Prcb = KeGetCurrentPrcb();
/* Get the P List First */
List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].P;
/* Attempt allocation */
List->L.TotalAllocates++;
Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead);
/* Check if the P List failed */
if (!Irp)
{
/* Let the balancer know */
List->L.AllocateMisses++;
/* Try the L List */
List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].L;
List->L.TotalAllocates++;
Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead);
}
}
/* Check if we have to use the pool */
if (!Irp)
{
/* Did we try lookaside and fail? */
if (Flags & IRP_ALLOCATED_FIXED_SIZE) List->L.AllocateMisses++;
/* Check if we should charge quota */
if (ChargeQuota)
{
/* Irp = ExAllocatePoolWithQuotaTag(NonPagedPool, Size, TAG_IRP); */
/* FIXME */
Irp = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_IRP);
}
else
{
/* Allocate the IRP With no Quota charge */
Irp = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_IRP);
}
/* Make sure it was sucessful */
if (!Irp) return(NULL);
}
else
{
/* In this case there is no charge quota */
Flags &= ~IRP_QUOTA_CHARGED;
}
/* Now Initialize it */
IoInitializeIrp(Irp, Size, StackSize);
/* Set the Allocation Flags */
Irp->AllocationFlags = Flags;
/* Return it */
IOTRACE(IO_IRP_DEBUG,
"%s - Allocated IRP %p with allocation flags %lx\n",
__FUNCTION__,
Irp,
Flags);
return Irp;
}
首先来看宏定义IoSizeOfIrp
#define IoSizeOfIrp(_StackSize) \
((USHORT) (sizeof(IRP) + ((_StackSize) * (sizeof(IO_STACK_LOCATION)))))
也就是说,初始化IRP同时,IRP头部与IO_STACK_LOCATION会同时创建,这里有一个技巧:如果IRP的StackCount=1,则标记为LookasideSmallIrpList,如果StackCount在1-8之间,则标记为LookasideLargeIrpList,并且都按StackSize=8处理,在这两种情况下,操作系统为我们准备了一份缓存,直接去缓存中取相应大小的内存就可以了,一般的PNP驱动都不会超过8层,这种设计能够满足大部分的应用场景,当StackSize>8时,就要去内核堆中动态申请一份内存就可以了。
IRP创建之后,紧跟在后面的是IRP的初始化:
Type与Size为结构体的头部,注意这个Size是包括IO_STACK_LOCATION的大小;MdlAddress指向内存描述表(MDL),如果创建设备指定设备Flags为DO_DIRECT_IO时,这个MDL就会派上大用场;AssociatedIRP是一个union,AssociatedIRP.SystemBuffer指向一个缓冲区,通过这个缓冲区可以与User模式交互数据,前提是设备Flags为DO_BUFFERED_IO;ThreadListEntry在IRP挂接到线程Thread时使用;IoStatus是一个结构体,用于IRP消息的返回;RequestMode用于指定请求来源是UserMode还是KernelMode,在不同模式下会做不同级别的检查;PenddingReturned标识IRP请求是否返回STATUS_PENDING,稍后我们还会继续做分析;StackCount表示栈的层数;CurrentLocation表示当前处于栈的哪一层;Cancel标识是否针对IRP调用了函数IoCancelIrp();CancelIrql是一个IRQL值;ApcEnviroment是一个枚举,跟APC函数的调用有关,这里我们不做过多表述;AllocationFlags是一个标志符,常见的标志有IRP_ALLOCATED_FIXED_SIZE、IRP_LOOKASIDE_ALLOCATION等,UserIosb指向结构体IO_STATUS_BLOCK,具体作用我还没弄明白,我们先不去管它;UserEvent是一个指向KEVENT的指针,这个KEVENT就是异步调用时,通过Overlap结构传递的Event,Overlay又是一个union,其中UserApcRoutine与UserApcContext对APC实现机制非常重要;CancelRoutine是驱动程序取消例程的地址,后面我们再讨论;UserBuffer用在非DIRECT非BUFFER的场合,不建议使用;最后的Tail也是一个union,其中的CurrentStackLocation是一个IO_STACK_LOCATION的指针,表示当前栈的位置,在后面的驱动程序分析过程当会经常碰到。
IoGetNextIrpStackLocation的代码我直接贴出来,就不多做分析了,需要说明的是,IRP初始创建时CurrentLocation的地址是指向最上层STACK_LOCATION+1的位置的,也就是一个无效地址,通过IoGetNextIrpStackLocation就将指针指向正确的地方。
PIO_STACK_LOCATION IoGetNextIrpStackLocation(_In_ PIRP Irp)
{
ASSERT(Irp->CurrentLocation > 0);
#ifdef NONAMELESSUNION
return ((Irp)->Tail.Overlay.s.u.CurrentStackLocation - 1 );
#else
return ((Irp)->Tail.Overlay.CurrentStackLocation - 1 );
#endif
}
紧接着是IO_STACK_LOCATION的赋值,其结构体定义如下:
MajorFunction与MinorFunction指定了当前IRP的请求代号,驱动程序会根据代号做不同的响应;Control常用于函数IoMarkIrpPending,有机会我会再重点说明这个参数的用法,Parameter是对IRP请求参数的统称,其结构是一个很大的结构体,具体内容请查找相关书籍,DeviceObject指向当前驱动设备对象,该对象由上层驱动程序指定;FileObject指向内核文件对象的地址,CreateFile返回的句柄对应的文件对象就是它;CompleteRoutine与Context分别指向完成例程的函数地址与参数,主要用于异步过程调用。
跳过后面文件操作的那一部分细节,我们直接来到IRP请求的发送这一节,首先可以看到的操作是将IRP插入到当前线程:
VOID IopQueueIrpToThread(IN PIRP Irp)
{
KIRQL OldIrql;
/* Raise to APC Level */
KeRaiseIrql(APC_LEVEL, &OldIrql);
/* Insert it into the list */
InsertHeadList(&Irp->Tail.Overlay.Thread->IrpList, &Irp->ThreadListEntry);
/* Lower irql */
KeLowerIrql(OldIrql);
}
接着就是重头戏,IoCallDriver的调用,IoCallDriver通过IofCallDriver实现:
NTSTATUS IofCallDriver(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
PDRIVER_OBJECT DriverObject;
PIO_STACK_LOCATION StackPtr;
/* Make sure this is a valid IRP */
ASSERT(Irp->Type == IO_TYPE_IRP);
/* Get the Driver Object */
DriverObject = DeviceObject->DriverObject;
/* Decrease the current location and check if */
Irp->CurrentLocation--;
if (Irp->CurrentLocation <= 0)
{
/* This IRP ran out of stack, bugcheck */
KeBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR)Irp, 0, 0, 0);
}
/* Now update the stack location */
StackPtr = IoGetNextIrpStackLocation(Irp);
Irp->Tail.Overlay.CurrentStackLocation = StackPtr;
/* Get the Device Object */
StackPtr->DeviceObject = DeviceObject;
/* Call it */
return DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject,Irp);
}
这个函数功能很简单:获取下层驱动对象,并调用驱动函数。
再来看IoCallDriver后面的这一段if{}.. else{}..。如果IoCallDriver返回值Status为STATUS_PENDING,则表示返回过程是异步的,需要静静的等待FileObject->Event变成有信号,函数才能返回(CreateFile本身是不存在异步的)。如果Status返回非STATUS_PENDING,那就是操作成功了,这时候需要将当前IRQL提升到APC Level,并将IRP从对应的线程中摘除,并释放IRP内存。最后就是返回值的复制与其他善后工作了。
调用DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject,Irp),进入驱动模块的设计范畴,OnCreate代码具体代码怎么写看个人的喜好,比较常见的做法是:
IO_STATUS OnCreate(DEVICE_OBJECT *device_object, IRP *irp)
{
...........
irp->IoStatus.Status = status;
irp->IoStatus.Information = info;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}
这里的IoCompleteRequest又是一个需要重点注意的函数,让我们来看看它的实现:
#define IoCompleteRequest IofCompleteRequest
VOID IofCompleteRequest(IN PIRP Irp,IN CCHAR PriorityBoost)
{
PIO_STACK_LOCATION StackPtr, LastStackPtr;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
PETHREAD Thread;
NTSTATUS Status;
PMDL Mdl, NextMdl;
ULONG MasterCount;
PIRP MasterIrp;
ULONG Flags;
NTSTATUS ErrorCode = STATUS_SUCCESS;
IOTRACE(IO_IRP_DEBUG,
"%s - Completing IRP %p\n",
__FUNCTION__,
Irp);
/* Make sure this IRP isn't getting completed twice or is invalid */
if ((Irp->CurrentLocation) > (Irp->StackCount + 1))
{
/* Bugcheck */
KeBugCheckEx(MULTIPLE_IRP_COMPLETE_REQUESTS, (ULONG_PTR)Irp, 0, 0, 0);
}
/* Some sanity checks */
ASSERT(Irp->Type == IO_TYPE_IRP);
ASSERT(!Irp->CancelRoutine);
ASSERT(Irp->IoStatus.Status != STATUS_PENDING);
ASSERT(Irp->IoStatus.Status != (NTSTATUS)0xFFFFFFFF);
/* Get the last stack */
LastStackPtr = (PIO_STACK_LOCATION)(Irp + 1);
if (LastStackPtr->Control & SL_ERROR_RETURNED)
{
/* Get the error code */
ErrorCode = PtrToUlong(LastStackPtr->Parameters.Others.Argument4);
}
/*
* Start the loop with the current stack and point the IRP to the next stack
* and then keep incrementing the stack as we loop through. The IRP should
* always point to the next stack location w.r.t the one currently being
* analyzed, so completion routine code will see the appropriate value.
* Because of this, we must loop until the current stack location is +1 of
* the stack count, because when StackPtr is at the end, CurrentLocation is +1.
*/
for (StackPtr = IoGetCurrentIrpStackLocation(Irp),
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++;
Irp->CurrentLocation <= (Irp->StackCount + 1);
StackPtr++,
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++)
{
/* Set Pending Returned */
Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;
/* Check if we failed */
if (!NT_SUCCESS(Irp->IoStatus.Status))
{
/* Check if it was changed by a completion routine */
if (Irp->IoStatus.Status != ErrorCode)
{
/* Update the error for the current stack */
ErrorCode = Irp->IoStatus.Status;
StackPtr->Control |= SL_ERROR_RETURNED;
LastStackPtr->Parameters.Others.Argument4 = UlongToPtr(ErrorCode);
LastStackPtr->Control |= SL_ERROR_RETURNED;
}
}
/* Check if there is a Completion Routine to Call */
if ((NT_SUCCESS(Irp->IoStatus.Status) &&
(StackPtr->Control & SL_INVOKE_ON_SUCCESS)) ||
(!NT_SUCCESS(Irp->IoStatus.Status) &&
(StackPtr->Control & SL_INVOKE_ON_ERROR)) ||
(Irp->Cancel &&
(StackPtr->Control & SL_INVOKE_ON_CANCEL)))
{
/* Clear the stack location */
IopClearStackLocation(StackPtr);
/* Check for highest-level device completion routines */
if (Irp->CurrentLocation == (Irp->StackCount + 1))
{
/* Clear the DO, since the current stack location is invalid */
DeviceObject = NULL;
}
else
{
/* Otherwise, return the real one */
DeviceObject = IoGetCurrentIrpStackLocation(Irp)->DeviceObject;
}
/* Call the completion routine */
Status = StackPtr->CompletionRoutine(DeviceObject,
Irp,
StackPtr->Context);
/* Don't touch the Packet in this case, since it might be gone! */
if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;
}
else
{
/* Otherwise, check if this is a completed IRP */
if ((Irp->CurrentLocation <= Irp->StackCount) &&
(Irp->PendingReturned))
{
/* Mark it as pending */
IoMarkIrpPending(Irp);
}
/* Clear the stack location */
IopClearStackLocation(StackPtr);
}
}
/* Check if the IRP is an associated IRP */
if (Irp->Flags & IRP_ASSOCIATED_IRP)
{
/* Get the master IRP and count */
MasterIrp = Irp->AssociatedIrp.MasterIrp;
MasterCount = InterlockedDecrement(&MasterIrp->AssociatedIrp.IrpCount);
/* Free the MDLs */
for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
{
/* Go to the next one */
NextMdl = Mdl->Next;
IoFreeMdl(Mdl);
}
/* Free the IRP itself */
IoFreeIrp(Irp);
/* Complete the Master IRP */
if (!MasterCount) IofCompleteRequest(MasterIrp, PriorityBoost);
return;
}
/* We don't support this yet */
ASSERT(Irp->IoStatus.Status != STATUS_REPARSE);
/* Check if we have an auxiliary buffer */
if (Irp->Tail.Overlay.AuxiliaryBuffer)
{
/* Free it */
ExFreePool(Irp->Tail.Overlay.AuxiliaryBuffer);
Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
}
/* Check if this is a Paging I/O or Close Operation */
if (Irp->Flags & (IRP_PAGING_IO | IRP_CLOSE_OPERATION))
{
/* Handle a Close Operation or Sync Paging I/O */
if (Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_CLOSE_OPERATION))
{
/* Set the I/O Status and Signal the Event */
Flags = Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_PAGING_IO);
*Irp->UserIosb = Irp->IoStatus;
KeSetEvent(Irp->UserEvent, PriorityBoost, FALSE);
/* Free the IRP for a Paging I/O Only, Close is handled by us */
if (Flags) IoFreeIrp(Irp);
}
else
{
#if 0
/* Page 166 */
KeInitializeApc(&Irp->Tail.Apc
&Irp->Tail.Overlay.Thread->Tcb,
Irp->ApcEnvironment,
IopCompletePageWrite,
NULL,
NULL,
KernelMode,
NULL);
KeInsertQueueApc(&Irp->Tail.Apc,
NULL,
NULL,
PriorityBoost);
#else
/* Not implemented yet. */
UNIMPLEMENTED_DBGBREAK("Not supported!\n");
#endif
}
/* Get out of here */
return;
}
/* Unlock MDL Pages, page 167. */
Mdl = Irp->MdlAddress;
while (Mdl)
{
MmUnlockPages(Mdl);
Mdl = Mdl->Next;
}
/* Check if we should exit because of a Deferred I/O (page 168) */
if ((Irp->Flags & IRP_DEFER_IO_COMPLETION) && !(Irp->PendingReturned))
{
/*
* Return without queuing the completion APC, since the caller will
* take care of doing its own optimized completion at PASSIVE_LEVEL.
*/
return;
}
/* Get the thread and file object */
Thread = Irp->Tail.Overlay.Thread;
FileObject = Irp->Tail.Overlay.OriginalFileObject;
/* Make sure the IRP isn't canceled */
if (!Irp->Cancel)
{
/* Initialize the APC */
KeInitializeApc(&Irp->Tail.Apc,
&Thread->Tcb,
Irp->ApcEnvironment,
IopCompleteRequest,
NULL,
NULL,
KernelMode,
NULL);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc,
FileObject,
NULL, /* This is used for REPARSE stuff */
PriorityBoost);
}
else
{
/* The IRP just got canceled... does a thread still own it? */
if (Thread)
{
/* Yes! There is still hope! Initialize the APC */
KeInitializeApc(&Irp->Tail.Apc,
&Thread->Tcb,
Irp->ApcEnvironment,
IopCompleteRequest,
NULL,
NULL,
KernelMode,
NULL);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc,
FileObject,
NULL, /* This is used for REPARSE stuff */
PriorityBoost);
}
else
{
/* Nothing left for us to do, kill it */
ASSERT(Irp->Cancel);
IopCleanupIrp(Irp, FileObject);
}
}
}
跳过前面的安全检查,来到后面for循环的这一段代码,这个循环遍历的对象是:从最底层IO_STACK_LOCATION到最上层IO_STACK_LOCATION,下面我们来逐步分析:
/* Set Pending Returned */
Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;
如果当前层的Control字段能够找到SL_PENDING_RETURN标记,则Irp的PendingReturned字段置为1,否则置为0,很显然,如果最下面一层堆叠是异步返回的,Control标记中肯定能找到SL_PENDING_RETURN标记,Irp的PendingReturned就会等于1,但这是在一个循环当中,所以在上层堆叠的循环过程中,这个函数又会被执行一次,上层Control是一个什么状态这个很不确定,这不就将最下层设置好的PendingReturned给覆盖了吗?
是的,为了解决这种问题,驱动函数中,驱动函数通常需要如下操作:
NTSTATUS pass_irp_down(libusb_device_t *dev, IRP *irp,
PIO_COMPLETION_ROUTINE completion_routine,
void *context)
{
if (completion_routine)
{
IoCopyCurrentIrpStackLocationToNext(irp);
IoSetCompletionRoutine(irp, completion_routine, context,
TRUE, TRUE, TRUE);
}
else
{
IoSkipCurrentIrpStackLocation(irp);
}
return IoCallDriver(dev->next_stack_device, irp);
}
上层堆叠向下层堆叠传递IRP时,如果有完成函数completion_routine,则将当前IO_STACK_LOCATION的参数复制到下一层,并设置完成函数;如果没有完成函数,则需要将堆叠指针+1,让下一层与本层共用一个IO_STACK_LOCATION。
当上下层没有共用IO_STACK_LOCATION的时候,completion_routine的实现体中通常需要进行如下操作:
static NTSTATUS DDKAPI on_xx_complete(DEVICE_OBJECT *device_object, IRP *irp, void *context)
{
........................
if (irp->PendingReturned)
{
IoMarkIrpPending(irp);
}
........................
return STATUS_SUCCESS;
}
这样一来,Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED操作就说得通了:下层驱动对象将irp->PendingReturned置为1,并调用上一层的完成函数complete_routine,完成函数又会将上一层的StackPtr->Control的SL_PENDING_RETURNED置位,循环到达上一层堆叠之后,又会再一次调用Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED,如此往复,就好像接力棒传递一样,最终到达IO管理器,管理器看到的结果就是Irp->PendingReturned = 1。
为什么要如此煞费苦心,直接返回irp->PendingReturned = 1不好吗?我想设计这套系统的人是不太愿意我们直接操作Irp这个数据结构的,每一层驱动都只能在自己的IO_STACK_LOCATION范围内活动,这样就避免了开发中的混乱。
另外,有一个小技巧就是:虽然完成函数是在当前堆叠中实现的,不过函数指针确实通过下一层堆叠保存的,StackPtr->CompletionRoutine调用的实际上是上一层的完成函数
VOID IoSetCompletionRoutine(
_In_ PIRP Irp,
_In_opt_ PIO_COMPLETION_ROUTINE CompletionRoutine,
_In_opt_ __drv_aliasesMem PVOID Context,
_In_ BOOLEAN InvokeOnSuccess,
_In_ BOOLEAN InvokeOnError,
_In_ BOOLEAN InvokeOnCancel)
{
PIO_STACK_LOCATION irpSp;
ASSERT( (InvokeOnSuccess || InvokeOnError || InvokeOnCancel) ? (CompletionRoutine != NULL) : TRUE );
irpSp = IoGetNextIrpStackLocation(Irp);
irpSp->CompletionRoutine = CompletionRoutine;
irpSp->Context = Context;
irpSp->Control = 0;
if (InvokeOnSuccess) {
irpSp->Control = SL_INVOKE_ON_SUCCESS;
}
if (InvokeOnError) {
irpSp->Control |= SL_INVOKE_ON_ERROR;
}
if (InvokeOnCancel) {
irpSp->Control |= SL_INVOKE_ON_CANCEL;
}
}
让我们再回到IofCompleteRequest函数,可以看到一条语句:
if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;
如果完成函数返回STATUS_SUCCESS,则传递irp->PendingReturned=X(0,1)的接力棒会一直传递下去,如果返回值是STATUS_MORE_PROCESSING_REQUIRED,则传递终止,本函数也执行结束。
一些书籍对此的解释是:完成函数返回STATUS_MORE_PROCESSING_REQUIRED时为了将IRP控制权交还给当前堆叠,并由当前堆叠去完成它。这么说好像有点抽象了,我们还是以代码进行佐证。
完成函数返回STATUS_MORE_PROCESSING_REQUIRED的代码模板:
static NTSTATUS DDKAPI on_xx_complete(DEVICE_OBJECT *device_object, IRP *irp, void *context)
{
........................
KeSetEvent((KEVENT *) context, IO_NO_INCREMENT, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
调用完成函数的模板:
ULONG OnMajor_xx(IN PDEVICE_EXTENSION deviceExtension, IN PIRP Irp)
{
.....................
KeInitializeEvent(&event,NotificationEvent,FALSE);
IoSetCompletionRoutine(Irp,on_usbd_complete,&event,TRUE,TRUE,TRUE);
IoCallDriver(deviceExtension->target_device, Irp);
KeWaitForSingleObject((PVOID) &event,Executive,KernelMode,FALSE,NULL);
status = irp->IoStatus.Status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}
很明白了吧,调用函数中创建一个等待事件对象,设置完成函数,然后调用IocallDriver,触发下层堆叠的操作,紧接着对事件等待;下层堆叠可能同步完成,也可能异步完成,不管怎么样,最终都会调用IofCompleteRequest,开启一个循环,循环遍历堆叠,碰到完成函数就执行,完成函数SetEvent,等待事件的堆叠对应的线程开始运作,最后再次调用IoCompleteRequest完成IRP操作。
还有一点需要说明的是:设置irp->PendingReturned时,完成函数必须返回STATUS_SUCCESS,STATUS_MORE_PROCESSING_REQUIRED通常与事件同步使用。
再次回到IofCompleteRequest函数的分析中来,if (Irp->Flags & IRP_ASSOCIATED_IRP),如果这是一个关联IRP,在释放自己的同时,还需要对被关联IRP进行操作。
if (Irp->Flags & (IRP_PAGING_IO | IRP_CLOSE_OPERATION))这一段,如果这是一个同步的Paging I/O或者是一个关闭操作,还需要对用户定义的Event进行触发,这样,在UserMode下面调用的WaitForSingleObject等函数才得以返回。
最后,如果不是IRP_DEFER_IO_COMPLETION的IRP,就需要初始化一个APC,并将其挂接到FileObject相应的域,在合适的时机,善后函数IopCompleteRequest(与函数IofCompleteRequest仅一个字母之差)函数会被调用。
VOID IopCompleteRequest(IN PKAPC Apc,
IN PKNORMAL_ROUTINE* NormalRoutine,
IN PVOID* NormalContext,
IN PVOID* SystemArgument1,
IN PVOID* SystemArgument2)
{
PFILE_OBJECT FileObject;
PIRP Irp;
PMDL Mdl, NextMdl;
PVOID Port = NULL, Key = NULL;
BOOLEAN SignaledCreateRequest = FALSE;
/* Get data from the APC */
FileObject = (PFILE_OBJECT)*SystemArgument1;
Irp = CONTAINING_RECORD(Apc, IRP, Tail.Apc);
IOTRACE(IO_IRP_DEBUG,
"%s - Completing IRP %p for %p\n",
__FUNCTION__,
Irp,
FileObject);
/* Sanity check */
ASSERT(Irp->IoStatus.Status != (NTSTATUS)0xFFFFFFFF);
/* Check if we have a file object */
if (*SystemArgument2)
{
/* Check if we're reparsing */
if ((Irp->IoStatus.Status == STATUS_REPARSE) &&
(Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT))
{
/* We should never get this yet */
UNIMPLEMENTED_DBGBREAK("Reparse support not yet present!\n");
return;
}
}
/* Handle Buffered case first */
if (Irp->Flags & IRP_BUFFERED_IO)
{
/* Check if we have an input buffer and if we succeeded */
if ((Irp->Flags & IRP_INPUT_OPERATION) &&
(Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED) &&
!(NT_ERROR(Irp->IoStatus.Status)))
{
/* Copy the buffer back to the user */
RtlCopyMemory(Irp->UserBuffer,
Irp->AssociatedIrp.SystemBuffer,
Irp->IoStatus.Information);
}
/* Also check if we should de-allocate it */
if (Irp->Flags & IRP_DEALLOCATE_BUFFER)
{
/* Deallocate it */
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
}
/* Now we got rid of these two... */
Irp->Flags &= ~(IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER);
/* Check if there's an MDL */
for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
{
/* Free it */
NextMdl = Mdl->Next;
IoFreeMdl(Mdl);
}
/* No MDLs left */
Irp->MdlAddress = NULL;
/*
* Check if either the request was completed without any errors
* (but warnings are OK!), or if it was completed with an error, but
* did return from a pending I/O Operation and is not synchronous.
*/
if (!(NT_ERROR(Irp->IoStatus.Status)) ||
(NT_ERROR(Irp->IoStatus.Status) &&
(Irp->PendingReturned) &&
!(IsIrpSynchronous(Irp, FileObject))))
{
/* Get any information we need from the FO before we kill it */
if ((FileObject) && (FileObject->CompletionContext))
{
/* Save Completion Data */
Port = FileObject->CompletionContext->Port;
Key = FileObject->CompletionContext->Key;
}
/* Use SEH to make sure we don't write somewhere invalid */
_SEH2_TRY
{
/* Save the IOSB Information */
*Irp->UserIosb = Irp->IoStatus;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Ignore any error */
}
_SEH2_END;
/* Check if we have an event or a file object */
if (Irp->UserEvent)
{
/* At the very least, this is a PKEVENT, so signal it always */
KeSetEvent(Irp->UserEvent, 0, FALSE);
/* Check if we also have a File Object */
if (FileObject)
{
/* Check if this is an Asynch API */
if (!(Irp->Flags & IRP_SYNCHRONOUS_API))
{
/* Dereference the event */
ObDereferenceObject(Irp->UserEvent);
}
/*
* Now, if this is a Synch I/O File Object, then this event is
* NOT an actual Executive Event, so we won't dereference it,
* and instead, we will signal the File Object
*/
if ((FileObject->Flags & FO_SYNCHRONOUS_IO) &&
!(Irp->Flags & IRP_OB_QUERY_NAME))
{
/* Signal the file object and set the status */
KeSetEvent(&FileObject->Event, 0, FALSE);
FileObject->FinalStatus = Irp->IoStatus.Status;
}
/*
* This could also be a create operation, in which case we want
* to make sure there's no APC fired.
*/
if (Irp->Flags & IRP_CREATE_OPERATION)
{
/* Clear the APC Routine and remember this */
Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
SignaledCreateRequest = TRUE;
}
}
}
else if (FileObject)
{
/* Signal the file object and set the status */
KeSetEvent(&FileObject->Event, 0, FALSE);
FileObject->FinalStatus = Irp->IoStatus.Status;
/*
* This could also be a create operation, in which case we want
* to make sure there's no APC fired.
*/
if (Irp->Flags & IRP_CREATE_OPERATION)
{
/* Clear the APC Routine and remember this */
Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
SignaledCreateRequest = TRUE;
}
}
/* Update transfer count for everything but create operation */
if (!(Irp->Flags & IRP_CREATE_OPERATION))
{
if (Irp->Flags & IRP_WRITE_OPERATION)
{
/* Update write transfer count */
IopUpdateTransferCount(IopWriteTransfer,
(ULONG)Irp->IoStatus.Information);
}
else if (Irp->Flags & IRP_READ_OPERATION)
{
/* Update read transfer count */
IopUpdateTransferCount(IopReadTransfer,
(ULONG)Irp->IoStatus.Information);
}
else
{
/* Update other transfer count */
IopUpdateTransferCount(IopOtherTransfer,
(ULONG)Irp->IoStatus.Information);
}
}
/* Now that we've signaled the events, de-associate the IRP */
IopUnQueueIrpFromThread(Irp);
/* Now check if a User APC Routine was requested */
if (Irp->Overlay.AsynchronousParameters.UserApcRoutine)
{
/* Initialize it */
KeInitializeApc(&Irp->Tail.Apc,
KeGetCurrentThread(),
CurrentApcEnvironment,
IopFreeIrpKernelApc,
IopAbortIrpKernelApc,
(PKNORMAL_ROUTINE)Irp->
Overlay.AsynchronousParameters.UserApcRoutine,
Irp->RequestorMode,
Irp->
Overlay.AsynchronousParameters.UserApcContext);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc, Irp->UserIosb, NULL, 2);
}
else if ((Port) &&
(Irp->Overlay.AsynchronousParameters.UserApcContext))
{
/* We have an I/O Completion setup... create the special Overlay */
Irp->Tail.CompletionKey = Key;
Irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;
KeInsertQueue(Port, &Irp->Tail.Overlay.ListEntry);
}
else
{
/* Free the IRP since we don't need it anymore */
IoFreeIrp(Irp);
}
/* Check if we have a file object that wasn't part of a create */
if ((FileObject) && !(SignaledCreateRequest))
{
/* Dereference it, since it's not needed anymore either */
ObDereferenceObjectDeferDelete(FileObject);
}
}
else
{
/*
* Either we didn't return from the request, or we did return but this
* request was synchronous.
*/
if ((Irp->PendingReturned) && (FileObject))
{
/* So we did return with a synch operation, was it the IRP? */
if (Irp->Flags & IRP_SYNCHRONOUS_API)
{
/* Yes, this IRP was synchronous, so return the I/O Status */
*Irp->UserIosb = Irp->IoStatus;
/* Now check if the user gave an event */
if (Irp->UserEvent)
{
/* Signal it */
KeSetEvent(Irp->UserEvent, 0, FALSE);
}
else
{
/* No event was given, so signal the FO instead */
KeSetEvent(&FileObject->Event, 0, FALSE);
}
}
else
{
/*
* It's not the IRP that was synchronous, it was the FO
* that was opened this way. Signal its event.
*/
FileObject->FinalStatus = Irp->IoStatus.Status;
KeSetEvent(&FileObject->Event, 0, FALSE);
}
}
/* Now that we got here, we do this for incomplete I/Os as well */
if ((FileObject) && !(Irp->Flags & IRP_CREATE_OPERATION))
{
/* Dereference the File Object unless this was a create */
ObDereferenceObjectDeferDelete(FileObject);
}
/*
* Check if this was an Executive Event (remember that we know this
* by checking if the IRP is synchronous)
*/
if ((Irp->UserEvent) &&
(FileObject) &&
!(Irp->Flags & IRP_SYNCHRONOUS_API))
{
/* This isn't a PKEVENT, so dereference it */
ObDereferenceObject(Irp->UserEvent);
}
/* Now that we've signaled the events, de-associate the IRP */
IopUnQueueIrpFromThread(Irp);
/* Free the IRP as well */
IoFreeIrp(Irp);
}
}
这个函数并没有太大难度,我们只需要抓住主线看它做了哪几件重要的事情就可以了:
数据的拷贝,返回用户数据
RtlCopyMemory(Irp->UserBuffer, Irp->AssociatedIrp.SystemBuffer, Irp->IoStatus.Information);
设置事件,对应UserMode下的Event
KeSetEvent(Irp->UserEvent, 0, FALSE);
设置事件,对应FileObject->Event,还记得IopParseDevice最后的那个KeWaitForSingleObject吗
KeSetEvent(&FileObject->Event, 0, FALSE);
将IRP从创建线程中移除,见IopParseDevice->IopQueueIrpToThread
IopUnQueueIrpFromThread(Irp);
好了,终于将这两个庞大的主题给讲完了,其中有太多的知识点我不便展开,需要各位自己花时间阅读一些基本书籍,每写一篇,我都会将我读到的参考资料贴出来,以供大家参考,书上有的我一般就没有多讲,我所列出来的都是我开始接触windows驱动比较困惑的地方,希望对每一个开始入门的同志有所帮助,我也是刚开始接触这一块,有什么不对的地方希望大家批评指正,呵呵~
参考资料:
《windows内核情景分析》
《Windows驱动开发技术详解》
http://blog.csdn.net/whatday/article/details/7106721