VPB的来龙去脉(二)

VPB全局锁:

为了保护对所有VPB成员的访问,I/O Manager用到了一个全局的spinlock:

0: kd> x nt!IopVpbSpinLock
fffff800`01af7640 nt!IopVpbSpinLock = <no type information>

这个全局spinlock并不能为系统驱动所直接引用(没有export出来),所以I/O Manager提供了两个支持函数(主要是为文件系统驱动所用):

VOID
IoAcquireVpbSpinLock(
    OUT PKIRQL Irql
    );

VOID 
IoReleaseVpbSpinLock(
    IN KIRQL  Irql
    );

所有对VPB成员的访问及修改都要在获取VPB锁的情况下进行,无论是I/O Manager还是文件系统驱动都要遵守。

但由于VPB spinlock是个全局锁,一旦被获取,后续所有的打开或创建文件操作(包括在其它文件系统卷上的)都将被阻止,所以持有此锁的时间不宜过长,必须尽可能快地释放。

关于IoAcquireVpbSpinLock()及IoReleaseVpbSpinLock()的内部实现,从XP以后在64位的系统上,I/O Manager用得是Queued SpinLock机制,有兴趣地话不妨自己动手去探索一下究竟。

VPB的引用及多头管理(Distributed Management):

VPB是靠ReferenceCount来管理其生存周期的,任何对设备卷的访问都会产生对此VPB的计数引用。

最主要的引用操作都在Naming Parsing阶段,即IopParseDevice()函数中。IopParseDevice()函数是I/O Manager向对象管理器(Object Manager)注册的“Device”一类对象的名称解析回调函数(Parse Procedure),这部分会在以后介绍“Name Parsing”时再做详述,在此先行略过。

IopParseDevice()在解析到卷设备的DeviceObject以后,会调用IopCheckVpbMounted()来检查此卷设备是不是已挂载文件系统。IopCheckVpbMounted()会做以下的事情:

  • 如果此卷还没有挂载文件系统,则调用IopMountVolume()轮询所有已注册的此类设备的文件系统驱动,尝试去挂载。一旦挂载成功,IopMountVolume()则会调用IopMountInitializeVpb()来设置Vpb内容,并标记VPB_MOUNTED标志。但是VPB_MOUNTED标志的清除却是由文件系统驱动在Dismount时做的,这也是一个典型的多头管理的例证。
  • 对已经挂载的卷设备,则直接增加VPB的引用计数并返回Vpb。

此时,如果返回的Vpb存在,则已经被引用过(ReferenceCount加1)。然后IopParseDevice()会创建FileObject对象,构造IRP_MJ_CREATE请求(文件打开或创建),分发给文件系统驱动。文件系统驱动会解析存储设备上目录及文件信息以定位此文件。

如果文件没有找到或创建失败,IopParseDevice()则要调用IopDereferenceVpbAndFree()以减小引用计数。对于一个则创建的"Clean”状态的Vpb,即便是它的ReferenceCount已归零,此Vpb并不会被IopDereferenceVpbAndFree()释放掉。原因是IopDereferenceVpbAndFree()只会释放非"Clean”状态(即Vcb->RealDevice->Vpb != Vpb)并且ReferenceCount为0的Vpb。正常情况下,从未被挂载过的卷设备的Vpb总是"Clean”的。(如果真得被释放掉,会有什么样的麻烦?)

一旦文件被成功打开或新创建,文件系统驱动会设置FileObject->Vpb项。注意,此操作是由文件系统驱动来做的,而不是I/O Manager,这又是另外一个多头管理的例证。当此文件被关闭时,它的文件对象/FileObject对Vpb的引用就会被清除(ReferenceCount减1)。关闭文件时,Object Manager会调用I/O Manager向其注册的"File” 对象的删除回调函数(Delete Procedure)IopDeleteFile(),然后IopDeleteFile()函数会减小Vpb的引用计数,并向文件系统驱动分发IRP_MJ_CLOSE请求以通知此FileObject的关闭操作。

VPB的释放:

正常情况下,VPB会随着DeviceObject的删除而被释放,具体操作在IopDeleteDevice()中执行。IopDeleteDevice()是I/O Manager所定义的"Device” 对象的删除回调函数(Delete Procedure),在设备移除时Object Manager会调用此函数来释放DeviceObject。

当然除正常情况下的寿终正寝外,总会有非正常死亡的情况存在。只是不同的文件系统驱动对VPB的处理不尽相同,如NTFS不支持Re-mount,可FastFat和CDFS却支持。在这里只以FastFat为例来展开介绍,毕竟WDK中有完整的FastFat的源码,研究起来也容易。

先介绍一下SwapVpb,它是FastFat VCB的一个成员:

0: kd> dt fastfat!_VCB
   +0x000 VolumeFileHeader : _FSRTL_ADVANCED_FCB_HEADER
   +0x058 VcbLinks         : _LIST_ENTRY
   +0x068 TargetDeviceObject : Ptr64 _DEVICE_OBJECT
   +0x070 Vpb              : Ptr64 _VPB
   +0x078 VcbState         : Uint4B
   +0x07c VcbCondition     : _VCB_CONDITION
      ……
   +0x408 ChangeCount      : Uint4B
   +0x410 SwapVpb          : Ptr64 _VPB
      ……
   +0x478 CloseContextCount : Uint4B

SwapVpb是在初始化Vcb时就创建一个备用的Vpb。之所以先准备好这个备用Vpb,是因为在真正需要新的Vpb记录时,有可能无法再从系统内存池中分配任何内存块,可以避免走到进退维谷的地步。

此过程会涉及Mount及Dismount操作,在后续的文章中我会对这两个过程加以详述,鉴于二者对理解VPB的释放过程并无太大妨碍,本文中将不作介绍。

非正常死亡的情况大体上分为两种:

Case 1: Force-dismount

Force-dismount一般发生于两种情形之下:

a) 用户执行FSCTL_DISMOUNT_VOLUME操作,即便FSCTL_LOCK_VOLUME操作失败

b) 用户强行将可插拔设备拔出(对于Changable/Removable Media设备,如CDROM/Floppy,将CD盘片及软盘退出并不属于此类)

这两种情形下,FastFat会调用FatCheckForDismount()并以Force=TRUE的方式强制解除卷设备对象Vpb->RealDevice和文件系统逻辑卷设备Vpb->DeviceObject之间的关联,具体操作可以参阅FatSwapVpb()代码。大体流程如下:

    /* Initialize Vcb->SwapVpb to a “Clean” state */
    Vcb->SwapVpb->Type = IO_TYPE_VPB;
    Vcb->SwapVpb->Size = sizeof( VPB );
    Vcb->SwapVpb->RealDevice = OldVpb->RealDevice;
    Vcb->SwapVpb->RealDevice->Vpb = Vcb->SwapVpb;
    Vcb->SwapVpb->Flags = FlagOn( OldVpb->Flags, VPB_REMOVE_PENDING );
    Vcb->SwapVpb = NULL;
    ……
    /* Update Vcb’s state to VcbBad */
    FatSetVcbCondition( Vcb, VcbBad);
    /* Let FastFat release Vcb->Vpb when destroying Vcb */
    SetFlag( Vcb->VcbState, VCB_STATE_FLAG_VPB_MUST_BE_FREED );

代码第一部分将Vcb->SwapVbp初始化成”Clean”状态,然后替换掉原来的Vpb,替换后RealDevice->Vbp指向了Vcb->SwapVpb,即RealDevice所表示的卷设备对象不再被任何文件系统所接管。但实际上,仍然会有文件正在被系统或应用程序打开,它们的FileObject->Vpb仍然指向老的Vpb,但所有的I/O操作将被文件系统拒绝,只有文件关闭请求仍会被处理,直到最好一个文件被关闭,FastFat的逻辑卷设备及Vcb才会被删除。

你可能会考虑到,在最后一个文件的FileObject被删除时,IopDeleteFile()会调用函数IoDereferenceVpbAndFree()减小Vpb的引用计数,此时的Vpb还是老的Vpb,那么老的Vpb会不会被释放呢?

答案是不会,因为老Vpb的ReferenceCount还没有归零,原因就在于文件系统并没有清除在卷挂载时由IopMountInitializeVpb()所增加的引用计数。FastFat的做法是在Vcb设置标志位:VCB_STATE_FLAG_VPB_MUST_BE_FREED,也就是说将此Vpb看作是Vcb的私有资源了。在Vcb被删除时,FatDeleteVcb()如果检测到此标志位将一并释放Vcb->Vpb。至于为什么要这么做,还有待进一步的研究。但是,如果将Vpb的释放交与I/O Manager(即IopDereferenceVpbAndFree())应该也是可行的。

下面来做个实验:

第一步:将插着FAT32格式SD卡的USB读卡器插入电脑,Windows会自动挂载并分配盘符,此时我们来观察一下其Vcb及Vpb的内容:

1: kd> ?? fastfat!FatData
struct _FAT_DATA
   +0x000 NodeTypeCode     : 0x500
   +0x002 NodeByteSize     : 304
   +0x008 LazyWriteThread  : 0xfffffa80`037151a0
   +0x010 VcbQueue         : _LIST_ENTRY [ 0xfffffa80`0384fa18 - 0xfffffa80`0384fa18 ]
    ……

1: kd> !list "-t nt!_LIST_ENTRY.Flink -e -x \"dd @$extret l4;  dt fastfat!_VCB @$extret-0x58\"  0xfffffa80`0384fa18"
dd @$extret l4;  dt fastfat!_VCB @$extret-0x58
fffffa80`0384fa18  070e34d0 fffff880 070e34d0 fffff880
   +0x000 VolumeFileHeader : _FSRTL_ADVANCED_FCB_HEADER
   +0x058 VcbLinks         : _LIST_ENTRY [ 0xfffff880`070e34d0 - 0xfffff880`070e34d0 ]
   +0x068 TargetDeviceObject : 0xfffffa80`053c7040 _DEVICE_OBJECT
   +0x070 Vpb              : 0xfffffa80`05b54a00 _VPB
   +0x078 VcbState         : 0x101002
   +0x07c VcbCondition     : 1 ( VcbGood )
     ……
   +0x410 SwapVpb          : 0xfffffa80`056f19e0 _VPB
   +0x418 AsyncCloseList   : _LIST_ENTRY [ 0xfffffa80`0384fdd8 - 0xfffffa80`0384fdd8 ]
   +0x428 DelayedCloseList : _LIST_ENTRY [ 0xfffff8a0`170d3880 - 0xfffff8a0`170d3880 ]
   +0x438 AdvancedFcbHeaderMutex : _FAST_MUTEX
   +0x470 CloseContext     : 0xfffff8a0`0af0eef0 CLOSE_CONTEXT
   +0x478 CloseContextCount : 2

1: kd> dt _VPB 0xfffffa80`05b54a00
nt!_VPB
   +0x000 Type             : 10
   +0x002 Size             : 96
   +0x004 Flags            : 1
   +0x006 VolumeLabelLength : 0x10
   +0x008 DeviceObject     : 0xfffffa80`0384f820 _DEVICE_OBJECT
   +0x010 RealDevice       : 0xfffffa80`05bab060 _DEVICE_OBJECT
   +0x018 SerialNumber     : 0x51e712c6
   +0x01c ReferenceCount   : 9
   +0x020 VolumeLabel      : [32]  "CANON_DC"

1: kd> dt _VPB 0xfffffa80`056f19e0
nt!_VPB
   +0x000 Type             : 0
   +0x002 Size             : 0
   +0x004 Flags            : 0
   +0x006 VolumeLabelLength : 0
   +0x008 DeviceObject     : (null)
   +0x010 RealDevice       : (null)
   +0x018 SerialNumber     : 0
   +0x01c ReferenceCount   : 0
   +0x020 VolumeLabel      : [32]  ""

1: kd> !devobj 0xfffffa80`05bab060
Device object (fffffa8005bab060) is for:
HarddiskVolume18 \Driver\volmgr DriverObject fffffa8004086e70
Current Irp 00000000 RefCount 9 Type 00000007 Flags 00003050
Vpb fffffa8005b54a00 Dacl fffff9a1171d06c0 DevExt fffffa8005bab1b0 DevObjExt fffffa8005bab318 Dope fffffa8004fa7fa0 DevNode fffffa80055d9d90
ExtensionFlags (0000000000) 
AttachedDevice (Upper) fffffa8005b4a040 \Driver\fvevol
Device queue is not busy.
1: kd> dt _DEVICE_OBJECT 0xfffffa80`05bab060
nt!_DEVICE_OBJECT
   +0x000 Type             : 3
   +0x002 Size             : 0x2b8
   +0x004 ReferenceCount   : 9
   +0x008 DriverObject     : 0xfffffa80`04086e70 _DRIVER_OBJECT
   +0x010 NextDevice       : 0xfffffa80`045ed440 _DEVICE_OBJECT
   +0x018 AttachedDevice   : 0xfffffa80`05b4a040 _DEVICE_OBJECT
   +0x020 CurrentIrp       : (null)
   +0x028 Timer            : (null)
   +0x030 Flags            : 0x3050
   +0x034 Characteristics  : 1
   +0x038 Vpb              : 0xfffffa80`05b54a00 _VPB
   +0x040 DeviceExtension  : 0xfffffa80`05bab1b0
   +0x048 DeviceType       : 7
   +0x04c StackSize        : 9 ''
   +0x050 Queue            : <unnamed-tag>
   +0x098 AlignmentRequirement : 0
   +0x0a0 DeviceQueue      : _KDEVICE_QUEUE
   +0x0c8 Dpc              : _KDPC
   +0x108 ActiveThreadCount : 0
   +0x110 SecurityDescriptor : 0xfffff8a0`170b0620
   +0x118 DeviceLock       : _KEVENT
   +0x130 SectorSize       : 0x200
   +0x132 Spare1           : 1
   +0x138 DeviceObjectExtension : 0xfffffa80`05bab318 _DEVOBJ_EXTENSION
   +0x140 Reserved         : (null)

可以看出Vcb(0xfffffa80`0384f9c0)的SwapVpb(0xfffffa80`056f19e)为空。其Vpb(0xfffffa80`05b54a00)与Vpb->RealDevice(0xfffffa80`05bab060)所指是一致的。

第二步:将USB读卡器拔出,然后再观察一下Vcb及Vpb的状态:

先看来看Vcb的内容:

0: kd> !list "-t nt!_LIST_ENTRY.Flink -e -x \"dd @$extret l4;  dt fastfat!_VCB @$extret-0x58\"  0xfffffa80`0384fa18"
dd @$extret l4;  dt fastfat!_VCB @$extret-0x58
fffffa80`0384fa18  070e34d0 fffff880 070e34d0 fffff880
   +0x000 VolumeFileHeader : _FSRTL_ADVANCED_FCB_HEADER
   +0x058 VcbLinks         : _LIST_ENTRY [ 0xfffff880`070e34d0 - 0xfffff880`070e34d0 ]
   +0x068 TargetDeviceObject : 0xfffffa80`053c7040 _DEVICE_OBJECT
   +0x070 Vpb              : 0xfffffa80`05b54a00 _VPB
   +0x078 VcbState         : 0x141102
   +0x07c VcbCondition     : 3 ( VcbBad )
    ……
   +0x410 SwapVpb          : (null)
    ……

可以看出,此时SwapVcb项已为空值,Vcb的状态已从VcbGood变成为了VcbBad,并且Vcb->VcbState已被设置上标志位VCB_STATE_FLAG_VPB_MUST_BE_FREED (0x00040000)。

接着再来看Vpb(0xfffffa80`05b54a00)的内容:

0: kd> dt _VPB 0xfffffa80`05b54a00
nt!_VPB
   +0x000 Type             : 10
   +0x002 Size             : 96
   +0x004 Flags            : 9
   +0x006 VolumeLabelLength : 0x10
   +0x008 DeviceObject     : 0xfffffa80`0384f820 _DEVICE_OBJECT
   +0x010 RealDevice       : 0xfffffa80`05bab060 _DEVICE_OBJECT
   +0x018 SerialNumber     : 0x51e712c6
   +0x01c ReferenceCount   : 6
   +0x020 VolumeLabel      : [32]  "CANON_DC"

Vpb->RealDevice指向0xfffffa80`05bab060,并没有变化。下面再来看RealDevice(0xfffffa80`05bab060)的内容:

0: kd> !devobj 0xfffffa80`05bab060
Device object (fffffa8005bab060) is for:
HarddiskVolume18 \Driver\volmgr DriverObject fffffa8004086e70
Current Irp 00000000 RefCount 6 Type 00000007 Flags 00003050
Vpb fffffa80056f19e0 Dacl fffff9a1171d06c0 DevExt fffffa8005bab1b0 DevObjExt fffffa8005bab318 Dope fffffa8004fa7fa0 DevNode fffffa80055d9d90
ExtensionFlags (0x00000004)  DOE_REMOVE_PENDING
AttachedDevice (Upper) fffffa8005b4a040 \Driver\fvevol
Device queue is not busy.
0: kd> dt _DEVICE_OBJECT 0xfffffa80`05bab060
nt!_DEVICE_OBJECT
   +0x000 Type             : 3
   +0x002 Size             : 0x2b8
   +0x004 ReferenceCount   : 6
   +0x008 DriverObject     : 0xfffffa80`04086e70 _DRIVER_OBJECT
   +0x010 NextDevice       : 0xfffffa80`045ed440 _DEVICE_OBJECT
   +0x018 AttachedDevice   : 0xfffffa80`05b4a040 _DEVICE_OBJECT
   +0x020 CurrentIrp       : (null)
   +0x028 Timer            : (null)
   +0x030 Flags            : 0x3050
   +0x034 Characteristics  : 1
   +0x038 Vpb              : 0xfffffa80`056f19e0 _VPB
   +0x040 DeviceExtension  : 0xfffffa80`05bab1b0
   +0x048 DeviceType       : 7
   +0x04c StackSize        : 9 ''
   +0x050 Queue            : <unnamed-tag>
   +0x098 AlignmentRequirement : 0
   +0x0a0 DeviceQueue      : _KDEVICE_QUEUE
   +0x0c8 Dpc              : _KDPC
   +0x108 ActiveThreadCount : 0
   +0x110 SecurityDescriptor : 0xfffff8a0`170b0620
   +0x118 DeviceLock       : _KEVENT
   +0x130 SectorSize       : 0x200
   +0x132 Spare1           : 1
   +0x138 DeviceObjectExtension : 0xfffffa80`05bab318 _DEVOBJ_EXTENSION
   +0x140 Reserved         : (null)

RealDevice(0xfffffa80`05bab060)的Vpb指向不再是0xfffffa80`05b54a00,而是0xfffffa80`056f19e0,0xfffffa80`056f19e0正是之前的Vcb->SwapVpb。下面现再显示一下Vpb(0xfffffa80`056f19e0)的内容,应该为”Clean”状态:

0: kd> dt _VPB 0xfffffa80`056f19e0
nt!_VPB
+0x000 Type : 10
+0x002 Size : 96
+0x004 Flags : 8
+0x006 VolumeLabelLength : 0
+0x008 DeviceObject : (null)
+0x010 RealDevice : 0xfffffa80`05bab060 _DEVICE_OBJECT
+0x018 SerialNumber : 0
+0x01c ReferenceCount : 0
+0x020 VolumeLabel : [32] ""

Vpb->ReferenceCount为0,Vpb->Flags的值为0x08,即VPB_REMOVE_PENDING标志。此标志由Pnp Manager设置,表示此设备已从系统中移除。

Case 2: Remount

Remount的前提是,用户已经将曾挂载好的Removable Media如光盘片、软盘或SD卡拔出了,并且文件系统经过了校验/Verify操作后已将此卷设定成VcbNotMounted状态。

当用户再次插入此Removable Media时,I/O Manager(IopCheckVpbMounted / IopMountVolume)会调用FatMountVolume()去尝试挂载此卷。FatMountVolume()会创建新的逻辑卷设备对象及Vcb结构,然后再于Vcb队列中(FatData.VcbQueue)中查找有无指向同一个设备同一个媒质的逻辑卷设备对象,如果Vcb队列中已存在,则进行Remount。Remount的实质就是重新启用先前生成的逻辑卷设备对象,并将当前新创建的逻辑卷设备删除。

这里只有一点值得强调:

IopMountVolume()通过Irp传递过来的Vpb (iosb->Parameters.MountVolume.Vpb)将会随着新创建的逻辑卷设备对象的删除而被释放掉,先前的挂载时的老Vpb将会重新启用。FastFat会更新老的Vpb至iosb->Parameters.MountVolume.Vpb,因为文件系统上层有可能有过滤/Filter驱动程序访问此iosb->Parameters.MountVolume.Vpb项,如果不更新就会导致内存访问错误发生。

VPB的问题综述:

VPB是缺乏对象化的一个结构体,结构虽不复杂,但其操作却相当琐碎且分散于I/O Manager及文件系统驱动的方方面面,与卷的Mount和Dismount过程息息相关。读者不妨考虑一个问题,能不能将VPB的操作都限制于I/O Manager之中,并做到对文件系统驱动完全透明吗?

VPB的来龙去脉(一)

VPB是Windows I/O Manager子系统的一个重要的数据结构,全称为 Volume Parameter Block。它的任务是绑定卷设备(如磁盘分区或虚拟磁盘)和接管此卷设备的文件系统(如FastFat,NTFS)。Windows系统上的挂载点(Mount Point,如盘符C:)只能定位至卷设备(如\Device\HarddiskVolume1),Windows Object Manager(对象管理器)在解析路径名时(即Name Parsing过程)还要通过卷设备的VPB信息进一步定位至接管此卷设备的文件系统驱动上。

Relationship of VPB, Storage Volume Device and File System Logical Volume Device

图一

VPB结构说明:

定义于WDK头文件inc\ddk\wdm.h中

typedef struct _VPB {
    CSHORT Type;
    CSHORT Size;
    USHORT Flags;
    USHORT VolumeLabelLength; // in bytes
    struct _DEVICE_OBJECT *DeviceObject;
    struct _DEVICE_OBJECT *RealDevice;
    ULONG SerialNumber;
    ULONG ReferenceCount;
    WCHAR VolumeLabel[MAXIMUM_VOLUME_LABEL_LENGTH / sizeof(WCHAR)];
} VPB, *PVPB

成员说明:

  • Type:  魔数标志 IO_TYPE_VPB
  • Size:   sizeof(VPB)
    Flags:  标志位,相关标志描述如下:
                VPB_MOUNTED (1<<0):       此卷已被文件系统识别并已挂载
                VPB_LOCKED (1<<1):           此卷已被文件系统锁定,锁定操作由请求FSCTL_LOCK_VOLUME完成
                VPB_PERSISTENT (1<<2):   将VPB一直保留在内存中(不释放),即使此VPB引用计数为0
                VPB_REMOVE_PENDING (1<<3): 表示此存储设备即将被卸载/删除。此标志由Pnp Manager(即插
                                                                  即用管理器)管理和使用。此标志在可在处理Pnp请求
                                                                   IRP_MN_CANCEL_REMOVE_DEVICE时清除
                 VPB_RAW_MOUNT (1<<4): 指定此卷仅由系统RAW文件系统接管
  • VolumeLabelLength:  卷标长度(in bytes)
  • DeviceObject:           未命名的文件系统逻辑卷对象(unnamed logical volume)
  • RealDevice:               物理卷设备(如\Device\HarddiskVolume1)
  • SerialNumber:           卷序列号
  • ReferenceCount:      VPB的引用计数,用以控制VPB的生命周期
  • VolumeLabel:             卷标,最长32个双字节
                                      #define MAXIMUM_VOLUME_LABEL_LENGTH (32 * sizeof(WCHAR))

    VPB的操作(包括初始化)是由I/O Manager及文件系统二者共同完成的,所以多头管理正是VPB的复杂性所在,好在正常情况下,VPB的申请和释放均由I/O Manager实施,此时VPB从创建到释放的生命周期中基本上是单线式发展的。但当遇到Force Dismount及Remount特殊情况时,文件系统会直接干预VPB的创建及释放,致使VPB的单线式的发展周期一分为二,致使VPB的管理变得更加复杂。

    我们先从正常情况开始介绍,先介绍几个VPB相关的内部函数:

    0: kd> x nt!*Vpb*
    fffff800`01d62940 nt!IovpBuildDriverObjectList = <no type information>
    fffff800`0189f150 nt!IopDereferenceVpbAndFree = <no type information>
    fffff800`01c54400 nt!IopCreateVpb = <no type information>
    fffff800`01968830 nt!IopReferenceVerifyVpb = <no type information>
    fffff800`01865d0c nt!IopMountInitializeVpb = <no type information>
    fffff800`01865db0 nt!IoAcquireVpbSpinLock = <no type information>
    fffff800`01af7640 nt!IopVpbSpinLock = <no type information>
    fffff800`01903ab0 nt!IopCheckVpbMounted = <no type information>
    fffff800`01865a48 nt!IopQueryVpbFlagsSafe = <no type information>
    fffff800`01c827b0 nt!HvpBuildMapAndCopy = <no type information>
    fffff800`01865de0 nt!IoReleaseVpbSpinLock = <no type information>
    fffff800`01c82ca0 nt!HvpBuildMap = <no type information>

    IoAcquireVpbSinLock()及IoReleaseVpbSpinLock()为系统支持函数(kernel support routine)。Windows I/O Manager在内核中维护一个全局的spinlock锁,以保护所有对Vpb结构体的访问。文件系统和I/O Manager本身在访问Vpb时都必须调用IoAcquireVpbSpinLock(),访问结束后再调用IoReleaseVpbSpinLock()释放全局自销锁。

    IopCreateVpb(): 从NonPagedPool中申请VPB,然后简单初始化VPB(type,size)并与RealDevice一起绑定

    IopCheckVpbMounted(): 检测DeviceObject是不是已经挂载,若没有则会调用IopMountVolume尝试去挂载

    IopMountInitializeVpb(): 设置新挂载卷设备的Vpb(设置VPB_MOUNTED标记,增加引用计数等),由
                                            IopMountVolume挂载成功后调用

    IopQueryVpbFlagsSafe(): 获取设备DeviceObject的VPB的标志位信息,即DeviceObject->Vpb->Flags。如
                                              果DeviceObject->Vpb为NULL,则返回0值。(较新Windows版本才引入的函数)
    IopReferenceVerifyVpb(): 如果卷设备已被挂载,则返回其VPB及对应的文件系统逻辑卷设备对象,此函数
                                              增加VPB的引用计数

    IopDereferenceVpbAndFree():顾名即知其义,此函数会减少VPB的引用计数,如果引用计数归0则会释放
                                                     此VPB至NonPagedPool。(特例VPB_PERSISTENT除外)

    VPB的创建及初始化:

    VPB真正是由IoCreateDevice()在创建新设备对象时分配的,当然IoCreateDevice()是调用的 IopCreateVpb()来做的VPB的分配及最初的初始化。

    IoCreateDevice()不是对所有的设备对象都分配VPB,而是只针对4种设备:

    • FILE_DEVICE_DISK (0x07)
    • FILE_DEVICE_TAPE (0x1f)
    • FILE_DEVICE_CD_ROM (0x02)
    • FILE_DEVICE_VIRTUAL_DISK (0x24)

    VPB的初始化并不复杂,因为VPB的结构体并不庞大,但是,其初始化操作相当分散,由下面的3个过程共同完成:

    • IoCreateDevice()/IopCreateVpb()
    • IopParseDevice()/IopCheckVpbMounted()/IopMountVolume()/File system IPR_MN_MOUNT_VOLUME
      handler(以FastFat为例:FatMountVolume)
    • IopParseDevice()/IopCheckVpbMounted()/IopMountVolume()/IopMountInitializeVpb()

    具体每一部分都做了什么并不难发掘,读者不妨用windbg作个简单实验。

    下面以FastFat (Windows 7 X64)为例介绍一个已加载卷的VPB内容,实验时我们插入一个FAT32格式的SD卡,系统会自动挂载。下面来枚举FastFat文件系统队列中所有的逻辑卷:

    0: kd> ?? fastfat!FatData
    struct _FAT_DATA
       +0x000 NodeTypeCode     : 0x500
       +0x002 NodeByteSize     : 304
       +0x008 LazyWriteThread  : (null)
       +0x010 VcbQueue         : _LIST_ENTRY [ 0xfffffa80`047a8b68 - 0xfffffa80`047a8b68 ]
       +0x020 DriverObject     : 0xfffffa80`05809e70 _DRIVER_OBJECT
       +0x028 DiskFileSystemDeviceObject : 0xfffffa80`04842880
       +0x030 CdromFileSystemDeviceObject : 0xfffffa80`05bf6890
       +0x038 Resource         : _ERESOURCE
       +0x0a0 OurProcess       : 0xfffffa80`036edb30 _KPROCESS
       +0x0a8 NumberProcessors : 2
       +0x0ac ChicagoMode      : 0y1
       +0x0ac FujitsuFMR       : 0y0
       +0x0ac AsyncCloseActive : 0y0
       +0x0ac ShutdownStarted  : 0y0
       +0x0ac CodePageInvariant : 0y1
       +0x0ac HighAsync        : 0y0
       +0x0ac HighDelayed      : 0y0
       +0x0b0 AsyncCloseCount  : 0
       +0x0b8 AsyncCloseList   : _LIST_ENTRY [ 0xfffff880`070e3578 - 0xfffff880`070e3578 ]
       +0x0c8 DelayedCloseCount : 1
       +0x0d0 DelayedCloseList : _LIST_ENTRY [ 0xfffff8a0`0b4315b0 - 0xfffff8a0`0b4315b0 ]
       +0x0e0 FatCloseItem     : 0xfffffa80`057e8480 _IO_WORKITEM
       +0x0e8 GeneralSpinLock  : 0
       +0x0f0 CacheManagerCallbacks : _CACHE_MANAGER_CALLBACKS
       +0x110 CacheManagerNoOpCallbacks : _CACHE_MANAGER_CALLBACKS

    0: kd> !list "-t nt!_LIST_ENTRY.Flink -e -x \"dd @$extret l4;  dt fastfat!_VCB @$extret-0x58\" 0xfffffa80`047a8b68"
    dd @$extret l4;  dt fastfat!_VCB @$extret-0x58
    fffffa80`047a8b68  070e34d0 fffff880 070e34d0 fffff880
       +0x000 VolumeFileHeader : _FSRTL_ADVANCED_FCB_HEADER
       +0x058 VcbLinks         : _LIST_ENTRY [ 0xfffff880`070e34d0 - 0xfffff880`070e34d0 ]
       +0x068 TargetDeviceObject : 0xfffffa80`053c7040 _DEVICE_OBJECT
       +0x070 Vpb              : 0xfffffa80`047e8830 _VPB
       +0x078 VcbState         : 0x101002
       +0x07c VcbCondition     : 1 ( VcbGood )
       +0x080 RootDcb          : 0xfffffa80`0480b940 _FCB
       +0x088 NumberOfWindows  : 2
       +0x090 Windows          : 0xfffff8a0`0b4b8530 _FAT_WINDOW
       +0x098 CurrentWindow    : 0xfffff8a0`0b4b853c _FAT_WINDOW
       +0x0a0 DirectAccessOpenCount : 0
       +0x0a4 ShareAccess      : _SHARE_ACCESS
       +0x0c0 OpenFileCount    : 1
       +0x0c4 ReadOnlyCount    : 1
       +0x0c8 InternalOpenCount : 2
       +0x0cc ResidualOpenCount : 2
       +0x0d0 Bpb              : BIOS_PARAMETER_BLOCK
       +0x100 First0x24BytesOfBootSector : (null)
       +0x108 AllocationSupport : <unnamed-tag>
       +0x128 DirtyFatMcb      : _LARGE_MCB
       +0x148 BadBlockMcb      : _LARGE_MCB
       +0x168 FreeClusterBitMap : _RTL_BITMAP
       +0x178 FreeClusterBitMapMutex : _FAST_MUTEX
       +0x1b0 Resource         : _ERESOURCE
       +0x218 ChangeBitMapResource : _ERESOURCE
       +0x280 VirtualVolumeFile : 0xfffffa80`0480aa10 _FILE_OBJECT
       +0x288 SectionObjectPointers : _SECTION_OBJECT_POINTERS
       +0x2a0 ClusterHint      : 2
       +0x2a8 CurrentDevice    : 0xfffffa80`047e8cd0 _DEVICE_OBJECT
       +0x2b0 VirtualEaFile    : (null)
       +0x2b8 EaFcb            : (null)
       +0x2c0 FileObjectWithVcbLocked : (null)
       +0x2c8 DirNotifyList    : _LIST_ENTRY [ 0xfffffa80`047a8dd8 - 0xfffffa80`047a8dd8 ]
       +0x2d8 NotifySync       : 0xfffffa80`056fec30
       +0x2e0 DirectoryFileCreationMutex : _FAST_MUTEX
       +0x318 VerifyThread     : (null)
       +0x320 CleanVolumeDpc   : _KDPC
       +0x360 CleanVolumeTimer : _KTIMER
       +0x3a0 LastFatMarkVolumeDirtyCall : _LARGE_INTEGER 0x0
       +0x3a8 Statistics       : 0xfffffa80`05b5ec80 _FILE_SYSTEM_STATISTICS
       +0x3b0 Tunnel           : TUNNEL
       +0x408 ChangeCount      : 0
       +0x410 SwapVpb          : 0xfffffa80`05b5ec10 _VPB
       +0x418 AsyncCloseList   : _LIST_ENTRY [ 0xfffffa80`047a8f28 - 0xfffffa80`047a8f28 ]
       +0x428 DelayedCloseList : _LIST_ENTRY [ 0xfffff8a0`0b4315c0 - 0xfffff8a0`0b4315c0 ]
       +0x438 AdvancedFcbHeaderMutex : _FAST_MUTEX
       +0x470 CloseContext     : 0xfffff8a0`0b553a90 CLOSE_CONTEXT
       +0x478 CloseContextCount : 1

    当前系统中只有一个FAT32卷,即我们刚插入的SD卡。下面打印出VPB的内容:

    0: kd> dt _VPB 0xfffffa80`047e8830
    nt!_VPB
       +0x000 Type             : 10
       +0x002 Size             : 96
       +0x004 Flags            : 1
       +0x006 VolumeLabelLength : 0x10
       +0x008 DeviceObject     : 0xfffffa80`047a8970 _DEVICE_OBJECT
       +0x010 RealDevice       : 0xfffffa80`047e8cd0 _DEVICE_OBJECT
       +0x018 SerialNumber     : 0x51e712c6
       +0x01c ReferenceCount   : 2
       +0x020 VolumeLabel      : [32]  "CANON_DC"

    Vpb->Flags显示卷是已挂载的,即标志位VBP_MOUNTED已被设置。从Vpb内容可知,此SD卡的卷标为"CANON_DC",实际上它就是CANON相机的存储卡。DeviceObject,即文件系统逻辑卷设备为0xfffffa80`047a8970;RealDevice,即存储栈上最底层的设备为0xfffffa80`047e8cd0

    0: kd> !devobj 0xfffffa80`047e8cd0
    Device object (fffffa80047e8cd0) is for:
    HarddiskVolume17 \Driver\volmgr DriverObject fffffa8004086e70
    Current Irp 00000000 RefCount 2 Type 00000007 Flags 00003050
    Vpb fffffa80047e8830 Dacl fffff9a109ae12b0 DevExt fffffa80047e8e20 DevObjExt fffffa80047e8f88 Dope fffffa800480c840 DevNode fffffa80047e8a00
    ExtensionFlags (0000000000) 
    AttachedDevice (Upper) fffffa80047e5ac0 \Driver\fvevol
    Device queue is not busy.

    0: kd> !devobj 0xfffffa80`047a8970
    Device object (fffffa80047a8970) is for:
      \FileSystem\fastfat DriverObject fffffa8005809e70
    Current Irp 00000000 RefCount 0 Type 00000008 Flags 00000000
    DevExt fffffa80047a8ac0 DevObjExt fffffa80047a8f90
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800

    0: kd> dt _DEVICE_OBJECT 0xfffffa80`047e8cd0
    nt!_DEVICE_OBJECT
       +0x000 Type             : 3
       +0x002 Size             : 0x2b8
       +0x004 ReferenceCount   : 2
       +0x008 DriverObject     : 0xfffffa80`04086e70 _DRIVER_OBJECT
       +0x010 NextDevice       : 0xfffffa80`045ed440 _DEVICE_OBJECT
       +0x018 AttachedDevice   : 0xfffffa80`047e5ac0 _DEVICE_OBJECT
       +0x020 CurrentIrp       : (null)
       +0x028 Timer            : (null)
       +0x030 Flags            : 0x3050
       +0x034 Characteristics  : 1
       +0x038 Vpb              : 0xfffffa80`047e8830 _VPB
       +0x040 DeviceExtension  : 0xfffffa80`047e8e20
       +0x048 DeviceType       : 7
       +0x04c StackSize        : 9 ''
       +0x050 Queue            : <unnamed-tag>
       +0x098 AlignmentRequirement : 0
       +0x0a0 DeviceQueue      : _KDEVICE_QUEUE
       +0x0c8 Dpc              : _KDPC
       +0x108 ActiveThreadCount : 0
       +0x110 SecurityDescriptor : 0xfffff8a0`099c1210
       +0x118 DeviceLock       : _KEVENT
       +0x130 SectorSize       : 0x200
       +0x132 Spare1           : 1
       +0x138 DeviceObjectExtension : 0xfffffa80`047e8f88 _DEVOBJ_EXTENSION
       +0x140 Reserved         : (null)

    0: kd> dt _DEVICE_OBJECT 0xfffffa80`047a8970
    nt!_DEVICE_OBJECT
       +0x000 Type             : 3
       +0x002 Size             : 0x620
       +0x004 ReferenceCount   : 0
       +0x008 DriverObject     : 0xfffffa80`05809e70 _DRIVER_OBJECT
       +0x010 NextDevice       : 0xfffffa80`05bf6890 _DEVICE_OBJECT
       +0x018 AttachedDevice   : 0xfffffa80`0480ab50 _DEVICE_OBJECT
       +0x020 CurrentIrp       : (null)
       +0x028 Timer            : (null)
       +0x030 Flags            : 0
       +0x034 Characteristics  : 0
       +0x038 Vpb              : (null)
       +0x040 DeviceExtension  : 0xfffffa80`047a8ac0
       +0x048 DeviceType       : 8
       +0x04c StackSize        : 12 ''
       +0x050 Queue            : <unnamed-tag>
       +0x098 AlignmentRequirement : 0
       +0x0a0 DeviceQueue      : _KDEVICE_QUEUE
       +0x0c8 Dpc              : _KDPC
       +0x108 ActiveThreadCount : 0
       +0x110 SecurityDescriptor : (null)
       +0x118 DeviceLock       : _KEVENT
       +0x130 SectorSize       : 0x200
       +0x132 Spare1           : 1
       +0x138 DeviceObjectExtension : 0xfffffa80`047a8f90 _DEVOBJ_EXTENSION
       +0x140 Reserved         : (null)

    Vcb->Vpb 与Vcb->Vbp->RealDevice->Vpb是一致的。下面再来研究一下存储设备栈的最上层的DeviceObject,即Vcb->TargetDeviceObject (0xfffffa80`053c7040):

    1: kd> !devobj 0xfffffa80`053c7040
    Device object (fffffa80053c7040) is for:
      \Driver\volsnap DriverObject fffffa80045c79b0
    Current Irp 00000000 RefCount 0 Type 00000007 Flags 00002010
    DevExt fffffa80053c7190 DevObjExt fffffa80053c7d38
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedTo (Lower) fffffa80047e5ac0 \Driver\fvevol
    Device queue is not busy.
    1: kd> dt _DEVICE_OBJECT 0xfffffa80`053c7040
    nt!_DEVICE_OBJECT
       +0x000 Type             : 3
       +0x002 Size             : 0xcf8
       +0x004 ReferenceCount   : 0
       +0x008 DriverObject     : 0xfffffa80`045c79b0 _DRIVER_OBJECT
       +0x010 NextDevice       : 0xfffffa80`04649060 _DEVICE_OBJECT
       +0x018 AttachedDevice   : (null)
       +0x020 CurrentIrp       : (null)
       +0x028 Timer            : (null)
       +0x030 Flags            : 0x2010
       +0x034 Characteristics  : 0x101
       +0x038 Vpb              : (null)
       +0x040 DeviceExtension  : 0xfffffa80`053c7190
       +0x048 DeviceType       : 7
       +0x04c StackSize        : 11 ''
       +0x050 Queue            : <unnamed-tag>
       +0x098 AlignmentRequirement : 0
       +0x0a0 DeviceQueue      : _KDEVICE_QUEUE
       +0x0c8 Dpc              : _KDPC
       +0x108 ActiveThreadCount : 0
       +0x110 SecurityDescriptor : (null)
       +0x118 DeviceLock       : _KEVENT
       +0x130 SectorSize       : 0x200
       +0x132 Spare1           : 0
       +0x138 DeviceObjectExtension : 0xfffffa80`053c7d38 _DEVOBJ_EXTENSION
       +0x140 Reserved         : (null)

    有没有觉察出有什么异常状况?TargetDeviceObject的DeviceType为FILE_DEVICE_DISK (0x07),但它的Vpb项却是空的。根据上面对IoCreateDevice的分析,对所有4种设备类型,在创建设备时都会分配VPB,难道是被释放了?

    经过研究才发现其中另有蹊跷。TargetDeviceObject是由VolSnap创建的,VolSnap是Windows Volume Shadow Copy Service的核心组件,VolSnap为每个卷设备都创建Fdo设备并将其加入设备栈中。但蹊跷的地方是,在创建设备对象时,VolSnap使用的DeviceType却是FILE_DEVICE_UNKNOWN (0x22),并在将设备加入存储设备栈后,再将TargetDeviceObject的DeviceType从FILE_DEVICE_UNKOWN改为FILE_DEVICE_DISK的,所以TargetDeviceObject的Vpb项为空。

    有兴趣地话可以反汇编一下volsnap!VolSnapAddDevice的代码。

    (to be continued)

  • 文件系统开发之注册篇

    文件系统是一类特殊的内核驱动,主要负责数据流(即文件)的管理。用户的视图所显示出的是各色各样的文件和目录,但最底层的存储设备则是以扇区为单位的连续的存储实体(如硬盘,光盘等),文件系统便处于二者中间,负责管理文件/目录在设备存储中的解析与定位,因此也可以将文件系统看作是一个转换机。

    不同类型的驱动都有不同的功能实现,并且在驱动加载时都要向操作系统注册,报告它是个什么类型的驱动及要管理什么样的设备等等。就如一个电饭煲驱动,要实现的功能大致有开关电源,温控及定时等功能,加载时可以向系统注册为烹调类设备的驱动。文件系统驱动也大致如此,只是功能更复杂,与内核的关系也更紧密,特别是和Virtual Memory, Cache Management及I/O Subsystem的关系和交互。

    不同的操作系统对文件系统的功能实现有不同的规范和要求,但从最基本的需求出发,各种文件系统的功能大致一样,只是系统的接口不同而已。这里只介绍Windows和Linux操作系统上文件系统的开发。Windows平台上称之为IFS (Installable File System),Linux系统则为VFS(Virtual Filesystem,也可解释为Virtual Filesystem Switch)。

    此篇文章只着重于文件系统的注册过程,至于卷的挂载(Mount)操作将在以后的文章中再作讲述。

     

    Windows平台上的文件系统注册过程:

    Windows内核(I/O Manager)提供了一组内核支持程序(kernel support routine)来完成文件系统的注册和移除。

    VOID
      IoRegisterFileSystem(
        IN OUT PDEVICE_OBJECT  DeviceObject   /* Deviect object representitive of file system*/
        );

    VOID
      IoUnregisterFileSystem(
        IN OUT PDEVICE_OBJECT  DeviceObject  /* Deviect object representitive of file system*/
        );

    注册调用很简单,以Ext2Fsd为例,Ext2Fsd针对光盘介质和磁盘介质的设备分别创建CdromdevObject及Diskdevobject,然后分别注册。

    NTSTATUS
    DriverEntry (
        IN PDRIVER_OBJECT   DriverObject,
        IN PUNICODE_STRING  RegistryPath
    )
    {
     

        PDEVICE_OBJECT              DiskdevObject = NULL;
        PDEVICE_OBJECT              CdromdevObject = NULL;

        ......

        /* create Ext2Fsd cdrom fs deivce */
        RtlInitUnicodeString(&DeviceName, CDROM_NAME); 
        Status = IoCreateDevice(  DriverObject,  0,  DeviceName,
                                                       
    FILE_DEVICE_CD_ROM_FILE_SYSTEM,
                                                        0,  FALSE,  &CdromdevObject );

        if (!NT_SUCCESS(Status)) {
            DEBUG(DL_ERR, ( "IoCreateDevice cdrom device object error.\n"));
            goto errorout;
        }

        /* create Ext2Fsd disk fs deivce */
        RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
        Status = IoCreateDevice(  DriverObject,  0, &DeviceName, 
                                                       FILE_DEVICE_DISK_FILE_SYSTEM
                                                       0,  FALSE,  &DiskdevObject );

        if (!NT_SUCCESS(Status)) {
            DEBUG(DL_ERR, ( "IoCreateDevice disk deviceobject error.\n"));
            goto errorout;
        }

        ......

        /* register file system devices for disk and cdrom */
       
    IoRegisterFileSystem(DiskdevObject); 
        ObReferenceObject(DiskdevObject);

        IoRegisterFileSystem(CdromdevObject);
        ObReferenceObject(CdromdevObject);

        ......

        return status;
    }

    你或许会迷惑为什么Ext2Fsd会注册两次(针对cdrom设备和disk设备)?下面我们就来探讨IoRegisterFileSystem的内部实现。

    Windows I/O Manager 管理着4个队列:每个队列都管理不同类别的所有文件系统:

    以Windows 7 X64为例:

    0: kd> x nt!iop*FileSystemQueueHead
    fffff800`04076200 nt!IopCdRomFileSystemQueueHead = <no type information>
    fffff800`04076210 nt!IopDiskFileSystemQueueHead = <no type information>
    fffff800`040761e0 nt!IopTapeFileSystemQueueHead = <no type information>
    fffff800`040761f0 nt!IopNetworkFileSystemQueueHead = <no type information>

    通过名字就能看出每个队列所管理的文件系统的类型,所以当I/O Manager新发现某一类型存储设备时,就只从相应的队列中依次调用队列上的文件系统驱动来识别存储设备上的文件卷,而不是调用注册于系统中的所有文件系统。

    Ext2Fsd除了支持磁盘介质设备外,还可以支持光盘设备,所以要针对不同的储存设备类型分别注册。

    在Windbg中可以很方便地罗列出所有挂载的文件系统,以IopDiskFileSystemQueueHead为例:

    0: kd> !list "-t nt!_LIST_ENTRY.Flink -e -x \"dd @$extret l4;  !devobj @$extret-0x50\" poi(nt!IopDiskFileSystemQueueHead)"
    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`04839450  044e3d50 fffffa80 04076210 fffff800
    Device object (fffffa8004839400) is for:
     Ext2Fsd \FileSystem\Ext2Fsd DriverObject fffffa800482d210
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt 00000000 DevObjExt fffffa8004839550
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedDevice (Upper) fffffa8004843040 \FileSystem\FltMgr
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`044e3d50  044e22b0 fffffa80 04839450 fffffa80
    Device object (fffffa80044e3d00) is for:
    ExFatRecognizer \FileSystem\Fs_Rec DriverObject fffffa80044e2060
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt fffffa80044e3e50 DevObjExt fffffa80044e3e60
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`044e22b0  044e24e0 fffffa80 044e3d50 fffffa80
    Device object (fffffa80044e2260) is for:
    FatDiskRecognizer \FileSystem\Fs_Rec DriverObject fffffa80044e2060
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt fffffa80044e23b0 DevObjExt fffffa80044e23c0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`044e24e0  0448d7f0 fffffa80 044e22b0 fffffa80
    Device object (fffffa80044e2490) is for:
    UdfsDiskRecognizer \FileSystem\Fs_Rec DriverObject fffffa80044e2060
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt fffffa80044e25e0 DevObjExt fffffa80044e25f0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`0448d7f0  036660b0 fffffa80 044e24e0 fffffa80
    Device object (fffffa800448d7a0) is for:
    Ntfs \FileSystem\Ntfs DriverObject fffffa800448d9c0
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt 00000000 DevObjExt fffffa800448d8f0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedDevice (Upper) fffffa80044d2040 \FileSystem\FltMgr
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`036660b0  04076210 fffff800 0448d7f0 fffffa80
    Device object (fffffa8003666060) is for:
    RawDisk \FileSystem\RAW DriverObject fffffa8003667770
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000050
    Dacl fffff9a100324650 DevExt 00000000 DevObjExt fffffa80036661b0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedDevice (Upper) fffffa800371b860 \FileSystem\FltMgr
    Device queue is not busy.

    可以看出,最晚注册的文件系统Ext2Fsd则被放在了双向链表的链首位置,即第一个;最早注册的Ntfs则是在链尾。不过有种例外,如果设备被指定DO_LOW_PRIORITY_FILESYSTEM标志,IoRegisterFileSystem会将此文件系统挂载至链表尾部。

    在Ntfs及Ext2Fsd之间,还有Windows系统本身提供的3个File system recognizer,均在模块Fs_Rec(即 fs_rec.sys)中实现。Recognizer是文件系统驱动中比较特殊的一类,它的功能很简单:如果发现有它能识别的卷设备,它则会加载相应的文件系统驱动来挂载此卷并卸载自己。Recognizer之所以存在的唯一目的就是节省内存资源。Recognizer驱动相比其文件系统驱动,代码量要小得多,比如,集多个Recognizer与一身的fs_rec.sys模块只有16K,而exfat及fastfat驱动在100K-200K之间,Ntfs更有1.5M之大。如果直接加载文件系统驱动,而实际系统中又没有此文件系统所管理的储存设备的话,那驱动程序所占用的内存就完全是资源的浪废。

    下面做个实验,插入一个FAT32格式的SD卡后,再来观察一下IopDiskFileSystemQueueHead链的变化:

    0: kd> !list "-t nt!_LIST_ENTRY.Flink -e -x \"dd @$extret l4;  !devobj @$extret-0x50\" poi(nt!IopDiskFileSystemQueueHead)"
    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`048428d0  04839450 fffffa80 04076210 fffff800
    Device object (fffffa8004842880) is for:
    Fat \FileSystem\fastfat DriverObject fffffa8005809e70
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt 00000000 DevObjExt fffffa80048429d0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedDevice (Upper) fffffa80047a6870 \FileSystem\FltMgr
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`04839450  044e3d50 fffffa80 048428d0 fffffa80
    Device object (fffffa8004839400) is for:
     Ext2Fsd \FileSystem\Ext2Fsd DriverObject fffffa800482d210
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt 00000000 DevObjExt fffffa8004839550
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedDevice (Upper) fffffa8004843040 \FileSystem\FltMgr
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`044e3d50  044e24e0 fffffa80 04839450 fffffa80
    Device object (fffffa80044e3d00) is for:
     ExFatRecognizer \FileSystem\Fs_Rec DriverObject fffffa80044e2060
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt fffffa80044e3e50 DevObjExt fffffa80044e3e60
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`044e24e0  0448d7f0 fffffa80 044e3d50 fffffa80
    Device object (fffffa80044e2490) is for:
     UdfsDiskRecognizer \FileSystem\Fs_Rec DriverObject fffffa80044e2060
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt fffffa80044e25e0 DevObjExt fffffa80044e25f0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`0448d7f0  036660b0 fffffa80 044e24e0 fffffa80
    Device object (fffffa800448d7a0) is for:
     Ntfs \FileSystem\Ntfs DriverObject fffffa800448d9c0
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
    Dacl fffff9a100324650 DevExt 00000000 DevObjExt fffffa800448d8f0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedDevice (Upper) fffffa80044d2040 \FileSystem\FltMgr
    Device queue is not busy.

    dd @$extret l4;  !devobj @$extret-0x50
    fffffa80`036660b0  04076210 fffff800 0448d7f0 fffffa80
    Device object (fffffa8003666060) is for:
     RawDisk \FileSystem\RAW DriverObject fffffa8003667770
    Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000050
    Dacl fffff9a100324650 DevExt 00000000 DevObjExt fffffa80036661b0
    ExtensionFlags (0x00000800) 
                                 Unknown flags 0x00000800
    AttachedDevice (Upper) fffffa800371b860 \FileSystem\FltMgr
    Device queue is not busy.

    FatDiskRecognizer发现FAT32格式的文件卷后会加载fastfat驱动,然后将自己卸载:

    新增加的Fat设备:

            Fat \FileSystem\fastfat DriverObject fffffa8005809e70

    消失的FatDiskRecognizer:

            FatDiskRecognizer \FileSystem\Fs_Rec DriverObject fffffa80044e2060

    Windows I/O Manager还提供文件系统注册事件的通知,这部分就是在IoRegisterFileSystem函数中实现的。IoRegisterFileSystem在将文件系统的Device Object挂入相应的队列中以后,还会调用所有的NotificationRoutine以通知其注册者。(注册者,即对此事件感兴趣的驱动。一般来说,对文件系统注册感兴趣的只有file system filter driver。)Windows 内核提供了3个函数用于文件系统注册事件的通知:

    NTSTATUS
      IoRegisterFsRegistrationChange(
        IN PDRIVER_OBJECT  DriverObject,
        IN PDRIVER_FS_NOTIFICATION  DriverNotificationRoutine
        );

    NTSTATUS
      IoRegisterFsRegistrationChangeEx(
        IN PDRIVER_OBJECT  DriverObject,
        IN PDRIVER_FS_NOTIFICATION  DriverNotificationRoutine
        );  /* available from Win2k SP4 and its successor OS */

    VOID 
      IoUnregisterFsRegistrationChange(
        IN PDRIVER_OBJECT  DriverObject,
        IN PDRIVER_FS_NOTIFICATION  DriverNotificationRoutine
        );

    关于这三个函数的使用请参阅DDK文档,在此不做熬述。

     

    Linux平台上的文件系统注册过程:

    Linux内核(VFS:Virtual Filesystem)提供了两个支持例程分别进行文件系统的注册和移除:

    int register_filesystem(struct file_system_type * fs)
    int unregister_filesystem(struct file_system_type * fs)

    struct file_system_type结构就代表一个文件系统驱动,此结构由文件系统驱动程序各自定义,其结构体如下:

    struct file_system_type {
        const char *name;
        int fs_flags;
        int (*get_sb) (struct file_system_type *, int,
                   const char *, void *, struct vfsmount *);
        void (*kill_sb) (struct super_block *);
        struct module *owner;
        struct file_system_type * next;
        struct list_head fs_supers;

        struct lock_class_key s_lock_key;
        struct lock_class_key s_umount_key;

        struct lock_class_key i_lock_key;
        struct lock_class_key i_mutex_key;
        struct lock_class_key i_mutex_dir_key;
        struct lock_class_key i_alloc_sem_key;
    };

    结构成员说明:

    • name: 文件系统名称:如ext3,ext4,nfs,nfs4,lustre等
    • fs_flags: 文件系统相关标志位,目前有如下取值:
      #define FS_REQUIRES_DEV 1            /* 此文件系统适用于卷设备,如硬盘分区等
                                                                      不适用于网络文件系统 */
      #define FS_BINARY_MOUNTDATA 2  /* 此文件系统有单独的mount tool, 参数的传递有
                                                                      自己的格式,此标志目前用于coda, FUSE, nfs,
                                                                      smbfs, ncpfs */
      #define FS_HAS_SUBTYPE 4                /* 仅 FUSE使用,支持多种user mode文件系统 */
      #define FS_REVAL_DOT    16384         /* 仅NFS使用,"."及".."会过期或失效  */
      #define FS_RENAME_DOES_D_MOVE    32768    /* 仅NFS使用,文件系统在.rename操作
                                                                                        已处理 d_move() 情形 */
    • get_sb:  挂载新卷时的回调函数,由VFS调用,多数情况是用户进行了mount(2)操作
    • kill_sb:   卸载已挂载卷时的回调函数,一般由用户进行umount(2)操作而触发
    • owner:   文件系统所在模块(module)
    • next:      供VFS使用,组成文件系统驱动的单向列表
    • fs_supers: 供VFS使用,双向链表链首,用以管理此文件系统所识别的所有的文件卷的
                      超级块( super_block)
    • s_lock_key … i_alloc_sem_key: 用于lockdep检查

    看一下kernel中ext4的相关代码:

    static struct file_system_type ext4_fs_type = {
        .owner        = THIS_MODULE,
        .name        = "ext4",
        .get_sb        = ext4_get_sb,
        .kill_sb    = kill_block_super,
        .fs_flags    = FS_REQUIRES_DEV,
    };

    /* 如果ext3模块没被定义,ext4文件系统将默认接管ext3文件卷 */
    #if !defined(CONTIG_EXT3_FS) && !defined(CONFIG_EXT3_FS_MODULE) && defined(CONFIG_EXT4_USE_FOR_EXT23)

    static struct file_system_type ext3_fs_type = {
        .owner        = THIS_MODULE,
        .name        = "ext3",
        .get_sb        = ext4_get_sb,
        .kill_sb    = kill_block_super,
        .fs_flags    = FS_REQUIRES_DEV,
    };

    static inline void register_as_ext3(void)
    {
        int err = register_filesystem(&ext3_fs_type);
        if (err)
            printk(KERN_WARNING
                   "EXT4-fs: Unable to register as ext3 (%d)\n", err);
    }

    static inline void unregister_as_ext3(void)
    {
        unregister_filesystem(&ext3_fs_type);
    }
    MODULE_ALIAS("ext3");
    #endif

    ext4模块的初始化函数(init_ext4_fs)会调用register_filesystem来注册文件系统为ext4.

    static int __init init_ext4_fs(void)
    {
        int err;

    ......

        register_as_ext3();
        err = register_filesystem(&ext4_fs_type);
        if (err)
            goto out;
    ......

            return err;
    }

    然后再研究一下register_filesystem及unregister_filesystem的代码($kernel/vfs/filesystems.c):

    static struct file_system_type *file_systems;
    static DEFINE_RWLOCK(file_systems_lock);

    /**
    *    register_filesystem - register a new filesystem
    *    @fs: the file system structure
    *
    *    Adds the file system passed to the list of file systems the kernel
    *    is aware of for mount and other syscalls. Returns 0 on success,
    *    or a negative errno code on an error.
    *
    *    The &struct file_system_type that is passed is linked into the kernel
    *    structures and must not be freed until the file system has been
    *    unregistered.
    */
     
    int register_filesystem(struct file_system_type * fs)
    {
        int res = 0;
        struct file_system_type ** p;

        BUG_ON(strchr(fs->name, '.'));
        if (fs->next)
            return -EBUSY;
        INIT_LIST_HEAD(&fs->fs_supers);
        write_lock(&file_systems_lock);
        p = find_filesystem(fs->name, strlen(fs->name));
        if (*p)
            res = -EBUSY;
        else
            *p = fs;
        write_unlock(&file_systems_lock);
        return res;
    }

    EXPORT_SYMBOL(register_filesystem);

    static struct file_system_type **find_filesystem(const char *name, unsigned len)
    {
        struct file_system_type **p;
        for (p=&file_systems; *p; p=&(*p)->next)
            if (strlen((*p)->name) == len &&
                strncmp((*p)->name, name, len) == 0)
                break;
        return p;
    }

    /**
    *    unregister_filesystem - unregister a file system
    *    @fs: filesystem to unregister
    *
    *    Remove a file system that was previously successfully registered
    *    with the kernel. An error is returned if the file system is not found.
    *    Zero is returned on a success.
    *   
    *    Once this function has returned the &struct file_system_type structure
    *    may be freed or reused.
    */
     
    int unregister_filesystem(struct file_system_type * fs)
    {
        struct file_system_type ** tmp;

        write_lock(&file_systems_lock);
        tmp = &file_systems;
        while (*tmp) {
            if (fs == *tmp) {
                *tmp = fs->next;
                fs->next = NULL;
                write_unlock(&file_systems_lock);
                return 0;
            }
            tmp = &(*tmp)->next;
        }
        write_unlock(&file_systems_lock);
        return -EINVAL;
    }

    通过代码可以得知,与Windows对文件系统驱动的管理不同的是,Linux只维护了一个全局的单向列表来管理所有的文件系统驱动,而且新文件系统默认被链接至链表末尾。

    Linux不允许同名的文件系统再次被注册,register_filesystem()每次都会调用find_filesystem()以检查同名的文件系统是不是已存在。Windows平台上因为是以DeviceObject为主体进行注册的,并不存在此项的检测,所以Windows允许多个同一卷类型的文件系统驱动的加载,但只有最晚加载的驱动才有效(默认无DO_LOW_PRIORITY_FILESYSTEM标志)。