在驱动程序中创建 IOCTL 请求

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

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

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

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

  4. 使用 IRP 调用 IoSetCompletionRoutine ,以便上层驱动程序可以提供 IoCompletion 例程(如有必要)执行以下操作:

    • 确定下级驱动程序如何处理给定请求。

    • 在较低的驱动程序完成请求的操作后,重复使用 IRP 发送另一个请求或释放驱动程序创建的 IRP。 驱动程序不能重复使用 IoBuildDeviceIoControlRequest 创建的 IRP。 有关详细信息,请参阅 重用 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 控制代码的参数很少包含嵌入的指针。 除某些 SCSI 请求外,缓冲区位于 Irp-AssociatedIrp。>SystemBufferIrp-MdlAddress > 和 Parameters。DeviceIoControl。驱动程序的 I/O 堆栈位置中的 Type3InputBuffer 不包含指向其他数据缓冲区的指针,也不包含包含系统定义的 I/O 控制代码的指针的结构。 有关如何将数据缓冲区与包含 I/O 控制代码的 IRP 配合使用的详细信息,请参阅 I/O 控制代码的缓冲区说明

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

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

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

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

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