在驱动程序中创建 IOCTL 请求

类驱动程序或其他更高级别驱动程序可以分配 I/O 控制请求的 IIP,并将其发送到下一个较低级别的驱动程序,如下所示:

  1. 在 IRP 中分配或重复使用 IRP (数据包) 主函数代码IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL 可以使用 IoBuildDeviceIoControlRequest 例程专门分配 IOCTL IRP。 还可使用常规用途 IRP 创建和初始化例程,例如 IoAllocateIrpIoReuseIrpIoInitializeIrp。 有关 IRP 分配详细信息,请参阅为 IRP 驱动程序Lower-Level IRP

  2. 使用 IOCTL_XXX 代码和适当的参数为 IRP 设置较低驱动程序的 I/O 堆栈位置。

  3. 如果要异步完成 IOCTL 请求,请调用 KeInitializeEvent 例程,将事件对象初始化为通知事件。 驱动程序使用此事件等待 I/O 操作完成。

  4. 使用 IRP 调用 IoSetCompletionRoutine ,以便上部驱动程序在必要时提供 IoCompletion 例程以执行以下操作:

    • 确定较低驱动程序如何处理给定请求。

    • 在较低级别驱动程序完成请求的操作后,重复使用 IRP 发送另一个请求或释放驱动程序创建的 IRP。 驱动程序不能重复使用 IoBuildDeviceIoControlRequest 创建的 IIP 。 有关详细信息,请参阅 重新使用 IRP

  5. 调用 IoCallDriver 将请求传递给较低驱动程序。

  6. 如果 IoCallDriver STATUS_PENDING,请调用 KeWaitForSingleObject 例程,将当前线程置于等待状态。 驱动程序将例程的 Object 参数设置为在调用 KeInitializeEvent 时初始化的事件对象的地址。

    注意 如果驱动程序调用 KeWaitForSingleObject ,其 Timeout 参数设置为 NULL 或包含非零值的变量的地址,则驱动程序必须在非二元线程上下文中的 IRQL <= APC_LEVEL 上运行。 否则,驱动程序必须在 IRQL <= DISPATCH_LEVEL。

IOCTL 请求完成后, 事件由其 IoCompletion 例程发出信号。 发出事件信号后,线程将恢复执行。

重要 如果驱动程序将事件对象分配为堆栈上的局部变量,则驱动程序必须调用 KeWaitForSingleObject ,其 WaitMode 参数设置为 KernelMode。 此参数值可防止对堆栈进行分页。

为了避免同步问题和可能的访问冲突,I/O 控制代码的参数很少包含嵌入的指针。 Irp-AssociatedIrp> 中的缓冲区(某些 SCSI 请求除外)。SystemBuffer位于 Irp-MdlAddress> 和ParametersDeviceIoControl。驱动程序 I/O 堆栈位置中的 Type3InputBuffer 不包含指向其他数据缓冲区的指针,也不包含包含系统定义的 I/O 控制代码的指针的结构。 有关如何将数据缓冲区与包含 I/O 控制代码的 IIP 一起使用,请参阅 I/O 控制代码的缓冲区说明。

不过,定义内部 I/O 控制代码的一对类/端口驱动程序可以将一个嵌入的指针传递到从较高级别的驱动程序到较低级别的驱动程序的驱动程序分配的内存。 此类/端口驱动程序对负责确保以下各项正确:

  • 一次只能有一个驱动程序访问数据。

  • 端口驱动程序可在任意线程上下文中访问专用数据缓冲区。

显示驱动程序可以调用 GDI 函数 EngDeviceIoControl,通过系统视频端口驱动程序向下发送到相应的特定于适配器的视频微型端口驱动程序,以发送特定于设备的专用 I/O 控制请求以及系统定义的公共 I/O 控制请求。

驱动程序包的任何用户模式组件都可以调用 DeviceIoControl 将 I/O 控制请求发送到驱动程序堆栈。 I/O 管理器 创建IRP_MJ_DEVICE_CONTROL请求 ,并传送给最高级别的驱动程序。