驱动程序使用 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

<think>我们正在讨论驱动程序应用程序之间的关系,以及它们与内核的关系。根据用户的问题提供的引用,我们可以总结如下: 1. **应用程序驱动程序的交互**: - 应用程序通过系统调用(如 open, read, write, ioctl 等)与设备交互。 - 这些系统调用由库函数(如glibc)提供,库函数内部会触发软中断(例如ARM的swi指令)进入内核态。 - 内核根据系统调用的参数找到对应的驱动程序,并调用驱动程序的相应函数。 2. **驱动程序与内核的关系**: - 驱动程序是内核的一部分,可以静态链接进内核或动态加载。 - 驱动程序被动工作,由应用程序的请求触发(尽管也可以有中断驱动的操作,比如定时器)。 - 驱动程序运行在内核空间,权限高,错误可能导致系统崩溃。 3. **内核的作用**: - 内核是核心,负责处理系统调用、调度、内存管理、文件系统等。 - 内核在系统调用异常处理中,根据参数调用相应的驱动程序函数。 4. **权限与空间隔离**: - 应用程序运行在用户空间,受到MMU的保护,错误通常不会影响整个系统。 - 驱动程序运行在内核空间,没有这种保护,所以必须谨慎编写。 现在,我们按照用户的要求,回答“驱动应用程序之间的关系以及驱动应用程序与内核的关系”。 注意:用户要求使用中文,并且按照指定的格式(行内公式用$...$,独立公式用$$...$$)。同时,在回答中引用段落末尾添加引用标识,例如[^1]。最后,生成相关问题。 由于之前的引用[1][2][3]都是关于这个主题的,我们将在回答中引用它们。 下面是回答的结构: 1. 首先概述三者之间的关系。 2. 详细描述应用程序如何通过系统调用访问驱动。 3. 说明驱动程序在内核中的位置角色。 4. 讨论用户空间内核空间的隔离及其影响。 最后,生成几个相关问题。 </think>### 驱动应用程序与内核的关系分析 #### 一、基础架构关系 1. **层级模型** Linux系统采用分层架构,三者关系可表示为: $$ \begin{array}{c} \text{应用程序} \\ \downarrow \\ \text{库函数 (glibc等)} \\ \downarrow \\ \text{内核} \\ \downarrow \\ \text{驱动程序} \\ \downarrow \\ \text{硬件} \end{array} $$ 2. **核心交互流程**(以LED操作为例[^2]): ```mermaid graph LR A[应用程序] -->|open/dev/led| B[库函数] B -->|swi指令| C[内核系统调用] C -->|查找驱动| D[驱动程序] D -->|操作寄存器| E[硬件] D -->|返回状态| A ``` #### 二、驱动应用程序关系 1. **被动服务模型** 驱动程序**不主动运行**,仅响应应用程序请求[^2]: - 加载时:向内核注册能力(如 `"我能控制LED"`) - 运行时:等待应用通过文件接口(`/dev/led`)触发操作 - 通信方式:通过**文件描述符**传递数据控制命令 2. **接口抽象** | 应用层操作 | 驱动层实现 | 内核桥梁 | |------------|------------------|-------------------| | `open()` | `xxx_open()` | 分配设备资源 | | `read()` | `xxx_read()` | 数据拷贝至用户空间| | `ioctl()` | `xxx_ioctl()` | 传递控制命令 | | `mmap()` | `xxx_mmap()` | 内存映射 | 3. **权限隔离** - 应用程序:运行于**用户空间**,受MMU保护 - 驱动程序:运行于**内核空间**,拥有特权指令权限 $$ \text{崩溃影响} = \begin{cases} \text{应用错误} \rightarrow \text{进程终止} \\ \text{驱动错误} \rightarrow \text{内核崩溃} \end{cases} $$ #### 三、驱动与内核关系 1. **集成方式** - **静态链接**:直接编译进内核镜像(`vmlinuz`) - **动态加载**:通过`insmod`加载内核模块(`.ko`文件) 2. **内核服务支持** 驱动依赖内核提供的核心服务: ```c // 驱动注册示例 static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, // 调用内核的VFS接口 .write = led_write, // 使用内核内存管理 }; module_init(led_init); // 通过内核初始化框架注册 ``` 3. **中断协作模型** 硬件中断触发时的工作流: $$ \text{硬件中断} \rightarrow \text{内核中断控制器} \rightarrow \text{驱动ISR} \rightarrow \text{唤醒等待队列} \rightarrow \text{应用进程} $$ #### 四、应用程序与内核关系 1. **系统调用机制** 应用通过库函数触发软中断进入内核态[^3]: ```asm ; ARM架构系统调用示例 MOV R0, #5 @ 文件描述符 MOV R7, #4 @ sys_write编号 SWI 0 @ 触发软中断 ``` 2. **数据处理流程** 应用读写设备时的数据流: $$ \text{用户缓冲区} \xleftrightarrow{\text{copy_to/from_user}} \text{内核空间} \leftrightarrow \text{DMA/寄存器} $$ 3. **性能关键因素** 上下文切换开销由用户态↔内核态转换产生: $$ T_{switch} \approx 1-2\mu s \quad \text{(现代处理器)} $$ #### 五、典型交互场景(传感器驱动) ```c // 应用层 int fd = open("/dev/sensor", O_RDWR); ioctl(fd, START_SAMPLING); // 触发驱动操作 read(fd, buffer, 1024); // 获取采样数据 // 驱动层 static long sensor_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch(cmd) { case START_SAMPLING: start_adc_conversion(); // 启动硬件采样 wait_event(adc_data_queue, data_ready); // 等待中断 copy_to_user(arg, adc_buffer, sizeof(adc_buffer)); break; } } ``` #### 六、安全隔离设计 | 层级 | 内存隔离 | 特权级别 | 崩溃影响域 | |------------|-------------|----------|------------| | 应用程序 | MMU用户空间 | Ring 3 | 单个进程 | | 内核 | 内核空间 | Ring 0 | 整个系统 | | 驱动程序 | 内核空间 | Ring 0 | 整个系统 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值