第 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 设备栈中有两种位置:
- 上层过滤(Upper Filter):位于 FDO 之上,接收应用层 IRP
- 下层过滤(Lower Filter):位于 FDO 之下,接收 PnP 传入的 IRP
过滤驱动的核心工作流:
- AddDevice:创建 Filter DO,附加到目标设备
- IRP 转发 :构造
IoCopyCurrentIrpStackLocationToNext+IoCallDriver - 完成例程 :通过
IoSetCompletionRoutine在下层完成后回到过滤驱动 - 修改 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);
}
IoSkipCurrentIrpStackLocation 把 CurrentStackLocation 前移一位,下层处理时会读取过滤驱动填写的栈单元。
转发 + 完成例程
如果需要在 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 关键工作
- 创建设备
\Device\KeyboardClass0(在 DriverEntry 中) - 附加到 i8042prt(在 AddDevice 中)
- 构造 KEYBOARD_INPUT_DATA(在 i8042prt 完成时)
- 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 过滤管理器
fltmgr(drivers/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操作会被特殊处理,存储到PreVolumeMount和PostVolumeMount字段。
高度(Altitude)机制
过滤管理器使用高度值来确定过滤器的加载顺序。高度是一个字符串值,数值越大表示过滤器越靠近应用层。ReactOS在Filter.c中实现了高度解析和排序:
c
static NTSTATUS GetFilterAltitude(
IN PFLT_FILTER Filter,
IN PUNICODE_STRING AltitudeString)
{
// 从注册表读取高度值
// 验证高度格式
// 解析高度范围
}
高度值的典型范围:
- 0-49999: 底层过滤器(如防病毒、加密)
- 50000-99999: 中间层过滤器(如备份、快照)
- 100000+: 高层过滤器(如日志、监控)
与Windows过滤管理器的对比
ReactOS的过滤管理器实现与Windows基本兼容,但存在一些差异:
- IRP控制栈优化: Windows使用更复杂的栈分配策略,ReactOS使用简单的lookaside列表
- 事务支持: Windows支持KTM(内核事务管理器)集成,ReactOS尚未完全实现
- 名称缓存: Windows有复杂的文件名缓存机制,ReactOS实现较简单
- 卷挂载检测: Windows使用更可靠的卷挂载通知机制
- 过滤器通信: Windows的过滤器通信端口支持更完善
尽管如此,ReactOS的过滤管理器已经能够支持大多数标准Minifilter驱动,包括基本的文件监控、防病毒和备份功能。
调试过滤驱动
过滤驱动的调试需要特殊技巧:
- 使用FltDbg工具: 查看已注册的过滤器和实例
- 检查高度值: 确保过滤器按正确顺序加载
- 验证回调返回 : PreOperation回调必须返回正确的
FLT_PREOP_CALLBACK_STATUS - 监控引用计数: 确保对象正确释放,避免内存泄漏
- 检查上下文管理: 确保上下文正确分配和释放
常见问题包括:
- 过滤器未正确附加到卷(检查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. 为什么 IoSkipCurrentIrpStackLocation 和 IoCopyCurrentIrpStackLocationToNext 不能混用?
两者的语义完全不同:
IoSkipCurrentIrpStackLocation:只是将CurrentStackLocation指针前移一位 ,不修改下层栈单元的内容。下层驱动看到的栈单元就是过滤驱动当前看到的那个。适用于不修改 IRP 参数的纯转发。IoCopyCurrentIrpStackLocationToNext:将当前栈单元的内容复制一份到下层栈单元 ,然后过滤驱动可以修改下层的参数。适用于修改 IRP 参数的场景(如修改 Read 长度、改变缓冲区)。
如果该用 Copy 的时候用了 Skip,下层驱动会读到未初始化的栈单元;如果该用 Skip 的时候用了 Copy,会浪费一个栈单元,而且如果 StackSize 计算错误还可能导致栈溢出。
4. 为什么完成例程返回 STATUS_SUCCESS 和 STATUS_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 的处理流程是:
- 文件系统识别到新卷
- 发送 VOLUME_MOUNT IRP
- fltmgr 捕获这个 IRP,触发所有过滤器的
InstanceSetupCallback - 每个过滤器决定是否附加到这个卷
- 按 Altitude 排序建立过滤实例链
如果不特殊处理挂载事件,新插入的 U 盘将完全没有过滤保护(防病毒不扫描、加密不生效),这是严重的安全漏洞。
9.7.6 总结
过滤设备驱动的核心要点:
- 两种位置:Upper Filter(应用 IRP 首先到达)、Lower Filter(PnP IRP 经过)
- AddDevice :创建 Filter DO +
IoAttachDeviceToDeviceStack - IRP 转发 :
IoSkipCurrentIrpStackLocation+IoCallDriver(不修改 IRP)IoCopyCurrentIrpStackLocationToNext+IoCallDriver(修改参数)
- 完成例程 :
IoSetCompletionRoutine在下层完成后回到过滤驱动 - kdclass 实例:环形缓冲 + IRP 循环 + 类驱动模式
- 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) | IoSetCompletionRoutine、IoSkipCurrentIrpStackLocation |
| fltmgr.h(file:///d:/reactos/drivers/filters/fltmgr/fltmgr.h) | FLT_REGISTRATION 等 |
| fltmgrint.h(file:///d:/reactos/drivers/filters/fltmgr/fltmgrint.h) | 内部结构 |