分类: WINDOWS
2006-09-26 20:08:55
已经存在许多讨论像dialogs,MFC,ATL,thread,process,registry等程序设计的文章。但是却很难找到任何带有完整源代码的探讨驱动设计的文章。主要原因是大多数驱动是硬件相关的。如果没有领域知识,大家从来都不想接触他。我深信许多软件开发者都会为第一次被卷入内核模式模式程序开发而发愁,而且并没有多少能够把他们从对DDK学习的过程转变到实际应用中来的资源。因此,我决定跟大家分享我在Windows驱动开发中的一些经验。这是一个简单的集中于介绍WDM驱动架构和Windows中的DMA和Buffered两种I/O模式,以及如何跟深居内核空间中的驱动程序进行交互和与驱动程序进行数据读写交换的一篇文章。
在读文章时你不需要具有掌握任何的硬件背景,因为这是一个虚拟的驱动程序。它的安装并不需要计算机中任何实存的物理设备。程序中的成员函数你可以作为在你随后驱动开发中的模版函数,以节约你的开发时间。
你可能是一个具有丰富经验的应用程序开发者,但是你想转行搞内核模式程序开发,比如
开发一个驱动程序。
创建你自己的WDM驱动程序: 一个假冒的驱动程序
开始之前,首先让我们声明所需要的函数和需要的数据结构。最重要的数据结构就是DEVICE_EXTENSION。
typedef struct tagDEVICE_EXTENSION {
PDEVICE_OBJECT DeviceObject; // 由该程序创建的设备对象
PDEVICE_OBJECT NextDeviceObject; // 该设备栈中的下一个设备对象
DEVICE_CAPABILITIES pdc; // 设备能力
IO_REMOVE_LOCK RemoveLock; // removal control locking structure
LONG handles; // # 打开句柄
PVOID DataBuffer; // Read/Write I/O内部句柄
UNICODE_STRING Device_Description; // 设备描述
SYSTEM_POWER_STATE SysPwrState; // 当前系统状态
DEVICE_POWER_STATE DevPwrState; // 当前设备电源状态
PIRP PowerIrp; // Current Handling Power-Related IRP
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
下面的代码片断示例创建一个有效WDM驱动的开始部分。
在WDM驱动中有许多强制的和可选的成员:一个有效的WDM驱动程序应该具有DriverEntry函数,该函数最重要的任务就是把所有的成员函数注册到内核。
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
RtlInitUnicodeString(
&Global_sz_Drv_RegInfo,
RegistryPath->Buffer);
// Initialize function pointers
DriverObject->DriverUnload = DriverUnload;
DriverObject->DriverExtension->AddDevice = AddDevice;
DriverObject->MajorFunction[IRP_MJ_CREATE] = PsdoDispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = PsdoDispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = PsdoDispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = PsdoDispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = PsdoDispatchDeviceControl;
DriverObject->MajorFunction[IRP_MJ_POWER] = PsdoDispatchPower;
DriverObject->MajorFunction[IRP_MJ_PNP] = PsdoDispatchPnP;
return STATUS_SUCCESS;
}
AddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
{
ULONG DeviceExtensionSize;
PDEVICE_EXTENSION p_DVCEXT;
PDEVICE_OBJECT ptr_PDO;
NTSTATUS status;
RtlInitUnicodeString(
&Global_sz_DeviceName, L"");
//Get DEVICE_EXTENSION required memory space
DeviceExtensionSize = sizeof(DEVICE_EXTENSION);
//Create Device Object
status = IoCreateDevice(
DriverObject,
DeviceExtensionSize,
&Global_sz_DeviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&ptr_PDO
);
if (NT_SUCCESS(status)) {
ptr_PDO->Flags &= ~DO_DEVICE_INITIALIZING;
ptr_PDO->Flags |= DO_BUFFERED_IO; //For Buffered I/O
//ptr_PDO->Flags |= DO_DIRECT_IO;
//For Direct I/O
p_DVCEXT = ptr_PDO->DeviceExtension;
p_DVCEXT->DeviceObject = ptr_PDO;
RtlInitUnicodeString(
/*
//Other initialization tasks go here
*/
//Store next-layered device object
//Attach device object to device stack
p_DVCEXT->NextDeviceObject =
IoAttachDeviceToDeviceStack(ptr_PDO, PhysicalDeviceObject);
}
return status;
}
下面的代码片断介绍如何支持IRP_MJ_CREATE
,
当客户端应用程序开始与Pseudo驱动接头的时候就会发出该IRP。在继续进行前,让我们观图来快速了解接头过程。
CreateFile
/fopen
与底层的驱动相联系。这也恰恰就是Win32 子系统发送IRP_MJ_CREATE并且请求驱动程序与目标设备联系的过程。PsdoDispatchCreate(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PIO_STACK_LOCATION p_IO_STK;
PDEVICE_EXTENSION p_DVCEXT;
NTSTATUS status;
p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
p_DVCEXT = DeviceObject->DeviceExtension;
status = IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, p_IO_STK->FileObject);
if (NT_SUCCESS(status)) {
CompleteRequest(Irp, STATUS_SUCCESS, 0);
return STATUS_SUCCESS;
} else {
IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, p_IO_STK->FileObject);
CompleteRequest(Irp, status, 0);
return status;
}
}
下面代码片断介绍了如何支持IRP_MJ_CLOSE
,当客户程序关闭根底层驱动程序之间的连接时就会有该IRP发生。让我们观图来快速了解关闭连接过程。
通常你需要使用Win32API CloseHandle
/fclose
来关闭与底层驱动程序的联系。这也就是Win32子系统发出该IRP并且请求驱动关闭与目标设备连接的时刻。
NTSTATUS
PsdoDispatchClose(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PIO_STACK_LOCATION p_IO_STK;
PDEVICE_EXTENSION p_DVCEXT;
p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
p_DVCEXT = DeviceObject->DeviceExtension;
IoReleaseRemoveLock(&p_DVCEXT->RemoveLock,
p_IO_STK->FileObject);
CompleteRequest(Irp, STATUS_SUCCESS, 0);
return STATUS_SUCCESS;
下面代码片说明了在I/O数据处理中的读操作. 正如我们所看到的, 我们注册的读函数是 DriverEntry
中的PsdoDispatchRead
, 这个函数将会从驱动程序中的变量 DataBuffer
中读出数据到应用程序中:
NTSTATUS
PsdoDispatchRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PVOID Buf; //Buffer provided by user program
ULONG BufLen; //Buffer length for user provided buffer
LONGLONG Offset;//Buffer Offset
PVOID DataBuf; //Buffer provided by Driver
ULONG DataLen; //Buffer length for Driver Data Buffer
ULONG ByteTransferred;
PIO_STACK_LOCATION p_IO_STK;
PDEVICE_EXTENSION p_DVCEXT;
DbgPrint("IRP_MJ_READ : Begin\r\n");
//Get I/o Stack Location & Device Extension
p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
p_DVCEXT = DeviceObject->DeviceExtension;
//Get User Output Buffer & Length
BufLen = p_IO_STK->Parameters.Read.Length;
Offset = p_IO_STK->Parameters.Read.ByteOffset.QuadPart;
Buf = (PUCHAR)(Irp->AssociatedIrp.SystemBuffer) + Offset;
//Get Driver Data Buffer & Length
DataBuf = p_DVCEXT->DataBuffer;
if (DataBuf == NULL)
DataLen = 0;
else
DataLen = 1024;
IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);
DbgPrint("Output Buffer Length : %d\r\n", BufLen);
DbgPrint("Driver Data Length : %d\r\n", DataLen);
//
if (BufLen <= DataLen) {
ByteTransferred = BufLen;
} else {
ByteTransferred = DataLen;
}
RtlCopyMemory(
Buf, DataBuf,
ByteTransferred);
IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);
DbgPrint("IRP_MJ_READ : End\r\n");
return STATUS_SUCCESS;
}
相反下边的代码片断说明了工作流程图中的支持正常从应用程序到驱动的写请求的可能任务项目。
NTSTATUS
PsdoDispatchWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PVOID Buf; //Buffer provided by user program
ULONG BufLen; //Buffer length for user provided buffer
LONGLONG Offset;//Buffer Offset
PVOID DataBuf; //Buffer provided by Driver
ULONG DataLen; //Buffer length for Driver Data Buffer
ULONG ByteTransferred;
PIO_STACK_LOCATION p_IO_STK;
PDEVICE_EXTENSION p_DVCEXT;
NTSTATUS status;
DbgPrint("IRP_MJ_WRITE : Begin\r\n");
//Get I/o Stack Location & Device Extension
p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
p_DVCEXT = DeviceObject->DeviceExtension;
//Get User Input Buffer & Length
BufLen = p_IO_STK->Parameters.Write.Length;
Offset = p_IO_STK->Parameters.Read.ByteOffset.QuadPart;
Buf = (PUCHAR)(Irp->AssociatedIrp.SystemBuffer) + Offset;
//Get Driver Data Buffer & Length
DataBuf = p_DVCEXT->DataBuffer;
DataLen = 1024;
IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);
DbgPrint("Input Buffer Length : %d\r\n", BufLen);
DbgPrint("Driver Data Length : %d\r\n", DataLen);
if (BufLen <= DataLen) {
ByteTransferred = BufLen;
} else {
ByteTransferred = DataLen;
}
ByteTransferred = BufLen;
RtlZeroMemory(
p_DVCEXT->DataBuffer,
1024);
RtlCopyMemory(
DataBuf,
Buf,
ByteTransferred);
IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);
DbgPrint("IRP_MJ_WRITE : End\r\n");
return STATUS_SUCCESS;
}
I/O 支持 : DMA
下面代码片断揭示了应用程序和驱动在DMA模式下是如何交互的. 在DMA模式中, 内存管理将创建MDL(内存描述表) 来引用用户区的缓冲区地址, 内核通过MDL可以引用所有的用户缓冲区数据。
MMXxx
函数支持你得到映射到用户物理地址区的缓冲区的MDL.下面代码片断陈述了在DMA模式下用户从内核缓冲区读数据过程. 这是通过一系列的 Mmxxx
函数来实现的, 请仔细的读, 并且你也可以在压缩包里边找到该代码. 你所需要的最重要的 MmXxx
函数是 - MmGetSystemAddressForMdlSafe
, 它可以返回引用用户缓冲区物理地址的MDL.
NTSTATUS
PsdoDispatchRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PVOID Buf; //Buffer provided by user program
ULONG BufLen; //Buffer length for user provided buffer
ULONG Offset;//Buffer Offset
PVOID DataBuf; //Buffer provided by Driver
ULONG DataLen; //Buffer length for Driver Data Buffer
ULONG ByteTransferred;
PIO_STACK_LOCATION p_IO_STK;
PDEVICE_EXTENSION p_DVCEXT;
DbgPrint("IRP_MJ_READ : Begin\r\n");
//Get I/o Stack Location & Device Extension
p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
p_DVCEXT = DeviceObject->DeviceExtension;
//Get User Output Buffer & Length
Buf = MmGetSystemAddressForMdlSafe(
Irp->MdlAddress, HighPagePriority);
if (Buf == NULL) {
DbgPrint("Can't get Virtual Address from MDL\r\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
BufLen = MmGetMdlByteCount(Irp->MdlAddress);
Offset = MmGetMdlByteOffset(Irp->MdlAddress);
//Get Driver Data Buffer & Length
DataBuf = p_DVCEXT->DataBuffer;
if (DataBuf == NULL)
DataLen = 0;
else
DataLen = 1024;
IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);
DbgPrint("Output Buffer Length : %d\r\n", BufLen);
DbgPrint("Offset for Buffer in the Memory Page: %d\r\n", Offset);
DbgPrint("Driver Data Length : %d\r\n", DataLen);
//
if (BufLen <= DataLen) {
ByteTransferred = BufLen;
} else {
ByteTransferred = DataLen;
}
RtlCopyMemory(Buf, DataBuf, ByteTransferred);
IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);
DbgPrint("IRP_MJ_READ : End\r\n");
return STATUS_SUCCESS;
}
下面代码展示了相反的操作:
NTSTATUS
PsdoDispatchWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PVOID Buf; //Buffer provided by user program
ULONG BufLen; //Buffer length for user provided buffer
ULONG Offset;//Buffer Offset
PVOID DataBuf; //Buffer provided by Driver
ULONG DataLen; //Buffer length for Driver Data Buffer
ULONG ByteTransferred;
PIO_STACK_LOCATION p_IO_STK;
PDEVICE_EXTENSION p_DVCEXT;
NTSTATUS status;
DbgPrint("IRP_MJ_WRITE : Begin\r\n");
//Get I/o Stack Location & Device Extension
p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
p_DVCEXT = DeviceObject->DeviceExtension;
//Get User Input Buffer & Length
Buf = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);
if (Buf == NULL) {
DbgPrint("Can't get Virtual Address from MDL\r\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
BufLen = MmGetMdlByteCount(Irp->MdlAddress);
Offset = MmGetMdlByteOffset(Irp->MdlAddress);
//Get Driver Data Buffer & Length
DataBuf = p_DVCEXT->DataBuffer;
DataLen = 1024;
IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);
DbgPrint("Input Buffer Length : %d\r\n", BufLen);
DbgPrint("Offset for Buffer in the Memory Page: %d\r\n", Offset);
DbgPrint("Driver Data Length : %d\r\n", DataLen);
if (BufLen <= DataLen) {
ByteTransferred = BufLen;
} else {
ByteTransferred = DataLen;
}
ByteTransferred = BufLen;
RtlZeroMemory(p_DVCEXT->DataBuffer, 1024);
RtlCopyMemory(DataBuf, Buf, ByteTransferred);
IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);
DbgPrint("IRP_MJ_WRITE : End\r\n");
return STATUS_SUCCESS;
}
chinaunix网友2008-03-28 13:24:50
就是翻译了一下,这个谁不会啊,而且翻译得不好! 原文链接http://www.codeproject.com/KB/system/WDM_Driver_development.aspx