Windows驱动. 驱动基础

驱动对象和设备对象

I/O管理器

I/O管理器(I/O Manager) 负责 I/O请求调度, 生命周期, IRP流转 的核心组件

所有的应用程序/内核 发起的I/O请求, 都会通过I/O管理器进行调度

无论对端口的读写, 还是文件的读写, 都统一为 IRP 的请求形式, 包含了操作的重要数据(读还是写, 操作多大内存, 读到哪里去)

IRP被传递到具体设备的驱动中, 由驱动处理这些IRP, 并将处理完成的IRP按原路返回用户模式下的应用程序中

总而言之, I/O管理器担当着用户模式代码和设备驱动程序之间的端口

驱动程序

I/O管理器接收到应用程序请求后, 创建对应IRP, 然后将请求传递给驱动, 由如下处理

  • 根据IRP请求, 直接操作对应硬件, 完成此IRP, 返回

  • 根据IRP请求, 将其转发给更底层的驱动, 等待底层驱动返回

  • 接收到IRP请求后, 分配新的IRP发送给其他驱动程序中, 等待其他驱动程序返回

驱动对象 DRIVER_OBJECT *PDRIVER_OBJECT

每个驱动程序会有一个 唯一 的驱动对象与其对应

当 I/O 管理器调用驱动程序的 DriverEntry 例程时,它会提供驱动程序的驱动程序对象的地址。 驱动程序对象包含用于许多驱动程序标准例程的入口点的存储。 驱动程序负责填写这些入口点。
— msdn

DRIVER_OBJECT 由I/O管理程序创建, 它为每个已安装和加载的驱动程序创建一个实例, 然后将实例的地址传递给 DriverEntrypDriverObject

入口程序
入口程序
NTSTATUS DriverEntry(
    IN PDRIVER_OBJECT pDriverObject,   // 👈 I/O管理创建好的 DRIVER_OBJECT 从这里传入
    IN PUNICODE_STRING pRegistryPath
)

随后驱动程序要把自己写的函数地址注册进PDRIVER_OBJECT, 这样 I/O 管理器就可以调用这些函数

注册函数
注册函数
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyDispatchCreate;
DriverObject->MajorFunction[IRP_MJ_READ]   = MyDispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE]  = MyDispatchWrite;
DriverObject->DriverUnload                 = MyDriverUnload;
类型名称描述备注

CSHORT

Type

驱动对象类型

CSHORT

Size

驱动对象大小

PDEVICE_OBJECT

DeviceObject

设备对象

每个驱动由一个或者多个设备对象组成, 此处为设备对象链表的第一个成员. 在卸载时, 需要手动删除所有设备对象

ULONG

Flags

驱动对象标志

PVOID

DriverStart

驱动程序入口地址

ULONG

DriverSize

驱动程序大小

PVOID

DriverSection

驱动链表

整个系统里驱动的链表管理

PDRIVER_EXTENSION

DriverExtension

驱动扩展

UNICODE_STRING

DriverName

驱动名

该字符串一般为 \Driver\[驱动名]

UNICODE_STRING

HardwareDatabase

硬件数据库

PFAST_IO_DISPATCH

FastIoDispatch

快速IO分发

PDRIVER_INITIALIZE

DriverInit

驱动初始化

PDRIVER_STARTIO

DriverStartIo

驱动开始IO

PDRIVER_UNLOAD

DriverUnload

驱动卸载

PDRIVER_DISPATCH[]

MajorFunction

驱动主要功能

数组每个成员记录着一个指针, 每个指针指向一个函数, 即为IRP派遣函数

设备对象 DEVICE_OBJECT *PDEVICE_OBJECT

操作系统使用 DEVICE_OBJECT 结构来表示设备对象。 设备对象表示驱动程序处理 I/O 请求的逻辑、虚拟或物理设备。

这里的设备范围很广, 除了物理设备外, 系统资源(内存管理, 进程, 线程管理) 也可以视作设备对象

类型名称描述备注

PDRIVER_OBJECT

DeviceObject

驱动对象

同属一个驱动程序的驱动对象指向的是同一个驱动对象

PDEVICE_OBJECT

NextDevice

下一个设备对象

设备对象链表的下一个成员

PDEVICE_OBJECT

AttachedDevice

附加设备对象

如果有更高一层的驱动附加到这个驱动的话, 它只想更高一层驱动

PVOID

DeviceExtension

设备扩展

每个设备都会指定一个设备扩展对象, 用于存储设备私有数据, 它是一个自定义结构体

DEVICE_TYPE

DeviceType

设备类型

设备类型

在老的NT模型中, 不支持PnP概念, 设备对象 DEVICE_OBJECT 是在 DriverEntry 里直接进行创建的

在WDM模型中, 设备对象需要想系统提供一个 AddDevice 例程, 此例程由PNP管理器负责调用, 主要职责是创建对象

C
pDriverObject->DriverExtension->AddDevice = MyAddDevice;

/**
* @brief 创建设备对象
* @param pDriverObject 驱动对象
* @param pPhysicalDeviceObject 由PNP管理器传递进来的底层物理驱动设备对象
* @return NTSTATUS
*/
#pragma PAGEDCODE                    // 在分页内存中执行
NTSTATUS MyAddDevice(PDRIVER_OBJECT pDriverObject, PDEVICE_OBJECT pPhysicalDeviceObject)
{
    PAGED_CODE();                    // 是否在分页内存中断言
    NTSTATUS status;
    UNICODE_STRING uDevName = { 0 }; // 设备名(内核可见)
    PDEVICE_OBJECT pDeviceObject = NULL;

    // 1. 初始化设备名
    RtlInitUnicodeString(&uDevName, L"\\Device\\MyDevice");

    // 2. 创建设备对象(安全驱动关键:FILE_DEVICE_SECURE_OPEN 确保设备安全)
    status = IoCreateDevice(
        pDriverObject,           // 驱动对象
        0,                       // 无设备扩展
        &uDevName,               // 设备名
        FILE_DEVICE_UNKNOWN,     // 安全驱动无特定设备类型
        FILE_DEVICE_SECURE_OPEN, // 安全属性:仅允许有权限的进程访问
        FALSE,                   // 非独占(安全驱动通常允许多进程访问)
        &pDeviceObject           // 输出设备对象
    );

    if (!NT_SUCCESS(status))
    {
        KdPrint(("IoCreateDevice 失败,状态=0x%X\n", status));
        return status;
    }

    // 3. 设置缓冲IO
    pDeviceObject->Flags |= DO_BUFFERED_IO;

    // 清除DO_DEVICE_INITIALIZING标志(设备初始化完成)
    pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    // 4. 创建符号链接(让应用层访问)
    RtlInitUnicodeString(&g_uSymLink, L"\\??\\MyDevice");
    status = IoCreateSymbolicLink(&g_uSymLink, &uDevName);
    if (!NT_SUCCESS(status))
    {
        KdPrint(("IoCreateSymbolicLink 失败,状态=0x%X\n", status));
        IoDeleteDevice(pDeviceObject);
        return status;
    }

    KdPrint(("AddDevice 创建设备完成\n"));
    return STATUS_SUCCESS;
}

设备名称必须是 \Device\[设备名] 的形式, Windows下所有设备都是这种命令, 例如C盘和D盘命名为 \Device\HarddiskVolume1\Device\HarddiskVolume2

如果不指定设备名, 则会自动分配一个数字作为设备名, 例如 \Device\Device00000001

如果指定了设备名, 则可以被内核模式其他驱动是被, 但是无法被用户模式下应用识别 , 如果要让用户模式下程序识别设备, 可以通过 符号链接/设备接口 两种方式

在内核模式下, 符号链接都是以 \??\ 开头, 用户模式下则是以 \\.\ 开头

例如C盘: 内核: \??\C: , 用户模式: \\.\C:

设备扩展 DEVICE_EXTENSION *PDEVICE_EXTENSION

每个设备对象都会有一个设备扩展对象, 用于存储设备私有数据, 它是一个自定义结构体, 由 I/O管理器创建

C
typedef struct _CUSTOM_DEVICE_EXTENSION {
    PDEVICE_OBJECT DeviceObject;
    PVOID PrivateData;
} CUSTOM_DEVICE_EXTENSION, *PCUSTOM_DEVICE_EXTENSION;

然后需要在创建设备时指定

C
status = IoCreateDevice(
    pDriverObject,                    // 驱动对象
    0,                                // 无设备扩展
    sizeof(CUSTOM_DEVICE_EXTENSION),  // 设备扩展大小
    &uDevName,                        // 设备名
    FILE_DEVICE_UNKNOWN,              // 安全驱动无特定设备类型
    FILE_DEVICE_SECURE_OPEN,          // 安全属性:仅允许有权限的进程访问
    FALSE,                            // 非独占(安全驱动通常允许多进程访问)
    &pDeviceObject                    // 输出设备对象
);

Windows驱动. 驱动基础
https://simonkimi.githubio.io/posts/20240801152244/
作者
simonkimi
发布于
2024年8月1日
许可协议