文件系统识别器.docx
- 文档编号:24308639
- 上传时间:2023-05-26
- 格式:DOCX
- 页数:21
- 大小:23KB
文件系统识别器.docx
《文件系统识别器.docx》由会员分享,可在线阅读,更多相关《文件系统识别器.docx(21页珍藏版)》请在冰豆网上搜索。
文件系统识别器
文件系统识别器
作者:
Microsoft
翻译:
TimiXu
本文转载自TimiXu的Blog
文件系统识别器是一个标准的NT内核模式驱动程序。
它只实现一项功能:
检查物理介质设备,如果它能够识别存储介质的格式便加载相应的文件系统驱动程序。
你可能要问:
为什么不把所有的文件系统一起加载呢?
因为系统几乎从不需要加载所有文件系统驱动程序,用一个小驱动可以节约数百K系统内存。
实际上,所有标准的NT物理介质文件系统都利用文件系统识别器。
举个例子来说,如果CD-ROM没有被访问,那么CDFS文件系统驱动程序将不会被加载。
文件系统识别器是怎么样知道磁盘上存在什么类型的文件系统呢?
一般说来,检查磁盘上的标识符就可以了。
标识符可能存储在于分区表里,从分区起始处加上一段偏移量就能定位这个唯一值,这个值可以是序列号或者其他某种标识符。
这些标识符必须完全不同,以免加载了不正确的文件系统驱动程序。
以下是一些常用的文件系统标识:
文件系统名
文件系统标识
HFS
0x4244
NTFS
''NTFS''
FAT
0xe9或0xeb或0x49
表1常见的文件系统标识符
当一个文件系统程序被加载后,它必须分析磁盘以便确定介质上是否包含了它可以识别的文件系统。
如果介质上是可以识别的文件系统,该文件系统驱动程序将“装配”这个文件系统。
文件系统识别器也分析介质来确认是否有可识别的文件系统。
但是文件系统识别器不是“装配”到卷上,而是加载文件系统驱动程序。
文件系统识别器完成任务便可以卸载了。
装配过程
在NT系统中,当一个卷被访问时才被装配。
一些卷在系统初始化被装配,用磁盘管理程序或可移动介质创建的卷会在晚些时候被装配。
因此,当你创建新的分区并且为它分配了盘符,直到有应用程序访问这个卷时这个卷才会被装配。
所以,当你为软驱更换了盘片,直到有程序访问软盘时卷才被装配。
一个WIN32应用程序通过盘符访问卷。
盘符只是对象管理器名字空间的一个符号连接。
你可以利用平台SDK里的工具WINOBJ查看。
盘符是物理磁盘卷的符号连接而不是文件文件系统驱动程序创建的设备的符号连接。
当IO管理器发现为物理存储设备创建的设备对象有FILE_DEVICE_DISK,FILE_DEVICE_TAPE,FILE_DEVICE_CD_ROM,或者FILE_DEVICE_VIRTUAL_DISK标记时,这些设备对象就有卷参数块(VolumeParameterBlock)。
VPB用于表示卷是否已经被装配了。
如果已经装配了,VPB指向属于文件系统驱动程序的设备对象。
如果没有被装配,IO管理器将尝试装配这个卷。
IO管理器为当前物理介质类型(FILE_DEVICE_DISK_FILE_SYSTEM,FILE_DEVICE_TAPE_FILE_SYSTEM,FILE_DEVICE_CD_ROM_FILE_SYSTEM)的卷调用每一个注册的文件系统驱动程序。
通过调用驱动的IRP_MJ_FILE_SYSTEM_CONTROL派遣例程,传递给派遣例程次功能码是IRP_MN_MOUNT_VOLUME便可以实现装配。
驱动程序返回给IO管理器该卷是否可以被装配的信息。
调用次序是后注册先调用。
所以被装载最频繁的文件系统驱动程序首先得到装配卷的机会。
实际上第一个注册的是RAW文件系统,它注册另外三种不同的文件系统。
当RAW文件系统装配卷时,它便注册这三种不同的文件系统。
属于RAW文件系统的卷只能被“全部访问("wholevolume")”操作打开。
磁盘管理器需要做这样的操作。
文件系统识别器实际上就是一个只处理装配请求的文件系统驱动程序。
因此,它用相应的文件系统类型创建设备对象,向IO管理器注册为文件系统,然后等待被调用去装配卷。
如果识别器确认了卷属于它的文件系统,它返回错误码STATUS_FS_DRIVER_REQUIRED,而不是接受这个装配请求。
接着IO管理器调用识别器,让它加载整个文件系统驱动程序。
具体细节是发送IRPIRP_MJ_FILE_SYSTEM_CONTROL,次功能码为IRP_MN_LOAD_FILE_SYSTEM。
实现
实现一个文件系统识别器是非常直接的,我们提供一个例子程序,你可以利用它创建自己的文件系统识别器。
#include
//定义可能随着你的文件系统而改变
#defineFSD_SERVICE_PATHL"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MyFsd"
#defineFSD_RECOGNIZER_NAMEL"\\FileSystem\\MyFsdRecognizer"
#defineDEVICE_LOGICAL_BLOCKSIZE512//每一个扇区的大小
//
//IFSKit中存档的函数
//
NTSYSAPINTSTATUSNTAPIZwLoadDriver(INPUNICODE_STRINGDriverServiceName);
NTKERNELAPIVOIDIoRegisterFileSystem(INOUTPDEVICE_OBJECTDeviceObject);
NTKERNELAPIVOIDIoUnregisterFileSystem(INOUTPDEVICE_OBJECTDeviceObject);
//
//全局变量
//
staticPDRIVER_OBJECTRecognizerDriverObject;
staticPDEVICE_OBJECTRecognizerDeviceObject;
staticVOIDUnload(PDRIVER_OBJECT);
staticNTSTATUSRecognizerFsControl(PDEVICE_OBJECT,PIRP);
staticNTSTATUSRecognizerDetectFileSystem(PIRPIrp);
staticNTSTATUSRecognizerIoControl(INPDEVICE_OBJECTdeviceObject,
INULONGIoctlCode,
INPVOIDInputBuffer,
INULONGInputBufferSize,
OUTPVOIDOutputBuffer,
OUTULONGOutputBufferSize);
staticBOOLEANRecognizerReadDiskSector(INPDEVICE_OBJECT
pDeviceObject,
INULONGDiskSector,
INUCHAR*Buffer);
上面的代码段定义了文件系统识别器需要的变量和外部函数的声明。
我们最关心的是IoRegisterFileSystem(),IoUnregisterFileSystem(),和ZwLoadDriver().识别调用IoRegisterFileSystem向IO管理器把自己注册成为文件系统驱动程序。
这意味着IO管理器将在新卷被装配时调用此识别器。
一旦识别器加载了整个文件系统驱动程序,它可以调用IoUnregisterFileSystem告诉IO管理器当新卷在装配过程中,不要调用识别器。
加载文件系统驱动程序的函数是ZwLoadDriver.
//DriverEntry
//
//驱动的入口点
//
//输入参数:
//DriverObject–驱动的驱动程序对象
//RegistryPath–驱动程序的服务键
//
//输出参数:
//None.
//
//返回值:
//success
//
//注意:
//这只是一个实验型的驱动.
//
NTSTATUSDriverEntry(PDRIVER_OBJECTDriverObject,PUNICODE_STRINGRegistryPath)
{
NTSTATUScode;
UNICODE_STRINGdriverName;
//保存驱动程序对象的全局指针
RecognizerDriverObject=DriverObject;
//为IRP_MJ_FILE_SYSTEM_CONTROL设置派遣例程入口点
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL]=
RecognizerFsControl;
//这个驱动是可卸载的
DriverObject->DriverUnload=Unload;
//为识别器的设备名初始化一个unicode字符串.
RtlInitUnicodeString(&driverName,FSD_RECOGNIZER_NAME);
//创建命名的设备对象.
code=IoCreateDevice(RecognizerDriverObject,
0,
&driverName,
FILE_DEVICE_DISK_FILE_SYSTEM,
0,
FALSE,
&RecognizerDeviceObject);
if(!
NT_SUCCESS(code)){
DbgPrint("Recognizerfailedtoload,failureinIoCreateDevicecallreturned0x%x\n",code);
//失败.
return(code);
}
//把设备对象注册为文件系统.
IoRegisterFileSystem(RecognizerDeviceObject);
//完成
returnSTATUS_SUCCESS;
}
上面的代码描述了识别器的入口点DriverEntry。
DriverEntry识别器初始化驱动程序。
向IO管理器注册两个回调例程。
一个处理IRP_MJ_FILE_SYSTEM_CONTROL,一个是卸载例程。
我们只需要识别文件系统的格式,所以不需要处理其他请求。
当真正的驱动程序被加载后,它会处理这些请求。
在DriverEntry中,创建了一个设备对象,并且指定了需要识别的文件系统的介质类型。
调用IoRegisterFileSystem把设备对象注册为文件系统。
整个过程非常简单。
需要注意的是,你可以在一个驱动程序中识别几种存储介质类型的文件系统。
这样的一个驱动程序需要为每一种存储介质类型创建一个设备对象。
为什么需要识别不同存储介质呢?
有两种不同的可能性。
假如你的文件系统像UDF文件系统一样支持不同种类的存储介质,便需要识别不同的存储介质。
因为WindowsNT通过匹配不同的存储介质类型来选择文件系统,你的识别器可以为每一种类型的存储介质创建一个设备对象。
在创建设备对象时指定介质类型就可以了,如用FILE_DEVICE_DISK_FILE_SYSTEM类型创建对象,就像例子代码中的那样,用FILE_DEVICE_CD_ROM_FILE_SYSTEM创建第二个设备对象。
这样,不管UDF文件系统存在与磁盘还是CD上都可以被你的识别器检测到。
另外一个可能的原因是需要处理多种存储介质类型。
这种方法被微软的文件系统驱动程序识别起采用,一个驱动程序可以识别FAT,NTFD,CDFS文件系统。
它用设备对象来决定加载哪个文件系统驱动程序。
它为每一个可以识别的文件系统创建一个设备对象。
//
//卸载例程
//
//输入参数:
//DeviceObject–可能是我们设备对象指针
//
//输出参数:
//None.
//
//返回值:
//None.
//
staticVOIDUnload(PDRIVER_OBJECTDriverObject)
{
//
//如果存在设备对象,则删除它.
//
if(RecognizerDeviceObject){
IoUnregisterFileSystem(RecognizerDeviceObject);
IoDeleteDevice(RecognizerDeviceObject);
RecognizerDeviceObject=0;
}
//
//完成
//
}
上面的代码是识别器unload例程。
当驱动被停止时(命令netstop)或者对驱动程序对象没有引用时(文件系统驱动程序已经加载完毕),此例程被调用。
如果停止驱动的命令有用户模式程序发出,RecognizerDeviceObject不为NULL,所以卸载,删除设备对象,返回。
如果请求有IO管理器发出,表示没有对该驱动的引用了。
staticNTSTATUSRecognizerFsControl(PDEVICE_OBJECTDeviceObject,PIRPIrp)
{
PIO_STACK_LOCATIONirpSp=IoGetCurrentIrpStackLocation(Irp);
UNICODE_STRINGdriverName;
NTSTATUScode;
//是否是本驱动创建的设备对象
if(DeviceObject!
=RecognizerDeviceObject){
//不是.
Irp->IoStatus.Status=STATUS_NOT_IMPLEMENTED;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
returnSTATUS_NOT_IMPLEMENTED;
}
//检查是否为“装配/加载”请求。
如果不是,什么也不做
if((irpSp->MinorFunction!
=IRP_MN_MOUNT_VOLUME)&&(irpSp->MinorFunction!
=IRP_MN_LOAD_FILE_SYSTEM))
{
//什么也不做
Irp->IoStatus.Status=STATUS_NOT_IMPLEMENTED;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
returnSTATUS_NOT_IMPLEMENTED;
}
//处理“加载”请求
if(irpSp->MinorFunction==IRP_MN_LOAD_FILE_SYSTEM){
//加载文件系统
RtlInitUnicodeString(&driverName,FSD_SERVICE_PATH);
code=ZwLoadDriver(&driverName);
//用加载的结果完成IRP
Irp->IoStatus.Status=code;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
//在成功加载了文件系统驱动程序的情况下,不在需要这个设备对象
if(NT_SUCCESS(code)){
IoUnregisterFileSystem(RecognizerDeviceObject);
IoDeleteDevice(RecognizerDeviceObject);
RecognizerDeviceObject=0;
//文件系统已经被加载了。
}
return(code);
}
//调用RecognizerDetectFileSystem决定该卷是否可以被本文件系统识别。
code=RecognizerDetectFileSystem(Irp);
//检查返回值,如果成功,说明这是可以识别的文件系统。
//接下来告诉调用者(IO管理器)应该调用加载文件系统驱动的例程(即发送IRP_MN_LOAD_FILE_SYSTEM)
if(NT_SUCCESS(code))
{
//卷可以被识别
Irp->IoStatus.Status=STATUS_FS_DRIVER_REQUIRED;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
returnSTATUS_FS_DRIVER_REQUIRED;
}
//卷不可以被识别
Irp->IoStatus.Status=STATUS_UNRECOGNIZED_VOLUME;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
returnSTATUS_UNRECOGNIZED_VOLUME;
}
上面给出了RecognizerFSControl的全部代码,这个函数用于处理IRP_MJ_FILE_SYSTEM_CONTROL请求。
这个函数总是在PASSIVE_LEVEL上被调用。
这个函数处理两个次功能码,分别是IRP_MN_MOUNT_VOLUME和IRP_MN_LOAD_FILE_SYSTEM。
当有一个卷被装配时,IRP_MN_MOUNT_VOLUME请求被发送。
IRP_MN_LOAD_FILE_SYSTEM在文件系统驱动程序必须被加载时调用。
在本文的前面部分,我们已经描述了在一个没有被装配的物理磁盘卷被访问时IO管理器如何调用所有注册了的文件系统。
每一个文件系统驱动程序都有机会去分析卷直到一个驱动程序声明可以识别此卷。
所以文件系统识别器在这里收到一个装配请求,文件系统识别器调用Recognizer_DetectFileSystem函数,这个函数将在稍候被描述。
如果该函数返回STATUS_SUCCESS,说明该卷该文件系统识别器的文件系统驱动识别。
接着返回STATUS_FS_DRIVER_REQUIRED给IO管理器。
这个错误码被IO管理器特殊对待,因为对文件系统识别器的支持是建立在IO管理器的装配过程。
一旦收到此IRP,IO管理器便发送一个新的IO请求给文件系统识别器。
这一次发送的次功能码IRP_MN_LOAD_FILE_SYSTEM。
识别器用ZwLoadDriver函数处理这个请求。
该函数唯一的参数是需要加载的驱动的注册表项。
如果加载驱动成功,ZwLoadDriver返回STATUS_SUCCESS,识别器可以反注册和删除识别器设备对象,这将导致识别器驱动程序被卸载。
从该文件系统被注册开始,这是它第一次被调用。
当整个文件系统被加载后,IO管理器将和文件系统程序再进行一次装配请求。
因为整个文件系统被加载了,识别器已经不再需要了。
随后的请求将被文件系统驱动程序处理。
//
//RecognizerDetectFileSystem
//
//用户需要根据自己文件系统的不同改变这些代码
//输入参数:
//Irp–请求装配卷的IRP.
//
//输出参数:
//None.
//
//Returns:
//STATUS_SUCCESS–这是我们的驱动可以识别的文件系统
//Other-I/Oerror
//
//Notes:
//None.
//
staticNTSTATUSRecognizerDetectFileSystem(PIRPIrp)
{
NTSTATUScode=STATUS_SUCCESS;
DISK_GEOMETRYdiskGeometry;
PARTITION_INFORMATIONpartitionInfo;
PIO_STACK_LOCATIONirpSp=IoGetCurrentIrpStackLocation(Irp);
PVPBvpb=irpSp->Parameters.MountVolume.Vpb;
PDEVICE_OBJECTmediaDeviceObject=irpSp->Parameters.MountVolume.DeviceObject;
unsignedchar*pBuffer=NULL;
//首先构造一个可以获得卷的分区表等信息的IRP,用RecognizerIoControl传递到下层驱动。
//IFerror{
//returnerror
//}
//else
//{
//用RecognizerReadDiskSector读卷参数块(VPB)}
//Ifreaderror{
//returnerror
//}
//else
//{检查文件系统的标识符,如果不匹配
//returnerror
//}
//else
//returncodeSTATUS_SUCCESS;
//endif
//endif
//endif
//
//现在返回错误码
code=STATUS_UNRECOGNIZED_VOLUME;
returncode;
}
上面的代码显示RecognizerDetectFileSystem的全部内容。
它负责实际的的卷的类型的检测。
因为它的实现依赖于实际的物理卷。
所以我们只提供一个通用的方法。
你需要修改使得它适用于实际的卷。
这个例程需要实现两个关键的功能,一个是从存储介质读取数据,另外一个是从存储介质中获得关于介质的关键信息。
这些功能被两个函数封装了,第一个是RecogizerReadDiskSector()从物理卷中读取用于分析的数据。
第二个函数RecognizerIoControl(…)用于向下层设备发送请求,这些请求返回关于物理介质的信息,例如每一个扇区多少个字节。
IOCTL_DISK_GET_PARTITION_INFORMATION用于返回已经被装配了的卷的信息。
NT文件系统用分区信息用作匹配签名算法的一部分。
注意RecognizerReadDiskSector()函数用的是相对于分区的扇区数而不是相对于磁盘的扇区数。
所以如果你传入参数0,你将得到的是分区的第一个扇区,而不是磁盘的第一个分区。
换句话说,如果分区从第540扇区开始,用0做参数调用函数,你得到的是相对于分区为0,而相对于磁盘为549的扇区数据。
.
//
//RecognizerIoControl
//
//这个函数用于发送特定的IRP个下层驱动
//
//输出参数:
//MediaHandle–特定设备的卷句柄
//Offset–读请求的逻辑偏移量
//Length–读的长度
//MDL–数据将要被拷贝的MDL链t
//
//输出参数:
//None.
//
//Returns:
//STATUS_SUCCESS-I/Ocompletedsuccessfully
//Other-I/Oerror
//
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 文件系统 识别