Reactos 第 9 章 设备驱动 — 9.7 一个过滤设备驱动模块的示例

第 9 章 设备驱动 --- 9.7 一个过滤设备驱动模块的示例

本节以 kbdclass(键盘类驱动)和 fltmgr(过滤管理器)为例剖析过滤设备驱动(Filter Device Driver)的实现模式。 过滤驱动位于设备栈中 功能驱动的上层或下层,用于添加额外行为而不修改原有驱动的代码。具体作用包括:

  • 日志审计:记录所有文件/设备的 I/O 操作(如 FileSpy)
  • 透明加密:对磁盘数据进行实时加解密(如 BitLocker、TrueCrypt)
  • 备份快照:拦截写操作以创建卷影副本(如 VSS 快照过滤器)
  • 防病毒扫描:在文件打开/写入时实时扫描恶意代码(如 Windows Defender)
  • 访问控制:拦截文件/设备访问以实施权限策略(如 USB 禁用工具)
  • 数据压缩/去重:在 I/O 路径中实时压缩或去重数据
  • 输入监控:拦截键盘/鼠标事件用于辅助功能或宏录制
  • 打印拦截:监控或重定向打印作业

理解过滤驱动的关键是把握 IoAttachDeviceToDeviceStack 的附加机制和 IRP 转发 / 完成例程链。ReactOS 的 drivers/filters/fltmgr/(file:///d:/reactos/drivers/filters/fltmgr/) 和 drivers/input/kbdclass/(file:///d:/reactos/drivers/input/kbdclass/) 是典型实例。


概述

过滤驱动在 WDM 设备栈中有两种位置:

  1. 上层过滤(Upper Filter):位于 FDO 之上,接收应用层 IRP
  2. 下层过滤(Lower Filter):位于 FDO 之下,接收 PnP 传入的 IRP

过滤驱动的核心工作流:

  1. AddDevice:创建 Filter DO,附加到目标设备
  2. IRP 转发 :构造 IoCopyCurrentIrpStackLocationToNext + IoCallDriver
  3. 完成例程 :通过 IoSetCompletionRoutine 在下层完成后回到过滤驱动
  4. 修改 IRP:在派发/完成时读取或修改 IRP 数据

本节内容概览

  • 9.7.0 框架图
  • 9.7.1 过滤驱动的两种位置
  • 9.7.2 AddDevice 与设备栈附加
  • 9.7.3 IRP 转发与完成例程
  • 9.7.4 kbdclass 实例
  • 9.7.5 fltmgr 过滤管理器
  • 9.7.6 总结与代码索引

学习目标

  • 理解过滤驱动在设备栈中的位置
  • 掌握 IRP 转发与完成例程
  • 知道 fltmgr 的基本架构

涉及的内核子系统

子系统 头文件/源文件 核心作用
过滤管理器 drivers/filters/fltmgr/(file:///d:/reactos/drivers/filters/fltmgr/) FltMgrRegisterFilter
键盘类 drivers/input/kbdclass/kbdclass.c(file:///d:/reactos/drivers/input/kbdclass/kbdclass.c) 典型上层过滤
IoAttachDevice ntoskrnl/io/iomgr/device.c(file:///d:/reactos/ntoskrnl/io/iomgr/device.c) IoAttachDeviceToDeviceStack
IoCallDriver ntoskrnl/io/iomgr/dispatch.c(file:///d:/reactos/ntoskrnl/io/iomgr/dispatch.c) IoCallDriver
完成例程 ntoskrnl/io/iomgr/irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) IoSetCompletionRoutine

9.7.0 框架图

复制代码
  应用层 IRP (CreateFileW / ReadFile)
     |
     v
  +-------------------------+  顶层设备
  | Upper Filter Device    |  <- kbdclass (上层过滤)
  | (kdbclass Device)      |
  +-------------------------+
     |
     v
  +-------------------------+  功能设备
  | FDO (Function DO)      |  <- i8042prt (PS/2 端口驱动)
  +-------------------------+
     |
     v
  +-------------------------+  物理设备
  | PDO (Physical DO)      |  <- pci 总线驱动
  +-------------------------+
     |
     v
  硬件

  IRP 路径:
  应用 → 顶层 → FDO → PDO → 硬件
       ↑ 完成例程链
  完成:硬件 → PDO → FDO → 顶层 → 应用

9.7.1 过滤驱动的两种位置

上层过滤(Upper Filter)

复制代码
应用 IRP
   ↓
[Upper Filter] ← 应用层 IRP 首先到达
   ↓
[FDO]
   ↓
[PDO]

用途:

  • 类驱动(如 kbdclass、mouclass):把设备 I/O 转换为标准 Win32 语义
  • 加密层:透明加解密
  • 日志层:记录所有 IRP

下层过滤(Lower Filter)

复制代码
应用 IRP
   ↓
[FDO]
   ↓
[Lower Filter] ← 接收 PnP 传入的 IRP
   ↓
[PDO]

用途:

  • 总线过滤:添加额外 PnP 逻辑
  • 设备劫持:备份软件、热插拔

设备栈关系

c 复制代码
/* 设备对象的关键字段 */
DeviceObject->AttachedDevice  // 附加在本设备顶端的设备
DeviceObject->NextDevice      // 同驱动内的下一个设备
DeviceObject->DeviceExtension // 设备扩展(私有)

IoAttachDeviceToDeviceStack(Filter, Target) 把 Filter 附加到 Target 上方,返回 下层设备(原 Target)。


9.7.2 AddDevice 与设备栈附加

过滤驱动的 AddDevice

c 复制代码
NTSTATUS NTAPI
KbdClassAddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT Pdo)
{
    PDEVICE_OBJECT FilterDevice;
    PDEVICE_EXTENSION Ext;
    NTSTATUS Status;

    /* 1. 创建 Filter Device */
    Status = IoCreateDevice(DriverObject,
                            sizeof(DEVICE_EXTENSION),
                            NULL,                  // 无名
                            FILE_DEVICE_KEYBOARD,  // 设备类型
                            FILE_DEVICE_SECURE_OPEN,
                            FALSE,
                            &FilterDevice);
    if (!NT_SUCCESS(Status)) return Status;

    /* 2. 初始化设备扩展 */
    Ext = (PDEVICE_EXTENSION)FilterDevice->DeviceExtension;
    RtlZeroMemory(Ext, sizeof(DEVICE_EXTENSION));
    Ext->PnpState = PnpStateNotStarted;

    /* 3. 附加到 PDO(这里 PDO 是键盘端口的 PDO) */
    Ext->LowerDevice = IoAttachDeviceToDeviceStack(FilterDevice, Pdo);
    if (!Ext->LowerDevice)
    {
        IoDeleteDevice(FilterDevice);
        return STATUS_DEVICE_NOT_CONNECTED;
    }

    /* 4. 设置标志 */
    FilterDevice->Flags |= DO_POWER_PAGABLE;
    FilterDevice->Flags &= ~DO_DEVICE_INITIALIZING;

    return STATUS_SUCCESS;
}

IoAttachDeviceToDeviceStack

c 复制代码
PDEVICE_OBJECT NTAPI
IoAttachDeviceToDeviceStack(
    IN PDEVICE_OBJECT SourceDevice,
    IN PDEVICE_OBJECT TargetDevice)
{
    PDEVICE_OBJECT AttachedDevice;
    KIRQL OldIrql;

    /* 找到 TargetDevice 所在栈的顶端 */
    AttachedDevice = TargetDevice->AttachedDevice;
    while (AttachedDevice)
    {
        TargetDevice = AttachedDevice;
        AttachedDevice = AttachedDevice->AttachedDevice;
    }

    /* 加锁 */
    KeAcquireSpinLock(&IopDatabaseLock, &OldIrql);

    /* 设置 AttachedDevice 链 */
    SourceDevice->AttachedDevice = TargetDevice->AttachedDevice;
    TargetDevice->AttachedDevice = SourceDevice;

    /* 设置 SourceDevice 的 StackSize */
    SourceDevice->StackSize = TargetDevice->StackSize + 1;
    SourceDevice->AlignmentRequirement = TargetDevice->AlignmentRequirement;
    SourceDevice->SectorSize = TargetDevice->SectorSize;

    KeReleaseSpinLock(&IopDatabaseLock, OldIrql);

    return TargetDevice;  // 返回下层设备
}

返回的 TargetDevice 是 下层设备(即 SourceDevice 附加的设备)。


9.7.3 IRP 转发与完成例程

简单 IRP 转发

过滤驱动常常 只转发 IRP 到下层,自己不做实际处理:

c 复制代码
NTSTATUS NTAPI
KbdClassDispatchPassThrough(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    /* 简单转发 */
    IoSkipCurrentIrpStackLocation(Irp);
    return IoCallDriver(Ext->LowerDevice, Irp);
}

IoSkipCurrentIrpStackLocationCurrentStackLocation 前移一位,下层处理时会读取过滤驱动填写的栈单元。

转发 + 完成例程

如果需要在 IRP 完成时做些事(如记录、修改结果),用完成例程:

c 复制代码
NTSTATUS NTAPI
KbdClassRead(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    /* 设置完成例程 */
    IoSetCompletionRoutine(Irp,
                           KbdClassReadComplete,  // 完成回调
                           Ext,                    // 上下文
                           TRUE,                   // OnSuccess
                           TRUE,                   // OnError
                           TRUE);                  // OnCancel

    /* 转发到下层 */
    return IoCallDriver(Ext->LowerDevice, Irp);
}

NTSTATUS NTAPI
KbdClassReadComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context)
{
    PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)Context;

    /* IRP 已完成:从下层返回的数据 */
    if (NT_SUCCESS(Irp->IoStatus.Status))
    {
        /* 处理数据:转换为 KEYBOARD_INPUT_DATA 等 */
    }

    /* 继续向上完成 */
    return STATUS_SUCCESS;  // STATUS_SUCCESS 让 IoCompleteRequest 继续
    // return STATUS_MORE_PROCESSING_REQUIRED; // 阻止 IoCompleteRequest
}

IoCopyCurrentIrpStackLocationToNext

如果过滤驱动 修改了输入参数,需要复制当前栈位置到下层:

c 复制代码
IoCopyCurrentIrpStackLocationToNext(Irp);
((PIO_STACK_LOCATION)IoGetNextIrpStackLocation(Irp))->Parameters.Read.Length = NewLength;
return IoCallDriver(Ext->LowerDevice, Irp);

IoCopyCurrentIrpStackLocationToNext 复制当前栈到下层栈单元(而不只是指针前移)。


9.7.4 kbdclass 实例

kbdclass 位于 drivers/input/kbdclass/kbdclass.c(file:///d:/reactos/drivers/input/kbdclass/kbdclass.c),是键盘的 类驱动(class driver) ,它把底层 I8042 端口驱动(i8042prt)的原始 I/O 转换为标准 Win32 键盘输入。

kbdclass 设备栈

复制代码
+-------------------------+
| 应用程序 (ReadConsoleInput)
+-------------------------+
       |
       v
+-------------------------+
| kbdclass 设备(\Device\KeyboardClass0)
+-------------------------+   <- 上层过滤
       |
       v
+-------------------------+
| i8042prt 设备(FDO)
+-------------------------+   <- 功能驱动
       |
       v
+-------------------------+
| 硬件(PS/2 键盘控制器)
+-------------------------+

kbdclass 关键工作

  1. 创建设备 \Device\KeyboardClass0(在 DriverEntry 中)
  2. 附加到 i8042prt(在 AddDevice 中)
  3. 构造 KEYBOARD_INPUT_DATA(在 i8042prt 完成时)
  4. Win32 ReadConsoleInput 转换(用户态)

kbdclass Read 派发

c 复制代码
NTSTATUS NTAPI
KbdClassRead(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);

    /* 1. 检查环形缓冲区(已读取的数据) */
    if (KbdClassRingBufferDataAvailable(Ext))
    {
        /* 直接从缓冲读 */
        KbdClassCopyReadDataToBuffer(Ext, Irp);
        Irp->IoStatus.Information = ...;
        Irp->IoStatus.Status = STATUS_SUCCESS;
        IoCompleteRequest(Irp, IO_KEYBOARD_INCREMENT);
        return STATUS_SUCCESS;
    }

    /* 2. 排队等待 */
    KbdClassAddIrpToQueue(Ext, Irp);
    IoMarkIrpPending(Irp);

    /* 3. 启动下层 IRP 循环 */
    KbdClassStartIo(DeviceObject, Irp);

    return STATUS_PENDING;
}

kbdclass StartIO

kbdclass 维护一个 IRP 循环:当一个 IRP 完成时,立即发送下一个 IRP 到下层,确保硬件中断时总有 IRP 等待。

c 复制代码
VOID NTAPI
KbdClassStartIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    /* 1. 启动下层读取 IRP */
    IoReuseIrp(Ext->ReadIrp, STATUS_SUCCESS);
    /* 设置下层 IRP 参数 */
    IoSetCompletionRoutine(Ext->ReadIrp, KbdClassReadComplete, Ext, TRUE, TRUE, TRUE);
    IoCallDriver(Ext->LowerDevice, Ext->ReadIrp);
}

NTSTATUS NTAPI
KbdClassReadComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context)
{
    PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)Context;
    PKEYBOARD_INPUT_DATA InputData = (PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer;

    if (NT_SUCCESS(Irp->IoStatus.Status))
    {
        /* 1. 把数据放入环形缓冲 */
        KbdClassAddDataToBuffer(Ext, InputData, Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA));

        /* 2. 唤醒等待的 IRP */
        KbdClassServeQueue(Ext);
    }

    /* 3. 重新启动下层 IRP 循环 */
    KbdClassStartIo(DeviceObject, Irp);

    return STATUS_MORE_PROCESSING_REQUIRED;  // 不让 IRP 完成(这是循环 IRP)
}

9.7.5 fltmgr 过滤管理器

fltmgrdrivers/filters/fltmgr/(file:///d:/reactos/drivers/filters/fltmgr/))是现代 Windows 的 Minifilter 框架,提供高级过滤驱动 API:

  • Altitude-based loading:按高度自动排序加载
  • Instance registration:动态注册过滤实例
  • Callback management:统一管理 pre/post-operation 回调
  • Filter communication ports:与用户态通信

fltmgr 设备栈

复制代码
+-------------------------+
| 应用层                  |
+-------------------------+
       |
       v
+-------------------------+
| Minifilter #1 (高度 100) |  <- FltMgr 统一管理
+-------------------------+
       |
       v
+-------------------------+
| Minifilter #2 (高度 200) |
+-------------------------+
       |
       v
+-------------------------+
| FDO                     |
+-------------------------+
       |
       v
+-------------------------+
| 硬件                    |
+-------------------------+

关键 API

c 复制代码
NTSTATUS FltRegisterFilter(
    IN PDRIVER_OBJECT Driver,
    IN CONST FLT_REGISTRATION *Registration,
    OUT PFLT_FILTER *Filter);

NTSTATUS FltStartFiltering(IN PFLT_FILTER Filter);

NTSTATUS FltCreateCommunicationPort(
    IN PFLT_FILTER Filter,
    OUT PFLT_PORT *ServerPort,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    IN PVOID ServerPortCookie,
    IN PFLT_CONNECT_NOTIFY ConnectNotify,
    IN PFLT_DISCONNECT_NOTIFY DisconnectNotify,
    IN PFLT_MESSAGE_NOTIFY MessageNotify,
    IN LONG MaxConnections);

关键回调

c 复制代码
typedef struct _FLT_REGISTRATION {
    USHORT Size;
    USHORT Version;
    PFLT_OPERATION_REGISTRATION OperationRegistration;
    PFLT_FILTER_UNLOAD_CALLBACK FilterUnloadCallback;
    PFLT_INSTANCE_SETUP_CALLBACK InstanceSetupCallback;
    PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK InstanceQueryTeardownCallback;
    PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownCallback;
    PFLT_GENERATE_FILE_NAME GenerateFileNameCallback;
    PFLT_NORMALIZE_NAME_COMPONENT NormalizeNameComponentCallback;
    PFLT_TRANSACTION_NOTIFICATION_CALLBACK TransactionNotificationCallback;
    PFLT_NORMALIZE_CONTEXT_CLEANUP NormalizeContextCleanupCallback;
} FLT_REGISTRATION, *PFLT_REGISTRATION;

重要回调

  • InstanceSetupCallback:决定是否附加到某卷
  • InstanceQueryTeardownCallback:决定是否允许 teardown
  • PreOperationCallback:IRP 处理前调用
  • PostOperationCallback:IRP 处理后调用

关键文件

文件 内容
Filter.c(file:///d:/reactos/drivers/filters/fltmgr/Filter.c) FltRegisterFilter
Dispatch.c(file:///d:/reactos/drivers/filters/fltmgr/Dispatch.c) IRP 派发
Context.c(file:///d:/reactos/drivers/filters/fltmgr/Context.c) 上下文管理
Lib.c(file:///d:/reactos/drivers/filters/fltmgr/Lib.c) 辅助库
Messaging.c(file:///d:/reactos/drivers/filters/fltmgr/Messaging.c) 通信端口
Object.c(file:///d:/reactos/drivers/filters/fltmgr/Object.c) Flt 对象管理
Volume.c(file:///d:/reactos/drivers/filters/fltmgr/Volume.c) 卷实例管理

9.7.5.1 过滤管理器内部架构深度剖析

ReactOS的过滤管理器(fltmgr)实现位于drivers/filters/fltmgr/目录,提供了现代Windows Minifilter框架的兼容实现。过滤管理器通过分层架构管理多个过滤驱动,每个过滤驱动可以注册回调函数来处理文件系统IRP。

核心数据结构

过滤管理器的内部结构定义在fltmgrint.h中,主要包括以下几个关键结构:

FLT_OBJECT基础结构

所有过滤管理器对象(过滤器、实例、卷)都继承自FLT_OBJECT基础结构:

c 复制代码
typedef enum _FLT_OBJECT_FLAGS
{
    FLT_OBFL_DRAINING = 1,           // 对象正在排空
    FLT_OBFL_ZOMBIED = 2,            // 对象已僵尸化
    FLT_OBFL_TYPE_INSTANCE = 0x1000000,
    FLT_OBFL_TYPE_FILTER = 0x2000000,
    FLT_OBFL_TYPE_VOLUME = 0x4000000
} FLT_OBJECT_FLAGS;

typedef struct _FLT_OBJECT
{
    volatile FLT_OBJECT_FLAGS Flags;  // 对象标志
    ULONG PointerCount;               // 引用计数
    EX_RUNDOWN_REF RundownRef;        // 排空保护
    LIST_ENTRY PrimaryLink;           // 主链表链接
} FLT_OBJECT;

这个基础结构提供了引用计数、排空保护和类型标识功能。EX_RUNDOWN_REF用于确保在对象销毁时所有活动操作都已完成。

FLTP_FRAME框架结构

FLTP_FRAME是过滤管理器的核心管理结构,每个框架对应一个过滤驱动集合:

c 复制代码
typedef struct _FLTP_FRAME
{
    FLT_TYPE Type;
    LIST_ENTRY Links;
    unsigned int FrameID;
    ERESOURCE AltitudeLock;
    UNICODE_STRING AltitudeIntervalLow;
    UNICODE_STRING AltitudeIntervalHigh;
    char LargeIrpCtrlStackSize;
    char SmallIrpCtrlStackSize;
    FLT_RESOURCE_LIST_HEAD RegisteredFilters;  // 已注册过滤器列表
    FLT_RESOURCE_LIST_HEAD AttachedVolumes;    // 已附加卷列表
    LIST_ENTRY MountingVolumes;                // 正在挂载的卷
    FLT_MUTEX_LIST_HEAD AttachedFileSystems;   // 已附加文件系统
    FLT_MUTEX_LIST_HEAD ZombiedFltObjectContexts; // 僵尸对象上下文
    ERESOURCE FilterUnloadLock;
    FAST_MUTEX DeviceObjectAttachLock;
    NPAGED_LOOKASIDE_LIST SmallIrpCtrlLookasideList;
    NPAGED_LOOKASIDE_LIST LargeIrpCtrlLookasideList;
} FLTP_FRAME;

框架结构维护了两个关键列表:RegisteredFilters存储所有已注册的过滤器,AttachedVolumes存储所有已附加的卷实例。AltitudeLock保护高度(Altitude)相关的操作,确保多个过滤器按正确顺序加载。

FLT_FILTER过滤器结构

FLT_FILTER结构表示一个已注册的过滤驱动:

c 复制代码
typedef struct _FLT_FILTER
{
    FLT_OBJECT Base;
    PFLTP_FRAME Frame;
    UNICODE_STRING Name;
    UNICODE_STRING DefaultAltitude;
    FLT_FILTER_FLAGS Flags;
    PDRIVER_OBJECT DriverObject;
    FLT_RESOURCE_LIST_HEAD InstanceList;      // 实例列表
    PVOID VerifierExtension;
    PFLT_FILTER_UNLOAD_CALLBACK FilterUnload;
    PFLT_INSTANCE_SETUP_CALLBACK InstanceSetup;
    PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK InstanceQueryTeardown;
    PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownStart;
    PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownComplete;
    PALLOCATE_CONTEXT_HEADER SupportedContextsListHead;
    PALLOCATE_CONTEXT_HEADER SupportedContexts[MAX_CONTEXT_TYPES];
    PVOID PreVolumeMount;
    PVOID PostVolumeMount;
    PFLT_GENERATE_FILE_NAME GenerateFileName;
    PFLT_NORMALIZE_NAME_COMPONENT NormalizeNameComponent;
    PFLT_NORMALIZE_CONTEXT_CLEANUP NormalizeContextCleanup;
    PFLT_OPERATION_REGISTRATION Operations;   // 操作回调数组
    PFLT_FILTER_UNLOAD_CALLBACK OldDriverUnload;
    FLT_MUTEX_LIST_HEAD ActiveOpens;
    FLT_MUTEX_LIST_HEAD ConnectionList;
    FLT_MUTEX_LIST_HEAD PortList;
    EX_PUSH_LOCK PortLock;
} FLT_FILTER;

过滤器结构包含了所有回调函数指针:FilterUnload在过滤器卸载时调用,InstanceSetup在附加到新卷时调用,Operations数组存储了所有IRP操作的Pre/Post回调。

FLT_INSTANCE实例结构

FLT_INSTANCE表示过滤器在特定卷上的实例:

c 复制代码
typedef struct _FLT_INSTANCE
{
    FLT_OBJECT Base;
    ULONG OperationRundownRef;
    PVOID Volume;
    PFLT_FILTER Filter;
    FLT_INSTANCE_FLAGS Flags;
    UNICODE_STRING Altitude;
    UNICODE_STRING Name;
    LIST_ENTRY FilterLink;
    ERESOURCE ContextLock;
    PVOID Context;
    PVOID TrackCompletionNodes;
    PVOID CallbackNodes[50];  // 回调节点数组
} FLT_INSTANCE;

每个实例都关联到一个过滤器和一个卷,CallbackNodes数组存储了该实例在各个IRP操作上的回调节点。

FltRegisterFilter注册流程

FltRegisterFilter是过滤驱动注册的核心函数,定义在Filter.c中:

c 复制代码
NTSTATUS NTAPI FltRegisterFilter(
    IN PDRIVER_OBJECT DriverObject,
    IN const FLT_REGISTRATION *Registration,
    OUT PFLT_FILTER *RetFilter)
{
    PFLT_OPERATION_REGISTRATION Callbacks;
    PFLT_FILTER Filter;
    ULONG CallbackBufferSize;
    ULONG FilterBufferSize;
    ULONG Count = 0;
    
    // 验证版本兼容性
    if ((Registration->Version & 0xFF00) != FLT_MAJOR_VERSION)
    {
        return STATUS_INVALID_PARAMETER;
    }
    
    // 验证命名空间回调
    if ((!Registration->GenerateFileNameCallback && 
         Registration->NormalizeNameComponentCallback) ||
        (!Registration->NormalizeNameComponentCallback && 
         Registration->NormalizeContextCleanupCallback))
    {
        return STATUS_INVALID_PARAMETER;
    }
    
    // 计算回调数量
    Callbacks = (PFLT_OPERATION_REGISTRATION)Registration->OperationRegistration;
    while (Callbacks && Callbacks->MajorFunction != IRP_MJ_OPERATION_END)
    {
        Count++;
        Callbacks++;
    }
    
    // 计算缓冲区大小
    CallbackBufferSize = Count * sizeof(FLT_OPERATION_REGISTRATION);
    FilterBufferSize = sizeof(FLT_FILTER) + CallbackBufferSize +
                       DriverObject->DriverExtension->ServiceKeyName.Length;
    
    // 分配过滤器结构
    Filter = ExAllocatePoolWithTag(NonPagedPool, FilterBufferSize, FM_TAG_FILTER);
    if (!Filter) return STATUS_INSUFFICIENT_RESOURCES;
    
    RtlZeroMemory(Filter, FilterBufferSize);
    
    // 初始化基础对象
    Filter->Base.Flags = FLT_OBFL_TYPE_FILTER;
    Filter->Base.PointerCount = 1;
    FltpExInitializeRundownProtection(&Filter->Base.RundownRef);
    
    // 设置回调函数
    Filter->FilterUnload = Registration->FilterUnloadCallback;
    Filter->InstanceSetup = Registration->InstanceSetupCallback;
    Filter->InstanceQueryTeardown = Registration->InstanceQueryTeardownCallback;
    Filter->InstanceTeardownStart = Registration->InstanceTeardownStartCallback;
    Filter->InstanceTeardownComplete = Registration->InstanceTeardownCompleteCallback;
    
    // 初始化实例列表
    ExInitializeResourceLite(&Filter->InstanceList.rLock);
    InitializeListHead(&Filter->InstanceList.rList);
    
    // 复制操作回调
    if (Registration->OperationRegistration)
    {
        Filter->Operations = (PFLT_OPERATION_REGISTRATION)(Filter + 1);
        RtlCopyMemory(Filter->Operations, 
                      Registration->OperationRegistration, 
                      CallbackBufferSize);
        
        // 特殊处理卷挂载回调
        for (Callbacks = Filter->Operations;
             Callbacks->MajorFunction != IRP_MJ_OPERATION_END;
             Callbacks++)
        {
            if (Callbacks->MajorFunction == IRP_MJ_VOLUME_MOUNT)
            {
                Filter->PreVolumeMount = Callbacks->PreOperation;
                Filter->PostVolumeMount = Callbacks->PostOperation;
            }
        }
    }
    
    *RetFilter = Filter;
    return STATUS_SUCCESS;
}

注册流程的关键步骤包括:验证版本兼容性、计算回调数量、分配过滤器结构、初始化引用计数、复制回调函数指针。特别注意IRP_MJ_VOLUME_MOUNT操作会被特殊处理,存储到PreVolumeMountPostVolumeMount字段。

高度(Altitude)机制

过滤管理器使用高度值来确定过滤器的加载顺序。高度是一个字符串值,数值越大表示过滤器越靠近应用层。ReactOS在Filter.c中实现了高度解析和排序:

c 复制代码
static NTSTATUS GetFilterAltitude(
    IN PFLT_FILTER Filter,
    IN PUNICODE_STRING AltitudeString)
{
    // 从注册表读取高度值
    // 验证高度格式
    // 解析高度范围
}

高度值的典型范围:

  • 0-49999: 底层过滤器(如防病毒、加密)
  • 50000-99999: 中间层过滤器(如备份、快照)
  • 100000+: 高层过滤器(如日志、监控)

与Windows过滤管理器的对比

ReactOS的过滤管理器实现与Windows基本兼容,但存在一些差异:

  1. IRP控制栈优化: Windows使用更复杂的栈分配策略,ReactOS使用简单的lookaside列表
  2. 事务支持: Windows支持KTM(内核事务管理器)集成,ReactOS尚未完全实现
  3. 名称缓存: Windows有复杂的文件名缓存机制,ReactOS实现较简单
  4. 卷挂载检测: Windows使用更可靠的卷挂载通知机制
  5. 过滤器通信: Windows的过滤器通信端口支持更完善

尽管如此,ReactOS的过滤管理器已经能够支持大多数标准Minifilter驱动,包括基本的文件监控、防病毒和备份功能。

调试过滤驱动

过滤驱动的调试需要特殊技巧:

  1. 使用FltDbg工具: 查看已注册的过滤器和实例
  2. 检查高度值: 确保过滤器按正确顺序加载
  3. 验证回调返回 : PreOperation回调必须返回正确的FLT_PREOP_CALLBACK_STATUS
  4. 监控引用计数: 确保对象正确释放,避免内存泄漏
  5. 检查上下文管理: 确保上下文正确分配和释放

常见问题包括:

  • 过滤器未正确附加到卷(检查InstanceSetup回调)
  • IRP处理顺序错误(检查高度值和回调链)
  • 上下文泄漏(确保实现Cleanup回调)
  • 死锁(避免在回调中等待其他IRP完成)

9.7.6 十问为什么

1. 为什么过滤驱动要分成上层过滤和下层过滤两种位置?

因为两者捕获 IRP 的时机和目的完全不同上层过滤 (Upper Filter)位于 FDO 之上,首先接收来自应用层的 IRP,适合做数据转换(如 kbdclass 把原始键盘扫描码转为 KEYBOARD_INPUT_DATA)、透明加密、访问日志记录。下层过滤(Lower Filter)位于 FDO 之下,接收经过 FDO 处理后的 IRP,适合做硬件层面的额外控制(如总线过滤、电源策略调整)。如果只有一个统一的过滤位置,类驱动和总线过滤的需求就会冲突,无法同时满足。

2. 为什么 AddDevice 中必须保存 LowerDevice 指针?

IoAttachDeviceToDeviceStack 只是把过滤设备插入到设备栈中,建立了 AttachedDevice 指针关系。但过滤驱动自身在后续所有 IRP 转发操作中都需要知道"下层设备是谁"才能调用 IoCallDriver。如果没有保存 LowerDevice,过滤驱动在收到 IRP 时就不知道该转发给谁,设备栈断裂,IRP 无法下传。这是过滤驱动开发中最容易被忽略但最关键的一步。

3. 为什么 IoSkipCurrentIrpStackLocationIoCopyCurrentIrpStackLocationToNext 不能混用?

两者的语义完全不同:

  • IoSkipCurrentIrpStackLocation :只是将 CurrentStackLocation 指针前移一位 ,不修改下层栈单元的内容。下层驱动看到的栈单元就是过滤驱动当前看到的那个。适用于不修改 IRP 参数的纯转发。
  • IoCopyCurrentIrpStackLocationToNext :将当前栈单元的内容复制一份到下层栈单元 ,然后过滤驱动可以修改下层的参数。适用于修改 IRP 参数的场景(如修改 Read 长度、改变缓冲区)。

如果该用 Copy 的时候用了 Skip,下层驱动会读到未初始化的栈单元;如果该用 Skip 的时候用了 Copy,会浪费一个栈单元,而且如果 StackSize 计算错误还可能导致栈溢出。

4. 为什么完成例程返回 STATUS_SUCCESSSTATUS_MORE_PROCESSING_REQUIRED 效果完全不同?

这是过滤驱动控制 IRP 完成流程的核心机制:

  • STATUS_SUCCESS :告诉 IoCompleteRequest "我处理完了,继续向上完成"。IRP 继续沿完成链向上传播,最终回到发起者。
  • STATUS_MORE_PROCESSING_REQUIRED :告诉 IoCompleteRequest "停止,这个 IRP 我还要用"。IoCompleteRequest 会立即停止向上传播,IRP 停留在当前层次。

kbdclass 的 KbdClassReadComplete 返回 STATUS_MORE_PROCESSING_REQUIRED,因为下层返回的 IRP 是一个循环 IRP (ReadIrp),kbdclass 需要重新使用它发起下一次读取。如果返回 STATUS_SUCCESS,这个 IRP 就会被释放,循环中断,键盘再也无法接收输入。

5. 为什么 kbdclass 要维护一个 IRP 循环(ReadIrp 循环)?

键盘是异步输入设备 ------用户随时可能按键,但应用层不会一直调用 ReadFile。kbdclass 的解决方案是:向下层(i8042prt)持续发送读取 IRP,形成一个无限循环 。当用户按键时,i8042prt 完成 IRP 并返回扫描码;kbdclass 在完成例程中把数据放入环形缓冲,然后立即重新发送同一个 IRP 到下层的 IoCallDriver。这样无论应用层是否正在读取,键盘数据都不会丢失。如果没有这个循环,应用层调用 ReadFile 之前按的键就会丢失。

6. 为什么 fltmgr 使用 Altitude(高度)机制而不是简单的注册表加载顺序?

因为文件系统过滤的顺序直接影响数据一致性。例如:

  • 防病毒过滤器(Altitude 低)需要先扫描文件内容
  • 加密过滤器(Altitude 中间)需要在防病毒扫描后、备份过滤器之前加密
  • 备份过滤器(Altitude 高)需要在数据完全处理后才能备份

如果使用简单的注册表顺序,管理员或安装程序可能搞错顺序,导致防病毒扫描到的是加密后的乱码。Altitude 是一个数值化的高度值,数值越小越靠近文件系统,数值越大越靠近应用层。fltmgr 按数值自动排序,强制保证正确的处理顺序,不受安装时序影响。

7. 为什么过滤驱动的回调中不能阻塞或等待其他 IRP 完成?

因为过滤驱动位于所有 I/O 路径的中间 。如果过滤驱动的回调(PreOperation 或完成例程)中等待另一个 IRP 完成,而那个 IRP 恰好也要经过这个过滤驱动,就会形成死锁 。例如:防病毒过滤器在 PreCreate 回调中等待另一个文件读取 IRP,而这个读取 IRP 也需要经过同一个防病毒过滤器的 PreRead 回调。更糟糕的是,IRP 处理通常在 DISPATCH_LEVEL 或 DIRQL 上,此时根本无法调用会阻塞的等待函数(如 KeWaitForSingleObject)。

8. 为什么 FLT_OBJECT 需要 RundownRef(排空保护)?

过滤管理器中的对象(Filter、Instance、Volume)有异步的生命周期 。例如,当用户卸载防病毒软件时,FltUnregisterFilter 被调用,但此时可能还有正在处理中的 IRP 引用了这个过滤器的回调。RundownRef 的作用是:

  • 标记对象进入"排空"状态,阻止新的引用
  • 等待所有已存在的引用完成
  • 确保对象在最后一个 IRP 完成后才释放

没有排空保护,对象可能在 IRP 还在使用它时被释放,导致 use-after-free 蓝屏。

9. 为什么过滤驱动通常不在 DriverEntry 中创建设备对象?

因为过滤驱动的设备对象数量是动态的、不可预知的 。以 kbdclass 为例,系统可能有 0 个键盘(无 PS/2 接口)、1 个键盘(标准 PS/2)或多个键盘(USB + PS/2)。过滤驱动不知道有多少个下层设备需要过滤。正确的做法是:在 DriverEntry 中只注册 AddDevice 回调,然后等待 PnP 管理器每次发现新的 PDO 时调用 AddDevice。这样每个下层设备都有一个对应的过滤设备对象,数量自动匹配。

10. 为什么 fltmgr 对 IRP_MJ_VOLUME_MOUNT 要做特殊处理?

因为卷挂载是过滤驱动附加的关键时机 。当一个新卷(如插入 U 盘)被挂载时,fltmgr 需要在该卷的设备栈构建完成后,按 Altitude 顺序将所有已注册的过滤器附加到这个新卷上IRP_MJ_VOLUME_MOUNT 的处理流程是:

  1. 文件系统识别到新卷
  2. 发送 VOLUME_MOUNT IRP
  3. fltmgr 捕获这个 IRP,触发所有过滤器的 InstanceSetupCallback
  4. 每个过滤器决定是否附加到这个卷
  5. 按 Altitude 排序建立过滤实例链

如果不特殊处理挂载事件,新插入的 U 盘将完全没有过滤保护(防病毒不扫描、加密不生效),这是严重的安全漏洞。


9.7.6 总结

过滤设备驱动的核心要点:

  1. 两种位置:Upper Filter(应用 IRP 首先到达)、Lower Filter(PnP IRP 经过)
  2. AddDevice :创建 Filter DO + IoAttachDeviceToDeviceStack
  3. IRP 转发
    • IoSkipCurrentIrpStackLocation + IoCallDriver(不修改 IRP)
    • IoCopyCurrentIrpStackLocationToNext + IoCallDriver(修改参数)
  4. 完成例程IoSetCompletionRoutine 在下层完成后回到过滤驱动
  5. kdclass 实例:环形缓冲 + IRP 循环 + 类驱动模式
  6. fltmgr 实例:Minifilter 框架 + Altitude 排序 + 高级回调

下一节 9.8 介绍 设备驱动模块的装载(NtLoadDriver / IopLoadDriverImage / SCM)。


本章代码索引

文件 内容
kbdclass.c(file:///d:/reactos/drivers/input/kbdclass/kbdclass.c) 键盘类驱动(典型 Upper Filter)
mouclass.c(file:///d:/reactos/drivers/input/mouclass/mouclass.c) 鼠标类驱动
fltmgr/Filter.c(file:///d:/reactos/drivers/filters/fltmgr/Filter.c) FltRegisterFilter
fltmgr/Dispatch.c(file:///d:/reactos/drivers/filters/fltmgr/Dispatch.c) 过滤 IRP 派发
io/iomgr/device.c(file:///d:/reactos/ntoskrnl/io/iomgr/device.c) IoAttachDeviceToDeviceStack
io/iomgr/dispatch.c(file:///d:/reactos/ntoskrnl/io/iomgr/dispatch.c) IoCallDriver
io/iomgr/irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) IoSetCompletionRoutineIoSkipCurrentIrpStackLocation
fltmgr.h(file:///d:/reactos/drivers/filters/fltmgr/fltmgr.h) FLT_REGISTRATION 等
fltmgrint.h(file:///d:/reactos/drivers/filters/fltmgr/fltmgrint.h) 内部结构
相关推荐
caimouse2 小时前
Reactos 第 7 章 视窗报文 — 7.7 鼠标器输入线程
windows
caimouse2 小时前
Reactos 第 7 章 视窗报文 — 7.2 视窗报文的接收
windows
caimouse2 小时前
Reactos 第 8 章 结构化异常处理 — 8.3 用户空间的结构化异常处理
windows
caimouse3 小时前
Reactos 第 9 章 设备驱动 — 9.6 中断处理
网络·windows
caimouse3 小时前
Reactos 第 7 章 视窗报文 — 7.6 键盘输入线程
windows
yinhunzw3 小时前
Claude code windows 安装
windows
七仔啊4 小时前
windows server 2022 部署前后端项目
windows
caimouse4 小时前
Reactos 第 7 章 视窗报文 — 7.4 用户空间的外挂函数
windows
辣香牛肉面5 小时前
Windows发票工具大全
windows·发票助手