做驱动开发的肯定会遇到应用层与内核层的通信的问题,首先说内核层与应用层的通信可以大概分为两个方面,第一是应用层向内核层主动传递消息,第二是内核层主动与应用层通信。下面我们将分开来谈两个方面。
我们先来看应用层向内核层传递的方法:
BOOL DeviceIoControl (
HANDLE hDevice, // 设备句柄
DWORD dwIoControlCode, // IOCTL请求操作代码
LPVOID lpInBuffer, // 输入缓冲区地址
DWORD nInBufferSize, // 输入缓冲区大小
LPVOID lpOutBuffer, // 输出缓冲区地址
DWORD nOutBufferSize, // 输出缓冲区大小
LPDWORD lpBytesReturned, // 存放返回字节数的指针
LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped结构体指针
);
DeviceIoControl 这个函数是重点中的重点,几乎所有应用层与内核层的通信与此函数有关,发送控制代码直接到指定的设备驱动程序,使相应的移动设备以执行相应的操作。
驱动程序可以通过CTL_CODE宏来组合定义一个控制码,并在IRP_MJ_DEVICE_CONTROL的实现中进行控制码的操作。在驱动层irpStack->Parameters.DeviceIoControl.IoControlCode表示了这个控制码。
说到这里我们不得不提的一个东西就是IRP:
IRP(IO请求包)用于win32和驱动程序通讯,NT内核有一个组件叫做IO管理器。IO管理器负责IRP的分发,驱动程序里创建好设备并且创建好符号链接后,Win32就可以加载驱动了。而要让一个驱动可以处理IRP,必需给驱动添加IRP处理例程。
添加的方法就是再DriverEntry里面对驱动对象DriverObject操作。该参数是一个指针,指向驱动对象,驱动对象内部有一个MajorFunction数组,该数组的类型是
NTSTATUS (*PDRIVER_DISPATCH) (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) 。这是一个函数指针,指向每个IRP对于的处理例程。最后就是为所有需要处理的IRP实现对应的例程。
应用层通过DeviceIoControl 下发指令到内核层,内核层又通过对消息头的判断取出缓冲区的数据进行一系列的操作。
总得来说来说,有DeviceIoControl的存在,应用层向内核层传递消息就是一件轻松加愉快的事情,我简单的贴一点应用层和内核层的代码,大家可以做参考:
应用层:
BOOL bOK = ::DeviceIoControl(hAdapter, IOCTL_PTUSERIO_OPEN_ADAPTER,
pszAdapterName, nBufferLength, NULL, 0, &dwBytesReturn, NULL);
// 检查结果
if(!bOK)
{
::CloseHandle(hAdapter);
return INVALID_HANDLE_VALUE;
}
return hAdapter;
驱动层:
NTSTATUS DevIoControl(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
// 假设失败
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
// 取得此IRP(pIrp)的I/O堆栈指针
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// 取得I/O控制代码
ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 取得I/O缓冲区指针和它的长度
PVOID pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
ULONG uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
ULONG uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
ULONG uTransLen = 0;
DBGPRINT((" DevIoControl... \n"));
switch(uIoControlCode)
{
case IOCTL_GET_SHARE_ADD:
case IOCTL_PTUSERIO_OPEN_ADAPTER: // 根据控制码响应
{
然后驱动就可以从设定好的缓冲区里取数据进行操作。
第二步咱们来聊聊驱动层如何主动向应用层发消息:
很多情况下,我们需要驱动主动将消息传递给应用层,例如我们写一个网卡驱动,里面过滤传递的数据包,当数据包经过的时候,我们就需要驱动层将消息主动传递给应用层,这时候简单的DeviceIoControl 已经不能实现,那我们可以用共享事件加共享内存的方式来实现。
原理:通过Ring3创建事件,并将该事件传递给Ring0,同时Ring3创建监控线程,等待Ring0发起事件,此为应用层驱动层共享事件。同时Ring0在内核分配非分页内存,通过DeviceIoControl 传递给Ring3,此为应用层驱动层共享内存。因在DeviceIoControl 中传送Buffer,涉及到内核数据拷贝,大数据量下使用效率很低,故用共享内存的方法。
应用层代码:
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // 创建事件
// 发事件给ring 0
if (0 == DeviceIoControl(hFile, SET_EVENT, &m_hEvent, sizeof(HANDLE), NULL, 0, &uRetBytes,NULL))
{
CloseHandle(hFile);
CloseHandle(m_hEvent);
printf("SET_EVENT failed\n");
return FALSE;
}
// 获得ring 0 的共享内存
PVOID add = NULL;
if (0 == DeviceIoControl(hFile, IOCTL_GET_SHARE_ADD, NULL, 0, &add, sizeof (PVOID), &uRetBytes,NULL))
{
CloseHandle(hFile);
CloseHandle(m_hEvent);
return FALSE;
}
m_pShareMem = (PVOID)add;
return TRUE;
while (1)
{
WaitForSingleObject(m_hEvent, INFINITE);
{
<span style="white-space:pre"> </span>//等待事件发生
}
}
内核层:
g_pSysAdd = ExAllocatePool(NonPagedPool, 2048*3);
g_pMdl = IoAllocateMdl(g_pSysAdd, 2048*3, FALSE, FALSE, NULL);
MmBuildMdlForNonPagedPool(g_pMdl);//建立共享内存
UserAddr = MmMapLockedPagesSpecifyCache(g_pMdl, UserMode, NonPagedPool, NULL, FALSE, NormalPagePriority);
*((PVOID*)pIoBuffer) = UserAddr;//将驱动建立的内存地址传递给应用
memset(g_pSysAdd,0,2048*3);
RtlCopyMemory(g_pSysAdd, test, (DataOffset - 54)*2);//将需要的数据写入内存
KeSetEvent((PKEVENT)g_pEvent, 0,FALSE);//通知应用层可以读了
接下来我们就可以在应用层读取里面的数据了,地址就是我们通过DeviceIocontrol得到的地址。