驱动程序使用 EVENT 主动和应用程序通信

背景

用户层程序使用 DeviceIoControl 将 IOCTL 控制码、输入缓冲区、输出缓冲区传入到内核;内核响应 IRP_MJ_DEVICE_CONTRL 消息,并从 IRP 中获取传入的 IOCTL 控制码、输入缓冲区、输出缓冲区,以此实现数据的交互。

但是,当内核层想主动传递数据到用户层,用户层又怎样才能知道呢?因为只有用户层知道内核层有数据输出的时候,它才会调用 DeviceIoControl 函数去获取数据。所以,本文要介绍的就是基于事件 EVENT 实现的同步框架,可以解决这个的问题。

函数介绍

	// 创建或打开一个事件对象。如果想为对象指定一个访问掩码,应当使用CreateEventEx函数。
    HANDLE CreateEvent(
        LPSECURITY_ATTRIBUTESlpEventAttributes, // 安全属性
        BOOLbManualReset,                       // 复位方式
        BOOLbInitialState,                      // 初始状态
        LPCTSTRlpName                           // 对象名称
    );
    
    // 提供对象句柄访问许可。如果访问被允许,返回相应的对象体的指针。
    NTSTATUS ObReferenceObjectByHandle(
      _In_ HANDLE Handle,					// 句柄。
      _In_ ACCESS_MASK DesiredAccess,		// 访问对象的类型。
      _In_opt_ POBJECT_TYPE ObjectType,		// 指向对象类型。
      _In_ KPROCESSOR_MODE AccessMode,		// 访问模式。
      _Out_ PVOID *Object,					// 指向映射句柄对象的指针。
      _In_opt_ POBJECT_HANDLE_INFORMATION HandleInformation		// 驱动程序设置为 NULL。
 	);

实现原理

我们通过事件 EVENT 实现用户层与内核层的同步操作,具体的实现原理如下:

  • 首先,我们在用户层程序中调用 CreateEvent 函数创建事件 EVENT 并获取事件 EVENT 的句柄。事件初始状态为无信号,而且自动复原。
  • 然后,用户层程序调用 DeviceIoControl 函数将上述创建的 EVENT 句柄传递到内核层驱动程序中,并调用 WaitForSingleObject 函数等待事件 EVENT 的响应。直到事件 EVENT 响应后,程序才会进行下一步操作。
  • 接着,内核层程序就通过 IRP_MJ_DEVICE_CONTROL 消息响应函数获取从用户层传入的事件 EVENT 的句柄。调用 ObReferenceObjectByHandle 内核函数获取内核事件 EVENT 对象。
  • 然后,内核驱动程序可以调用 PsCreateSystemThread 创建多线程,继续执行操作。要想通知用户层程序进行下一步操作,只需调用 KeSetEvent 内核函数,将事件 EVENT 对象的状态设置为信号状态。那么,用户层程序中的事件 EVENT 就是一个有信号状态,WaitForSingleObject 函数就不会阻塞,而是继续往下执行。这样,就可以成功从内核层通知到用户层进行操作了。
  • 最后,用户层调用 CloseHandle 关闭事件 EVENT 句柄;内核层调用 ObDereferenceObject 释放内核事件 EVENT 对象。

这个框架的核心原理就是,将用户层的事件句柄传入内核层的驱动程序中,并有内核驱动程序设置事件对象的信号状态,以此触发用户层的响应。

编码实现

用户层代码:

    int _tmain(int argc, _TCHAR* argv[])
    {
        HANDLE hEvent = NULL;
        HANDLE hDevice = NULL;
        char szOutput[MAX_PATH] = { 0 };
        DWORD dwOutput = MAX_PATH;
        DWORD dwRet = 0;
        BOOL bRet = FALSE;
        // 创建事件, 设置自动复位,初始状态为无信号
        hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
        if (NULL == hEvent)
        {
            printf("CreateEvent Error[%d]\n", ::GetLastError());
        }
        // 打开设备
        hDevice = ::CreateFile(SYM_NAME, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_ARCHIVE, NULL);
        if (INVALID_HANDLE_VALUE == hDevice)
        {
            printf("CreateFile Error[%d]\n", ::GetLastError());
        }
        // 数据交互, 向内核层中传入事件句柄
        bRet = ::DeviceIoControl(hDevice, IOCTL_MY_TEST, &hEvent, sizeof(hEvent), szOutput, dwOutput, &dwRet, NULL);
        if (FALSE == bRet)
        {
            printf("DeviceIoControl Error[%d]\n", ::GetLastError());
        }
        // 一直等待事件的响应
        ::WaitForSingleObject(hEvent, INFINITE);
        // 数据交互, 从内核层中获取数据
        bRet = ::DeviceIoControl(hDevice, IOCTL_MY_OUTPUT, NULL, 0, szOutput, dwOutput, &dwRet, NULL);
        if (FALSE == bRet)
        {
            printf("DeviceIoControl Error[%d]\n", ::GetLastError());
        }
        // 显示
        printf("[From Kernel Output]%s\n", szOutput);
        // 关闭设备句柄
        ::CloseHandle(hEvent);
        ::CloseHandle(hDevice);
        system("pause");
        return 0;
    }

内核层代码:

    // IRP_MJ_DEVICE_CONTROL 消息处理函数
    NTSTATUS DriverControlHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
    {
        NTSTATUS status = STATUS_SUCCESS;
        // 获取当前 IRP 栈空间数据
        PIO_STACK_LOCATION pIoStackLocation = IoGetCurrentIrpStackLocation(pIrp);
        // 获取输入/输出缓冲区
        PVOID pBuffer = pIrp->AssociatedIrp.SystemBuffer;
        // 获取输入缓冲区数据长度
        ULONG ulInputLength = pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
        // 获取输出缓冲区数据长度
        ULONG ulOutputLength = pIoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
        // 实际输出数据长度
        ULONG ulInfo = 0;
        // 获取控制码
        ULONG ulControlCode = pIoStackLocation->Parameters.DeviceIoControl.IoControlCode;
        // 根据操作码分别进行操作
        switch (ulControlCode)
        {
        case IOCTL_MY_TEST:
        {
            // 获取传入的事件句柄
            HANDLE hUserEvent = *(HANDLE *)pBuffer;
            // 处理类型32位、64位下类型不匹配的情况
            if (4 == ulInputLength)
            {
                hUserEvent = (HANDLE)((SIZE_T)hUserEvent & 0x0FFFFFFFF);
            }
            // 根据事件句柄获取内核事件对象
            status = ObReferenceObjectByHandle(hUserEvent, EVENT_MODIFY_STATE, *ExEventObjectType, KernelMode, (PVOID)(&g_pKernelEvent), NULL);
            if (!NT_SUCCESS(status))
            {
                DbgPrint("ObReferenceObjectByHandle Error[0x%X]\n", status);
                g_pKernelEvent = NULL;
                break;
            }
            // 创建多线程, 执行操作, 执行完毕后, 发送事件通知用户层
            HANDLE hThread = NULL;
            PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, ThreadProc, g_pKernelEvent);
            break;
        }
        case IOCTL_MY_OUTPUT:
        {
            RtlCopyMemory(pBuffer, g_szOutputBuffer, 50);
            ulInfo = 50;
            break;
        }
        default:
            break;
        }
        pIrp->IoStatus.Status = status;
        pIrp->IoStatus.Information = ulInfo;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        return status;
    }

    // 多线程处理函数
    VOID ThreadProc(PVOID StartContext)
    {
        DbgPrint("Enter ThreadProc\n");
        // 获取内核对象
        PKEVENT pKernelEvent = (PKEVENT)StartContext;
        // 设置输出缓冲区
        RtlCopyMemory(g_szOutputBuffer, "I am DemonGan From Kernel Event.", (1 + strlen("I am DemonGan From Kernel Event.")));
        // 发送事件, 将事件对象设置为信号状态
        if (NULL != pKernelEvent)
        {
            KeSetEvent(pKernelEvent, IO_NO_INCREMENT, FALSE);
        }
        // 释放事件对象
        ObDereferenceObject(pKernelEvent);
        pKernelEvent = NULL;
        DbgPrint("Leave ThreadProc\n");
    }

相关文章:
https://cloud.tencent.com/developer/article/1446024

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值