周末实在不太想写我的数据库程序,又花了一个小时练习了一下DriverWorks里的排队IO请求模型,其实这和DDK中的模型基本上是一样的,因为参考了Programming the Microsoft Windows Driver Model里的一些代码,并且少看了DriverWorks关于排队IO的一句话,我还得到了一个BSOD,不过这个BSOD让我注意到了帮助文档上我没有注意到的地方,呵呵!
废话不多说,来一个段代码!
#define VDW_MAIN
#define DRIVER_FUNCTION_UNLOAD
#define DRIVER_FUNCTION_CREATE
#define DRIVER_FUNCTION_CLEANUP
#define DRIVER_FUNCTION_CLOSE
#define DRIVER_FUNCTION_READ
#define DRIVER_FUNCTION_WRITE
#define DRIVER_FUNCTION_STARTIO
#define DRIVER_FUNCTION_DEVICE_CONTROL
#include "vdw.h"
//Begin Device Code///////////////////////////////////////////////////////
class CQueueDevice:public KDevice
{
SAFE_DESTRUCTORS;
public:
CQueueDevice();
NTSTATUS ComplateIrp(KIrp I);
DEVMEMBER_DISPATCHERS;
DEVMEMBER_CANCELIRP(CQueueDevice,CancelIo);
NTSTATUS SerialRead(KIrp I);
NTSTATUS SerialWrite(KIrp I);
NTSTATUS SerialIoControl(KIrp I);
};
CQueueDevice::CQueueDevice():KDevice(L"CQueueDevice",FILE_DEVICE_UNKNOWN,L"CQueueDevice",0,DO_DIRECT_IO)
{
if(!NT_SUCCESS(ConstructorStatus()))
{
DbgPrint(__FUNCTION__":Failed to Create Device\n");
}
}
NTSTATUS CQueueDevice::ComplateIrp(KIrp I)
{
I.Information()=0;
I.Complete(STATUS_SUCCESS);
return STATUS_SUCCESS;
}
NTSTATUS CQueueDevice::Create(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
return ComplateIrp(I);
}
NTSTATUS CQueueDevice::CleanUp(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
return ComplateIrp(I);
}
NTSTATUS CQueueDevice::Close(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
return ComplateIrp(I);
}
NTSTATUS CQueueDevice::DeviceControl(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
//排队并设置取消例程
return QueueIrp(I,LinkTo(CancelIo));
}
NTSTATUS CQueueDevice::Read(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
//排队并设置取消例程
return QueueIrp(I,LinkTo(CancelIo));
}
NTSTATUS CQueueDevice::Write(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
//排队并设置取消例程
return QueueIrp(I,LinkTo(CancelIo));
}
NTSTATUS CQueueDevice::SerialRead(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
KMemory mem(I.Mdl());
PVOID pOutputBuf=mem.MapToSystemSpace();
ULONG OutputSize=I.ReadSize();
//从设备读数据输出到pOutputBuf里
//填写输出长度,它<=OutputSize
ULONG nBytesReturned=0;
I.Information()=nBytesReturned;
//返回处理状态
return STATUS_SUCCESS;
}
NTSTATUS CQueueDevice::SerialWrite(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
KMemory mem(I.Mdl());
PVOID pInputBuf=mem.MapToSystemSpace();
ULONG InputSize=I.WriteSize();
//将pInputBuf里的数据输出到设备
I.Information()=0;
//返回处理状态
return STATUS_SUCCESS;
}
NTSTATUS CQueueDevice::SerialIoControl(KIrp I)
{
DbgPrint(__FUNCTION__"\n");
I.Information()=0;
return STATUS_SUCCESS;
}
VOID CQueueDevice::CancelIo(KIrp I)
{
if(CurrentIrp()==I)
{
//如果取消的是当前的请求,直接返回NextIrp会完成这个请求,
//并开始下一个请求
CancelSpinLock::Release(I.CancelIrql());
I.Information() = 0;
I.Status() = STATUS_CANCELLED;
NextIrp(I);
}
else
{
//如果是一个排队的请求,则从设备排队摘除,完成这个请求,
//并返回"已取消"状态码
KDeviceQueue dq(DeviceQueue());
BOOLEAN bRemoved=dq.RemoveSpecificEntry(I);
CancelSpinLock::Release(I.CancelIrql());
if(bRemoved)
{
I.Information()=0;
I.Complete(STATUS_CANCELLED);
}
}
}
VOID CQueueDevice::StartIo(KIrp I)
{
CancelSpinLock::Acquire();
if (I.WasCanceled())
{
//如果是已经取消的
CancelSpinLock::Release();
return;
}
else
{
//如果没有取消,则设空取消例程,然后处理
I.SetCancelRoutine(NULL);
CancelSpinLock::Release();
}
NTSTATUS status=STATUS_SUCCESS;
switch(I.MajorFunction()) {
case IRP_MJ_READ:
status=SerialRead(I);
break;
case IRP_MJ_WRITE:
status=SerialWrite(I);
break;
case IRP_MJ_DEVICE_CONTROL:
status=SerialIoControl(I);
}
NextIrp(I);
//不要在这里需要调用I.Complete(),因为KDevice::NextIrp里调用了一次
return;
}
/*
我在上面这个return前调用了I.Complete,结果得到了一个BSOD,错误代码是"多次
完成IRP请求",发现我的英文水平略有长进,呵呵!在没有金山词霸的帮助下(BSOD了),
可以知道是什么错误.
文档上有这样一段:
Subclasses of KDevice that use StartIo can use member function CurrentIrp to obtain a pointer to the IRP that the device is currently processing.
When processing of the IRP is complete, the driver calls NextIrp. This function does two things: it tells the I/O Manager to pass the next IRP in the device's IRP queue to StartIo, and it tells the I/O Manager that processing of the current IRP is complete.
说了NextIrp做两件事情,我没有看仔细结果蓝屏了,看一下KDevice::NextIrp的实现代码就知道为什么了:
inline VOID KDevice::NextIrp(KIrp I, CCHAR PriorityBoost, BOOLEAN Cancelable)
{
BOUNDS_CHECKER(NEXT_IRP, (this, I.m_Irp));
IoStartNextPacket(m_pDeviceObject, Cancelable);
if (!I.IsNull())
{IoCompleteRequest(I.m_Irp, PriorityBoost);}
}
*/
//End of Device Code//////////////////////////////////////////////////////
//Begin Driver Code//////////////////////////////////////////////////////////
class CQueueDriver:public KDriver
{
SAFE_DESTRUCTORS;
public:
virtual NTSTATUS DriverEntry(IN PUNICODE_STRING RegistryPath);
virtual VOID Unload();
};
DECLARE_DRIVER_CLASS(CQueueDriver,NULL)
NTSTATUS CQueueDriver::DriverEntry(IN PUNICODE_STRING RegistryPath)
{
DbgPrint(__FUNCTION__"\n");
NTSTATUS status=STATUS_SUCCESS;
CQueueDevice *pDevice=new(NonPagedPool)CQueueDevice();
if(pDevice)
{
status=pDevice->ConstructorStatus();
if(!NT_SUCCESS(status))
{
delete pDevice;
}
}
return status;
}
VOID CQueueDriver::Unload()
{
DbgPrint(__FUNCTION__"\n");
KDriver::Unload();
}
//End of Driver code//////////////////////////////////////////////////////
其实这只是一个简化的IO请求模型演示,因为我没有什么具体的PCI设备可供我写驱动用,正如Programming the Microsoft Windows Driver Model一书中的一张图表示的那样:
一个读写请求,首先到达IO管理器,然后到达派遣例程(Read,Write,DeviceControl它们排队IRP请求),再到达StartIo中处理IRP,如果必要的话,StartIo会发出in,out之类的命令给硬件设备,可能立即返回也可能设备完成操作后发出一个中断,然后驱动的中断服务例程ISR被执行,ISR调度DPC来管理这个中断,DPC拿到数据后完成这个请求,因为DPC例程是在DISPATCH_LEVEL级上运行的,它比ISR受到的限制少,它处理完数据后可以调用IoCompleteRequest或是IoStartNextPacket这样的例程,最后IRP回到IO管理器,返回上层.在DriverWorks的例子驱动BasicPci中可以非常清楚的看到这样的代码流程.
这些概念在看Programming the Microsoft Windows Driver Model一书时使终弄的不是很清楚,但用DriverWorks写驱动后,感觉自己对驱动程序的整体结构有了更深入的了解.看来我还是适合看一些面向对象的代码,再由这些代码底层实现到学习如何在非面向对象状态时怎么实现这些东西.也许这就是从做应用开发转到驱动开发的人和直接从事驱动开发的人,接受东西的角度不同吧!Driverworks在用纯DDK写驱动的人看来是很烦琐的,但在我看来代码结构却是那么简洁合理.在这个例子里实现这些机制,代码里还有注释但却不到250行,而且代码非常容易理解,呵呵!