在NT式的驱动中,创建设备对象是由IoCreateDevice内核函数完成的。
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject
);
* DriverObject: 输入参数,每个驱动程序中。会有唯一的驱动对象与之对应,但每个驱动对象会有若干个设备对象。DriverObject指向的就是驱动对象指针。
* DeviceExtensionSize:输入参数,指定设备扩展的大小,I/O管理器会根据这个大小,在内存中创建设备扩展,并与驱动对象关联。
* DeviceName:输入参数,设置设备对象的名字。
* DeviceCharacteristics:输入参数,设置设备对象的特征。
* Exclusive:输入参数,设置设备对象是否为内核模式下使用,一般设置为TRUE。
* DeviceObject:输出参数,I/O管理器负责创建这个设备对象,并返回设备对象的地址。
* 返回值:返回此函数的调用状态。
设备名称用UNICODE字符串指定,并且字符串必须是"\Device\[设备名]"的形式。
在Windows下的所有的设备都是以类似名字命名的,例如,磁盘分区的C盘、D盘、E盘、F盘就是被命名为"\Device\HarddiskVolume1"、"\Device\HarddiskVolume2"、"\Device\HarddiskVolume3"、"\Device\HarddiskVolume4"。
当然也可以不指定设备名字,如果在IoCreateDevice中没有指定设备对象的名字,I/O管理器会自动分配一个数字作为设备的设备名,例如"\Device\00000001"、"\Device\00000002"、"\Device\00000003"。
如果指定了设备名,只能被内核模式下的其他驱动所识别。但是在用户模式下的应用程序无法识别这个设备。让用户模式下的应用程序能识别设备有两种办法:
第一种是通过符号链接找到设备;
第二种是通过设备接口找到设备。设备接口的办法在NT驱动中很少使用,经常在在WDM驱动中使用。
符号链接可以理解为设备对象起了一个“别名”。设备对象的名称只能被内核模式的驱动识别,而别名可以被用户模式下的应用程序识别。例如,常说的C盘、D盘就是符号链接。所谓C盘就是名为"C:"的符号链接,其真正的设备对象是“\Device\HarddiskVolume1”,而"D:"所代表的真正的设备对象是“\Device\HarddiskVolume2”。创建符号链接的函数是IoCreateSymbolicLink,其函数声明如下:
NTSTATUS IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
);
* SymbolicLinkName: 输入参数,符号链接的字符串,用UNICODE字符串表示。
* DeviceName:输入参数,设备对象名字的字符串,用UNICODE字符串表示。
* 返回值:返回创建符号链接是否成功。
在内核模式下,符号链接是以“\??\”开头的(或者是“\DosDevices\”开头的)。而在用户模式下,则是以"\\.\"开头的,如C盘就是“\\.\C:”。在HelloDDK中,设备对象的符号链接是“\??\HelloDDK”。
#pragma INITCODE
NTSTATUS CreateDevice (
IN PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
//创建设备
status = IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&pDevObj );
// 判断设备对象是否创建成功
if (!NT_SUCCESS(status))
return status;
// 将设备对象设置为缓冲区设备
pDevObj->Flags |= DO_BUFFERED_IO;
// 得到设备扩展
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
// 设置设备扩展的设备对象
pDevExt->pDevice = pDevObj;
// 设置设备扩展中的设备名称
pDevExt->ustrDeviceName = devName;
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
pDevExt->ustrSymLinkName = symLinkName;
// 创建符号链接
status = IoCreateSymbolicLink( &symLinkName,&devName );
// 判断是否成功创建符号链接
if (!NT_SUCCESS(status))
{
// 删除符号链接
IoDeleteDevice( pDevObj );
return status;
}
return STATUS_SUCCESS;
}
其中在创建设备对象的时候,指定的设备类型是FILE_DEVICE_UNKNOWN,说明此设备是常用设备之外的设备,一般虚拟设备使用这个作为设备类型。在创建设备对象后,对Flags的DO_BUFFERED_IO位进行设置,这里是将设备设置成“缓冲区设备”。关于将设备设置成“缓冲区设备”或者“直接设备”,后面在详细说明。随后设定设备的设备扩展,设备扩展是程序员自己定义的,在这里我们使用的非常简单:
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //设备名称
UNICODE_STRING ustrSymLinkName; //符号链接名
PUCHAR MemBar0; // 内存基地址0
PUCHAR MemBar1; // 内存基地址1
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
* pDevice: 设备对象中的 DeviceExtension 指向设备扩展,pDevice可以指回设备对象。
* ustrDeviceName:设备名
* ustrSymLinkName:符号链接名
* MemBar0、MemBar1:外设的基地址
在设备扩展中记录以上几个信息,以备其他回调函数或者派遣函数使用,使用的时候,只需从驱动设备中获取,类似于以下使用方式:
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;