内核层与应用层通信详解

做驱动开发的肯定会遇到应用层与内核层的通信的问题,首先说内核层与应用层的通信可以大概分为两个方面,第一是应用层向内核层主动传递消息,第二是内核层主动与应用层通信。下面我们将分开来谈两个方面。

我们先来看应用层向内核层传递的方法:

 

 

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得到的地址。
 

 

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值