同我们学习c语言的时候一样,首先要从程序入口点开始。在驱动程序中,这个入口点就是DriverEntry函数(相当于c语言的main函数),它在驱动程序被加载进内存的时候调用。DriverEntry函数有两个参数,其中第一个参数PDRIVER_OBJECT pDriverObj是指向该驱动程序对应的驱动程序对象的指针。在DriverEntry函数中,一个重要的任务就是要设定驱动程序对象的几个函数指针,这样,该驱动程序对象关联的设备对象在接收到上层的IRP的时候,就会通过驱动程序对象中设置的函数指针,找到相应的函数来做处理:
pDriverObj->DriverUnload = DriverUnload;
pDriverObj->MajorFunction[IRP_MJ_CREATE] =
pDriverObj->MajorFunction[IRP_MJ_CLOSE] =
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch;
除此之外,DriverEntry函数还有一个重要的任务,就是要创建设备对象并为其建立符号连接。(这里说明一下,在规范的WDM程序中,创建设备对象的任务本来该由AddDevice函数来做,而这个函数也是通过驱动程序对象的一个函数指针来定位的。在这种规范的WDM程序中,一旦有新硬件加入,系统就会自动通过驱动程序对象的函数指针找到AddDevice函数,并调用它来创建设备对象。但是在这里,我并不是在为实际存在的硬件写驱动,而只是写一个内核模式下的程序,因此就只需要在DriverEntry函数中创建一个设备对象就行了。)
IoCreateDevice( pDriverObj,
0,
&deviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
true,
&pDeviceObj ); //创建设备对象
IoCreateSymbolicLink( &linkName, &deviceName ); //建立符号连接
从上面调用IoCreateDevice函数的参数中还可以看出,设备对象和驱动程序对象是相关联的,这也就可以解释为什么是设备接收到IRP,而相应的处理函数却是由驱动程序对象中的函数指针定位的。至于建立符号连接,那是为了方便在用户模式中调用设备对象。
从前面设定的驱动程序对象中的函数指针可以看到,主要有两个函数:卸载函数DriverUnload和派遣函数DriverDispatch。DriverUnload函数应该很容易理解,它是在驱动程序被卸载出内存的时候调用,主要做一些释放内存之类的工作。而在我的这个程序中,所有的IRP都是在同一个函数里面进行处理的,这就是派遣函数DriverDispatch(实际上很多WDM程序都是这样做的)。下面就分别介绍一下这两个函数。
DriverUnload函数的主要任务是将创建的设备对象和符号连接删除掉,当然如果在程序中还分配了其他内存需要释放,也是在这里完成。
IoDeleteSymbolicLink( &linkName );
IoDeleteDevice( pDriverObj->DeviceObject );
派遣函数DriverDispatch主要负责处理上层的IRP。这里先要提一下,每个IRP都与两个数据结构相关联,就是IRP本身和IRP Stack——IO_STACK_LOCATION结构。在这两个结构里面,包含了所有上层传递给本层设备对象的信息。最重要的一个信息就是:在IO_STACK_LOCATION结构中,包含了IRP的功能码MajorFunction和MinorFunction(IRP的功能码标识了该IRP具体是什么请求,比如读请求的MajorFunction值为IRP_MJ_READ)。
DriverDispatch函数的处理流程一般是这样的:首先通过IRP获得IPR Stack;然后从IRP Stack中得到该IRP的主功能码MajorFunction,判断主功能码并做相应处理;处理完该请求后,根据具体情况选择完成该请求或者向下一层设备对象传递该IRP。获得IRP Stack很简单,只需要调用函数IoGetCurrentIrpStackLocation即可:
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
判断主功能码并做相应的处理这一步一般是由一个switch-case语句实现的:
switch( pIrpStack->MajorFunction )
{
case IRP_MJ_CREATE:
DbgPrint( "Info: Create!\n" );
break;
case IRP_MJ_CLOSE:
DbgPrint( "Info: Close!\n" );
break;
case IRP_MJ_DEVICE_CONTROL:
{
switch( pIrpStack->Parameters.DeviceIoControl.IoControlCode )
{
case IOCTL_GET_INFO:
{
RtlCopyMemory( pIrp->UserBuffer, "This is a test driver!", 23 );
information = 23;
break;
}
default:
break;
}
}
default:
break;
}
最后一步,如果需要完成该请求,那么应该先设置IRP结构中的IoStatus域,然后调用函数IoCompleteRequest:
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = information;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
如果需要向下一层设备对象传递该IRP,则要先初始化往下传递的IRP对应IRP Stack(可以直接将当前的IRP Stack复制给下层IRP Stack),然后调用IoCallDriver函数往下层传递该IRP:
IoCopyCurrentIrpStackLocationToNext(pIrp);
status = IoCallDriver(pLowerDeviceObj, pIrp);
以上就是对我写的这个驱动试验程序的简单分析,也是我对WDM驱动程序框架结构的一个初步理解。由于我只是初学WDM,因此一定有很多理解错误或者遗漏的地方。
下面,我再简单介绍一下怎样从用户模式的程序中调用驱动。用户模式的程序要调用驱动,首先就要打开设备,也就是驱动程序中创建的设备对象。这可以通过调用CreateFile函数来实现。CreateFile函数本来是用于打开文件,它的第一个参数就是文件名。而这里,我们以设备名作为它的第一个参数传入,那么该函数打开的就是设备了。这里所说的设备名,实际上是驱动程序里面为设备对象建立的符号连接名。比如用户模式中给出的设备名为” \\.\MyDevice”,I/O管理器在执行名称搜索前先自动把”\\.\”转换成”\??\”,这样就成了” \??\MyDevice”,这就是驱动程序里面建立的符号连接名了。打开设备后,用户模式的程序就可以调用ReadFile、WriteFile和DeviceIoControl等函数向驱动程序发出请求了。
最后,给出我的试验程序的源码。
//Driver部分:
#ifdef __cplusplus
extern "C" {
#endif
#include "ntddk.h"
#define DEVICE_NAME L"\\Device\\MyDevice"
#define LINK_NAME L"\\??\\MyDevice"
#define IOCTL_GET_INFO \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
void DriverUnload( PDRIVER_OBJECT pDriverObj );
NTSTATUS DriverDispatch( PDEVICE_OBJECT pDeviceObj, PIRP pIrp );
// 驱动程序加载时调用DriverEntry例程:
NTSTATUS DriverEntry( PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString )
{
DbgPrint( "DriverEntry!\n" );
NTSTATUS status;
PDEVICE_OBJECT pDeviceObj;
UNICODE_STRING deviceName;
RtlInitUnicodeString( &deviceName, DEVICE_NAME );
status = IoCreateDevice( pDriverObj, 0, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, true, &pDeviceObj );
if( !NT_SUCCESS( status ) )
{
DbgPrint( "Error: Create device failed!\n" );
return status;
}
UNICODE_STRING linkName;
RtlInitUnicodeString( &linkName, LINK_NAME );
status = IoCreateSymbolicLink( &linkName, &deviceName );
if( !NT_SUCCESS( status ) )
{
DbgPrint( "Error: Create symbolic link failed!\n" );
IoDeleteDevice( pDeviceObj );
return status;
}
pDriverObj->DriverUnload = DriverUnload;
pDriverObj->MajorFunction[IRP_MJ_CREATE] =
pDriverObj->MajorFunction[IRP_MJ_CLOSE] =
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch;
return STATUS_SUCCESS;
}
void DriverUnload( PDRIVER_OBJECT pDriverObj )
{
DbgPrint( "DriverUnload!\n" );
if( pDriverObj->DeviceObject != NULL )
{
UNICODE_STRING linkName;
RtlInitUnicodeString( &linkName, LINK_NAME );
IoDeleteSymbolicLink( &linkName );
IoDeleteDevice( pDriverObj->DeviceObject );
}
return;
}
NTSTATUS DriverDispatch( PDEVICE_OBJECT pDeviceObj, PIRP pIrp )
{
ULONG information = 0;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
switch( pIrpStack->MajorFunction )
{
case IRP_MJ_CREATE:
DbgPrint( "Info: Create!\n" );
break;
case IRP_MJ_CLOSE:
DbgPrint( "Info: Close!\n" );
break;
case IRP_MJ_DEVICE_CONTROL:
{
switch( pIrpStack->Parameters.DeviceIoControl.IoControlCode )
{
case IOCTL_GET_INFO:
{
RtlCopyMemory( pIrp->UserBuffer, "This is a test driver!", 23 );
information = 23;
break;
}
default:
break;
}
}
default:
break;
}
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = information;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
#ifdef __cplusplus
}
#endif
//用户模式程序部分:
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#define DEVICE_NAME "\\\\.\\MyDevice"
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
#define FILE_DEVICE_UNKNOWN 0x00000022
#define METHOD_NEITHER 3
#define FILE_ANY_ACCESS 0
#define IOCTL_GET_INFO \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
int main(int argc, char* argv[])
{
HANDLE hDevice = CreateFile( DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if( hDevice == INVALID_HANDLE_VALUE )
{
printf( "Error: Can't open the device!\n" );
return -1;
}
unsigned long numOfBytesReturned;
char info[32] = {0};
if( DeviceIoControl( hDevice, IOCTL_GET_INFO, NULL, 0, info, 32, &numOfBytesReturned,
NULL ) == true )
printf( "Information: %s \n", info );
CloseHandle( hDevice );
Sleep( 3000 );
return 0;
}