http://hi.baidu.com/jonathan2004/item/cb7fd9406301deac60d7b95d
本文档的CopyRight归jonathan所有,可自由转载,转载时请保持文档的完整性。
/*----------------------------------------------------------------------------------------------------------------------------*/
经常看到一些问文件对象、文件系统设备、卷设备之间关系的问题,这里也归纳一下自己的经验,仅供参考。不对之处,请多指教。
0 前沿
先描述一下DEVICE_OBJECT的结构。
1 关系说明
1.1 FILE_OBJECT->VPB 指向VPB ; FILE_OBJECT->DeviceObject指向卷设备;通过ObpLookupObjectName初始文件对象相应值;通过IoGetRelatedDeviceObject(FILE_OBJECT)获取设备栈;
1.2 文件系统设备对象->Vcb->VPB 指向VPB; VPB->RealDeviceObject指向卷设备; VPB->DeviceObject指向文件系统设备对象。
1.3 下层文件系统过滤设备的AttachedDevice指向邻接的上层设备;文件系统过滤对象扩展->AttachedTo指向下层设备。
2 简单描述
2.1 即插即用管理器遍历存储设备栈,注册卷更改通知接口。
2.2 即插即用管理器建立卷设备栈, 通过通知机制来与存储设备栈建立相互关系.
2.3 文件系统挂载(文件系统以过滤驱动方式挂载), 建立文件系统与卷设备栈之间关系.
这里以FAT文件系统为例说明:
NTSTATUS
FatMountVolume (
IN PIRP_CONTEXT IrpContext,
IN PDEVICE_OBJECT TargetDeviceObject,
IN PVPB Vpb
)
{
...
if (!NT_SUCCESS(Status = IoCreateDevice( FatData.DriverObject,
sizeof(VOLUME_DEVICE_OBJECT) - sizeof(DEVICE_OBJECT),
NULL,
FILE_DEVICE_DISK_FILE_SYSTEM,
0,
FALSE,
(PDEVICE_OBJECT *)&VolDo))) {
try_return( Status );
}
...
FatInitializeVcb( IrpContext, &VolDo->Vcb, TargetDeviceObject, Vpb);
...
/* 建立 卷到文件系统的关系 */
Vpb->DeviceObject = (PDEVICE_OBJECT)VolDo;
}
VOID
FatInitializeVcb (
IN PIRP_CONTEXT IrpContext,
IN OUT PVCB Vcb,
IN PDEVICE_OBJECT TargetDeviceObject,
IN PVPB Vpb
)
{
...
ObReferenceObject( TargetDeviceObject );
Vcb->TargetDeviceObject = TargetDeviceObject;
/* 建立文件系统到卷的关系 */
Vcb->Vpb = Vpb;
Vcb->CurrentDevice = Vpb->RealDevice;
...
}
以上 卷 和 文件系统 建立了相互关系.
2.3 文件对象 与 卷和文件系统的关系
2.3.1 创建文件对象
IO管理器通过ObpLookupObjectName查找相应的文件.
这里以设备对象类型为IoDeviceObjectType来说明.
RtlInitUnicodeString( &nameString, L"Device" );
objectTypeInitializer.DefaultNonPagedPoolCharge = sizeof( DEVICE_OBJECT );
objectTypeInitializer.ParseProcedure = IopParseDevice;
objectTypeInitializer.CaseInsensitive = TRUE;
objectTypeInitializer.DeleteProcedure = IopDeleteDevice;
objectTypeInitializer.SecurityProcedure = IopGetSetSecurityObject;
objectTypeInitializer.QueryNameProcedure = (OB_QUERYNAME_METHOD)NULL;
if (!NT_SUCCESS( ObCreateObjectType( &nameString,
&objectTypeInitializer,
(PSECURITY_DESCRIPTOR) NULL,
&IoDeviceObjectType ))) {
return FALSE;
}
/* 这是个重要函数接口,所有有关创建文件的地方都要走这里 */
NTSTATUS
IopParseDevice(
IN PVOID ParseObject,
IN PVOID ObjectType,
IN PACCESS_STATE AccessState,
IN KPROCESSOR_MODE AccessMode,
IN ULONG Attributes,
IN OUT PUNICODE_STRING CompleteName,
IN OUT PUNICODE_STRING RemainingName,
IN OUT PVOID Context OPTIONAL,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
OUT PVOID *Object
)
{
...
vpb = NULL;
//
// If the related open was a direct device open then we should go through the full mount
// path for this open as this may not be a direct device open.
//
if (op->RelatedFileObject && (!(op->RelatedFileObject->Flags & FO_DIRECT_DEVICE_OPEN))) {
deviceObject = (PDEVICE_OBJECT)ParseObject;
if (op->RelatedFileObject->Vpb) {
vpb = op->RelatedFileObject->Vpb;
//
// Synchronize here with the file system to make sure that
// volumes don't go away while en route to the FS.
//
IopInterlockedIncrementUlong( LockQueueIoVpbLock,
(PLONG) &vpb->ReferenceCount);
}
if (op->InternalFlags & IOP_CREATE_USE_TOP_DEVICE_OBJECT_HINT) {
if (vpb) {
deviceObject = vpb->DeviceObject;
}
checkDeviceHint = TRUE;
}
} else {
deviceObject = parseDeviceObject;
if (parseDeviceObject->Vpb && !directDeviceOpen) {
vpb = IopCheckVpbMounted( op,
parseDeviceObject,
RemainingName,
&status );
//
// Device object reference is decremented in IopCheckVpbMounted.
//
if ( !vpb ) {
return status;
}
//
// Set the address of the device object associated with the VPB.
//
/* 获取 卷设备 */
deviceObject = vpb->DeviceObject;
}
//
// If the top deviceobject hint is set use the hint if possible.
//
if (op->InternalFlags & IOP_CREATE_USE_TOP_DEVICE_OBJECT_HINT) {
checkDeviceHint = TRUE;
} else {
//
// Walk the attached device list.
//
if (deviceObject->AttachedDevice) {
/* 加入栈中 */
deviceObject = IoGetAttachedDevice( deviceObject );
}
}
}
...
/* 准备打开相应的文件或者设备 */
irp = IopAllocateIrp( deviceObject->StackSize, FALSE );
if (!irp) {
//
// An IRP could not be allocated. Cleanup and return an appropriate
// error status code.
//
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
return STATUS_INSUFFICIENT_RESOURCES;
}
irp->Tail.Overlay.Thread = CurrentThread;
irp->RequestorMode = AccessMode;
irp->Flags = IRP_CREATE_OPERATION | IRP_SYNCHRONOUS_API | IRP_DEFER_IO_COMPLETION;
securityContext.SecurityQos = SecurityQos;
securityContext.AccessState = AccessState;
securityContext.DesiredAccess = desiredAccess;
securityContext.FullCreateOptions = op->CreateOptions;
//
// Get a pointer to the stack location for the first driver. This is where
// the original function codes and parameters are passed.
//
irpSp = IoGetNextIrpStackLocation( irp );
irpSp->Control = 0;
...
irp->Overlay.AllocationSize = op->AllocationSize;
irp->AssociatedIrp.SystemBuffer = op->EaBuffer;
irpSp->Parameters.Create.Options = (op->Disposition << 24) | (op->CreateOptions & 0x00ffffff);
irpSp->Parameters.Create.FileAttributes = op->FileAttributes;
irpSp->Parameters.Create.ShareAccess = op->ShareAccess;
irpSp->Parameters.Create.SecurityContext = &securityContext;
//
// Fill in local parameters so this routine can determine when the I/O is
// finished, and the normal I/O completion code will not get any errors.
//
irp->UserIosb = &ioStatus;
irp->MdlAddress = (PMDL) NULL;
irp->PendingReturned = FALSE;
irp->Cancel = FALSE;
irp->UserEvent = (PKEVENT) NULL;
irp->CancelRoutine = (PDRIVER_CANCEL) NULL;
irp->Tail.Overlay.AuxiliaryBuffer = (PVOID) NULL;
...
/* 创建文件对象 */
status = ObCreateObject( KernelMode,
IoFileObjectType,
&objectAttributes,
AccessMode,
(PVOID) NULL,
fileObjectSize,
0,
0,
(PVOID *) &fileObject );
/* 设置文件对象相应属性,为打开文件作准备 */
RtlZeroMemory( fileObject, sizeof( FILE_OBJECT ) );
if (op->CreateOptions & (FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT)) {
fileObject->Flags = FO_SYNCHRONOUS_IO;
if (op->CreateOptions & FILE_SYNCHRONOUS_IO_ALERT) {
fileObject->Flags |= FO_ALERTABLE_IO;
}
}
...
fileObject->Type = IO_TYPE_FILE;
fileObject->Size = sizeof( FILE_OBJECT );
fileObject->RelatedFileObject = op->RelatedFileObject;
fileObject->DeviceObject = parseDeviceObject;
irp->Tail.Overlay.OriginalFileObject = fileObject;
irpSp->FileObject = fileObject;
...
RtlCopyUnicodeString( &fileObject->FileName, RemainingName );
...
/* 调用fastio接口 */
if (op->QueryOnly) {
PFAST_IO_DISPATCH fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;
BOOLEAN result;
if (fastIoDispatch &&
fastIoDispatch->SizeOfFastIoDispatch > FIELD_OFFSET( FAST_IO_DISPATCH, FastIoQueryOpen ) &&
fastIoDispatch->FastIoQueryOpen) {
IoSetNextIrpStackLocation( irp );
irpSp->DeviceObject = deviceObject;
result = (fastIoDispatch->FastIoQueryOpen)( irp,
op->NetworkInformation,
deviceObject );
if (result) {
op->FinalStatus = irp->IoStatus.Status;
op->Information = irp->IoStatus.Information;
//
// The operation worked, so simply dereference and free the
// resources acquired up to this point.
//
if ((op->FinalStatus == STATUS_REPARSE) &&
irp->Tail.Overlay.AuxiliaryBuffer) {
ASSERT( op->Information > IO_REPARSE_TAG_RESERVED_ONE );
ExFreePool( irp->Tail.Overlay.AuxiliaryBuffer );
irp->Tail.Overlay.AuxiliaryBuffer = NULL;
op->RelatedFileObject = (PFILE_OBJECT) NULL;
}
if (fileObject->FileName.Length) {
ExFreePool( fileObject->FileName.Buffer );
}
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
#if DBG
irp->CurrentLocation = irp->StackCount + 2;
#endif // DBG
IoFreeIrp( irp );
//
// Finally, indicate that the parse routine was actually
// invoked and that the information returned herein can be
// used.
//
op->ParseCheck = OPEN_PACKET_PATTERN;
status = STATUS_SUCCESS;
if (!op->FullAttributes) {
try {
op->BasicInformation->FileAttributes = op->NetworkInformation->FileAttributes;
} except(EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
}
return status;
} else {
//
// The fast I/O operation did not work, so take the longer
// route.
//
irp->Tail.Overlay.CurrentStackLocation++;
irp->CurrentLocation++;
}
}
}
//
// Finally, initialize the file object's event to the Not Signaled state
// and remember that a file object was created.
//
KeInitializeEvent( &fileObject->Event, NotificationEvent, FALSE );
op->FileObject = fileObject;
//
// Insert the packet at the head of the IRP list for the thread.
//
IopQueueThreadIrp( irp );
//
// Now invoke the driver itself to open the file.
//
status = IoCallDriver( deviceObject, irp );
...
if (!NT_SUCCESS( status )) {
....
} else if (status == STATUS_REPARSE) { /* 重新解析文件 */
...
RtlCopyUnicodeString( CompleteName, &fileObject->FileName );
...
}else{
...
/* fileobject 中的Vpb可能发生变化,这是由于可能发生了文件定向 */
deviceObjectThatOpenedFile = IoGetRelatedDeviceObject(fileObject);
if (deviceObject != deviceObjectThatOpenedFile) {
PVPB newVpb;
//
// The device that opened the related file is not the one
// that opened this file. So, readjust the vpb reference
// counts.
//
newVpb = fileObject->Vpb;
//
// If the new VPB is not the same as the original VPB
// then reference the new one before freeing the old one.
// If a filter was just added to the fileobject stack the VPB will not
// change. So check for a difference in VPB as well.
//
if (newVpb != vpb) {
if (newVpb) {
IopInterlockedIncrementUlong( LockQueueIoVpbLock,
(PLONG) &newVpb->ReferenceCount);
}
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
}
}
...
}
}
IopCheckVpbMounted 获取卷设备的VPB。如果当前卷设备还没有挂载,则调用IopMountVolume挂载相应设备。
NTSTATUS
IopMountVolume(
IN PDEVICE_OBJECT DeviceObject,
IN BOOLEAN AllowRawMount,
IN BOOLEAN DeviceLockAlreadyHeld,
IN BOOLEAN Alertable,
OUT PVPB *Vpb
)
{
...
if (DeviceObject->DeviceType == FILE_DEVICE_DISK ||
DeviceObject->DeviceType == FILE_DEVICE_VIRTUAL_DISK) {
queueHeader = &IopDiskFileSystemQueueHead;
} else if (DeviceObject->DeviceType == FILE_DEVICE_CD_ROM) {
queueHeader = &IopCdRomFileSystemQueueHead;
} else {
queueHeader = &IopTapeFileSystemQueueHead;
}
rawMountOnly = (DeviceObject->Vpb->Flags & VPB_RAW_MOUNT);
//
// Now loop through each of the file systems which have been loaded in
// the system to see whether anyone understands the media in the device.
//
for (entry = queueHeader->Flink;
entry != queueHeader && !NT_SUCCESS( status );
entry = entry->Flink) {
...
extraStack = 1;
...
irp = IoAllocateIrp ((CCHAR) (attachedDevice->StackSize + extraStack), FALSE);
if ( !irp ) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
irp->Flags = IRP_MOUNT_COMPLETION | IRP_SYNCHRONOUS_PAGING_IO;
irp->RequestorMode = KernelMode;
irp->UserEvent = &event;
irp->UserIosb = &ioStatus;
irp->Tail.Overlay.Thread = CurrentThread;
irpSp = IoGetNextIrpStackLocation( irp );
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
irpSp->MinorFunction = IRP_MN_MOUNT_VOLUME;
irpSp->Flags = AllowRawMount;
irpSp->Parameters.MountVolume.Vpb = DeviceObject->Vpb;
irpSp->Parameters.MountVolume.DeviceObject = attachedDevice;
...
status = IoCallDriver( fsDeviceObject, irp );
}
...
return status;
}
2.3.2 文件对象中的Vpb
通过上面的分析可以看出, 在上面中并没有给File_Object中的VPB负值,那么文件对象的中这个VPB哪里来的呢?
这就是在文件系统的创建函数接口中来获取的.下面仍然以Fat文件系统为例:
NTSTATUS
FatCommonCreate (
IN PIRP_CONTEXT IrpContext,
IN PIRP Irp
)
{
...
if ( RelatedFileObject != NULL ) {
FileObject->Vpb = RelatedFileObject->Vpb;
}
...
if (RelatedFileObject != NULL) {
...
FileObject->Vpb = RelatedFileObject->Vpb;
...
} else {
...
}
...
}
2.3.3 使用文件对象
IopReadFile等接口函数通过调用IoGetRelatedDeviceObject(文件对象)来获取相应的设备对象。