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标志)。

    献给逝去的生命

    grass-0706

    grass-0801

     

    总心为
    它会永远年轻
    只要有空气和水
    就可以保持着茁壮
    郁郁葱葱地成长

    总心为
    每天给它浇水
    就是对它最百般的呵护
    就能让它一直固守满头的倔强

    总心为
    来日肯定方长
    但青青的发丝
    却渐显出 枯萎的模样


    就当作
    生命总有轮回
    今生走过,还有来世
    来世里,蜕变成天使
    振翅飞翔

    就当作
    死亡不是生命的结束
    而是 生命永不朽的开始
    纵使是沧桑岁月
    再也不能
    为你的眉梢
    描上哪怕再轻微的皱纹

    就当作
    是对世间最好的洗礼
    一点点的付出
    都会让活着的人
    能更好地活着
    一点点的触动
    都会让喧闹的世界平息
    享受它应该有的  一刻安祥

    学而时习之

    懒惰是人之天性,所以偷懒是不需要理由的。

    偷懒不一定就指无所事事,不爱动脑筋也是一种偷懒。我就比较偏好后者。很多时候看书或看文档,眼睛只是输入,大脑也仅是暂储,只有短时记忆,以致于入目过后即忘或者只能记个大概,这个坏习惯的养成,除天性外,还有个原因:资讯的查找与搜索太方便了,随时随地都可以google或baidu,不再有“一定要记住”的渴望了。

    学习不该是个只是将知识囤积至自己记忆的过程,更应该有的是分析和消化。固然要学,但时习之更显重要。