⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 windows文件系统过滤驱动开发教程.txt

📁 微过滤器驱动开发指南,本文翻译仅仅用做交流学习。我不打算保留任何版权或者承担任何责任。不要引用到赢利出版物中给您带来版权官司。本文的翻译者是楚狂人
💻 TXT
📖 第 1 页 / 共 4 页
字号:
	不过实际情况并不这么简单.这里的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;
// 任务的初始化
_inline wd_void wd_work_init(wd_work_item *item,
			 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的绑定.

	希望你没有被弄昏头:),我们下回再分解.	


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的设备名,不太友好.

	我现在偷一个懒,我现在只打印没有盘符的路径名.先写一个函数,从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;
}
		
	尽管已经写了很多,尽管我们得到了读过程的所有参数和结果,我们依然不知道如果自己写一个文件系统,该如何完成读请求,或者过滤驱动中,如何修改读请求等等.我们下一节继续讨论读操作.
			

		
	
	

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -