(转载)Windows文件系统过滤驱动开发教程 作者,楚狂人自述 (2)
这两个Fast I/O例程有相同的接口:
typedef BOOLEAN (*PFAST_IO_QUERY_ABSIC_INFO)(
IN struct _FILE_OBJECT *FileObject,
IN BOOLEAN Wait,
OUT PFILE_BASIC_INFORMATION Buffer,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject);
Wait参数表示调用者是否阻塞以等待获取信息的返回,如果设置为FALSE,则调用要么立刻完成,要么返回FALSE,如果返回FALSE,则会生成一个包含整个操作必要的上下文件的IRP。有趣的是在NT3.51中当Wait设置为FALSE时这两个例程将不被调用,当然这在以后的版本中会修改。
这两个例程一但被调用,它们会查询文件对象第一次打开时保存的信息,这些信息也可能被实时变化,例如,文件的最近访问时间属性会被文件系统设置的当前的系统时间,当然设置这些属性取决于文件系统的实现。
FastIoLock,FastIoUnLockSingle,FastIoUnLockAll,and FastIoUnLockAllByKey
这些例程被用来控制特殊文件的加锁状态。对文件的加锁以字节为单位,所以可以对文件的多个字节进行加密。标准的NT文件系统使用文件系统运行时开发包(FsRtl函数)所提供的通用代码来验证锁权限并存储文件的加锁范围。锁状态通过调用NT API函数NtLockFile和NtUnLockFile来控制。
在Windows NT中有两种锁,一种是排它锁,是写锁,说明加锁的地放要进行修改;另一种是共享锁,是读锁,说明加锁的地放用来的读的。多个共享锁在重叠操作中可以被授权,并且一直保存到释放为止。将各种加锁的信息存储起来在访问这些信息的时候会提高速度。
FastIoLock的原型:
Typedef BOOLEAN (*PFAST_IO_LOCK)(
IN struct _FILE_OJBECT *FileObject,
IN PLARGE_INTEGER FileOffset,
IN PLARGE_INTEGER Length,
PEPROCESS ProcessId,
ULONG Key,
BOOLEAN FailImmediately,
BOOLEAN ExclusiveLock,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
FileOffset 和Length参数对应加锁的范围,ProcessId标识加锁的进程,如果进行退出,锁将会清除。Key参数提供一个非透明的值,用来关联多个锁,例如调用FastIoUnLockAllByKey可以快速访问多个锁。FailImmediately用来标识当锁无效时是立刻返回失败还是阻塞直到锁可用。对于FsRtl函数,如果是无效的锁则忽略FailImmediately参数,函数返回FALSE。ExclusiveLock参数用来标识是排它锁还是共享锁。
FastUnlockSingle例程被用来释放对文件的加锁,原型如下:
Typedef BOOLEAN (*PFAST_IO_UNLOCK_SINGLE)(
IN struct _FILE_OBJECT *FileObject,
IN PLARGE_INTEGER FileOffset,
IN PLARGE_INTEGER Length,
PEPROCESS ProcessId,
ULONG Key,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
对大多文件系统来说,如果文件没有加锁,此例程总是返回TRUE,即使朝无效的锁,操作也会完成,因为用IRP来操作也同样会产生相同的错误。
如果这个解锁操作成功,那么FileOffset,Length,ProcessId,和Key必须和相应的锁信息匹配,否则操作会返回错误STATUS_RANGE_NOT_LOCKED。FastIoUnlockAll例程用来释放特殊文件所有的锁,函数原型如下:
typedef BOOLEAN(*PFAST_IO_UNLOCK_ALL)(
IN struct _FILE_OBJECT *FileObject,
PEPROCESS ProcessId,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
在这种情况下,Fast I/O例程查找进程ProcessId所操作的文件所有的锁,并删除,无论是排它锁还是共享锁。这个例程常用在当系统由于关掉程序或终止程序而调用NtCloseFile时。
FastIoUnlockAllByKey操作用来通过一些特殊的键值来删除一系列锁。原型如下:
Typedef BOOLEAN (*PFAST_IO_UNLOCK_ALL_BY_KEY)(
IN struct _FILE_OBJECT *FileObject,
PVOID ProcessId,
ULONG Key,
OUT PIO_STATUS_BLOCK_IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
提供这个例程是为了便于文件服务如SRV。在NT3.51的I/O管理器中没有出现这个调用。键值用来文件服务给远程客户机文件加锁时分配的。因为许多远程客户端,仅有ProcessId是远远不够的。同样,对于多文件服务器,仅使用键值也会在其它的文件服务器释放时导致错误。二者同时使用以确保正确操作并允许远程加锁。
4.设备栈,过滤,文件系统的感知
前边都在介绍文件系统驱动的结构,却还没讲到我们的过滤驱动如何能捕获所有发给文件系统驱动的irp,让我们自己来处理?前面已经解释过了设备对象。现在来解释一下设备栈。
任何设备对象都存在于某个设备栈中。设备栈自然是一组设备对象。这些设备对象是互相关联的,也就是说,如果得到一个DO指针,你就可以知道它所处的设备栈。
任何来自应用的请求,最终被windows io mgr翻译成irp的,总是发送给设备栈的顶端那个设备。
原始irp irp irp irp
--------------> ------> -------> ----->
DevTop Dev2 ... DevVolumne ... ???
<-------------- <------ <------- <-----
原始irp(返回) irp irp irp
上图向右的箭头表示irp请求的发送过程,向左则是返回。可见irp是从设备栈的顶端开始,逐步向下发送。DevVolumue表示我们实际要过滤的Volume设备,DevTop表示这个设备栈的顶端。我们只要在这个设备栈的顶端再绑定一个设备,那发送给Volume的请求,自然会先发给我们的设备来处理。
有一个系统调用可以把我们的设备绑定到某个设备的设备栈的顶端。这个调用是IoAttachDeviceToDeviceStack,这个调用2000以及以上系统都可以用(所以说到这点,是因为还有一个IoAttachDeviceToDeviceStackSafe,是2000所没有的。这常常导致你的filter在2000下不能用。)
我自己写了一个函数来帮我实现绑定功能:
//----------------------wdf.h中的内容----------------------------------
// 这个例程把源设备绑定到目标设备的设备栈中去,并返回源设备所直
// 接绑定的设备。注意源设备未必直接绑定在目标设备上。它应绑定在
// 目标设备的设备栈的顶端。
_inline wd_stat wd_dev_attach(in wd_dev *src,
in wd_dev *dst,
in out wd_dev **attached)
{
*attached = dst;
*attached = IoAttachDeviceToDeviceStack(src,dst);
if(*attached == NULL)
return wd_stat_no_such_dev;
return wd_stat_suc;
}
到这里,我们已经知道过滤对Volume的请求的办法。比如“C:”这个设备,我已经知道符号连接为“C:”,不难得到设备名。得到设备名后,又不难得到设备。这时候我们IoCreateDevice()生成一个Device Object,然后调用wd_dev_attach绑定,不是一切ok吗?所有发给“C:”的irp,就必然先发送给我们的驱动,我们也可以捕获所有对文件的操作了!
这确实是很简单的处理方法。我得到的FileMon的代码就是这样处理的,如果不想处理动态的Volume,你完全可以这样做。但是我们这里有更高的要求。当你把一个U盘插入usb口,一个“J:”之类的Volume动态诞生的时候,我们依然要捕获这个事件,并生成一个Device来绑定它。
一个新的存储媒质被系统发现并在文件系统中生成一个Volume的过程称为Mounting.其过程开始的时候,FS的CDO将得到一个IRP,其Major Function Code为IRP_MJ_FILE_SYSTEM_CONTROL,Minor Function Code为IRP_MN_MOUNT。换句话说,如果我们已经生成了一个设备绑定文件系统的CDO,那么我们就可以得到这样的IRP,在其中知道一个新的Volume正在Mount.这时候我们可以执行上边所说的操作。
现在的问题是如何知道系统中有那些文件系统,还有就是我应该在什么时候绑定它们的控制设备。
IoRegisterFsRegistrationChange()是一个非常有用的系统调用。这个调用注册一个回调函数。当系统中有任何文件系统被激活或者是被注销的时候,你注册过的回调函数就会被调用。
//----------------------wdf.h中的内容----------------------------------
wd_stat wdff_reg_notify(
in wd_drv *driver,
in wdff_notify_func func
)
{
return IoRegisterFsRegistrationChange(driver,func);
}
你有必要为此写一个回调函数。
//-------------------我的回调处理函数----------------------------------
wd_void my_fs_notify(
in wd_dev *dev,
in wd_bool active)
{
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
// 如果注册了,就应该得到通知
wd_printf0("notify: a file sys have been acitved!!! rn");
// 得到文件系统对象的名字,然后打印出来
wd_obj_get_name(dev,&name);
wd_printf0("notify : file sys name = %wZrn",&name);
if(active)
{
wd_printf0("notify: try to attach.rn");
// ... 请在这里绑定文件系统的控制设备
}
else
{
wd_printf0("notify: unactive.rn");
// ...
}
}
应该如何绑定一个文件系统CDO?我们在下面的章节再详细描述。
现在我们应该再在wd_main函数中加上下边的内容:
if(wdff_reg_notify(driver,my_fs_notify) != wd_stat_suc)
{
wd_printf0("error: reg notify failed.rn");
wd_fio_disp_release(driver);
wd_dev_del(g_cdo);
g_cdo = wd_null;
return wd_stat_insufficient_res;
};
wd_printf0("success: reg notify ok.n");
我们再次回顾一下,wd_main中,应该做哪些工作。
a.生成一个控制设备。当然此前你必须给控制设置指定名称。
b.设置Dispatch Functions.
c.设置Fast Io Functions.
d.编写一个my_fs_notify回调函数,在其中绑定刚激活的FS CDO.
e.使用wdff_reg_notify调用注册这个回调函数。
5.绑定FS CDO,文件系统识别器,设备扩展
上一节讲到我们打算绑定一个刚刚被激活的FS CDO.前边说过简单的调用wd_dev_attach可以很容易的绑定这个设备。但是,并不是每次my_fs_notify调用发现有新的fs激活,我就直接绑定它。
首先判断是否我需要关心的文件系统类型。我用下面的函数来获取设备类型。
// ------------------wdf.h中的内容-------------------
_inline wd_dev_type wd_dev_get_type(in wd_dev *dev)
{
return dev->DeviceType;
}
文件系统的CDO的设备类型有下边的几种可能,你的过滤驱动可能只对其中某些感兴趣。
enum {
wd_dev_disk_fs = FILE_DEVICE_DISK_FILE_SYSTEM,
wd_dev_cdrom_fs = FILE_DEVICE_CD_ROM_FILE_SYSTEM,
wd_dev_network_fs = FILE_DEVICE_NETWORK_FILE_SYSTEM
};
你应该自己写一个函数来判断该fs是否你所关心的。
// -------------一个函数,判断是否我所关心的fs---------------
wd_bool my_care(wd_ulong type)
{
return (((type) == wd_dev_disk_fs) ||
((type) == wd_dev_cdrom_fs) ||
((type) == wd_dev_network_fs));
}
下一个问题是我打算跳过文件系统识别器。文件系统识别器是文件系统驱动的一个很小的替身。为了避免没有使用到的文件系统驱动占据内核内存,windows系统不加载这些大驱动,而代替以该文件系统驱动对应的文件系统识别器。当新的物理存储媒介进入系统,io管理器会依次的尝试各种文件系统对它进行“识别”。识别成功,立刻加载真正的文件系统驱动,对应的文件系统识别器则被卸载掉。对我们来说,文件系统识别器的控制设备看起来就像一个文件系统控制设备。但我们不打算绑定它。
分辨的方法是通过驱动的名字。凡是文件系统识别器的驱动对象的名字(注意是DriverObject而不是DeviceObject!)都为“FileSystem\Fs_Rec”.
//-------------------用这些代码来跳过文件系统识别器----------------------
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name,tmp;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
wd_ustr_init(&tmp,L"\FileSystem\Fs_Rec");
// 我不绑定识别器。所以如果是识别器,我直接返回成功。查看是否是识别
// 器的办法是看是否是FileSystemFs_Rec的设备。
wd_obj_get_name(wd_dev_drv(fs_dev),&name);
if(wd_ustr_cmp(&name,&tmp,wd_true) == 0)
{
wd_printf0("attach fs dev:is a recogonizer.rn");
return wd_stat_suc;
}
wd_printf0("attach fs dev: not a recogonizer.rn");
接下来我将要生成我的设备。这里要提到设备扩展的概念。设备对象是一个数据结构,为了表示不同的设备,里边将有一片自定义的空间,用来给你记录这个设备的特有信息。我们为我们所生成的设备确定设备扩展如下:
// 文件过滤系统驱动的设备扩展
typedef struct _my_dev_ext
{
// 我们绑定的文件系统驱动
wd_dev * attached_to;
// 上边这个设备的设备名。
wd_ustr dev_name;
// 这是上边的unicode字符串的缓冲区
wd_wchar name_buf[wd_dev_name_max_len];
} my_dev_ext;
之所以如此简单,是因为我们现在还没有多少东西要记录。只要记得自己绑定在哪个设备上就好了。如果以后需要更多的信息,再增加不迟。扩展空间的大小是在wdf_dev_create(也就是这个设备生成)的时候指定的。得到设备对象指针后,我用下面这个函数来获取设备扩展指针:
// --------------wdf.h中的内容------------------
_inline wd_void * wd_dev_ext(wd_dev *dev)
{
return (dev->DeviceExtension);
}
生成设备后,为了让系统看起来,你的设备和原来的设备没什么区别,你必须设置一些该设备的标志位与你所绑定的设备相同。
_inline wd_void wd_dev_copy_flag(wd_dev *new_dev,
wd_dev *old_dev)
{
if(old_dev->Flags & DO_BUFFERED_IO)
new_dev->Flags &= DO_BUFFERED_IO;
if(old_dev->Flags & DO_DIRECT_IO)
new_dev->Flags &= DO_DIRECT_IO;
if (old_dev->Characteristics & FILE_DEVICE_SECURE_OPEN)
new_dev->Characteristics &= FILE_DEVICE_SECURE_OPEN;
}
DO_BUFFERED_IO,DO_DIRECT_IO这两个标志的意义在于外部向这些设备发送读写请求的时候,所用的缓冲地址将有所不同。这点以后在过滤文件读写的时候再讨论。现在一切事情都搞完,你应该去掉你的新设备上的DO_DEVICE_INITIALIZING标志,以表明的的设备已经完全可以用了。
// --------------wdf.h中的内容------------------
_inline wd_void wd_dev_clr_init_flag(wd_dev *dev)
{
dev->Flags &= ~DO_DEVICE_INITIALIZING;
}
现在我写一个函数来完成以上的这个过程。你只要在上一节中提示的位置调用这个函数,就完成对文件系统控制设备的绑定了。
//-----------绑定一个文件系统驱动设备-------------------------
wd_stat my_attach_fs_dev(wd_dev *fs_dev)
{
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name,tmp;
wd_dev *new_dev;
wd_stat status;
my_dev_ext *ext;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
wd_ustr_init(&tmp,L"\FileSystem\Fs_Rec");
// 如果不是我关心的类型,我直接返回成功
if(!my_care(wd_dev_get_type(fs_dev)))
{
wd_printf0(("attach fs dev:not a cared type.rn"));
return wd_stat_suc;
}
wd_printf0("attach fs dev: is my cared type.rn");
// 我不绑定识别器。所以如果是识别器,我直接返回成功。查看是否是识别
// 器的办法是看是否是FileSystem\Fs_Rec的设备。
wd_obj_get_name(wd_dev_drv(fs_dev),&name);
if(wd_ustr_cmp(&name,&tmp,wd_true) == 0)
{
wd_printf0("attach fs dev:is a recogonizer.rn");
return wd_stat_suc;
}
wd_printf0("attach fs dev: not a recogonizer.rn");
// 现在来生成一个设备用来绑定
status = wd_dev_create(g_drv,sizeof(my_dev_ext),NULL,
wd_dev_get_type(fs_dev),
0,wd_false,&new_dev);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev create failed.rn");
return status;
}
wd_printf0("attach fs dev: create dev success.rn");
// 接着设置设备的各种标志与之要绑定的标志一致
wd_dev_copy_flag(new_dev,fs_dev);
ext = (my_dev_ext *)wd_dev_ext(new_dev);
wd_printf0("begin to attach.rn");
status = wd_dev_attach(new_dev,fs_dev,&ext->attached_to);
wd_printf0("attach over.status = %8xrn",status);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev attach failed.rn");
UNREFERENCED_PARAMETER(new_dev);
wd_dev_del(new_dev);
return status;
}
wd_printf0("attach fs dev: attach %wZ succeed.rn",&name);
wd_ustr_init_em(&ext->dev_name,ext->name_buf,wd_dev_name_max_len);
wd_ustr_copy(&ext->dev_name,&name);
wd_dev_clr_init_flag(new_dev);
return status;
}
6.IRP的传递,File System Control Dispatch
我们现在不得不开始写dispatch functions.因为你的设备已经绑定到文件系统控制设备上去了。windows发给文件系统的请求发给你的驱动。如果你不能做恰当的处理,你的系统的就会崩溃。
最简单的处理方式是把请求不加改变的传递到我们所绑定的设备上去。如何获得我们所绑定的设备?上一节已经把该设备记录在我们的设备扩展里。
//------------我用这个函数快速得到我所绑定的设备-----------
// 得到绑定的设备
_inline wd_dev *my_dev_attached(wd_dev *dev)
{
return ((wdff_dev_ext *)wd_dev_ext(dev))->attached_to;
}
如何传递请求?使用IoCallDriver,该调用的第一个参数是设备对象指针,第二个参数是IRP指针。
一个IRP拥有一组IO_STACK_LOCATION.前面说过IRP在一个设备栈中传递。IO_STACK_LOCATION是和这个设备栈对应的。用于保存IRP请求在当前设备栈位置中的部分参数。如果我要把请求往下个设备传递,那么我应该把当前IO_STATCK_LOCATION复制到下一个。
我写了一些函数来处理IO_STACK_LOCATION,另外wd_irp_call用来包装IoCallDriver的功能。
//---------------------wdf.h中的内容----------------------------
typdef wd_irpsp PIO_STACK_LOCAION;
_inline wd_irpsp *wd_cur_io_stack(wd_irp *irp)
{
return IoGetCurrentIrpStackLocation(irp);
}
_inline wd_void wd_skip_io_stack(wd_pirp irp)
{
IoSkipCurrentIrpStackLocation(irp);
}
_inline wd_void wd_copy_io_stack(wd_irp *irp)
{
IoCopyCurrentIrpStackLocationToNext(irp);
}
_inline wd_stat wd_irp_call(wd_dev *dev,wd_pirp irp)
{
return IoCallDriver(dev,irp);
}
有了上边这些,我现在可以写一个默认的Dispatch Functions.
// 默认的处理很简单,忽略当前调用栈,直接发送给绑定设备
wd_stat my_disp_default(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
if(is_my_cdo(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
attached_dev = my_dev_attached(dev);
if(!attached_dev)
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
wd_skip_io_stack(irp);
return wd_irp_call(attached_dev,irp);
}
上边有一个函数is_my_dev来判断是否我的设备。这个判断过程很简单。通过dev可以得到DriverObject指针,判断一下是否我自己的驱动即可。is_my_cdo()来判断这个设备是否是我的控制设备,不要忘记在wd_main()中我们首先生成了一个本驱动的控制设备。实际这个控制设备还不做任何事情,所以对它发生的任何请求也是非法的。返回错误即可。wd_irp_failed这个函数立刻让一个irp失败。其内容如下:
// 这个函数可以立刻失败掉一个irp
_inline wd_stat wd_irp_failed(wd_pirp irp,wd_stat status_error)
{
irp->IoStatus.Status = status_error;
irp->IoStatus.Information = 0;
return wd_irp_over(irp);
}
如此一来,本不改发到我的驱动的irp,就立刻返回错误非法请求。但是实际上这种情况是很少发生的。
如果你现在想要你的驱动立刻运行,让所有的dispacth functions都调用my_disp_default.这个驱动已经可以绑定文件系统的控制设备,并输出一些调试信息。但是还没有绑定Volume.所以并不能直接监控文件读写。
对于一个绑定文件系统控制设备的设备来说,其他的请求直接调用上边的默认处理就可以了。重点需要注意的是上边曾经挂接IRP_MJ_FILE_SYSTEM_CONTROL的dispatch处理的函数my_disp_file_sys_ctl().
IRP_MJ_FILE_SYSTEM_CONTROL这个东西是IRP的主功能号。每个主功能号下一般都有次功能号。这两个东西标示一个IRP的功能。
主功能号和次功能号是IO_STACK_LOCATION的开头两字节。
//----------------我重新定义的次功能号-------------------
enum {
wd_irp_mn_mount = IRP_MN_MOUNT_VOLUME,
wd_irp_mn_load_filesys = IRP_MN_LOAD_FILE_SYSTEM,
wd_irp_mn_user_req = IRP_MN_USER_FS_REQUEST
};
enum {
wdf_fsctl_dismount = FSCTL_DISMOUNT_VOLUME
};
要得到功能号,要先得到当前的IO_STACK_LOCATION,这个上边已经有函数wd_cur_io_stack,相信这个不能难倒你。
当有Volumne被Mount或者dismount,你写的my_disp_file_sys_ctl()就被调用。具体的判断方法,就见如下的代码了:
// 可以看到分发函数中其他的函数处理都很简单,但是file_sys_ctl的
// 处理会比较复杂。我们已经在notify函数中绑定了文件系统驱动的控
// 制对象。当文件系统得到实际的介质的时候,会生成新的设备对象,
// 这种设备称为卷(Volume),而这种设备是在file_sys中的mount中生
// 成的,而且也是unmount中注销掉的。我们捕获这样的操作之后,就必
// 须生成我们的设备对象,绑定在这样的“卷”上,才能绑定对这个卷
// 上的文件的操作。
wd_stat my_disp_file_sys_ctl(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
wd_io_stack *stack = wd_cur_io_stack(irp);
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
switch(wd_irpsp_minor(stack))
{
case wd_irp_mn_mount:
// 在这里,一个Volume正在Mount
return my_fsctl_mount(dev,irp);
case wd_irp_mn_load_filesys:
return my_fsctl_load_fs(dev,irp);
case wd_irp_mn_user_req:
{
switch(wd_irpsp_fs_ctl_code(stack))
{
case wdf_fsctl_dismount:
// 在这里,一个Volume正dismount
return my_fsctl_dismount(dev,irp);
}
}
}
wd_skip_io_stack(irp);
attached_dev = my_dev_attached(dev);
return wd_irp_call(attached_dev,irp);
}
你发现你又得开始写两个新的函数,my_fsctl_mount()和my_fsctl_dismount(),来处理卷的Mount和Dismount.显然,你应该在其中生成设备或者删除,绑定或者解除绑定。很快,你就能完全监控所有的卷了。
这样做是动态监控所有的卷的完美的解决方案。
如果是在xp以上,有一个调用可以获得一个文件系统上已经被Mount的卷。但是2000下不能使用。所以我们没有使用那个方法。何况仅仅得到已经Mount的卷也不是我想要的。
这里另外还有一个my_fsctl_load_fs函数。发生于IRP_MN_LOAD_FILESYS。这个功能码我只做一点点解释:当一个文件识别器(见上文)决定加载真正的文件系统的时候,会产生一个这样的irp。
你现在可以修改你的驱动,使插入拔出u盘的时候,在Volume加载卸载时候输出调试信息。回首一下我们的脉络:
a.生成一个控制设备。当然此前你必须给控制设置指定名称。
b.设置Dispatch Functions.
c.设置Fast Io Functions.
d.编写一个my_fs_notify回调函数,在其中绑定刚激活的FS CDO.
e.使用wdff_reg_notify调用注册这个回调函数。
f.编写默认的dispatch functions.
e.处理IRP_MJ_FILE_SYSTEM_CONTROL,在其中监控Volumne的Mount和Dismount.
f.下一步自然是绑定Volumne了,请听下回分解。
7.IRP完成函数,中断级,如何超越中断级别的限制
先讨论一下Volumne设备是如何得到的.首先看以下几个函数:
// ------------------wdf.h 中的内容 -------------------------
typedef VPB wd_vpb;
_inline wd_vpb * wd_dev_vbp(wd_dev *dev)
{
return dev->Vpb;
}
_inline wd_dev * wd_vbp_dev(wd_vpb *vpb)
{
return vpb->DeviceObject;
}
typedef BOOLEAN (*PFAST_IO_QUERY_ABSIC_INFO)(
IN struct _FILE_OBJECT *FileObject,
IN BOOLEAN Wait,
OUT PFILE_BASIC_INFORMATION Buffer,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject);
Wait参数表示调用者是否阻塞以等待获取信息的返回,如果设置为FALSE,则调用要么立刻完成,要么返回FALSE,如果返回FALSE,则会生成一个包含整个操作必要的上下文件的IRP。有趣的是在NT3.51中当Wait设置为FALSE时这两个例程将不被调用,当然这在以后的版本中会修改。
这两个例程一但被调用,它们会查询文件对象第一次打开时保存的信息,这些信息也可能被实时变化,例如,文件的最近访问时间属性会被文件系统设置的当前的系统时间,当然设置这些属性取决于文件系统的实现。
FastIoLock,FastIoUnLockSingle,FastIoUnLockAll,and FastIoUnLockAllByKey
这些例程被用来控制特殊文件的加锁状态。对文件的加锁以字节为单位,所以可以对文件的多个字节进行加密。标准的NT文件系统使用文件系统运行时开发包(FsRtl函数)所提供的通用代码来验证锁权限并存储文件的加锁范围。锁状态通过调用NT API函数NtLockFile和NtUnLockFile来控制。
在Windows NT中有两种锁,一种是排它锁,是写锁,说明加锁的地放要进行修改;另一种是共享锁,是读锁,说明加锁的地放用来的读的。多个共享锁在重叠操作中可以被授权,并且一直保存到释放为止。将各种加锁的信息存储起来在访问这些信息的时候会提高速度。
FastIoLock的原型:
Typedef BOOLEAN (*PFAST_IO_LOCK)(
IN struct _FILE_OJBECT *FileObject,
IN PLARGE_INTEGER FileOffset,
IN PLARGE_INTEGER Length,
PEPROCESS ProcessId,
ULONG Key,
BOOLEAN FailImmediately,
BOOLEAN ExclusiveLock,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
FileOffset 和Length参数对应加锁的范围,ProcessId标识加锁的进程,如果进行退出,锁将会清除。Key参数提供一个非透明的值,用来关联多个锁,例如调用FastIoUnLockAllByKey可以快速访问多个锁。FailImmediately用来标识当锁无效时是立刻返回失败还是阻塞直到锁可用。对于FsRtl函数,如果是无效的锁则忽略FailImmediately参数,函数返回FALSE。ExclusiveLock参数用来标识是排它锁还是共享锁。
FastUnlockSingle例程被用来释放对文件的加锁,原型如下:
Typedef BOOLEAN (*PFAST_IO_UNLOCK_SINGLE)(
IN struct _FILE_OBJECT *FileObject,
IN PLARGE_INTEGER FileOffset,
IN PLARGE_INTEGER Length,
PEPROCESS ProcessId,
ULONG Key,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
对大多文件系统来说,如果文件没有加锁,此例程总是返回TRUE,即使朝无效的锁,操作也会完成,因为用IRP来操作也同样会产生相同的错误。
如果这个解锁操作成功,那么FileOffset,Length,ProcessId,和Key必须和相应的锁信息匹配,否则操作会返回错误STATUS_RANGE_NOT_LOCKED。FastIoUnlockAll例程用来释放特殊文件所有的锁,函数原型如下:
typedef BOOLEAN(*PFAST_IO_UNLOCK_ALL)(
IN struct _FILE_OBJECT *FileObject,
PEPROCESS ProcessId,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
在这种情况下,Fast I/O例程查找进程ProcessId所操作的文件所有的锁,并删除,无论是排它锁还是共享锁。这个例程常用在当系统由于关掉程序或终止程序而调用NtCloseFile时。
FastIoUnlockAllByKey操作用来通过一些特殊的键值来删除一系列锁。原型如下:
Typedef BOOLEAN (*PFAST_IO_UNLOCK_ALL_BY_KEY)(
IN struct _FILE_OBJECT *FileObject,
PVOID ProcessId,
ULONG Key,
OUT PIO_STATUS_BLOCK_IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
提供这个例程是为了便于文件服务如SRV。在NT3.51的I/O管理器中没有出现这个调用。键值用来文件服务给远程客户机文件加锁时分配的。因为许多远程客户端,仅有ProcessId是远远不够的。同样,对于多文件服务器,仅使用键值也会在其它的文件服务器释放时导致错误。二者同时使用以确保正确操作并允许远程加锁。
4.设备栈,过滤,文件系统的感知
前边都在介绍文件系统驱动的结构,却还没讲到我们的过滤驱动如何能捕获所有发给文件系统驱动的irp,让我们自己来处理?前面已经解释过了设备对象。现在来解释一下设备栈。
任何设备对象都存在于某个设备栈中。设备栈自然是一组设备对象。这些设备对象是互相关联的,也就是说,如果得到一个DO指针,你就可以知道它所处的设备栈。
任何来自应用的请求,最终被windows io mgr翻译成irp的,总是发送给设备栈的顶端那个设备。
原始irp irp irp irp
--------------> ------> -------> ----->
DevTop Dev2 ... DevVolumne ... ???
<-------------- <------ <------- <-----
原始irp(返回) irp irp irp
上图向右的箭头表示irp请求的发送过程,向左则是返回。可见irp是从设备栈的顶端开始,逐步向下发送。DevVolumue表示我们实际要过滤的Volume设备,DevTop表示这个设备栈的顶端。我们只要在这个设备栈的顶端再绑定一个设备,那发送给Volume的请求,自然会先发给我们的设备来处理。
有一个系统调用可以把我们的设备绑定到某个设备的设备栈的顶端。这个调用是IoAttachDeviceToDeviceStack,这个调用2000以及以上系统都可以用(所以说到这点,是因为还有一个IoAttachDeviceToDeviceStackSafe,是2000所没有的。这常常导致你的filter在2000下不能用。)
我自己写了一个函数来帮我实现绑定功能:
//----------------------wdf.h中的内容----------------------------------
// 这个例程把源设备绑定到目标设备的设备栈中去,并返回源设备所直
// 接绑定的设备。注意源设备未必直接绑定在目标设备上。它应绑定在
// 目标设备的设备栈的顶端。
_inline wd_stat wd_dev_attach(in wd_dev *src,
in wd_dev *dst,
in out wd_dev **attached)
{
*attached = dst;
*attached = IoAttachDeviceToDeviceStack(src,dst);
if(*attached == NULL)
return wd_stat_no_such_dev;
return wd_stat_suc;
}
到这里,我们已经知道过滤对Volume的请求的办法。比如“C:”这个设备,我已经知道符号连接为“C:”,不难得到设备名。得到设备名后,又不难得到设备。这时候我们IoCreateDevice()生成一个Device Object,然后调用wd_dev_attach绑定,不是一切ok吗?所有发给“C:”的irp,就必然先发送给我们的驱动,我们也可以捕获所有对文件的操作了!
这确实是很简单的处理方法。我得到的FileMon的代码就是这样处理的,如果不想处理动态的Volume,你完全可以这样做。但是我们这里有更高的要求。当你把一个U盘插入usb口,一个“J:”之类的Volume动态诞生的时候,我们依然要捕获这个事件,并生成一个Device来绑定它。
一个新的存储媒质被系统发现并在文件系统中生成一个Volume的过程称为Mounting.其过程开始的时候,FS的CDO将得到一个IRP,其Major Function Code为IRP_MJ_FILE_SYSTEM_CONTROL,Minor Function Code为IRP_MN_MOUNT。换句话说,如果我们已经生成了一个设备绑定文件系统的CDO,那么我们就可以得到这样的IRP,在其中知道一个新的Volume正在Mount.这时候我们可以执行上边所说的操作。
现在的问题是如何知道系统中有那些文件系统,还有就是我应该在什么时候绑定它们的控制设备。
IoRegisterFsRegistrationChange()是一个非常有用的系统调用。这个调用注册一个回调函数。当系统中有任何文件系统被激活或者是被注销的时候,你注册过的回调函数就会被调用。
//----------------------wdf.h中的内容----------------------------------
wd_stat wdff_reg_notify(
in wd_drv *driver,
in wdff_notify_func func
)
{
return IoRegisterFsRegistrationChange(driver,func);
}
你有必要为此写一个回调函数。
//-------------------我的回调处理函数----------------------------------
wd_void my_fs_notify(
in wd_dev *dev,
in wd_bool active)
{
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
// 如果注册了,就应该得到通知
wd_printf0("notify: a file sys have been acitved!!! rn");
// 得到文件系统对象的名字,然后打印出来
wd_obj_get_name(dev,&name);
wd_printf0("notify : file sys name = %wZrn",&name);
if(active)
{
wd_printf0("notify: try to attach.rn");
// ... 请在这里绑定文件系统的控制设备
}
else
{
wd_printf0("notify: unactive.rn");
// ...
}
}
应该如何绑定一个文件系统CDO?我们在下面的章节再详细描述。
现在我们应该再在wd_main函数中加上下边的内容:
if(wdff_reg_notify(driver,my_fs_notify) != wd_stat_suc)
{
wd_printf0("error: reg notify failed.rn");
wd_fio_disp_release(driver);
wd_dev_del(g_cdo);
g_cdo = wd_null;
return wd_stat_insufficient_res;
};
wd_printf0("success: reg notify ok.n");
我们再次回顾一下,wd_main中,应该做哪些工作。
a.生成一个控制设备。当然此前你必须给控制设置指定名称。
b.设置Dispatch Functions.
c.设置Fast Io Functions.
d.编写一个my_fs_notify回调函数,在其中绑定刚激活的FS CDO.
e.使用wdff_reg_notify调用注册这个回调函数。
5.绑定FS CDO,文件系统识别器,设备扩展
上一节讲到我们打算绑定一个刚刚被激活的FS CDO.前边说过简单的调用wd_dev_attach可以很容易的绑定这个设备。但是,并不是每次my_fs_notify调用发现有新的fs激活,我就直接绑定它。
首先判断是否我需要关心的文件系统类型。我用下面的函数来获取设备类型。
// ------------------wdf.h中的内容-------------------
_inline wd_dev_type wd_dev_get_type(in wd_dev *dev)
{
return dev->DeviceType;
}
文件系统的CDO的设备类型有下边的几种可能,你的过滤驱动可能只对其中某些感兴趣。
enum {
wd_dev_disk_fs = FILE_DEVICE_DISK_FILE_SYSTEM,
wd_dev_cdrom_fs = FILE_DEVICE_CD_ROM_FILE_SYSTEM,
wd_dev_network_fs = FILE_DEVICE_NETWORK_FILE_SYSTEM
};
你应该自己写一个函数来判断该fs是否你所关心的。
// -------------一个函数,判断是否我所关心的fs---------------
wd_bool my_care(wd_ulong type)
{
return (((type) == wd_dev_disk_fs) ||
((type) == wd_dev_cdrom_fs) ||
((type) == wd_dev_network_fs));
}
下一个问题是我打算跳过文件系统识别器。文件系统识别器是文件系统驱动的一个很小的替身。为了避免没有使用到的文件系统驱动占据内核内存,windows系统不加载这些大驱动,而代替以该文件系统驱动对应的文件系统识别器。当新的物理存储媒介进入系统,io管理器会依次的尝试各种文件系统对它进行“识别”。识别成功,立刻加载真正的文件系统驱动,对应的文件系统识别器则被卸载掉。对我们来说,文件系统识别器的控制设备看起来就像一个文件系统控制设备。但我们不打算绑定它。
分辨的方法是通过驱动的名字。凡是文件系统识别器的驱动对象的名字(注意是DriverObject而不是DeviceObject!)都为“FileSystem\Fs_Rec”.
//-------------------用这些代码来跳过文件系统识别器----------------------
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name,tmp;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
wd_ustr_init(&tmp,L"\FileSystem\Fs_Rec");
// 我不绑定识别器。所以如果是识别器,我直接返回成功。查看是否是识别
// 器的办法是看是否是FileSystemFs_Rec的设备。
wd_obj_get_name(wd_dev_drv(fs_dev),&name);
if(wd_ustr_cmp(&name,&tmp,wd_true) == 0)
{
wd_printf0("attach fs dev:is a recogonizer.rn");
return wd_stat_suc;
}
wd_printf0("attach fs dev: not a recogonizer.rn");
接下来我将要生成我的设备。这里要提到设备扩展的概念。设备对象是一个数据结构,为了表示不同的设备,里边将有一片自定义的空间,用来给你记录这个设备的特有信息。我们为我们所生成的设备确定设备扩展如下:
// 文件过滤系统驱动的设备扩展
typedef struct _my_dev_ext
{
// 我们绑定的文件系统驱动
wd_dev * attached_to;
// 上边这个设备的设备名。
wd_ustr dev_name;
// 这是上边的unicode字符串的缓冲区
wd_wchar name_buf[wd_dev_name_max_len];
} my_dev_ext;
之所以如此简单,是因为我们现在还没有多少东西要记录。只要记得自己绑定在哪个设备上就好了。如果以后需要更多的信息,再增加不迟。扩展空间的大小是在wdf_dev_create(也就是这个设备生成)的时候指定的。得到设备对象指针后,我用下面这个函数来获取设备扩展指针:
// --------------wdf.h中的内容------------------
_inline wd_void * wd_dev_ext(wd_dev *dev)
{
return (dev->DeviceExtension);
}
生成设备后,为了让系统看起来,你的设备和原来的设备没什么区别,你必须设置一些该设备的标志位与你所绑定的设备相同。
_inline wd_void wd_dev_copy_flag(wd_dev *new_dev,
wd_dev *old_dev)
{
if(old_dev->Flags & DO_BUFFERED_IO)
new_dev->Flags &= DO_BUFFERED_IO;
if(old_dev->Flags & DO_DIRECT_IO)
new_dev->Flags &= DO_DIRECT_IO;
if (old_dev->Characteristics & FILE_DEVICE_SECURE_OPEN)
new_dev->Characteristics &= FILE_DEVICE_SECURE_OPEN;
}
DO_BUFFERED_IO,DO_DIRECT_IO这两个标志的意义在于外部向这些设备发送读写请求的时候,所用的缓冲地址将有所不同。这点以后在过滤文件读写的时候再讨论。现在一切事情都搞完,你应该去掉你的新设备上的DO_DEVICE_INITIALIZING标志,以表明的的设备已经完全可以用了。
// --------------wdf.h中的内容------------------
_inline wd_void wd_dev_clr_init_flag(wd_dev *dev)
{
dev->Flags &= ~DO_DEVICE_INITIALIZING;
}
现在我写一个函数来完成以上的这个过程。你只要在上一节中提示的位置调用这个函数,就完成对文件系统控制设备的绑定了。
//-----------绑定一个文件系统驱动设备-------------------------
wd_stat my_attach_fs_dev(wd_dev *fs_dev)
{
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name,tmp;
wd_dev *new_dev;
wd_stat status;
my_dev_ext *ext;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
wd_ustr_init(&tmp,L"\FileSystem\Fs_Rec");
// 如果不是我关心的类型,我直接返回成功
if(!my_care(wd_dev_get_type(fs_dev)))
{
wd_printf0(("attach fs dev:not a cared type.rn"));
return wd_stat_suc;
}
wd_printf0("attach fs dev: is my cared type.rn");
// 我不绑定识别器。所以如果是识别器,我直接返回成功。查看是否是识别
// 器的办法是看是否是FileSystem\Fs_Rec的设备。
wd_obj_get_name(wd_dev_drv(fs_dev),&name);
if(wd_ustr_cmp(&name,&tmp,wd_true) == 0)
{
wd_printf0("attach fs dev:is a recogonizer.rn");
return wd_stat_suc;
}
wd_printf0("attach fs dev: not a recogonizer.rn");
// 现在来生成一个设备用来绑定
status = wd_dev_create(g_drv,sizeof(my_dev_ext),NULL,
wd_dev_get_type(fs_dev),
0,wd_false,&new_dev);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev create failed.rn");
return status;
}
wd_printf0("attach fs dev: create dev success.rn");
// 接着设置设备的各种标志与之要绑定的标志一致
wd_dev_copy_flag(new_dev,fs_dev);
ext = (my_dev_ext *)wd_dev_ext(new_dev);
wd_printf0("begin to attach.rn");
status = wd_dev_attach(new_dev,fs_dev,&ext->attached_to);
wd_printf0("attach over.status = %8xrn",status);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev attach failed.rn");
UNREFERENCED_PARAMETER(new_dev);
wd_dev_del(new_dev);
return status;
}
wd_printf0("attach fs dev: attach %wZ succeed.rn",&name);
wd_ustr_init_em(&ext->dev_name,ext->name_buf,wd_dev_name_max_len);
wd_ustr_copy(&ext->dev_name,&name);
wd_dev_clr_init_flag(new_dev);
return status;
}
6.IRP的传递,File System Control Dispatch
我们现在不得不开始写dispatch functions.因为你的设备已经绑定到文件系统控制设备上去了。windows发给文件系统的请求发给你的驱动。如果你不能做恰当的处理,你的系统的就会崩溃。
最简单的处理方式是把请求不加改变的传递到我们所绑定的设备上去。如何获得我们所绑定的设备?上一节已经把该设备记录在我们的设备扩展里。
//------------我用这个函数快速得到我所绑定的设备-----------
// 得到绑定的设备
_inline wd_dev *my_dev_attached(wd_dev *dev)
{
return ((wdff_dev_ext *)wd_dev_ext(dev))->attached_to;
}
如何传递请求?使用IoCallDriver,该调用的第一个参数是设备对象指针,第二个参数是IRP指针。
一个IRP拥有一组IO_STACK_LOCATION.前面说过IRP在一个设备栈中传递。IO_STACK_LOCATION是和这个设备栈对应的。用于保存IRP请求在当前设备栈位置中的部分参数。如果我要把请求往下个设备传递,那么我应该把当前IO_STATCK_LOCATION复制到下一个。
我写了一些函数来处理IO_STACK_LOCATION,另外wd_irp_call用来包装IoCallDriver的功能。
//---------------------wdf.h中的内容----------------------------
typdef wd_irpsp PIO_STACK_LOCAION;
_inline wd_irpsp *wd_cur_io_stack(wd_irp *irp)
{
return IoGetCurrentIrpStackLocation(irp);
}
_inline wd_void wd_skip_io_stack(wd_pirp irp)
{
IoSkipCurrentIrpStackLocation(irp);
}
_inline wd_void wd_copy_io_stack(wd_irp *irp)
{
IoCopyCurrentIrpStackLocationToNext(irp);
}
_inline wd_stat wd_irp_call(wd_dev *dev,wd_pirp irp)
{
return IoCallDriver(dev,irp);
}
有了上边这些,我现在可以写一个默认的Dispatch Functions.
// 默认的处理很简单,忽略当前调用栈,直接发送给绑定设备
wd_stat my_disp_default(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
if(is_my_cdo(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
attached_dev = my_dev_attached(dev);
if(!attached_dev)
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
wd_skip_io_stack(irp);
return wd_irp_call(attached_dev,irp);
}
上边有一个函数is_my_dev来判断是否我的设备。这个判断过程很简单。通过dev可以得到DriverObject指针,判断一下是否我自己的驱动即可。is_my_cdo()来判断这个设备是否是我的控制设备,不要忘记在wd_main()中我们首先生成了一个本驱动的控制设备。实际这个控制设备还不做任何事情,所以对它发生的任何请求也是非法的。返回错误即可。wd_irp_failed这个函数立刻让一个irp失败。其内容如下:
// 这个函数可以立刻失败掉一个irp
_inline wd_stat wd_irp_failed(wd_pirp irp,wd_stat status_error)
{
irp->IoStatus.Status = status_error;
irp->IoStatus.Information = 0;
return wd_irp_over(irp);
}
如此一来,本不改发到我的驱动的irp,就立刻返回错误非法请求。但是实际上这种情况是很少发生的。
如果你现在想要你的驱动立刻运行,让所有的dispacth functions都调用my_disp_default.这个驱动已经可以绑定文件系统的控制设备,并输出一些调试信息。但是还没有绑定Volume.所以并不能直接监控文件读写。
对于一个绑定文件系统控制设备的设备来说,其他的请求直接调用上边的默认处理就可以了。重点需要注意的是上边曾经挂接IRP_MJ_FILE_SYSTEM_CONTROL的dispatch处理的函数my_disp_file_sys_ctl().
IRP_MJ_FILE_SYSTEM_CONTROL这个东西是IRP的主功能号。每个主功能号下一般都有次功能号。这两个东西标示一个IRP的功能。
主功能号和次功能号是IO_STACK_LOCATION的开头两字节。
//----------------我重新定义的次功能号-------------------
enum {
wd_irp_mn_mount = IRP_MN_MOUNT_VOLUME,
wd_irp_mn_load_filesys = IRP_MN_LOAD_FILE_SYSTEM,
wd_irp_mn_user_req = IRP_MN_USER_FS_REQUEST
};
enum {
wdf_fsctl_dismount = FSCTL_DISMOUNT_VOLUME
};
要得到功能号,要先得到当前的IO_STACK_LOCATION,这个上边已经有函数wd_cur_io_stack,相信这个不能难倒你。
当有Volumne被Mount或者dismount,你写的my_disp_file_sys_ctl()就被调用。具体的判断方法,就见如下的代码了:
// 可以看到分发函数中其他的函数处理都很简单,但是file_sys_ctl的
// 处理会比较复杂。我们已经在notify函数中绑定了文件系统驱动的控
// 制对象。当文件系统得到实际的介质的时候,会生成新的设备对象,
// 这种设备称为卷(Volume),而这种设备是在file_sys中的mount中生
// 成的,而且也是unmount中注销掉的。我们捕获这样的操作之后,就必
// 须生成我们的设备对象,绑定在这样的“卷”上,才能绑定对这个卷
// 上的文件的操作。
wd_stat my_disp_file_sys_ctl(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
wd_io_stack *stack = wd_cur_io_stack(irp);
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
switch(wd_irpsp_minor(stack))
{
case wd_irp_mn_mount:
// 在这里,一个Volume正在Mount
return my_fsctl_mount(dev,irp);
case wd_irp_mn_load_filesys:
return my_fsctl_load_fs(dev,irp);
case wd_irp_mn_user_req:
{
switch(wd_irpsp_fs_ctl_code(stack))
{
case wdf_fsctl_dismount:
// 在这里,一个Volume正dismount
return my_fsctl_dismount(dev,irp);
}
}
}
wd_skip_io_stack(irp);
attached_dev = my_dev_attached(dev);
return wd_irp_call(attached_dev,irp);
}
你发现你又得开始写两个新的函数,my_fsctl_mount()和my_fsctl_dismount(),来处理卷的Mount和Dismount.显然,你应该在其中生成设备或者删除,绑定或者解除绑定。很快,你就能完全监控所有的卷了。
这样做是动态监控所有的卷的完美的解决方案。
如果是在xp以上,有一个调用可以获得一个文件系统上已经被Mount的卷。但是2000下不能使用。所以我们没有使用那个方法。何况仅仅得到已经Mount的卷也不是我想要的。
这里另外还有一个my_fsctl_load_fs函数。发生于IRP_MN_LOAD_FILESYS。这个功能码我只做一点点解释:当一个文件识别器(见上文)决定加载真正的文件系统的时候,会产生一个这样的irp。
你现在可以修改你的驱动,使插入拔出u盘的时候,在Volume加载卸载时候输出调试信息。回首一下我们的脉络:
a.生成一个控制设备。当然此前你必须给控制设置指定名称。
b.设置Dispatch Functions.
c.设置Fast Io Functions.
d.编写一个my_fs_notify回调函数,在其中绑定刚激活的FS CDO.
e.使用wdff_reg_notify调用注册这个回调函数。
f.编写默认的dispatch functions.
e.处理IRP_MJ_FILE_SYSTEM_CONTROL,在其中监控Volumne的Mount和Dismount.
f.下一步自然是绑定Volumne了,请听下回分解。
7.IRP完成函数,中断级,如何超越中断级别的限制
先讨论一下Volumne设备是如何得到的.首先看以下几个函数:
// ------------------wdf.h 中的内容 -------------------------
typedef VPB wd_vpb;
_inline wd_vpb * wd_dev_vbp(wd_dev *dev)
{
return dev->Vpb;
}
_inline wd_dev * wd_vbp_dev(wd_vpb *vpb)
{
return vpb->DeviceObject;
}