CSDN首页 空间 新闻 论坛 Blog 下载 读书 网摘 搜索 .NET Java 视频 接项目 求职 在线学习 买书 程序员 通知
可用分押宝游戏火热进行中... 专题改版:Java Web 专题
CSDN社区
搜索 收藏 打印 关闭
CSDN社区 >  VC/MFC >  硬件/系统

[转]IFS教程,作者楚狂人

楼主codewarrior(会思考的草)2006-07-01 22:33:30 在 VC/MFC / 硬件/系统 提问

Windows文件系统过滤驱动开发教程    
   
  0.   作者,楚狂人自述    
   
  我长期网上为各位项目经理充当“技术实现者”的角色。我感觉Windows文件系统驱动的开发能找到的资料比较少。为了让技术经验不至于遗忘和引起大家交流的兴趣我以我的工作经验撰写本教程。    
   
  我的理解未必正确,有错误的地方望多多指教。有问题欢迎与我联系。我们也乐于接受各种驱动项目的开发。邮箱为MFC_Tan_Wen@163.com,QQ为16191935。    
   
  对于这本教程,您可以免费获得并随意修改,向任何网站转贴。但是不得剽窃任何内容作为任何赢利出版物的全部或者部分。    
   
  1.   概述,钻研目的和准备    
   
   
  我经常在网上碰到同行请求开发文件系统驱动。windows的pc机上以过滤驱动居多。其目的不外乎有以下几种:    
   
  一是用于防病毒引擎。希望在系统读写文件的时候,捕获读写的数据内容,然后检测其中是否含有病毒代码。    
   
  二是用于加密文件系统,希望在文件写过程中对数据进行加密,在读的过程中进行解密。    
   
  三是设计透明的文件系统加速。读写磁盘的时候,合适的cache算法是可以大大提高磁盘的工作效率。windows本身的cache算法未必适合一些特殊的读写    
   
  磁盘操作(如流媒体服务器上读流媒体文件)。设计自己的cache算法的效果,我已在工作中有所感受。    
   
  如果你刚好有以上此类的要求,你可以阅读本教程。    
   
  文件系统驱动是windows系统中最复杂的驱动种类之一。不能对ifsddk中的帮助抱太多希望,以我的经验看来,文件系统相关的ddk帮助极其简略,很多重要的部分仅仅轻描淡写的带过。如果安装了ifsddk,应该阅读src\filesys\OSR_docs下的文档。而不仅仅是ddk帮助。    
   
  文件系统驱动开发方面的书籍很少。中文资料我仅仅见过侯捷翻译过的一本驱动开发的书上有两三章涉及,也仅仅是只能用于9x的vxd驱动。NT文件系统我见过一本英文书。我都不记得这两本书的书名了。    
   
  如果您打算开发9x或者nt文件系统驱动,建议你去网上下载上文提及的书。那两本书都有免费的电子版本下载。如果你打算开发Windows2000\WindowsXP\Window2003的文件系统驱动,你可以阅读本教程。虽然本教程仅仅讲述文件系统过滤驱动。但是如果您要开发一个全新的文件系统驱动的话,本教程依然对你有很大的帮助。    
   
  学习文件系统驱动开发之前,应该在机器上安装ifsddk。ddk版本越高级,其中头文件中提供的系统调用也越多。经常有人询问如xpddk编译的驱动能不能在2000上运行等等的问题。我想可以这样解释:高级版本的ddk应该总是可以编译低级驱动的代码,而且得到的二进制版本也总是可以在低级系统上运行。但是    
  反过来就未必可以了。如果在高级系统上编写用于低级系统上的驱动,要非常认真的注意仅仅调用低级系统上有的系统调用。    
   
  ifsddk可以在某些ftp上免费下载。    
   
  我的使用的是ifs   ddk   for   xp,但是我实际用来开发的两台机器有一台是windows   2000,另一台是windows   2003.我尽量使我编译出来的驱动,可以在2000\xp\2003三种系统上都通过测试。    
   
  安装配置ddk和在vc中开发驱动的方法网上有很多的介绍。ifsddk安装之后,src目录下的filesys目录下有文件系统驱动的示例。阅读这些代码你就可以快速的学会文件系统驱动开发。    
   
  filter目录下的sfilter是一个文件系统过滤驱动的例子。另一个filespy完全是用这个例子的代码加工得更复杂而已。    
   
  如何用ddk编译这个例子请自己查看相关的资料。    
   
  文件系统过滤驱动编译出来后你得到的是一个扩展名为sys的文件。同时你需要写一个.inf文件来实现这个驱动的安装。我这里不讨论.inf文件的细节,你可以直接用sfilter目录下的inf文件修改。    
   
  对inf文件点鼠标右键弹出菜单选择“安装”,即可安装这个过滤驱动。但是必须重新启动系统才生效。    
   
  如果重启后蓝屏无法启动,可以用其他方式引导系统后到system32\drivers目录下删除你的.sys文件再重启即可。我尝试这种情况下用安全模式结果还    
   
  是蓝屏。所以我后来不得不在机器上装了两个2000系统。双系统情况下,一个系统崩溃了用另一个系统启动,删除原来的驱动即可。    
   
  如果要调试代码,请安装softice.    
   
  打开sfilter目录下的文件sources(这个文件没有扩展名),加入一行    
  BROWSER_INFO=1    
   
  然后打开Symbol   Loader,File->Open选中你编译出来的xxx.sys,Modul->Load,Modul->Translate,然后就可以调试了。    
   
  打开softice,输入file   *就可以看见代码。    
   
  如果准备好了,我们就可以开始琢磨windows文件系统过滤驱动的开发了。  
  问题点数:0、回复次数:48Top

1 楼codewarrior(会思考的草)回复于 2006-07-01 22:33:44 得分 0

Windows文件系统过滤驱动开发教程  
   
  2.hello   world,驱动对象与设备对象  
   
  这里所说的驱动对象是一种数据结构,在DDK中名为DRIVER_OBJECT。任何驱动程序都对应一个DRIVER_OBJECT.如何获得本人所写的驱动对应的DRIVER_OBJECT呢?驱动程序的入口函数为DriverEntry,因此,当你写一个驱动的开始,你会写下如下的代码:  
   
  NTSTATUS   DriverEntry(IN   PDRIVER_OBJECT   DriverObject,   IN   PUNICODE_STRING   RegistryPath   )  
  {  
  }  
   
  这个函数就相当与喜欢c语言的你所常用的main().IN是无意义的宏,仅仅表明后边的参数是一种输入,而对应的OUT则代表这个参数是一种返回。这里没有使用引用,因此如果想在参数中返回结果,一律传入指针。  
   
  DriverObject就是你所写的驱动对应的DRIVER_OBJECT,是系统在加载你的驱动时候所分配的。RegisteryPath是专用于你记录你的驱动相关参数的注册表路径。  
   
  DriverObject重要之处,在于它拥有一组函数指针,称为dispatch   functions.  
   
  开发驱动的主要任务就是亲手撰写这些dispatch   functions.当系统用到你的驱动,会向你的DO发送IRP(这是windows所有驱动的共同工作方式)。你的任务是在dispatch   function中处理这些请求。你可以让irp失败,也可以成功返回,也可以修改这些irp,甚至可以自己发出irp。  
   
  设备对象则是指DEVICE_OBJECT.下边简称DO.  
   
  但是实际上每个irp都是针对DO发出的。只有针对由该驱动所生成的DO的IRP,  
  才会发给该驱动来处理。  
   
  当一个应用程序打开文件并读写文件的时候,windows系统将这些请求变成irp发送给文件系统驱动。  
   
  文件系统过滤驱动将可以过滤这些irp.这样,你就拥有了捕获和改变文件系统操作的能力。  
   
  象Fat32,NTFS这样的文件系统(File   System,简称FS),可能生成好几种设备。首先文件系统驱动本身往往生成一个控制设备(CDO).这个设备的主要任务是修改整个驱动的内部配置。因此一个Driver只对应一个CDO.  
   
  另一种设备是被这个文件系统Mount的Volume。一个FS可能有多个Volume,也可能一个都没有。解释一下,如果你有C:,D:,E:,F:四个分区。C:,D:为NTFS,E:,F:为Fat32.那么C:,D:则是Fat的两个Volume设备对象.  
   
  实际上"C:"是该设备的符号连接(Symbolic   Link)名。而不是真正的设备名。可以打开Symbolic   Links   Viewer,能看到:  
   
  C:   DeviceHarddiskVolume1  
   
  因此该设备的设备名为“DeviceHarddiskVolume1”.  
   
  这里也看出来,文件系统驱动是针对每个Volume来生成一个DeviceObject,而不是针对每个文件的。实际上对文件的读写的irp,都发到Volume设备对象上去了。并不会生成一个“文件设备对象”。  
   
  掌握了这些概念的话,我们现在用简单的代码来生成我们的CDO,作为我们开发文件系统驱动的第一步牛刀小试。  
   
  我不喜欢用微软风格的代码。太长而且难看。我对大部分数据结构和函数进行了重定义。为此我写了一个名为wdf.h的头文件帮助我转换。有兴趣的读者可以发邮件向索取这个文件。没有也没有关系,我总是会写出wd_xxx系列的东西在DDK中的原形。  
   
   
  //   -----------------wdf_filter.c中的内容-------------------------  
   
  #include   "wdf.h"  
   
  wd_stat   wdff_cdo_create(in   wd_drv   *driver,   in   wd_size   exten_len,  
  in   wd_ustr   *name,   out   wd_dev   **device)  
   
  {  
  return   wd_dev_create(driver,   exten_len,   name,   wd_dev_disk_fs,  
  wdf_dev_secure_open,   wd_false,   device);  
  }  
   
  wd_stat   wd_main(in   wd_drv*   driver,   in   wd_ustr*   reg_path)  
  {  
  wd_ustr   name;  
  wd_stat   status   =   wd_stat_suc;  
   
  //   然后我生成控制设备,虽然现在我的控制设备什么都不干  
  wd_ustr_init(&name,L"\FileSystem\Filters\our_fs_filter");  
  status   =   wdff_cdo_create(driver,0,&name,&g_cdo);  
   
  if(!wd_suc(status))  
  {  
  if(status   ==   wd_stat_path_not_found)  
  {  
  //   这种情况发生于FileSystemFilters路径不存在。这个路径是  
  //   在xp上才加上的。所以2000下会运行到这里  
  wd_ustr_init(&name,L"\FileSystem\our_fs_filter");  
  status   =   wdff_cdo_create(driver,0,&name,&g_cdo);  
  };  
  if(!wd_suc(status))  
  {  
  wd_printf0("error:   create   cdo   failed.rn");  
  return   status;  
  }  
  }    
   
  wd_printf0("success:   create   cdo   ok.rn");  
  return   status;  
  }    
   
  为了让代码看起来象上边的那样,我不得不做了很多转换。如  
   
  #define   DriverEntry   wd_main  
   
  一种爽的感觉,终于可以在写看起来更象是main()的函数中工作了。   wd_dev_create   这个函数内部调用的是IoCreateDevice.而wd_suc实际上是SUCCESS()这样的宏。  
   
  //   ----------------------wdf.h中的内容------------------------------  
  #include   "ntifs.h"  
   
  #define   in   IN  
  #define   out   OUT  
  #define   optional   OPTIONAL  
  #define   wd_ustr   UNICODE_STRING  
  #define   wdp_ustr   PUNICODE_STRING  
  #define   wd_main   DriverEntry  
   
  //   设备、驱动对象类型  
  typedef   DRIVER_OBJECT   wd_drv;  
  typedef   DEVICE_OBJECT   wd_dev;  
  typedef   PDRIVER_OBJECT   wd_pdrv;  
  typedef   PDEVICE_OBJECT   wd_pdev;  
   
  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  
  };  
   
  //   状态相关的类型和宏  
  typedef   NTSTATUS   wd_stat;  
   
  enum   {  
  wd_stat_suc   =   STATUS_SUCCESS,  
  wd_stat_path_not_found   =   STATUS_OBJECT_PATH_NOT_FOUND,  
  wd_stat_insufficient_res   =   STATUS_INSUFFICIENT_RESOURCES,  
  wd_stat_invalid_dev_req   =   STATUS_INVALID_DEVICE_REQUEST,  
  wd_stat_no_such_dev   =   STATUS_NO_SUCH_DEVICE,  
  wd_stat_image_already_loaded   =   STATUS_IMAGE_ALREADY_LOADED,  
  wd_stat_more_processing   =   STATUS_MORE_PROCESSING_REQUIRED,  
  wd_stat_pending   =   STATUS_PENDING  
  };  
  (inline的使用是有所限制的,inline   函数一般必须在头文件内,inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while   switch,并且不能内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。)  
  _inline   wd_bool   wd_suc(wd_stat   state)         //内聯函數  
  {  
  return   NT_SUCCESS(state);  
  }  
   
  #define   wd_printf0   DbgPrint  
   
  _inline   wd_void   wd_ustr_init(in   out   wd_ustr*   str,  
  in   const   wd_wchar*   chars)  
  {  
  RtlInitUnicodeString(str,chars);  
  };  
   
  _inline   wd_void   wd_ustr_init_em(in   out   wd_ustr   *str   ,in   wd_wchar   *chars,  
                                                                                      in   wd_size   size)  
  {  
  RtlInitEmptyUnicodeString(str,chars,size);  
  };  
   
   
  wdf.h这个文件我仅仅节选了需要的部分。以上您已经拥有了一个简单的“驱动”的完整的代码。它甚至可以编译,安装(请修改sfilter.inf文件,其方法不过是将多处sfilter改为"our_fs_filter",希望这个过程中您不会出现问题)。然后把wdf.h和wdf_filter.c放在您新建立的目录下,这个目录下还应该有另两个文件。一个是Makefile,请从sfilter目录下拷贝。另一个是SOURCES,请输入如下内容:  
  TARGETNAME=our_fs_filter  
  TARGETPATH=obj  
  TARGETTYPE=DRIVER  
  DRIVERTYPE=FS  
  BROWSER_INFO=1  
  SOURCES=wdf_filter.c  
   
  使用DDK编译之后您将得到our_fs_filter.sys.把这个文件与前所描述的   inf   文件同一目录,按上节所叙述方法安装。  
   
  这个驱动不起任何作用,但是你已经成功的完成了"hello   world".  
   
  Top

2 楼codewarrior(会思考的草)回复于 2006-07-01 22:34:09 得分 0

Windows文件系统过滤驱动开发教程    
   
  3.分发例程,fast   io    
   
  上一节仅仅生成了控制设备对象。但是不要忘记,驱动开发的主要工作是撰写分发例程(dispatch   functions.).接上一接,我们已经知道自己的DriverObject保存在上文代码的driver中。现在我写如下一个函数来指定一个默认的dispatch   function给它。    
   
  //-----------------wdf.h中的代码----------------------    
  typedef   PDRIVER_DISPATCH   wd_disp_fuc;    
  _inline   wd_void   wd_drv_set_dispatch(in   wd_drv*   driver,    
  in   wd_disp_fuc   disp)    
  {    
  wd_size   i;    
  for   (i   =   0;   i   <=   IRP_MJ_MAXIMUM_FUNCTION;   i++)    
  driver->MajorFunction[i]   =   disp;    
  }    
   
  在前边的wd_main中,我只要加    
   
  wd_drv_set_dispatch(driver,my_dispatch_func);    
   
  就为这个驱动指定了一个默认的Dispatch   Function.所有的irp请求,都会被发送到这个函数。但是,我可能不希望这个函数处理过于复杂,而希望把一些常见的请求独立出来,如Read,Write,Create,Close,那我又写了几个函数专门用来设置这几个Dispatch   Functions.    
   
  //-----------------wdf.h中的代码----------------------    
  _inline   wd_void   wd_drv_set_read(    
  in   wd_drv*   driver,    
  in   wd_disp_fuc   read)    
  {    
          driver->MajorFunction[IRP_MJ_READ]   =   read;    
  }    
  _inline   wd_void   wd_drv_set_write(    
  in   wd_drv*   driver,    
  in   wd_disp_fuc   write)    
  {    
          driver->MajorFunction[IRP_MJ_WRITE]   =   write;    
  }    
   
  wd_void   wd_drv_set_create(in   wd_drv*   driver,    
  in   wd_disp_fuc   create)    
  {    
          driver->MajorFunction[IRP_MJ_CREATE]   =   create;    
          driver->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE]   =   create;    
          driver->MajorFunction[IRP_MJ_CREATE_MAILSLOT]   =   create;    
  }    
   
  wd_void   wd_drv_set_file_sys_control(in   wd_drv*   driver,    
  in   wd_disp_fuc   control)    
  {    
          driver->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL]   =   control;    
  }    
   
  wd_void   wd_drv_set_clean_up(in   wd_drv*   driver,    
  in   wd_disp_fuc   clean_up)    
  {    
          driver->MajorFunction[IRP_MJ_CLEANUP]   =   clean_up;    
  }    
   
  wd_void   wd_drv_set_close(in   wd_drv*   driver,    
  in   wd_disp_fuc   close)    
  {    
          driver->MajorFunction[IRP_MJ_CLOSE]   =   close;    
  }    
   
  别看我罗列n多代码,其实就是在设置driver->MajorFunction这个数组而已。因此在wd_main对dispatch   functions的设置,就变成了下边这样的:    
   
  //   开始设置几个分发例程    
  wd_drv_set_dispatch(driver,my_disp_default);    
  wd_drv_set_create(driver,my_disp_create);    
  wd_drv_set_clean_up(driver,my_disp_clean_up);    
  wd_drv_set_file_sys_control(driver,my_disp_file_sys_ctl);    
  wd_drv_set_close(driver,my_disp_close);    
  wd_drv_set_read(driver,my_disp_read);    
  wd_drv_set_write(driver,my_disp_write);    
   
  下面的任务都在写my_xxx系列的这些函数了。但是对于这个DriverObject的设置,还并不是仅仅这么简单。    
   
  由于你的驱动将要绑定到文件系统驱动的上边,文件系统除了处理正常的IRP之外,还要处理所谓的FastIo.FastIo是Cache   Manager调用所引发的一种没有irp的请求。换句话说,除了正常的Dispatch   Functions之外,你还得为DriverObject撰写另一组Fast   Io   Functions.这组函数的指针在driver->FastIoDispatch.我不知道这个指针留空会不会导致系统崩溃。在这里本来是没有空间的,所以为了保存这一组指针,你必须自己分配空间。    
   
  下面是我常用的内存分配函数。    
   
  //-----------------wdf.h中的代码----------------------    
  //   最简单的分配内存的函数,可以指定分页非分页    
  _inline   wd_pvoid   wd_malloc(wd_bool   paged,wd_size   size)    
  {    
          if(paged)    
                  return   ExAllocatePool(PagedPool,size);    
          else    
                  return   ExAllocatePool(NonPagedPool,size);    
  }    
   
  //   释放内存    
  _inline   wd_void   wd_free(wd_pvoid   point)    
  {    
          ExFreePool(point);    
  }    
   
  _inline   wd_void   wd_memzero(    
  wd_pvoid   point,    
  wd_size   size)    
  {    
          RtlZeroMemory(point,size);    
  }    
   
  有了上边的基础,我就可以自己写一个初始化FastIoDispatch指针的函数。    
   
  //-----------------wdf.h中的代码----------------------    
  wd_bool   wd_fio_disp_init(wd_drv   *driver,wd_ulong   size)    
  {    
  wd_fio_disp   *disp   =   wd_malloc(wd_false,size);    
  if(disp   ==   wd_null)    
  return   wd_false;    
  wd_memzero((wd_pvoid)disp,size);    
  driver->FastIoDispatch   =   disp;    
  driver->FastIoDispatch->SizeOfFastIoDispatch   =   size;    
  return   wd_true;    
  }    
   
  这个函数为FastIoDispacth指针分配足够的空间并填写它的大小。下面是再写一系列的函数来设置这个函数指针数组。实际上,FastIo接口函数实在太多了,所以我仅仅写出这些设置函数的几个作为例子:    
  //-----------------wdf.h中的代码----------------------    
  _inline   wd_void   wd_fio_disp_set_query_standard(    
  wd_drv   *driver,    
  wd_fio_query_standard_func   func)    
  {    
          driver->FastIoDispatch->FastIoQueryStandardInfo   =   func;    
  }    
   
  _inline   wd_void   wd_fio_disp_set_io_lock(    
  wd_drv   *driver,    
  wd_fio_io_lock_func   func)    
  {    
          driver->FastIoDispatch->FastIoLock   =   func;    
  }    
   
  _inline   wd_void   wd_fio_disp_set_io_unlock_s(    
  wd_drv   *driver,    
  wd_fio_unlock_single_func   func)    
  {    
          driver->FastIoDispatch->FastIoUnlockSingle   =   func;    
  }    
   
  ...    
   
  好,如果你坚持读到了这里,应该表示祝贺了。我们回顾一下,wd_main中,应该做哪些工作。    
   
  a.生成一个控制设备。当然此前你必须给控制设置指定名称。    
   
  b.设置Dispatch   Functions.    
   
  c.设置Fast   Io   Functions.    
   
  //   ----------------wd_main   的近况----------------------------    
   
  ...    
   
  wd_dev   *g_cdo   =   NULL;    
   
  wd_stat   wd_main(in   wd_drv*   driver,    
  in   wd_ustr*   reg_path)    
  {    
  wd_ustr   name;    
  wd_stat   status   =   wd_stat_suc;    
   
  //   然后我生成控制设备,虽然现在我的控制设备什么都不干    
  wd_ustr_init(&name,L\"\\FileSystem\\Filters\\our_fs_filters\");    
  status   =   wdff_cdo_create(driver,0,&name,&g_cdo);    
   
  if(!wd_suc(status))    
  {    
      if(status   ==   wd_stat_path_not_found)    
      {    
          //   这种情况发生于FileSystemFilters路径不存在。这个路径是    
          //   在xp上才加上的。所以2000下可能会运行到这里    
          wd_ustr_init(&name,L\"\\FileSystem\\our_fs_filters\");    
          status   =   wdff_cdo_create(driver,0,&name,&g_cdo);    
      };    
      if(!wd_suc(status))    
      {    
          wd_printf0(\"error:   create   cdo   failed.rn\");    
              return   status;    
      }    
  }    
   
  wd_printf0(\"success:   create   cdo   ok.rn\");    
   
  //   开始设置几个分发例程    
  wd_drv_set_dispatch(driver,my_disp_default);    
  wd_drv_set_create(driver,my_disp_create);    
  wd_drv_set_clean_up(driver,my_disp_clean_up);    
  wd_drv_set_file_sys_control(driver,my_disp_file_sys_ctl);    
  wd_drv_set_close(driver,my_disp_close);    
  wd_drv_set_read(driver,my_disp_read);    
  wd_drv_set_write(driver,my_disp_write);    
   
  //   指定fast   io处理函数    
  if(!wd_fio_disp_init(driver,sizeof(wd_fio_disp)))    
  {    
          wd_dev_del(g_cdo);    
          wd_printf0(\"error:   fast   io   disp   init   failed.rn\");    
          return   wd_stat_insufficient_res;    
  }    
   
  //   下面指定的这些函数都定义在wdf_filter_fio.h中,其实这些函数都统    
  //   一的返回了false    
  wd_fio_disp_set_check(    
  driver,    
  my_fio_check);    
  wd_fio_disp_set_read(    
  driver,    
  my_fio_read);    
  wd_fio_disp_set_write(    
  driver,    
  my_fio_write);    
  wd_fio_disp_set_query_basic(    
  driver,    
  my_fio_query_basic_info);    
   
  ...    
   
  }    
  FastIo函数个数数量不明,我只觉得很多。因此不打算全部罗列,以\"...\"敷衍之。某些读者可能会认为这些代码无法调试安装。其实您可以参考sfilter中的示例自己完成这些代码。    
   
  现在我们的my_xxx系列的函数还没有开始写,因此驱动也不能编译通过。在后边的内容中再逐步介绍。  
   
  [编辑   -   4/19/05   by   znsoft]Top

3 楼codewarrior(会思考的草)回复于 2006-07-01 22:34:20 得分 0

Windows文件系统过滤驱动开发教程    
   
  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!!!   \r\n");    
   
  //   得到文件系统对象的名字,然后打印出来    
  wd_obj_get_name(dev,&name);    
  wd_printf0("notify   :   file   sys   name   =   %wZ\r\n",&name);    
   
  if(active)    
  {    
  wd_printf0("notify:   try   to   attach.\r\n");    
  //   ...   请在这里绑定文件系统的控制设备    
  }    
  else    
  {    
  wd_printf0("notify:   unactive.\r\n");    
  //   ...    
  }    
  }    
   
  应该如何绑定一个文件系统CDO?我们在下面的章节再详细描述。    
   
  现在我们应该再在wd_main函数中加上下边的内容:    
   
  if(wdff_reg_notify(driver,my_fs_notify)   !=   wd_stat_suc)    
  {    
  wd_printf0("error:   reg   notify   failed.\r\n");    
  wd_fio_disp_release(driver);    
  wd_dev_del(g_cdo);    
  g_cdo   =   wd_null;    
  return   wd_stat_insufficient_res;    
  };   .0  
   
  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调用注册这个回调函数。  
  Top

4 楼codewarrior(会思考的草)回复于 2006-07-01 22:34:31 得分 0

Windows文件系统过滤驱动开发教程    
   
  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");    
   
  //   我不绑定识别器。所以如果是识别器,我直接返回成功。查看是否是识别    
  //   器的办法是看是否是\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.\r\n");    
  return   wd_stat_suc;    
  }    
   
  wd_printf0("attach   fs   dev:   not   a   recogonizer.\r\n");    
   
  接下来我将要生成我的设备。这里要提到设备扩展的概念。设备对象是一个数据结构,为了表示不同的设备,里边将有一片自定义的空间,用来给你记录这个设备的特有信息。我们为我们所生成的设备确定设备扩展如下:    
   
  //   文件过滤系统驱动的设备扩展    
  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.\r\n"));    
  return   wd_stat_suc;    
  }    
   
  wd_printf0("attach   fs   dev:   is   my   cared   type.\r\n");    
   
  //   我不绑定识别器。所以如果是识别器,我直接返回成功。查看是否是识别    
  //   器的办法是看是否是\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.\r\n");    
  return   wd_stat_suc;    
  }    
   
  wd_printf0("attach   fs   dev:   not   a   recogonizer.\r\n");    
   
  //   现在来生成一个设备用来绑定    
  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.\r\n");    
  return   status;    
  }    
   
  wd_printf0("attach   fs   dev:   create   dev   success.\r\n");    
   
  //   接着设置设备的各种标志与之要绑定的标志一致    
  wd_dev_copy_flag(new_dev,fs_dev);    
  //得到设备扩展  
  ext   =   (my_dev_ext   *)wd_dev_ext(new_dev);    
   
  wd_printf0("begin   to   attach.\r\n");    
   
  //绑定设备  
  status   =   wd_dev_attach(new_dev,fs_dev,&ext->attached_to);    
  wd_printf0("attach   over.status   =   %8x\r\n",status);    
   
  if(!wd_suc(status))    
  {    
  //绑定失败处理  
  wd_printf0("attach   fs   dev:   dev   attach   failed.\r\n");    
  UNREFERENCED_PARAMETER(new_dev);    
  wd_dev_del(new_dev);    
  return   status;    
  }    
   
  wd_printf0("attach   fs   dev:   attach   %wZ   succeed.\r\n",&name);    
   
  //初始化设备标志  
  wd_ustr_init_em(&ext->dev_name,ext->name_buf,wd_dev_name_max_len);    
  //设置标志  
  wd_ustr_copy(&ext->dev_name,&name);    
  //清除initialize标志  
  wd_dev_clr_init_flag(new_dev);    
   
  return   status;    
  }  
   
   
  Top

5 楼codewarrior(会思考的草)回复于 2006-07-01 22:34:43 得分 0

Windows文件系统过滤驱动开发教程    
   
  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))    
  //   这个函数可以立刻失败掉一个irp  
  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.    
   
  g.处理IRP_MJ_FILE_SYSTEM_CONTROL,在其中监控Volumne的Mount和Dismount.    
   
  h.下一步自然是绑定Volumne了,请听下回分解。  
  Top

6 楼codewarrior(会思考的草)回复于 2006-07-01 22:34:54 得分 0

Windows文件系统过滤驱动开发教程(7)  
   
   
   
  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;    
  }    
   
   
  VPB是Volume   parameter   block.一个数据结构.它的主要作用是把实际存储媒介设备对象和文件系统上的卷设备对象联系起来.    
   
  wd_dev_vbp可以让你从一个Storage   Device   Object得到一个VPB,而wd_vbp_dev这个函数可以得到这个VPB所对应的Volmue设备.    
   
  现在首先要得到Storage   Device   Object.实际上这个东西保存在当前IO_STACK_LOCATION中.    
   
  //   ------------------wdf.h   中的内容   -----------------------    
  _inline   wd_dev   *wd_irpsp_mount_storage(wd_io_stack   *irpsp)    
  {    
  return   irpsp->Parameters.MountVolume.Vpb->RealDevice;    
  };    
   
  那么,从irp出发,我最终可以通过以下的方式得到Volumue设备:    
   
  wd_irpsp   *irpsp   =   wd_cur_io_stack(irp);    
  wd_dev   *storage_dev   =   wd_irpsp_mount_storage(irp);    
  wd_vpb   *vpb   =   wd_dev_vbp(storage_dev);    
  wd_dev   *volume_dev   =   wd_vbp_dev(vpb);    
   
  不过实际情况并不这么简单.这里的IRP是一个MOUNT请求.而volume设备对象实际上是这个请求完成之后的返回结果.因此,在这个请求还没有完成之前,我们就试图去获得Volume设备对象,当然是竹篮打水一场空了.    
   
  这里,你可以直接拷贝当前IO_STACK_LOCATION,然后向下发送请求,但在此之前,要先给irp分配一个完成函数.irp一旦完成,你的完成函数将被调用.这样的话,你可以在完成函数中得到Volume设备,并实施你的绑定过程.    
   
  这里要讨论一下中断级别的问题.常常碰到人问某函数只能在Passive   Level调用是什么意思.总之我们的任何代码执行的时候,总是处在某个当前的中断级之中.某些系统调用只能在低级别中断级中执行.请注意,如果一个调用可以在高处运行,那么它能在低处运行,反过来则不行.    
   
  我们需要知道的只是我们关心Passive   Level和Dispatch   Level.而且Dispatch   Level的中断级较高.一般ddk上都会标明,如果注明irq   level>=dispatch,那么你就不能在passive   level的代码中调用它们了.    
   
  那么你如何判断当前的代码在哪个中断级别中呢?我一般是这么判断的:如果你的代码执行是由于应用程序(或者说上层)的调用而引发的,那么应该在Passive   Level.如果你的代码执行是由于下层硬件而引发的,那么则可能在dispatch   level.    
   
  希望不要机械的理解我的话!以上只是极为粗略的便于记忆的理解方法.实际的应用应该是这样的:所有的dispatch   functions由于是上层发来的irp而导致的调用,所以应该都是Passive   Level,在其中你可以调用绝大多数系统调用.而如网卡的OnReceive,硬盘读写完毕,返回而导致的完成函数,都有可能在Dispatch级.注意都是有可能,而不是绝对是.但是一旦有可能,我们就应该按就是考虑.    
   
  好,现在我们发现,我们已经注册了完成函数,并且这个函数执行中可能是dispatch   level.    
   
  现在面临的问题是,我们已经决定在完成函数中调用   IoAttachDeviceToDeviceStack来绑定Volume.而DDK说明有:Callers   of   IoAttachDeviceToDeviceStack   must   be   running   at   IRQL   <=   DISPATCH_LEVEL.    
   
  实际上前边说过有IoAttachDeviceToDeviceStackSafe,这个调用可以在Dispatch   level进行.无奈这个调用仅仅出现在Xp以上的系统中.    
   
  超越中断级别的限制有几种方法.第一种是自己生成一个系统线程来完成此事.系统线程将保证在Passive   Level中运行.另一种方法就是把自己的任务插入Windows工作者线程,这会使你的任务迟早得到执行.如果你的任务比较小,可以实行第二种方法.对系统来说比较省事,对程序员来说则反正都是麻烦.    
   
  我做了以下几个函数专门来插入任务到工作者线程.    
  //---------------wdf.h   中的内容   ------------------------    
  typedef   WORK_QUEUE_ITEM   wd_work_item;    
  typedef   PWORKER_THREAD_ROUTINE   wd_work_func;    
  //   任务的初始化    
   
   
  wd_work_func   worker,    
  wd_void   *context)    
  {    
  ExInitializeWorkItem(item,worker,context);    
  }    
   
  //   三种任务队列    
  typedef   enum   _wd_work_quque_type{    
  wd_work_crit   =   CriticalWorkQueue,    
  wd_work_delay   =   DelayedWorkQueue,    
  wd_work_hyper   =   HyperCriticalWorkQueue    
  }   wd_work_queue_type;    
   
  _inline   wd_void   wd_work_queue(in   wd_work_item   *item,    
  in   wd_work_queue_type   type)    
  {    
  ExQueueWorkItem(item,(WORK_QUEUE_TYPE)type);    
  }    
   
  _inline   wd_void   wd_work_run(in   wd_work_item   *item)    
  {    
  (item->WorkerRoutine)(item->Parameter);    
  }    
   
  任务是一个数据结构,已经被我重定义为wd_work_item,wd_work_init能初始化它.初始化的时候你只需要填写一个你的任务的函数.同时一个context用来记录上下相关参数.(这是个空指针,你可以只想你任何想要的参数类型).    
   
  一般这个任务会自动执行,但是有时我们也想不插入队列,我们自己执行它.那么调用wd_work_run即可.    
   
  然后调用wd_work_queque插入工作者队列,之后会被执行.插入类型这里选择wd_work_delay.    
   
  希望你没有被这一串东西搞糊涂.现在我会写一个"设置完成函数"的函数.执行后,自动在Passive   Level级执行你的完成函数.希望不会把你搞得晕头转向的:).    
   
  //   完成例程上下文。好几个fsctl需要注册完成例程。而例程中的工作可能    
  //   只能在passive   level中运行,因此不得不加入一个work_item,把任务塞    
  //   入工作线程等待完成    
  typedef   struct   _my_fsctl_comp_con    
  {    
  wd_work_item   work;    
  wd_dev   *dev;    
  wd_irp   *irp;    
  wd_dev   *new_dev;   //   这个元素仅仅用于mount的时候。因为我    
  //   们要生成一个新设备来绑定vdo.    
  }   my_fsctl_comp_con;    
   
  wd_bool   my_fsctl_set_comp(wd_dev   *dev,    
  wd_irp   *irp,    
  wd_dev   *new_dev,    
  wd_irp_comp_func   complete,    
  wd_work_func   work_complete)    
  {    
  my_fsctl_comp_con   *context;    
  context   =   (wdff_fsctl_comp_con   *)wd_malloc(wd_false,    
  sizeof(wdff_fsctl_comp_con));    
  if(context   ==   NULL)    
  {    
  wd_printf0("fsctl   set   comp:   failed   to   malloc   context.\r\n");    
  return   wd_false;    
  }    
   
  //   初始化工作细节    
  wd_work_init(&context->work,    
  work_complete,    
  context);    
   
  context->dev   =   dev;    
  context->irp   =   irp;    
  context->new_dev   =   new_dev;    
   
  //   设置irp完成例程    
  wd_irp_comp(irp,complete,context);    
   
  return   wd_true;    
  }    
   
  //   以下函数作为以上complete的参数被使用    
  wd_stat   my_fsctl_comp(in   wd_dev   *dev,    
  in   wd_irp   *irp,    
  in   wd_void   *context)    
  {    
  wd_printf0("fsctl_comp:   come   in!!!\r\n");    
  UNREFERENCED_PARAMETER(dev);    
  UNREFERENCED_PARAMETER(irp);    
  //   判断当前中断级    
  if(wd_get_cur_irql()   >   wd_irql_passive)    
  {    
  wd_printf0("fsctl_comp:into   quque!!!\r\n");    
  //   如果在passive更低的中断级别,必须插入延迟队列中运行    
  wd_work_queue((wd_work_item   *)context,wd_work_delay);    
  }    
  else    
  {    
  //   否则可以直接执行    
  wd_printf0("fsctl_comp:run   directly!!!\r\n");    
  wd_work_run((wd_work_item   *)context);    
  }    
  return   wd_stat_more_processing;    
  }    
   
  我想以上的过程应该已经可以理解了!注册了基本的完成历程complete函数(也就是我最后写的函数my_fsctl_comp后),irp执行完毕回调my_fsctl_comp,而我事先已经把已经做好的任务(wd_work_item)写在上下文指针中(context)中.一回调这个函数,我就wd_work_queque插入队列.结果wd_work_item中记录的work_complete函数显然会在Passive   level中执行.我们的系统也将保持稳定.    
   
  work_complete函数将从context上下文指针中得到足够的参数,来完成对Volume的绑定.    
   
  希望你没有被弄昏头:),我们下回再分解.  
  Top

7 楼codewarrior(会思考的草)回复于 2006-07-01 22:35:08 得分 0

Windows文件系统过滤驱动开发教程    
   
  注:   有任何问题与建议请加QQ16191935,邮箱MFC_Tan_Wen@163.com    
   
  8   终于绑定了Volume,读操作的捕获与分析    
   
  上文已经讲到绑定Volume之前的关键操作.我们一路逢山开路,逢水架桥,相信你从中也学到了驱动开发的基本方法.以后的工作,无非灵活运用这些方法而已.前边已经举出过绑定FS   CDO的操作.那么现在绑定Volume,无非照猫画虎,而以后的教程中,我也不会逐一详尽的列举出细节的代码了.    
   
  但是绑定Volume的过程中,还是有些地方需要稍微注意:    
   
  1.获得Storage   Device   Object的问题.前边已经说过,为了得到Vbp,我要先得到Storage   Device   Object,方法如下:    
   
  wd_irpsp   *irpsp   =   wd_cur_io_stack(irp);    
  wd_dev   *storage_dev   =   wd_irpsp_mount_storage(irpsp);    
   
  这是在Irp完成之前,这样调用是对的.但是到完成函数中,情况就不同了.因为这个irpsp下保存的storage_dev可能被修改掉(这并非我自己调试的结果,而是阅读sfilter代码中的注释而得到的信息).既然有这个可能,我们只好先把storage_dev这个东西保存下来.实际上,可以在Device扩展增加一个项目storage_dev.在irp完成之前,生成我要绑定的设备(只是不绑定),并把这个设备指针传入完成函数上下文.    
   
  这样在完成函数中,我就能得到这个storage_dev,最终找到Volmue设备的指针,此时进行绑定就可以了.    
   
  2.绑定的过程,未必一次就能成功.因为Volmue设备的Initilize标记被清除之前,我的绑定是不能成功的.对这种情况,我抄袭了sfilter中的方法,就是延时500毫秒,然后再尝试.一共尝试8次.    
   
  我包装了一个函数用来延时:    
   
  _inline   wd_void   wd_delay_milli_se(wd_ulong   milli_sec)    
  {    
  wd_llong   delay   =   milli_sec*(-10)*1000;    
  wd_lgint   interval;    
  interval.QuadPart   =   delay;    
  KeDelayExecutionThread(KernelMode,FALSE,&interval);    
  }    
   
  这个函数的参数是毫秒,但是我并不清楚有多小的精度.    
   
  其他的就不说了,祝愿你的运气足够的好.现在我们处理IRP_MJ_READ,如果你已经绑定了Volume,那么显然,发送给Volume的请求就会先发送给你.处理IRP_MJ_READ,能捕获文件的读操作.    
   
  进入你的my_disp_read()函数(假设你注册了这个函数来处理IRP_MJ_READ,请见前面关于分发函数的讲述),首先判断这个Dev是不是绑定Volume的设备.如果是,那么就是一个读文件的操作.    
   
  如何判断?记得我们先绑定Volume的时候,在我们的设备扩展中设置了storage_dev,如果不是(比如是FS   CDO,我们没设置过),那么这么判断即可:    
   
  if(is_my_dev(dev))    
  {    
  my_dev_ext   *ext   =   (my_dev_ext   *)wd_dev_ext(dev);    
  if(ext->storage_dev)    
  {    
  //   ...   到这里说明是对文件的读操作    
  }    
  }    
   
  其他的情况不需要捕获,请直接传递到下层.    
   
  读请求的IRP情况非常复杂,请有足够的心理准备.并不要过于依赖帮助,最好的办法就是自己打印IRP的各个细节,亲自查看文件读操作的完成过程.而我现在所说的东西,换另一我未尝试过的版本的windows是否还正确,我也无法下断言.    
   
  不过基本的东西是不会变的.    
   
  首先关心被读写的文件.IRP下有一个FileObject指针.这个东西指向一个文件对象.你可以得到文件对象的名字,这个名字是没有盘符的文件全路径.有人要问那么盘符如何获得?因为你已经知道了Volume,前边已经说过盘符不过是Volume的符号连接名,那么想要真正的全路径问题应该也不大了.    
   
  我现在偷一个懒,我现在只打印没有盘符的路径名.先写一个函数,从IRP得到FileObject.    
   
  _inline   wd_file   *wd_irp_file(wd_irpsp   *irpsp)    
  {    
  return   irpsp->FileObject;    
  }    
   
  然后写一个函数来获得文件名.这个函数参考的是FileMon的代码.    
   
  wd_void   wd_file_get_name(in   wd_file   *file,    
  in   out   wd_ustr   *name)    
  {    
  if(   file->FileName.Buffer   &&    
  !(file->Flags   &   FO_DIRECT_DEVICE_OPEN)   )    
  RtlCopyUnicodeString(name,&file->FileName);    
  }    
   
  接下来有必要得到读文件的偏移量.和vxd的文件系统驱动不同,2000下文件系统得到的偏移量似乎都是从文件起始位置开始计算的.偏移量是一个LARGE_INTEGER.因为现在确实有些文件已经超过了长整型所能表示的大小.    
   
  以下函数用来得到偏移量.wd_lgint是经过重定义的LARGE_INTEGER.    
   
  _inline   wd_lgint   wd_irp_read_offset(wd_irpsp   *irpsp)    
  {    
  return   irpsp->Parameters.Read.ByteOffset;    
  }    
   
  注意以上的参数不是irp.是当前IO_STACK_LOCATION,也就是我的wd_irpsp.前面已经讲述过如何获取当前irpsp.    
   
  此外我还希望能得到我所读到的数据.这要注意,我们捕获这个请求的时候,这个请求还没有完成.既然没有完成,当然无数据可读.如果要获取,那就设置完成函数,在完成函数中完成请求.    
   
  完成Irp的时候忽略还是拷贝当前IO_STACK_LOCATION,返回什么STATUS,以及完成函数中如何结束Irp,是不那么容易搞清楚的一件事情.我想做个总结如下:    
   
  1.如果对irp完成之后的事情无兴趣,直接忽略当前IO_STACK_LOCATION,(对我的程序来说,调用wd_ship_cur_io_stack),然后向下传递请求,返回wd_irp_call()所返回的状态.    
   
  2.不但对irp完成之后的事情无兴趣,而且我不打算继续传递,打算立刻返回成功或失败.那么我不用忽略或者拷贝当前IO_STACK_LOCATION,填写参数后调用IoCompleteRequest,并返回我想返回的结果.    
   
  3.如果对irp完成之后的事情有兴趣,并打算在完成函数中处理,应该首先拷贝当前IO_STACK_LOCATION(wd_copy_cur_io_stack()),然后指定完成函数,并返回wd_irp_call()所返回的status.完成函数中,不需要调用IoCompleteRequest!直接返回Irp的当前状态即可.    
   
  4.同3的情况,有时候,会把任务塞入系统工作者线程或者希望在另外的线程中去完成Irp,那么完成函数中应该返回wd_stat_more_processing,此时完成Irp的时候应该调用IoCompleteRequest.另一种类似的情况是在dispatch函数中等待完成函数中设置事件,那么完成函数返回wd_stat_more_processing,dispatch函数在等待结束后调用IoCompleteRequest.    
   
  前边已经提到过设备的DO_BUFFERED_IO,DO_DIRECT_IO这两个标记.情况是3种:要么是两个标记中其中一个,要么是一个都没有.Volume设备出现DO_BUFFERED的情况几乎没有,我碰到的都是一个标记都没有.DO_DIRECT_IO表示数据应该返回到Irp->MdlAddress所指向的MDL所指向的内存.在无标记的情况下,表明数据读好,请返回到    
  Irp->UseBuffer中即可.    
   
  UseBuffer是一个只在当前线程上下文才有效的地址.如果你打算按这个地址获得数据,你最好在当前线程上下文中.完成函数与my_disp_read并非同一个线程.所以在完成函数中按这个地址去获取数据是不对的.如何回到当前线程?我采用简单的办法.在my_disp_read中设置一个事件,调用wd_irp_call(即ddk中的IoCallDriver)之后开始等待这个事件.而在完成函数中设置这个事件.这样等待结束的时候,刚好Irp已经完成,我也回到了我的my_disp_read原来的线程.    
   
  wd_stat   my_disp_read(in   wd_dev   *dev,in   wd_pirp   irp)    
  {    
  my_dev_ext   *ext;    
  wd_dev   *attached_dev;    
  wd_irpsp   *irpsp   =   wd_cur_io_stack(irp);    
  wd_stat   status;    
  wd_file   *file   =   wd_irp_file(irpsp);    
  wd_lgint   offset   =   wd_irp_read_offset(irpsp);    
  wd_size   length   =   wd_irp_read_length(irpsp);    
  wd_wchar   name_buf[512];    
  wd_ustr   name;    
  wd_event   event;    
   
  //   检查是否我的设备    
  if(!is_my_dev(dev))    
  return   wd_irp_failed(irp,wd_stat_invalid_dev_req);    
   
  ext   =   (wdff_dev_ext   *)wd_dev_ext(dev);    
  attached_dev   =   wdff_dev_attached(dev);    
   
  //   到这里判断得到这是对一个被绑定了的卷的读操作    
  if(ext->storage_dev   ==   NULL)    
  {    
  wd_skip_io_stack(irp);    
  return   wd_irp_call(attached_dev,irp);    
  }    
   
  //   到了这里,确认是对文件的读    
  wd_ustr_init_em(&name,name_buf,512);    
  if(file)    
  wd_file_get_name((wd_void   *)file,&name);    
  else    
  {    
  wd_skip_io_stack(irp);    
  return   wd_irp_call(attached_dev,irp);    
  }    
   
  wd_printf1("xxx   irp   flag   =   %x\r\n",wd_irp_flags(irp));    
  wd_printf1("xxx   file   read:   %wZ   \r\n",&name);    
  wd_printf1("xxx   read   offset   =   %ld   ",offset);    
  wd_printf1("xxx   read   length   =   %ld\r\n",length);    
   
  //   以上我已经打印了读请求的相关参数,下面我希望得到读出的内容    
  wd_event_init(&event);    
  //   先拷贝当前io_stack,然后再指定完成例程    
   
  wd_copy_io_stack(irp);    
  wd_irp_set_comp(irp,my_disp_read_comp,&event);    
   
  //   对实际设备呼叫irp    
  status   =   wd_irp_call(attached_dev,irp);    
  if(status   ==   wd_stat_pending)    
  wd_event_wait(&event);    
   
  wd_printf1("test   read   end:status   =   %x   \r\n",status);    
  //   如果此时status   =   0,那么内容应该就在Irp->UserBuffer中,请自己打印...    
  wd_printf1("test   read   end:read   length   =   %ld\r\n",wd_irp_infor(irp));    
   
  return   wd_irp_over(irp);    
  }    
   
  然后是my_disp_read_comp的内容,可以看见只是简单的设置事件,然后返回wd_stat_more_processing.    
   
  wd_stat   my_disp_read_comp(in   wd_dev   *dev,    
  in   wd_irp   *irp,    
  in   wd_void   *context)    
  {    
  wd_event   *   event=    
  (wd_event   *)context;    
  UNREFERENCED_PARAMETER(dev);    
  UNREFERENCED_PARAMETER(irp);    
  wd_event_set(event);    
  return   wd_stat_more_processing;    
  }    
   
  尽管已经写了很多,尽管我们得到了读过程的所有参数和结果,我们依然不知道如果自己写一个文件系统,该如何完成读请求,或者过滤驱动中,如何修改读请求等等.我们下一节继续讨论读操作.  
  Top

8 楼codewarrior(会思考的草)回复于 2006-07-01 22:35:21 得分 0

Windows文件系统过滤驱动开发教程    
   
  注:   有任何问题与建议请加QQ16191935,邮箱MFC_Tan_Wen@163.com    
   
  9   完成读操作    
   
  除非是一个完整的文件系统,完成读操作似乎是不必要的。过滤驱动一般只需要把请求交给下层的实际文件系统来完成。但是有时候比如加解密操作,我希望从下层读到数据,解密后,我自己来完成这一IRP请求。    
   
  这里要谈到IRP的minor   function   code.以前已经讨论到如果major   function   code   是IRP_MJ_READ则是Read请求。实际上有些主功能号下面有一些子功能号,如果是IRP_MJ_READ,检查其MINOR,应该有几种情况:IRP_MN_NORMAL,IRP_MN_MDL,IRP_MN_MDL|IRP_COMPLETE(这个其实就是IRP_MN_MDL_COMPLETE).还有其他几种情况,资料上有解释,但是我没自己调试过,也就不胡说了。只拿自己调试过的几种情况来说说。    
   
  先写个函数,得到次功能号。    
   
  _inline   wd_uchar   wd_irpsp_minor(wd_io_stack   *irpsp)    
  {    
  return   irpsp->MinorFunction;    
  }    
   
  enum   {    
  wd_mn_mdl   =   IRP_MN_MDL,    
  wd_mn_mdl_comp   =   IRP_MN_MDL_COMPLETE,    
  wd_mn_normal   =   IRP_MN_NORMAL    
  };    
   
  希望你喜欢我重新定义过的次功能号码。    
   
  wd_mn_normal的情况完全与上一节同(读者可能要骂了,上一节明明说就是这样的,结果还有其他的情况,那不是骗人么?但是微软的东西例外就是多,我也实在拿他没办法...   ).    
   
  注意如上节所叙述,wd_mn_normal的情况,既有可能是在Irp->MdlAddress中返回数据,也可能是在Irp->UserBuffer中返回数据,这个取决于Device的标志.    
   
  但是如果次功能号为wd_mn_mdl则完全不是这个意思。这种irp一进来看数据,就赫然发现Irp->MdlAddress和Irp->UserBuffer都为空。那你得到数据后把数据往哪里拷贝呢?    
   
  wd_mn_mdl的意思是请自己分配一个mdl,然后把mdl指向你的数据所在的空间,然后返回给上层。自然mdl是要释放的,换句话说事业使用完毕要归还,所以又有wd_mn_mdl_comp,意思是一个mdl已经使用完毕,可以释放了。    
   
  mdl用于描述内存的位置。据说和NDIS_BUFFER用的是同一个结构。这里不深究,我写一些函数来分配和释放mdl,并把mdl指向内存位置或者得到mdl所指向的内存:    
   
  //   释放mdl    
  _inline   wd_void   wd_mdl_free(wd_mdl   *mdl)    
  {    
  IoFreeMdl(mdl);    
  }    
   
  //   这个这个东西分配mdl,缓冲必须是非分页的。可以在dispatch   level跑。    
  _inline   wd_mdl   *wd_mdl_alloc(wd_void*   buf,    
  wd_ulong   length)    
  {    
  wd_mdl   *   pmdl   =   IoAllocateMdl(buf,length,wd_false,wd_false,NULL);    
  if(pmdl   ==   NULL)    
  return   NULL;    
  MmBuildMdlForNonPagedPool(pmdl);    
  return   pmdl;    
  }    
   
  //   这个函数分配一个mdl,并且带有一片内存    
  _inline   wd_mdl   *wd_mdl_malloc(wd_ulong   length)    
  {    
  wd_mdl   *mdl;    
  wd_void   *point   =   wd_malloc(wd_false,length);    
  if(point   ==   NULL)    
  return   NULL;    
  mdl   =   wd_mdl_alloc(point,length);    
  if(mdl   ==   NULL)    
  {    
  wd_free(point);    
  return   NULL;    
  }    
  return   mdl;    
  }    
   
  //   这个函数释放mdl并释放mdl所带的内存。    
  _inline   wd_void   wd_mdl_mfree(wd_mdl   *mdl)    
  {    
  wd_void   *point   =   wd_mdl_vaddr(mdl);    
  wd_mdl_free(mdl);    
  wd_free(point);    
  }    
   
  //   得到地址。如果想往一个MdlAddress里边拷贝数据   ...    
  _inline   wd_void   *wd_mdl_vaddr(wd_mdl   *mdl)    
  {    
  return   MmGetSystemAddressForMdlSafe(mdl,NormalPagePriority);    
  }    
   
  另外我通过这两个函数来设置和获取Irp上的mdl.    
   
  _inline   wd_mdl   *wd_irp_mdl(wd_irp   *irp)    
  {    
  return   irp->MdlAddress;    
  }    
   
  _inline   wd_void   wd_irp_mdl_set(wd_irp   *irp,    
  wd_mdl   *mdl)    
  {    
  irp->MdlAddress   =   mdl;    
  }    
   
  一个函数获得UserBuffer.    
  _inline   wd_void   *   wd_irp_user_buf(wd_irp   *irp)    
  {    
  return   irp->UserBuffer;    
  }    
   
  要完成请求还有一个问题。就是irp->IoStatus.Information.在这里你必须填上实际读取得到的字节数字。不然上层不知道有多少数据返回。这个数字不一定与你的请求的长度等同(其实我认为几乎只要是成功,就应该都是等同的,唯一的例外是读取到文件结束的地方,长度不够了的情况)。我用下边两个函数来获取和设置这个数值:    
   
  _inline   wd_void   wd_irp_infor_set(wd_irp   *irp,    
  wd_ulong   infor)    
  {    
  irp->IoStatus.Information   =   infor;    
  }    
   
  _inline   wd_ulong   wd_irp_infor(wd_irp   *irp)    
  {    
  return   irp->IoStatus.Information;    
  }    
   
   
  也许你都烦了,但是还有事情要做。作为读文件的情况,如果你是自己完成请求,不能忘记移动一下文件指针。否则操作系统会不知道文件指针移动了而反复读同一个地方永远找不到文件尾,我碰到过这样的情况。    
   
  一般是这样的,如果文件读取失败,请保持原来的文件指针位置不要变。如果文件读取成功,请把文件指针指到“读请求偏移量+成功读取长度”的位置。    
   
  这个所谓的指针是指Irp->FileObject->CurrentByteOffset.    
   
  我跟踪过正常的windows文件系统的读行为,我认为并不一定是向我上边说的这样做。情况很复杂,有时动,有时不动(说复杂当然是因为我不理解),但是按我上边说的方法来完成,我还没有发现过错误。    
   
  我用以下函数来设置和获取这个。    
   
  _inline   wd_void   wd_file_offset_add(wd_file   *file,wd_ulong   len)    
  {    
  file->CurrentByteOffset.QuadPart   +=   len;    
  }    
   
  _inline   wd_void   wd_file_offset_set(wd_file   *file,wd_lgint   offset)    
  {    
  file->CurrentByteOffset   =   offset;    
  }    
   
  _inline   wd_lgint   wd_file_offset(wd_file   *file)    
  {    
  return   file->CurrentByteOffset;    
  }    
   
   
  现在看看怎么完成这些请求,假设我已经有数据了。现在假设本设备缓冲标记为0,即正常情况采用Irp->UserBuffer返回数据.   这些当然都是在my_disp_read中或者是其他想完成这个irp的地方做的(希望你还记得我们是如何来到这里),假设其他必要的判断都已经做了:    
   
  wd_irpsp   *irpsp   =   wd_irp_cur_io_stack(irp);    
  switch(wd_irpsp_minor(irpsp))    
  {    
  //   我先保留文件的偏移位置    
  case   wd_mn_normal:    
  {    
  wd_void   *point   =   wd_irp_user_buf(irp);    
   
  //   如果有数据,就往point   ...里边拷贝   ...    
   
  wd_irp_infor_set(irp,length);    
  wd_irp_status_set(irp,wd_suc);    
  wd_file_offset_set(wd_irp_file(irp),offset+length);    
   
  return   wd_irp_over(irp);    
  }    
  case   wd_mn_mdl:    
  {    
  wd_void   *mdl   =   wd_mdl_malloc(length);   //   情况比上边的复杂,请先分配mdl    
  if(mdl   ==   NULL)    
  {    
  //   ...   返回资源不足   ...    
  }    
   
  wd_irp_mdl_set(irp,mdl);    
  wd_irp_infor_set(irp,length);    
  wd_irp_status_set(irp,wd_suc);    
   
  wd_file_offset_set(wd_irp_file(irp),offset+length);    
   
  return   wd_irp_over(irp);    
   
  }    
  case   wd_mn_mdl_comp:    
  {    
  //   没有其他任务,就是释放mdl    
  wd_mdl_mfree(wd_irp_mdl(irp));    
  wd_irp_mdl_set(irp,0);    
  wd_irp_infor_set(irp,0);    
  wd_irp_status_set(irp,wd_status_suc);    
   
  return   wd_irp_over(irp);    
  }    
  default:    
  {    
  //   我认为其他的情况不过滤比较简单   ...    
  }    
  }    
   
  重要提醒:wd_mn_mdl的情况,需要分配一个mdl,并且这个mdl所带有的内存是有一定长度的,这个长度必须与后来的irp->IoStatus.Information相同!似乎上层并不以irp->IoStatus.Information返回的长度为准。比如明明只读了50个字节,但是你返回了一个mdl指向内存长度为60字节,则操作系统则认为已经读了60个字节!这非常糟糕。    
   
  最后提一下文件是如何结尾的。如果到某一处,返回成功,但是实际读取到的数据没有请求的数据长,这时还是返回wd_status_suc(STATUS_SUCCESS),但是此后操作系统会马上发irp来读最后一个位置,此时返回长度为0,返回状态STATUS_FILE_END即可。    
   
  已经费巨大篇幅解释读请求。我不会再讲解写请求了。相信读者有能力自己搞清楚。  
  Top

9 楼codewarrior(会思考的草)回复于 2006-07-01 22:35:32 得分 0

Windows文件系统过滤驱动开发教程    
   
  注:   有任何问题与建议请加QQ16191935,邮箱MFC_Tan_Wen@163.com    
   
  工作忙,好久没有来过了,请大家谅解。    
   
  10   自己发送Irp完成读请求    
   
  关于这个有一篇文档解释得很详细,不过我认为示例的代码有点太简略了,这篇文档在IFS所附带的OSR文档中,请自己寻找。    
   
  为何要自己发送Irp?在一个文件过滤驱动中,如果你打算读写文件,可以试用ZwReadFile.但是这有一些问题。Zw系列的Native   API使    
   
  用句柄。而句柄是有线程环境限制的。此外也有中断级别的限制。再说,Zw系列函数来读写文件,最终还是要发出Irp,又会被自己的过滤驱动    
   
  捕获到。结果带来判断上的困难。对资源也是浪费。那么最应该的办法是什么呢?当然是直接对卷设备发Irp了。    
   
  但是Irp是非常复杂的数据结构,而且又被微软所构造的很多未空开的部件所处理。所以自己发irp并不是一件简单的事情。    
   
  比较万能的方法是IoAllocateIrp,分配后自己逐个填写。问题是细节实在太多,很多无文档可寻。有兴趣的应该看看我上边所提及的    
   
  那篇文章“Rolling   Your   Own”。    
   
  有意思的是这篇文章后来提到了捷径,就是利用三个函数:    
   
  IoBuildAsynchronousFsdRequest(...)    
  IoBuildSynchronousFsdRequest(...)    
  IoBuildDeviceIoControlRequest(...)    
   
  于是我参考了他这方面的示例代码,发现运行良好,程序也很简单。建议怕深入研究的选手就可以使用我下边提供的方法了。    
   
  首先的建议是使用IoBuildAsynchronousFsdRequest(),而不要使用同步的那个。使用异步的Irp使irp和线程无关。而你的过滤驱动一    
   
  般很难把握当前线程(如果你开一个系统线程来专门读取文件那例外)。此时,你可以轻松的在Irp的完成函数中删除你分配过的Irp,避免去追    
   
  究和线程相关的事情。    
   
  但是这个方法有局限性。文档指出,这个方法仅仅能用于IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS,和IRP_MJ_SHUTDOWN.    
   
  刚好我这里仅仅要求完成文件读。    
   
  用Irp完成文件读需要一个FILE_OBJECT.FileObject是比Zw系列所用的句柄更好的东西。因为这个FileObject是和线程无关的。你可以    
   
  放心的在未知的线程中使用他。    
   
  自己要获得一个FILE_OBJECT必须自己发送IRP_MJ_CREATE的IRP.这又不是一件轻松的事情。不过我跳过了这个问题。因为我是文件系    
   
  统过滤驱动,所以我从上面发来的IRP中得到FILE_OBJECT,然后再构造自己的IRP使用这个FILE_OBJECT,我发现运行很好。    
   
  但是又出现一个问题,如果IRP的irp->Flags中有IRP_PAGING(或者说是Cache管理器所发来的IRP)标记,则其FileObject我获得并使    
   
  用后,老是返回错误。阅读网上的经验表明,带有IRP_PAGINGE的FileObject不可以使用.于是我避免使用这时的FileObject.我总是使用不带    
   
  IRP_PAGING的Irp(认为是用户程序发来的读请求)的FileObject。    
   
  好,现在废话很多了,现在来看看构造irp的代码:    
   
  _inline   wd_irp   *wd_irp_fsd_read_alloc(wd_dev   *dev,    
  wd_void   *buf,    
  wd_ulong   length,    
  wd_lgint   *offset,    
  wd_io_stat_block   *io_stat)    
  {    
  return   IoBuildAsynchronousFsdRequest(IRP_MJ_READ,dev,    
  buf,length,    
  offset,    
  io_stat);    
  }    
   
  io_stat我不知道是做何用,我一般填写NULL.类型是PIO_STATUS_BLOCK.buf则是缓冲。在Irp中被用做UserBuffer接收数据。offset是    
   
  这次读的偏移量。    
   
  以上函数构造一个读irp.请注意,此时您还没有设置FileObject.实际上我是这样发出请求的:    
   
  irp   =   wd_irp_fsd_read_alloc(dev,buf,len,&start,NULL);    
  if(irp   ==   NULL)    
  return;    
  irpsp   =   wd_irp_next_sp(irp);    
  wd_irpsp_file_set(irpsp,file);    
  wd_irp_comp(irp,my_req_comp,context);    
   
  请注意wd_irp_next_sp,我是得到了这个irp的下一个IO_STACK_LOCATION,然后我设置了FileObject.接下来应该设置了完成后,我就    
   
  可以发送请求了!请求发送完毕后,一旦系统完成请求就会调用你的my_req_comp.    
   
  再看看my_req_comp如何收场:    
   
  wd_stat   my_req_comp(in   wd_dev   *dev,    
  in   wd_irp   *irp,    
  in   wd_void   *context)    
  {    
   
  //   请注意,无论成功还是失败,我都得在这里彻底销毁irp    
  wd_irp_send_by_myself_free(irp);    
   
  //   返回这个,仅仅是因为irp我已经销毁,不想让系统再次销毁它而已。    
  return   wd_stat_more_processing;    
  }    
   
  wd_stat_more_prcessing就是STATUS_MORE_PROCESSING_REQUIRED。之所以返回这个,是因为我已经把irp给删除了。我不想windows系    
   
  统再对这个irp做什么。所以干脆说我还要继续处理,这样避免了io管理器再去动用这个irp的可能。    
   
  最后是那个irp删除函数的代码:    
   
  _inline   wd_void   wd_irp_send_by_myself_free(wd_irp   *irp)    
  {    
  if   (irp->MdlAddress)    
  {    
  MmUnmapLockedPages(MmGetSystemAddressForMdl(irp->MdlAddress),    
  irp->MdlAddress);    
  MmUnlockPages(irp->MdlAddress);    
  IoFreeMdl(irp->MdlAddress);    
  }    
  IoFreeIrp(irp);    
  };    
   
  因为我自己没有分配过MdlAddress下去过。所以如果下边返回了MDL,我得附带清理它。    
   
  好了,祝愿你心情愉快。如果你何我一样懒惰的话,不妨就用上边的简单方法自己发irp来读文件了。  
  Top

10 楼codewarrior(会思考的草)回复于 2006-07-01 22:35:45 得分 0

Windows文件系统过滤驱动开发教程(11)    
   
  有问题请联系QQ16191935,Email   MFC_Tan_Wen@163.com    
   
  11.文件和目录的生成打开,关闭与删除    
   
  我们已经分析了读,写与读类似。文件系统还有其他的操作。比如文件或目录的打开(打开已经存在的或者创建新的),关闭。文件或目录的移动,删除。    
   
  实际上FILE_OBJECT并不仅仅指文件对象。在windows文件系统中,目录和文件都是用FileObject来抽象的。这里产生一个问题,对于一个已经有的FileObject,我如何判断这是一个目录还是一个文件呢?    
   
  对于一个已经存在的FileObject,我没有找到除了发送IRP来向卷设备询问这个FileObject的信息之外更好的办法。自己发送IRP很麻烦。不是我很乐意做的那种事情。但是FileObject都是在CreateFile的时候诞生的。在诞生的过程中,确实有机会得到这个即将诞生的FileObject,是一个文件还是一个目录。    
   
  Create的时候,获得当前IO_STACK_LOCATION,假设为irpsp,那么irpsp->Parameters.Create的结构为:    
   
  struct   {    
  PIO_SECURITY_CONTEXT   SecurityContext;    
  ULONG   Options;    
  USHORT   FileAttributes;    
  USHORT   ShareAccess;    
  ULONG   EaLength;    
  };    
   
  这个结构中的参数是与CreateFile这个api中的参数对应的,请自己研究。我先写一些函数包装来方便读取irpsp.    
   
  _inline   wd_ulong   wd_irpsp_file_opts(wd_irpsp   *irpsp)    
  {    
  return   irpsp->Parameters.Create.Options;    
  }    
   
  _inline   wd_ushort   wd_irpsp_file_attrs(wd_irpsp   *irpsp)    
  {    
  return   irpsp->Parameters.Create.FileAttributes;    
  }    
&nbs