Windows驱动编程基础教程 联系客服

发布时间 : 星期日 文章Windows驱动编程基础教程更新完毕开始阅读01cc64564b73f242326c5f2a

楚狂人Windows驱动编程基础教程

为了保证交互的成功与安全性,应该用服务程序与之交互。 但是依然有时候必须用普通用户打开设备。为了这个目的,设备必须是对所有的用户开放的。此时不能用IoCreateDevice。必须用IoCreateDeviceSecure。这个函数的原型如下:

NTSTATUS

IoCreateDeviceSecure(

IN PDRIVER_OBJECT DriverObject, IN ULONG DeviceExtensionSize,

IN PUNICODE_STRING DeviceName OPTIONAL, IN DEVICE_TYPE DeviceType,

IN ULONG DeviceCharacteristics, IN BOOLEAN Exclusive,

IN PCUNICODE_STRING DefaultSDDLString, IN LPCGUID DeviceClassGuid,

OUT PDEVICE_OBJECT *DeviceObject

)

这个函数增加了两个参数(其他的没变)。一个是DefaultSDDLString。这个一个用于描述权限的字符串。描述这个字符串的格式需要大量的篇幅。但是没有这个必要。字符串“D:P(A;;GA;;;WD)”将满足“人人皆可以打开”的需求。

另一个参数是一个设备的GUID。请随机手写一个GUID。不要和其他设备的GUID冲突(不要复制粘贴即可)。

下面是例子:

// 随机手写一个GUID

const GUID DECLSPEC_SELECTANY MYGUID_CLASS_MYCDO = {0x26e0d1e0L, 0x8189, 0x12e0, {0x99,0x14, 0x08, 0x00, 0x22, 0x30, 0x19, 0x03}}; // 全用户可读写权限 UNICODE_STRING sddl =

RLT_CONSTANT_STRING(L\// 生成设备

status = IoCreateDeviceSecure( DriverObject, 0,

&device_name,

FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddl,

(LPCGUID)&SFGUID_CLASS_MYCDO, &device);

使用这个函数的时候,必须连接库wdmsec.lib。

楚狂人Windows驱动编程基础教程

7.5 符号链接的用户相关性

从前面的例子看,符号链接的命名貌似很简单。简单的符号链接(之所以称为简单,是因为还有一种使用GUID的符号链接,这在本书讨论范围之外)总是命名在\\DosDevices\\之下。但是实际上这会有一些问题。

比较高级的Windows系统(哪个版本的操作系统很难讲,可能必须判定补丁号),符号链接也带有用户相关性。换句话说,如果一个普通用户创建了符号链接“\\DosDevices\\MyCDOSL”,那么,其实其他的用户是看不见这个符号链接的。

但是读者又会发现,如果在DriverEntry中生成符号链接,则所有用户都可以看见。原因是DriverEntry总是在进程“System”中执行。系统用户生成的符号链接是大家都可以看见的。

当前用户总是取决于当前启动当前进程的用户。实际编程中并不一定要在DriverEntry中生成符号链接。一旦在一个不明用户环境下生成符号链接,就可能出现注销然后换用户登录之后,符号链接“不见了”的严重错误。这也是常常让初学者抓狂几周都不知道如何解决的一个问题。

其实解决的方案很简单,任何用户都可以生成全局符号链接,让所有其他用户都能看见。路径“\\DosDevices\\MyCDOSL”改为“\\DosDevices\\Global\\MyCDOSL”即可。

但是在不支持符号链接用户相关性的系统上,生成“\\DosDevices\\Global\\MyCDOSL”这样的符号链接是一种错误。为此必须先判断一下。幸运的是,这个判断并不难。下面是一个例子,这个例子生成的符号链接总是随时可以使用,不用担心用户注销:

UNICODE_STRING device_name; UNICODE_STRING symbl_name;

if (IoIsWdmVersionAvailable(1, 0x10)) {

// 如果是支持符号链接用户相关性的版本的系统,用\\DosDevices\\Global. RtlInitUnicodeString(&symbl_name, L\ } else {

// 如果是不支持的,则用\\DosDevices

RtlInitUnicodeString(&symbl, L\ }

// 生成符号链接

IoCreateSymbolicLink(&symbl_name, &device_name);

楚狂人Windows驱动编程基础教程

第八章 处理请求

8.1 IRP与IO_STACK_LOCATION

开发一个驱动要有可能要处理各种IRP。但是本书范围内,只处理为了应用程序和驱动交互而产生的IRP。IRP的结构非常复杂,但是目前的需求下没有必要去深究它。应用程序为了和驱动通信,首先必须打开设备。然后发送或者接收信息。最后关闭它。这至少需要三个IRP:第一个是打开请求。第二个发送或者接收信息。第三个是关闭请求。 IRP的种类取决于主功能号。主功能号就是前面的说的DRIVER_OBJECT中的分发函数指针数组中的索引。打开请求的主功能号是IRP_MJ_CREATE,而关闭请求的主功能号是IRP_MJ_CLOSE。

如果写有独立的处理IRP_MJ_CREATE和IRP_MJ_CLOSE的分发函数,就没有必要自然判断IRP的主功能号。如果像前面的例子一样,使用一个函数处理所有的IRP,那么首先就要得到IRP的主功能号。IRP的主功能号在IRP的当前栈空间中。

IRP总是发送给一个设备栈。到每个设备上的时候拥有一个“当前栈空间”来保存在这个设备上的请求信息。读者请暂时忽略这些细节。下面的代码在MyDispatch中获得主功能号,同时展示了几个常见的主功能号:

NTSTATUS MyDispatchFunction(PDEVICE_OBJECT device,PIRP irp) {

// 获得当前irp调用栈空间

PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp); NTSTATUS status = STATUS_UNSUCCESSFUL; swtich(irpsp->MajorFunction) {

// 处理打开请求 case IRP_MJ_CREATE: ?? break;

// 处理关闭请求 case IRP_MJ_CLOSE: ?? break;

// 处理设备控制信息

case IRP_MJ_DEVICE_CONTROL: ?? break; // 处理读请求 case IRP_MJ_READ: ?? break;

楚狂人Windows驱动编程基础教程

}

// 处理写请求

case IRP_MJ_WRITE: ?? break; default: ? break; }

return status;

用于与应用程序通信时,上面这些请求都由应用层API引发。对应的关系大致如下:

应用层调用的API CreateFile CloseHandle DeviceIoControl ReadFile WriteFile 驱动层收到的IRP主功能号 IRP_MJ_CREATE IRP_MJ_CLOSE IRP_MJ_DEVICE_CONTROL IRP_MJ_READ IRP_MJ_WRITE

了解以上信息的情况下,完成相关IRP的处理,就可以实现应用层和驱动层的通信了。具体的编程在紧接后面的两小节里完成。

8.2 打开与关闭的处理

如果打开不能成功,则通信无法实现。要打开成功,只需要简单的返回成功就可以了。在一些有同步限制的驱动中(比如每次只允许一个进程打开设备)编程要更加复杂一点。但是现在忽略这些问题。暂时认为我们生成的设备任何进程都可以随时打开,不需要担心和其他进程冲突的问题。

简单的返回一个IRP成功(或者直接失败)是三部曲,如下:

1. 设置irp->IoStatus.Information为0。关于Information的描述,请联系前面关

于IO_STATUS_BLOCK结构的解释。 2. 设置irp->IoStatus.Status的状态。如果成功则设置STATUS_SUCCESS,否则设置

错误码。

3. 调用IoCompleteRequest (irp,IO_NO_INCREMENT)。这个函数完成IRP。

以上三步完成后,直接返回irp->IoStatus.Status即可。示例代码如下。这个函数能完成打开和关闭请求。

NTSTATUS

MyCreateClose(

IN PDEVICE_OBJECT device, IN PIRP irp) {