定义 I/O 控制代码

本文介绍如何创建唯一的 I/O 控件代码(IOCTL)。 IOCTL 可以是:

  • 公共 IOCTL,通常由 Microsoft 系统定义和记录。
  • 专用IOCTL通常专门用于供应商的软件组件之间的通信。 它们通常在供应商的头文件中定义,并且不会由Microsoft记录。

IOCTL 布局

IOCTL 是一个包含多个字段的 32 位值。 下图说明了 IOCTL 的按位布局:

说明 32 位 i/o 控件代码的按位布局的关系图。

IOCTL 中的每个字段都有特定用途,如下表所述:

领域 IOCTL 中的位 DESCRIPTION
常见 31 供应商在使用 DeviceType 的供应商分配值时必须设置此位。
DeviceType 16-30 标识设备的类型。 此值必须与驱动程序DEVICE_OBJECT结构的 DeviceType 成员中设置的值匹配。 供应商应使用从 32768 到 65535 的值(0x8000到 0xffff),并应设置 Common 位。 值 0 到 32767(0x0000到0x7fff)保留为Microsoft。 有关详细信息,请参阅 指定设备类型
访问 14-15 指示调用方在打开表示设备的文件对象时必须请求的访问类型(请参阅 IRP_MJ_CREATE)。 仅当调用方请求指定的访问权限时,I/O 管理器才会创建 IRP 并使用特定的 IOCTL 调用驱动程序。 此字段使用以下系统定义的常量指定:FILE_ANY_ACCESS、FILE_READ_DATA和FILE_WRITE_DATA。
自定义 13 设置后,指示 IOCTL 是供应商定义的 IOCTL。
功能 2-12 用于标识要执行的函数的驱动程序的唯一代码。 对于供应商创建的 IOCTL,请使用值 2048 到 4095(0x800到 0xfff),并设置 自定义 位。 小于 2048(0x000到0x7ff)的值保留为Microsoft。
方法 0-1 指示系统如何在 DeviceIoControl (或 IoBuildDeviceIoControlRequest)的调用方与处理 IRP 的驱动程序之间传递数据。 有关详细信息,请参阅 设置方法位的指南

用于定义 I/O 控制代码的宏

使用系统提供的 CTL_CODE 宏来定义新的 I/O 控制代码。 此宏在 devioctl.h 中定义,如下所示:

#define CTL_CODE( DeviceType, Function, Method, Access ) (                 \
    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)

有关 DeviceTypeFunctionMethodAccess 的说明,请参阅前面的部分。

定义新的 I/O 控制代码时,请记住以下规则:

新的 IOCTL 代码的定义,无论是用于 IRP_MJ_DEVICE_CONTROL 还是 IRP_MJ_INTERNAL_DEVICE_CONTROL 请求,都使用以下格式:

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

为 IOCTL 选择一个描述性常量名称,其形式为 IOCTL_Device_Function,其中 Device 指示设备的类型,Function 指示操作。 例如,系统提供的IOCTL_VIDEO_ENABLE_CURSOR常量使用“VIDEO”表示设备,使用“ENABLE_CURSOR”表示函数

设置访问位的指南

定义新的 IOCTL 时,必须为 Access 位字段选择一个值,该值指示调用方 在打开表示设备的文件对象时必须请求的访问类型。 仅当调用方请求指定的访问权限时,I/O 管理器才会创建 IRP 并使用特定的 IOCTL 调用驱动程序。

Access 是使用以下系统定义的常量指定的:

  • FILE_ANY_ACCESS

    如果调用方拥有代表目标设备对象的文件对象的句柄,则 I/O 管理器就会向其发送 IRP。 在为新的 IOCTL 代码指定FILE_ANY_ACCESS之前,必须绝对确定允许对设备的不受限制的访问不会为恶意用户创建可能的路径来入侵系统。

  • 文件_读取_数据

    I/O 管理器仅为具有读取访问权限的调用方发送 IRP,从而允许基础设备驱动程序将数据从设备传输到系统内存。

  • 文件写入数据

    I/O 管理器仅为具有写入访问权限的调用方发送 IRP,从而允许基础设备驱动程序将数据从系统内存传输到其设备。

如果调用方必须同时拥有读取和写入访问权限,则 FILE_READ_DATA 和 FILE_WRITE_DATA 可以通过 OR 并列。

某些系统定义的 I/O 控制代码具有 FILE_ANY_ACCESS 的 Access 值,这允许调用方发送特定的 IOCTL,而不考虑授予设备的访问权限。 例如,发送给专用设备驱动程序的 I/O 控制代码。

其他系统定义的 I/O 控制代码要求调用方具有读取访问权限、写入访问权限或两者兼有权限。 例如,公共IOCTL_DISK_SET_PARTITION_INFO IOCTL 的以下定义表明,仅当调用方具有读取和写入访问权限时,才能将此 I/O 请求发送到驱动程序:

#define IOCTL_DISK_SET_PARTITION_INFO\
        CTL_CODE(IOCTL_DISK_BASE, 0x008, METHOD_BUFFERED,\
        FILE_READ_DATA | FILE_WRITE_DATA)

驱动程序可以使用 IoValidateDeviceIoControlAccess 执行比 IOCTL 的 Access 位更为严格的访问检查。

设置方法位指南

定义新的 IOCTL 时,必须为 方法 位字段选择一个值,该值指示系统如何在 DeviceIoControl (或 IoBuildDeviceIoControlRequest)的调用方与处理 IRP 的驱动程序之间传递数据。

使用以下系统定义的常量之一设置 Method 字段。

  • METHOD_BUFFERED

    指定 缓冲的 I/O 方法,该方法通常用于传输每个请求的少量数据。 设备和中间驱动程序的大多数 I/O 控制代码都使用此值。

    有关系统如何为 METHOD_BUFFERED I/O 控制代码指定数据缓冲区的信息,请参阅 I/O 控制代码的缓冲区说明

    有关缓冲 I/O 的详细信息,请参阅 使用缓冲 I/O

  • METHOD_IN_DIRECT 或 METHOD_OUT_DIRECT

    指定 直接 I/O 方法,该方法通常用于使用必须快速传输的 DMA 或 PIO 读取或写入大量数据。

    • 如果 DeviceIoControlIoBuildDeviceIoControlRequest 的调用方将数据传递给驱动程序,请指定METHOD_IN_DIRECT。

    • 如果 DeviceIoControlIoBuildDeviceIoControlRequest 的调用方从驱动程序接收数据,请指定METHOD_OUT_DIRECT。

    有关如何系统为METHOD_IN_DIRECT和METHOD_OUT_DIRECT I/O 控制代码指定数据缓冲区的信息,请参阅 I/O 控制代码的缓冲区说明

    有关直接 I/O 的详细信息,请参阅 使用直接 I/O

  • METHOD_NEITHER

    指定未缓冲或直接的 I/O 方法。 I/O 管理器不提供任何系统缓冲区或 MDL。 IRP 提供为 DeviceIoControlIoBuildDeviceIoControlRequest 指定的输入和输出缓冲区的用户模式虚拟地址,而无需验证或映射它们。

    有关系统如何为METHOD_NEITHER I/O 控制代码指定数据缓冲区的信息,请参阅 I/O 控制代码的缓冲区说明

    仅当保证驱动程序在发起 I/O 控件请求的线程的上下文中运行时,才能使用此方法。 只有最高级别的内核模式驱动程序可以保证满足此条件,因此 METHOD_NEITHER 很少用于传递给低级别设备驱动程序的 IOCTL。

    使用此方法时,最高级别的驱动程序:

    • 必须确定是否在收到请求时设置缓冲或直接访问用户数据。
    • 可能必须锁定用户缓冲区。
    • 必须在结构化异常处理程序中包装对用户缓冲区的访问权限(请参阅 处理异常)。

    否则,发起的用户模式调用方可能会在驱动程序使用缓冲数据之前更改其内容,或者在驱动程序访问用户缓冲区的过程中,调用方可能会被换出。

    有关详细信息,请参阅 无需缓冲和直接 I/O 的使用

其他有用的宏

以下宏可用于从 IOCTL 中提取 16 位 DeviceType 和 2 位 方法 字段。

#define DEVICE_TYPE_FROM_CTL_CODE(ctrlCode)   (((ULONG)(ctrlCode & 0xffff0000)) >> 16)
#define METHOD_FROM_CTL_CODE(ctrlCode)        ((ULONG)(ctrlCode & 3))

这些宏在 Wdm.hNtddk.h 中定义。