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即可。
已经费巨大篇幅解释读请求。我不会再讲解写请求了。相信读者有能力自己搞清楚。