应用程序和驱动程序的通信
一、基础介绍
1.1 设备与驱动的关系
设备由驱动去创建,访问一个设备,是首先得访问驱动。如果驱动在卸载的时候没有删除符号,r3下也是不能去访问设备的。
驱动程序和系统其他组件之间的交互是通过给设备发送或者接受发给设备的请求来交互的。换句话说,一个没有任何设备的驱动是不能按规范方式和系统交互的。当然也不会收到任何IRP,分发函数也失去了意义。如果驱动程序要和应用程序之间通信,则需要生成设备。此外还必须为设备生成应用程序可以访问的符号链接。
也就是说,应用程序与驱动的交互是通过设备来完成的,设备成了中间桥梁。下文中的符号链接又成了设备名的中间桥梁。记住,核心中介是设备。其他都是为他们的通信提供的机制。
1.2 设备符号链接名与设备名
Ring3不能直接访问设备,需要中间桥梁。这个中间桥梁是Ring0驱动为设备名注册的链接符号(Symbol Link)。
从图中可以看出,R3要访问设备,是通过设备符号名去访问设备的,而R3应用层下的设备符号名和设备管理器中的设备符号名有出入,必须,也只能写成\.\设备符号名 的方式 ,系统会自动转为??\设备符号名 ,紧接着通过设备符号名这个中间桥梁可以转化为真正的R0下的设备名。
也就是说,符号链接名在应用层只是简单的符号转化过程,有些类似宏定义,设备符号名主体是相同的。而设备名和设备符号名可以不相同。因为设备符号名仅仅是个中间桥梁。
总结:
- R0下的设备名格式为
\Device\自定义设备名
- R0下的设备符号名格式为
\??\自定义符号名
,其中自定义符号名
和自定义设备名
可以不一致。 - R3下不能直接访问设备,只能用设备符号名去访问设备,但是也不能直接使用
\??\设备符号名
去获得设备名 - R3要访问设备,必须使用
\\.\设备符号名
,这个设备符号名由R0给定。
1.3 通信机制
- 应用程序和驱动程序的通信是通过
IRP (i/o request packet) (IO请求包)
来完成的。 - 利用其它进程通信方式,如socket、pip、共享内存等(注意r0的api调用方式不一致)
二、IRP方式
2.1 定义IO控制码
可以看做一种通信协议,宏定义:
#define CTL_CODE( DeviceType, Function, Method, Access ) (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
可以看到,这个宏四个参数,自然是一个32位分成了4部分:
- 高16位存储设备类型
- 14~15位访问权限
- 2~13位操作功能
- 最后0/1两位就是确定缓冲区是如何与I/O和文件系统数据缓冲区进行数据传递方式
自定义CTL_CODE
#define IOCTL_TEST_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
- IOCTL_TEST_BUFFERED:生成的IRP的MinorFunction
- FILE_DEVICE_UNKNOWN:设备对象的类型。设备类型可参考:http://blog.csdn.net/liyun123gx/article/details/38058965
- 0x900 :自定义的IO控制码。自己定义时取0x800到0xFFF,因为0x0到0x7FF是微软保留的。
- METHOD_BUFFERED:缓冲区进行数据传递方式
- FILE_ANY_ACCESS :数据的操作模式。
内存访问方式
有四种方式:
#define METHOD_BUFFERED 0
#define METHOD_IN_DIRECT 1
#define METHOD_OUT_DIRECT 2
#define METHOD_NEITHER 3
METHOD_BUFFERED:表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer来缓冲,因此这种方式的通信比较安全。
METHOD_IN_DIRECT或METHOD_OUT_DIRECT:表示系统会将输入缓冲在pIrp->AssociatedIrp.SystemBuffer中,并将输出缓冲区锁定,然后在内核模式下重新映射一段地址,这样也是比较安全的。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别,仅在于打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT方式的IoControl将会成功,而METHOD_OUT_DIRECT方式将会失败。如果以读写权限打开设备,两种方式都会成功。
METHOD_NEITHER:“其他方式”,虽然通信的效率提高了,但是不够安全。驱动的派遣函数中输入缓冲区可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。由于驱动中的派遣函数不能保证传递进来的用户输入和输出地址,因此最好不要直接去读写这些地址的缓冲区。应该在读写前使用ProbeForRead和ProbeForWrite函数探测地址是否可读和可写。
METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址,驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。
由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。
2.2 定义驱动设备名,符号链接名
关于在Ring0层中要设置驱动设备名的同时还要设置符号链接名的原因,是因为只有符号链接名才可以被用户模式下的应用程序识别。
如果IoCreateDevice中没有指定设备名称,那么I/O管理器会自动分配一个数字作为设备的名称。
// 驱动设备名:设备与设备之间通信
#define DEVNAME L"\\Device\\kmdfDriver2"
// 符号链接名:设备与Ring3之间通信
#define LNKNAME L"\\??\\kmdfDriver2"
2.3 设备同驱动通信
驱动程序要做的最后一步,先用IoCreateDevice函数创建设备对象,再用IoCreateSymbolicLink将符号链接名与设备对象名称关联,大功告成,等待IO控制码。文章来源:https://www.toymoban.com/news/detail-405179.html
// 创建设备对象名称
RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
// 创建设备对象
Status = IoCreateDevice(DriverObject,NULL,
&DeviceObjectName,
FILE_DEVICE_UNKNOWN,
0, FALSE,
&DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
}
// 创建设备连接名称
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
// 将设备连接名称与设备名称关联
Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);
if (!NT_SUCCESS(Status))
{
IoDeleteDevice(DeviceObject);
return Status;
}
驱动程序铺垫打理好之后,应用程序就可以由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个DeviceHandle发送控制码了。文章来源地址https://www.toymoban.com/news/detail-405179.html
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice, // CreateFile函数打开的设备句柄
_In_ DWORD dwIoControlCode,// 自定义的控制码
_In_opt_ LPVOID lpInBuffer, // 输入缓冲区
_In_ DWORD nInBufferSize, // 输入缓冲区的大小
_Out_opt_ LPVOID lpOutBuffer, // 输出缓冲区
_In_ DWORD nOutBufferSize, // 输出缓冲区的大小
_Out_opt_ LPDWORD lpBytesReturned, // 实际返回的字节数,对应驱动程序中pIrp->IoStatus.Information。
_Inout_opt_ LPOVERLAPPED lpOverlapped // 重叠操作结构指针。同步设为NULL,DeviceIoControl将进行阻塞调用;否则,应在编程时按异步操作设计
);
HANDLE CreateFile(
LPCTSTR lpFileName, // 打开的文件名
DWORD dwDesiredAccess, // 访问权限
DWORD dwShareMode, // 共
到了这里,关于windows驱动开发7:应用程序和驱动程序的通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!