内存空间分为用户层和系统层,普通的应用程序只能运行在用户层,为了可以操作系统层的内存 所以引入了驱动程序,有了驱动就可以通过用户层来操作系统层的内存及函数,所以驱动就是应用层和系统层之间的一个桥梁
在应用层通过创建符号链接,自动产生驱动层的IRP事件,即可执行系统层的IRP函数,从而将应用层的数据传到系统层。
首先加载驱动使得系统层存在一个符号链接,然后应用层就可以创建跟系统层同名的符号链接
其实本质上是驱动加载完成时会产生一块共享内存用于R3和R0数据交换,控制码用于控制读写哪块内存
R0创建驱动对象->R0创建驱动设备->R0创建符号链接->R3打开符号链接->R3传入控制码(读、写)->R0执行IRP函数 -> R0根据控制码判断读写哪块共享内存->R3收到R0的读写结果 ->R0删除符号链接 ->R0删除驱动设备
#include<ntifs.h>
//控制码与用户层保持一致
#define ReadCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x803,METHOD_BUFFERED,FILE_ANY_ACCESS) //读控制码
#define WriteCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x804,METHOD_BUFFERED,FILE_ANY_ACCESS) //写控制码
#define RWCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x805,METHOD_BUFFERED,FILE_ANY_ACCESS) //读写控制码
void IRP_IO_Read(PIRP pirp)
char* buff = (char*)pirp->AssociatedIrp.SystemBuffer;
//获取R3传来的参数(控制码)
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(pirp);
//将R0读取到的数据写入到向共享缓冲区
char R0returnbuf[] = "zxxx R0 read data \n";
ULONG len = sizeof(R0returnbuf);
memcpy_s(buff, len, R0returnbuf, len);
KdPrint(("zxxx IRP_IO_Read read data to SystemBuffer \n"));
//每次IRP执行完了 要执行下面三行 作为返回结果
pirp->IoStatus.Status = STATUS_SUCCESS;
pirp->IoStatus.Information = len; //共享缓冲区返回的长度
IoCompleteRequest(pirp, IO_NO_INCREMENT);
void IRP_IO_Write(PIRP pirp)
void IRP_IO_ReadWrite(PIRP pirp)
//创建驱动对象->创建驱动设备->创建符号链接->使用符号链接 ->删除符号链接 ->删除驱动设备
//当用户层打开符号链接时 会产生IRP事件执行IRP函数 通过IRP函数与内核通信
//创建驱动对象并绑定符号链接
NTSTATUS CreateDevice(PDRIVER_OBJECT driver)
NTSTATUS status;
UNICODE_STRING MyDriver; //驱动名称
PDEVICE_OBJECT device; //驱动设备
RtlInitUnicodeString(&MyDriver,L"\\DEVICE\\MyDriver");//初始化驱动名称
//在驱动对象上创建驱动设备
status = IoCreateDevice(driver, sizeof(driver->DriverExtension),&MyDriver,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&device);
if (status == STATUS_SUCCESS)
KdPrint(("zxxx 驱动设备对象创建成功 \n"));
//创建符合链接
UNICODE_STRING uzSymbolName;
RtlInitUnicodeString(&uzSymbolName,L"\\??\\MyDriver"); //初始化符号链接 符号链接格式 L"\\??\\名字
//为驱动设备绑定符号链接 后续不会使用驱动对象与内核交换,而是使用符号链接与内核交换
status = IoCreateSymbolicLink(&uzSymbolName,&MyDriver);
if (status == STATUS_SUCCESS)
KdPrint(("zxxx 符号链接创建成功 %wZ \n",&uzSymbolName));
KdPrint(("zxxx 符号链接创建失败 %wZ \n", &uzSymbolName));
KdPrint(("zxxx 驱动设备对象创建失败 \n"));
IoDeleteDevice(device);
return status;
//传入驱动设备的IRP事件
NTSTATUS IRP_CALL(PDEVICE_OBJECT device,PIRP pirp)
device;
KdPrint(("zxxx 发生IRP事件 进入IRP函数 \n"));
PIO_STACK_LOCATION irpStackL;
irpStackL = IoGetCurrentIrpStackLocation(pirp);
switch (irpStackL->MajorFunction)
case IRP_MJ_CREATE:
KdPrint(("zxxx IRP_MJ_CREATE \n"));
break;
case IRP_MJ_CLOSE:
KdPrint(("zxxx IRP_MJ_CLOSE \n"));
break;
case IRP_MJ_DEVICE_CONTROL:
KdPrint(("zxxx IRP_MJ_DEVICE_CONTROL \n"));
//取到的R3的控制码
UINT32 CtlCode = irpStackL->Parameters.DeviceIoControl.IoControlCode;
KdPrint(("zxxx IRP_MJ_DEVICE_CONTROL R0控制码:%X \n", CtlCode));
if ( CtlCode == ReadCtl )
KdPrint(("zxxx IRP_MJ_DEVICE_CONTROL ReadCtl R0控制码:%X \n", CtlCode));
IRP_IO_Read(pirp); //这里写入到共享缓冲剂即可,打印R3访问共享缓冲区打印
return STATUS_SUCCESS;
else if ( CtlCode == WriteCtl )
KdPrint(("zxxx IRP_MJ_DEVICE_CONTROL WriteCtl R0控制码:%X \n", CtlCode));
//取出R3缓冲区的数据
//根据控制代码来选择使用AssociatedIrp.SystemBuffer的读缓冲区还是写缓冲区
char* R3buff = (char*)pirp->AssociatedIrp.SystemBuffer;
KdPrint(("zxxx IRP_MJ_DEVICE_CONTROL R0缓冲区:%s \n", R3buff));
else if (CtlCode == RWCtl )
KdPrint(("zxxx IRP_MJ_DEVICE_CONTROL RWCtl R0控制码:%X \n", CtlCode));
break;
//注意 只要pirp这个对象发生变化 就要跟着下面这三行
pirp->IoStatus.Status = STATUS_SUCCESS;
pirp->IoStatus.Information = 4;
IoCompleteRequest(pirp,IO_NO_INCREMENT);
KdPrint(("zxxx 结束IRP事件 离开IRP函数 \n"));
return STATUS_SUCCESS;
//卸载驱动
void Unload(PDRIVER_OBJECT pDriver)
KdPrint(("zxxx unload %p \n", pDriver));
//先删除符号链接
//再删除驱动设备
if (pDriver->DeviceObject)
UNICODE_STRING uzSymbolName;
RtlInitUnicodeString(&uzSymbolName, L"\\??\\MyDriver");
IoDeleteSymbolicLink(&uzSymbolName);
IoDeleteDevice(pDriver->DeviceObject);
KdPrint(("zxxx 删除符号链接 \n"));
KdPrint(("zxxx 删除驱动设备 \n"));
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
//初始化驱动对象
DriverObject->DriverUnload = Unload; //指定卸载驱动函数
DriverObject->MajorFunction[IRP_MJ_CREATE] = IRP_CALL; //指定IRP事件函数
DriverObject->MajorFunction[IRP_MJ_CLOSE] = IRP_CALL; //指定IRP事件函数
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IRP_CALL; //指定IRP事件函数
//创建驱动设备
CreateDevice(DriverObject);
RegistryPath;
KdPrint(("zxxx entry \n"));
return 0;
应用层(MFC)
//通过不同的控制码让驱动执行不同的函数
//控制码在用户层和驱动层都需要定义且保持一致
#include<winioctl.h>
#define ReadCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x803,METHOD_BUFFERED,FILE_ANY_ACCESS) //读控制码
#define WriteCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x804,METHOD_BUFFERED,FILE_ANY_ACCESS) //写控制码
#define RWCtl CTL_CODE(FILE_DEVICE_UNKNOWN,0x805,METHOD_BUFFERED,FILE_ANY_ACCESS) //读写控制码
static HANDLE DeviceHandle = NULL;
//打开驱动按钮
void CMFCApplication1Dlg::OnBnClickedButton1()
// TODO: 在此添加控件通知处理程序代码
//创建驱动
//执行 CreateFileW 会触发IRP事件 执行IRP函数 IRP_MJ_CREATE被执行
DeviceHandle = CreateFileW(
L"\\??\\MyDriver", //符号链接
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
//关闭驱动按钮
void CMFCApplication1Dlg::OnBnClickedButton2()
// TODO: 在此添加控件通知处理程序代码
//执行 CloseHandle 会触发IRP事件 执行IRP函数 IRP_MJ_CLOSE被执行
CloseHandle(DeviceHandle);
//读数据按钮
void CMFCApplication1Dlg::OnBnClickedButton3()
// TODO: 在此添加控件通知处理程序代码
DWORD dwRetSize = 0;
typedef struct TINPUT_BUF
DWORD m_arg1;
DWORD m_arg2;
DWORD m_arg3;
DWORD m_arg4;
DWORD m_arg5;
DWORD m_arg6;
}TINPUT_BUF;
//打印控制码 测试 验证R3与R0是否一致
char buftest[256];
sprintf_s(buftest, "zxxx R3 控制码:%x", ReadCtl);
OutputDebugStringA(buftest);
char WriteData[100] = "zxxx R3 DeviceIoControl read test \n";
TINPUT_BUF inBuf = { 0 }; //写数据 R3的数据写入到R0
CHAR OutBuf[512] = { 0 }; //输出缓冲区 //读数据 R0的数据读出到R3
//IRP函数DeviceIoControl
//这里的控制码+缓冲区 指明了R3读(写)了哪块共享内存,后续驱动用控制码即可执行对应的读写操作
DeviceIoControl(
DeviceHandle, //CreateFile 打开驱动设备返回的句柄
ReadCtl, //控制码 CTL_CODE 与IRP事件对应
WriteData, //输入缓冲区 &inBuf
sizeof(inBuf), //输入缓冲区大小
&OutBuf, //输出缓冲区
512, //输出缓冲区大小
&dwRetSize, //返回字节数
//打印返回参数
CString csStr;
csStr.Format(L"zxxx R3读到的数据 %x \n", OutBuf[0]);
OutputDebugStringA("zxxx R3读到的数据 \n");
strcat_s(OutBuf," zxxx R3");
//R3读取到的结果
OutputDebugStringA(OutBuf);
//写数据按钮
void CMFCApplication1Dlg::OnBnClickedButton4()
// TODO: 在此添加控件通知处理程序代码
DWORD dwRetSize = 0;
typedef struct TINPUT_BUF
DWORD m_arg1;
DWORD m_arg2;
DWORD m_arg3;
DWORD m_arg4;
DWORD m_arg5;
DWORD m_arg6;
}TINPUT_BUF;
//打印控制码 测试 验证R3与R0是否一致
char buftest[256];
sprintf_s(buftest,"zxxx R3 控制码:%x", WriteCtl);
OutputDebugStringA(buftest);
char WriteData[100] = "zxxx R3 DeviceIoControl write test \n";
//打印缓冲区 测试 验证R3与R0是否一致
char buftest2[100];
memcpy(buftest2, WriteData,100);
OutputDebugStringA(buftest2);
TINPUT_BUF inBuf = { 1,2,3,4,5,0x6ABC666 }; //写数据 R3的数据写入到R0
DWORD OutBuf[6] = { 0 }; //输出缓冲区 //读数据 R0的数据读出到R3
//IRP函数DeviceIoControl
//这里的控制码+缓冲区 指明了R3读(写)了哪块共享内存,后续驱动使用控制码即可执行对应的读写操作
DeviceIoControl(
DeviceHandle, //CreateFile 打开驱动设备返回的句柄
WriteCtl, //控制码 CTL_CODE 与IRP事件对应
WriteData, //输入缓冲区 &inBuf
sizeof(inBuf), //输入缓冲区大小
&OutBuf, //输出缓冲区
sizeof(OutBuf), //输出缓冲区大小
&dwRetSize, //返回字节数
MFC一些配置问题:
直接设备读写
其原理是锁定用户空间内存(这解决了分页内存置换导致缺页异常的问题)并将其映射到内核空间地址,直接对该内核空间地址进行写入即可,由于进程的内核空间是共享的。所以属于任何进程的线程都可以写入。
注意的点:
1. 要为设备添加DO_DIRECT_IO标志
2. 为R3下ReadFile和WriteFile派遣函数时获取对对应传入参数
a. 其中WriteFile的用户输入内存地址和ReadFile的设备输出地址通过MmGetSystemAddressForMdlSafe函数锁定IRP.
在上一节课我们证实了在用户层调用CreateFile函数时,相应的在驱动层会响应一个IRP_MJ_CREATE的事件。
这节课我们来看看用户层和驱动层是怎么交换数据的。
首先来介绍一下控制码,由CTL_CODE宏创建,是一个唯一的32位系统I/O控制代码,这个控制代码包括4部分组成:
DeviceType(设备类型,高16位(16-31位)),
Function(功能 2-13位),
Method(I/O传递的方式),有4种(METHOD_BUFFERED,METHOD_IN_DIRECT,METHOD_