第 9 章 设备驱动 --- 9.4 内核劳务线程
本节深入剖析内核劳务线程(Kernel Worker Threads)机制。 DPC(9.3 节)虽然轻量,但它在 DISPATCH_LEVEL 运行,不能访问分页内存、不能等待。ReactOS 提供 内核劳务线程 用于运行"需要 PASSIVE_LEVEL 上下文但又不属于某个具体用户线程"的工作。关键 API 包括 ExQueueWorkItem、IoQueueWorkItem、IoAllocateWorkItem,实现位于 ntoskrnl/ex/work.c(file:///d:/reactos/ntoskrnl/ex/work.c) 和 ntoskrnl/io/iomgr/work.c(file:///d:/reactos/ntoskrnl/io/iomgr/work.c)。
概述
内核劳务线程机制由三个层次构成:
- EX 工作项层 :
ExQueueWorkItem、ExQueueWorkItemToFront、ExInitializeWorkItem(无状态) - IO 工作项层 :
IoQueueWorkItem、IoAllocateWorkItem、IoFreeWorkItem(带对象引用) - 设备线程层 :
IoQueueWorkItemToDeviceThread(设备专用线程)
每个工作项都是一条 延迟执行的工作单元 ,会被某个系统线程(ExWorkerThread)取出并执行。
本节内容概览
- 9.4.0 框架图
- 9.4.1 工作项数据结构
- 9.4.2
ExInitializeWorkItem/ExQueueWorkItem - 9.4.3
IoAllocateWorkItem/IoQueueWorkItem/IoFreeWorkItem - 9.4.4 系统工作线程
ExpWorkerThread - 9.4.5 设备专用线程
- 9.4.6 工作项与 DPC 的选择
- 9.4.7 总结与代码索引
学习目标
- 能够区分 DPC、APC、WorkItem 三种延迟机制
- 理解
IoQueueWorkItem与ExQueueWorkItem的差异 - 知道何时使用工作线程 vs DPC
- 理解系统工作线程的循环结构
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| EX 工作项 | ntoskrnl/ex/work.c(file:///d:/reactos/ntoskrnl/ex/work.c) | ExInitializeWorkItem、ExQueueWorkItem、ExpWorkerThread |
| IO 工作项 | ntoskrnl/io/iomgr/work.c(file:///d:/reactos/ntoskrnl/io/iomgr/work.c) | IoAllocateWorkItem、IoQueueWorkItem |
| 设备线程 | ntoskrnl/io/iomgr/devicethread.c(file:///d:/reactos/ntoskrnl/io/iomgr/devicethread.c) | IoQueueWorkItemToDeviceThread |
| EX 工作项类型 | sdk/include/xdk/extypes.h(file:///d:/reactos/sdk/include/xdk/extypes.h) | WORK_QUEUE_TYPE、PIO_WORKITEM |
| KeWaitFor 同步 | ntoskrnl/ke/wait.c(file:///d:/reactos/ntoskrnl/ke/wait.c) | KeWaitForSingleObject |
9.4.0 框架图
+------------------------+
| 驱动 / 内核组件 |
| 触发工作: |
| IoQueueWorkItem |
| ExQueueWorkItem |
+------------------------+
|
v (链表插入)
+------------------------+
| ExWorkQueue[] | 按类型分队列:
| - CriticalWorkQueue | 0=Critical, 1=Delayed, 2=HyperCritical
| - DelayedWorkQueue |
| - HyperCritical |
+------------------------+
|
v (信号量)
+------------------------+
| ExpWorkerThread | 等待 WorkQueue 的 Semaphore
| (系统工作线程) | 取出 work_item 执行
+------------------------+
|
v
+------------------------+
| WorkItem->Routine() | PASSIVE_LEVEL
| WorkItem->Context | 任意工作:
| | - 访问分页内存
| | - 等待资源
| | - 长时间任务
+------------------------+
9.4.1 工作项数据结构
PWORK_QUEUE_ITEM(EX 层)
c
typedef struct _WORK_QUEUE_ITEM {
LIST_ENTRY List; // 队列链表节点
PWORKER_THREAD_ROUTINE WorkerRoutine; // 回调函数
PVOID Parameter; // 回调参数
} WORK_QUEUE_ITEM, *PWORK_QUEUE_ITEM;
PIO_WORKITEM(IO 层)
c
typedef struct _IO_WORKITEM {
WORK_QUEUE_ITEM WorkItem; // 内嵌 EX 工作项
PIO_WORKITEM_ROUTINE Routine; // 回调函数
PDEVICE_OBJECT DeviceObject; // 关联设备对象
PVOID Context; // 驱动上下文
PETHREAD Thread; // 调用 IoAllocateWorkItem 的线程
} IO_WORKITEM, *PIO_WORKITEM;
WORK_QUEUE_TYPE
c
typedef enum _WORK_QUEUE_TYPE {
CriticalWorkQueue = 0, // 关键队列(高优先级)
DelayedWorkQueue = 1, // 延迟队列(普通)
HyperCriticalWorkQueue = 2, // 超关键队列
MaximumWorkQueue = 3
} WORK_QUEUE_TYPE;
9.4.2 ExInitializeWorkItem / ExQueueWorkItem
ExInitializeWorkItem
c
VOID NTAPI
ExInitializeWorkItem(
IN PWORK_QUEUE_ITEM Item,
IN PWORKER_THREAD_ROUTINE Routine,
IN PVOID Context);
初始化一个 EX 工作项(不入队):
c
VOID NTAPI
ExInitializeWorkItem(IN PWORK_QUEUE_ITEM Item, IN PWORKER_THREAD_ROUTINE Routine, IN PVOID Context)
{
Item->WorkerRoutine = Routine;
Item->Parameter = Context;
}
ExQueueWorkItem
c
VOID NTAPI
ExQueueWorkItem(
IN PWORK_QUEUE_ITEM Item,
IN WORK_QUEUE_TYPE Type);
把工作项加入指定队列:
c
VOID NTAPI
ExQueueWorkItem(IN PWORK_QUEUE_ITEM Item, IN WORK_QUEUE_TYPE Type)
{
PEX_WORK_QUEUE Queue = &ExWorkQueue[Type];
/* 加锁 */
ExAcquireSpinLockExclusive(&Queue->Lock);
/* 加入队列 */
InsertTailList(&Queue->QueueHeader, &Item->List);
/* 释放信号量唤醒工作线程 */
KeReleaseSemaphore(&Queue->Semaphore, IO_NO_INCREMENT, 1, FALSE);
ExReleaseSpinLockExclusive(&Queue->Lock);
}
ExQueueWorkItemToFront
c
VOID NTAPI
ExQueueWorkItemToFront(
IN PWORK_QUEUE_ITEM Item,
IN WORK_QUEUE_TYPE Type);
把工作项加入队列头(高优先级)。
典型用法
c
WORK_QUEUE_ITEM WorkItem;
ExInitializeWorkItem(&WorkItem, MyWorkerRoutine, MyContext);
ExQueueWorkItem(&WorkItem, DelayedWorkQueue);
VOID MyWorkerRoutine(PVOID Context)
{
/* PASSIVE_LEVEL 上下文 */
/* 访问分页内存、等待资源 */
...
}
9.4.3 IoAllocateWorkItem / IoQueueWorkItem / IoFreeWorkItem
IoAllocateWorkItem
c
PIO_WORKITEM NTAPI
IoAllocateWorkItem(IN PDEVICE_OBJECT DeviceObject);
分配一个 IO 工作项,与设备对象关联:
c
PIO_WORKITEM NTAPI
IoAllocateWorkItem(IN PDEVICE_OBJECT DeviceObject)
{
PIO_WORKITEM IoWorkItem;
IoWorkItem = ExAllocatePoolWithTag(NonPagedPool, sizeof(IO_WORKITEM), 'WIoR');
if (!IoWorkItem) return NULL;
RtlZeroMemory(IoWorkItem, sizeof(IO_WORKITEM));
IoWorkItem->DeviceObject = DeviceObject;
ObReferenceObject(DeviceObject); // 引用计数
return IoWorkItem;
}
IoQueueWorkItem
c
VOID NTAPI
IoQueueWorkItem(
IN PIO_WORKITEM IoWorkItem,
IN PIO_WORKITEM_ROUTINE Routine,
IN WORK_QUEUE_TYPE Type,
IN PVOID Context);
把 IO 工作项加入队列:
c
VOID NTAPI
IoQueueWorkItem(IN PIO_WORKITEM IoWorkItem,
IN PIO_WORKITEM_ROUTINE Routine,
IN WORK_QUEUE_TYPE Type,
IN PVOID Context)
{
IoWorkItem->Routine = Routine;
IoWorkItem->Context = Context;
ExQueueWorkItem(&IoWorkItem->WorkItem, Type);
}
IoFreeWorkItem
c
VOID NTAPI
IoFreeWorkItem(IN PIO_WORKITEM IoWorkItem);
释放 IO 工作项,解除设备对象引用:
c
VOID NTAPI
IoFreeWorkItem(IN PIO_WORKITEM IoWorkItem)
{
ObDereferenceObject(IoWorkItem->DeviceObject);
ExFreePoolWithTag(IoWorkItem, 'WIoR');
}
典型用法(驱动 Unload)
c
/* 在驱动中存储 */
typedef struct _DEVICE_EXTENSION {
PIO_WORKITEM WorkItem;
...
} DEVICE_EXTENSION;
/* 初始化 */
Ext->WorkItem = IoAllocateWorkItem(DeviceObject);
/* 触发工作 */
IoQueueWorkItem(Ext->WorkItem, MyWorkerRoutine, DelayedWorkQueue, Ext);
/* 清理 */
VOID MyUnload(PDRIVER_OBJECT DriverObject) {
PDEVICE_OBJECT DevObj = DriverObject->DeviceObject;
while (DevObj) {
PDEVICE_EXTENSION Ext = DevObj->DeviceExtension;
IoFreeWorkItem(Ext->WorkItem);
IoDeleteDevice(DevObj);
DevObj = DevObj->NextDevice;
}
}
关键优势
IoAllocateWorkItem 比 ExQueueWorkItem 更安全:
- 对象引用:自动管理设备对象引用计数
- 关联性:与具体设备对象绑定
- 释放安全 :
IoFreeWorkItem在引用计数归零时安全释放 - 异常保护:内核 SEH 包装
9.4.4 系统工作线程 ExpWorkerThread
定义于 ntoskrnl/ex/work.c(file:///d:/reactos/ntoskrnl/ex/work.c):
c
VOID NTAPI
ExpWorkerThread(PVOID StartContext)
{
PEX_WORK_QUEUE WorkQueue = (PEX_WORK_QUEUE)StartContext;
PLIST_ENTRY ListEntry;
PWORK_QUEUE_ITEM WorkItem;
PWORKER_THREAD_ROUTINE WorkerRoutine;
PVOID Parameter;
NTSTATUS Status;
/* 工作线程主循环 */
for (;;)
{
/* 等待工作 */
Status = KeWaitForSingleObject(&WorkQueue->Semaphore, Executive, KernelMode, FALSE, NULL);
if (!NT_SUCCESS(Status)) break;
/* 加锁 */
ExAcquireSpinLockExclusive(&WorkQueue->Lock);
/* 取出工作 */
if (!IsListEmpty(&WorkQueue->QueueHeader))
{
ListEntry = RemoveHeadList(&WorkQueue->QueueHeader);
WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ITEM, List);
WorkerRoutine = WorkItem->WorkerRoutine;
Parameter = WorkItem->Parameter;
ExReleaseSpinLockExclusive(&WorkQueue->Lock);
/* 执行工作(PASSIVE_LEVEL) */
WorkerRoutine(Parameter);
}
else
{
ExReleaseSpinLockExclusive(&WorkQueue->Lock);
}
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
系统启动
c
VOID NTAPI
ExpInitializeWorkerThreads(VOID)
{
ULONG i;
/* 初始化三个队列 */
for (i = 0; i < MaximumWorkQueue; i++)
{
InitializeListHead(&ExWorkQueue[i].QueueHeader);
KeInitializeSemaphore(&ExWorkQueue[i].Semaphore, 0, MAXLONG);
ExInitializeSpinLock(&ExWorkQueue[i].Lock);
}
/* 创建系统线程(每个队列一个) */
for (i = 0; i < MaximumWorkQueue; i++)
{
NTSTATUS Status;
HANDLE ThreadHandle;
OBJECT_ATTRIBUTES ObjAttr;
InitializeObjectAttributes(&ObjAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
Status = PsCreateSystemThread(&ThreadHandle, THREAD_ALL_ACCESS, &ObjAttr,
NULL, NULL, ExpWorkerThread, &ExWorkQueue[i]);
/* ... */
}
}
关键设计
- 每队列一个线程 :每个
WORK_QUEUE_TYPE至少有一个工作线程 - 信号量同步 :
KeWaitForSingleObject等信号量 - 链表取工作:每次取一个
- PASSIVE_LEVEL :
ExpWorkerThread是普通内核线程,IRQL = 0 - 完整特权:可以访问分页内存、可以调用等待、可以访问所有内核 API
9.4.5 设备专用线程
IoQueueWorkItemToDeviceThread 用于 设备专用线程 (Ethread->DeviceThread):
c
VOID NTAPI
IoQueueWorkItemToDeviceThread(
IN PIO_WORKITEM IoWorkItem,
IN PIO_WORKITEM_ROUTINE Routine,
IN PVOID Context);
设备线程
每个内核线程对象(ETHREAD)有一个 DeviceThread 字段:
c
struct _ETHREAD {
...
PDEVICE_THREAD DeviceThread;
...
};
DeviceThread 是一个 设备专用的工作队列,适合处理与特定线程相关的 I/O 工作(如文件系统)。
应用场景
- 文件系统的延迟写(lazy writer)
- 文件系统清理工作
- 设备专用排队工作
实现
c
VOID NTAPI
IoQueueWorkItemToDeviceThread(
IN PIO_WORKITEM IoWorkItem,
IN PIO_WORKITEM_ROUTINE Routine,
IN PVOID Context)
{
IoWorkItem->Routine = Routine;
IoWorkItem->Context = Context;
IoWorkItem->WorkItem.WorkerRoutine = IopDeviceThreadWorker;
IoWorkItem->WorkItem.Parameter = IoWorkItem;
/* 插入设备线程队列 */
ExAcquireSpinLockExclusive(&KeGetCurrentThread()->DeviceThread->Lock);
InsertTailList(&KeGetCurrentThread()->DeviceThread->ListHead, &IoWorkItem->WorkItem.List);
ExReleaseSpinLockExclusive(&KeGetCurrentThread()->DeviceThread->Lock);
/* 唤醒设备线程 */
KeReleaseSemaphore(&KeGetCurrentThread()->DeviceThread->Semaphore, 0, 1, FALSE);
}
9.4.6 工作项与 DPC 的选择
| 维度 | DPC | WorkItem |
|---|---|---|
| IRQL | DISPATCH_LEVEL | PASSIVE_LEVEL |
| 延迟 | 极低 | 较高 |
| 适用场景 | 中断后处理、快速任务 | 耗时任务、需等待、需分页 |
| 同步原语 | 自旋锁、原子 | 全部内核 API |
| 内存访问 | NonPagedPool only | 全部 |
| API | KeInsertQueueDpc、IoRequestDpc |
IoQueueWorkItem、ExQueueWorkItem |
典型选择
- 中断后处理:DPC
- 必须快速完成的小任务(如唤醒 ISR 排队的 IRP):DPC
- 耗时的 I/O 完成处理:WorkItem
- 需要等待的事件:WorkItem
- 需要访问分页内存的工作:WorkItem
- 文件系统延迟写:WorkItem (CriticalWorkQueue)
注意事项
- WorkItem 在线程上下文运行,可以被抢占
- WorkItem 不能从 DPC 上下文排队(DPC 中只能用 ExInitializeWorkItem 然后延迟排队)
- 长期运行的工作项会影响其他工作的延迟
9.4.A ReactOS 工作线程实现的深度剖析
ExpWorkerThreadEntryPoint 的完整实现
ReactOS 的工作线程入口点 ExpWorkerThreadEntryPoint(ntoskrnl/ex/work.c(file:///d:/reactos/ntoskrnl/ex/work.c))比文档中简化的版本更为复杂,包含了动态线程管理和错误检测机制:
c
VOID NTAPI
ExpWorkerThreadEntryPoint(IN PVOID Context)
{
PWORK_QUEUE_ITEM WorkItem;
PLIST_ENTRY QueueEntry;
WORK_QUEUE_TYPE WorkQueueType;
PEX_WORK_QUEUE WorkQueue;
LARGE_INTEGER Timeout;
PLARGE_INTEGER TimeoutPointer = NULL;
PETHREAD Thread = PsGetCurrentThread();
KPROCESSOR_MODE WaitMode;
EX_QUEUE_WORKER_INFO OldValue, NewValue;
/* 检查是否是动态线程 */
if ((ULONG_PTR)Context & EX_DYNAMIC_WORK_THREAD)
{
/* 动态线程:10 分钟超时 */
Timeout.QuadPart = Int32x32To64(10, -10000000 * 60);
TimeoutPointer = &Timeout;
}
/* 获取队列类型和工作队列 */
WorkQueueType = (WORK_QUEUE_TYPE)((ULONG_PTR)Context & ~EX_DYNAMIC_WORK_THREAD);
WorkQueue = &ExWorkerQueue[WorkQueueType];
/* 选择等待模式 */
WaitMode = (UCHAR)WorkQueue->Info.WaitMode;
/* 初始化线程标志 */
ASSERT(Thread->ExWorkerCanWaitUser == 0);
if (WaitMode == UserMode) Thread->ExWorkerCanWaitUser = TRUE;
/* 禁用栈交换(如果需要) */
if (!ExpWorkersCanSwap) KeSetKernelStackSwapEnable(FALSE);
/* 增加工作线程计数 */
do
{
if (WorkQueue->Info.QueueDisabled)
{
KeSetKernelStackSwapEnable(TRUE);
PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN);
}
OldValue = WorkQueue->Info;
NewValue = OldValue;
NewValue.WorkerCount++;
}
while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,
*(PLONG)&NewValue,
*(PLONG)&OldValue) != *(PLONG)&OldValue);
Thread->ActiveExWorker = TRUE;
/* 主工作循环 */
ProcessLoop:
for (;;)
{
/* 等待工作项 */
QueueEntry = KeRemoveQueue(&WorkQueue->WorkerQueue,
WaitMode,
TimeoutPointer);
/* 检查是否超时(动态线程) */
if ((NTSTATUS)(ULONG_PTR)QueueEntry == STATUS_TIMEOUT) break;
/* 增加已处理工作项计数 */
InterlockedIncrement((PLONG)&WorkQueue->WorkItemsProcessed);
/* 获取工作项 */
WorkItem = CONTAINING_RECORD(QueueEntry, WORK_QUEUE_ITEM, List);
/* 验证回调函数地址 */
ASSERT((ULONG_PTR)WorkItem->WorkerRoutine > MmUserProbeAddress);
/* 调用工作例程 */
WorkItem->WorkerRoutine(WorkItem->Parameter);
/* 检查 APC 是否被禁用(错误检测) */
if (Thread->Tcb.CombinedApcDisable != 0)
{
DPRINT1("Warning: Broken Worker Thread: %p %p %p came back "
"with APCs disabled!\n",
WorkItem->WorkerRoutine,
WorkItem->Parameter,
WorkItem);
ASSERT(Thread->Tcb.CombinedApcDisable == 0);
Thread->Tcb.CombinedApcDisable = 0;
}
/* 检查 IRQL 是否正确 */
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
{
KeBugCheckEx(WORKER_THREAD_RETURNED_AT_BAD_IRQL,
(ULONG_PTR)WorkItem->WorkerRoutine,
KeGetCurrentIrql(),
(ULONG_PTR)WorkItem->Parameter,
(ULONG_PTR)WorkItem);
}
/* 检查模拟状态 */
if (Thread->ActiveImpersonationInfo)
{
KeBugCheckEx(IMPERSONATING_WORKER_THREAD,
(ULONG_PTR)WorkItem->WorkerRoutine,
(ULONG_PTR)WorkItem->Parameter,
(ULONG_PTR)WorkItem,
0);
}
}
/* 动态线程超时,检查是否有挂起的 IRP */
if (!IsListEmpty(&Thread->IrpList)) goto ProcessLoop;
/* 检查队列是否被禁用 */
if (WorkQueue->Info.QueueDisabled) goto ProcessLoop;
/* 减少工作线程计数 */
do
{
OldValue = WorkQueue->Info;
NewValue = OldValue;
NewValue.WorkerCount--;
}
while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,
*(PLONG)&NewValue,
*(PLONG)&OldValue) != *(PLONG)&OldValue);
/* 减少动态线程计数 */
InterlockedDecrement(&WorkQueue->DynamicThreadCount);
Thread->ActiveExWorker = FALSE;
/* 重新启用栈交换 */
KeSetKernelStackSwapEnable(TRUE);
return;
}
这段代码揭示了多个关键设计:
-
动态线程超时 :动态工作线程在 10 分钟无工作后自动退出,避免资源浪费。静态线程(
EX_DYNAMIC_WORK_THREAD标志未设置)永不超时。 -
错误检测:工作线程在执行完工作例程后检查三个条件:
- APC 是否被禁用(
CombinedApcDisable != 0) - IRQL 是否为
PASSIVE_LEVEL - 是否启用了线程模拟(
ActiveImpersonationInfo)
违反这些条件会导致蓝屏(BugCheck),因为工作线程必须在干净的状态下返回。
- APC 是否被禁用(
-
IRP 挂起检查:动态线程在超时退出前检查是否有挂起的 IRP。如果有,继续工作循环而不是退出,防止 IRP 泄漏。
-
队列禁用处理 :如果队列被禁用(
QueueDisabled),线程会终止而不是继续等待。这用于系统关机时的工作线程清理。
动态工作线程的创建
ReactOS 实现了动态工作线程机制,根据工作负载自动调整线程数量:
c
VOID NTAPI
ExpCreateWorkerThread(WORK_QUEUE_TYPE WorkQueueType, IN BOOLEAN Dynamic)
{
PETHREAD Thread;
HANDLE hThread;
ULONG Context;
KPRIORITY Priority;
NTSTATUS Status;
Context = WorkQueueType;
if (Dynamic) Context |= EX_DYNAMIC_WORK_THREAD;
/* 创建系统线程 */
Status = PsCreateSystemThread(&hThread,
THREAD_ALL_ACCESS,
NULL,
NULL,
NULL,
ExpWorkerThreadEntryPoint,
UlongToPtr(Context));
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to create worker thread: 0x%08x\n", Status);
return;
}
/* 增加动态线程计数 */
if (Dynamic)
{
InterlockedIncrement(&ExWorkerQueue[WorkQueueType].DynamicThreadCount);
}
/* 设置线程优先级 */
if (WorkQueueType == DelayedWorkQueue)
{
Priority = EX_DELAYED_QUEUE_PRIORITY_INCREMENT; // 4
}
else if (WorkQueueType == CriticalWorkQueue)
{
Priority = EX_CRITICAL_QUEUE_PRIORITY_INCREMENT; // 5
}
else
{
Priority = EX_HYPERCRITICAL_QUEUE_PRIORITY_INCREMENT; // 7
}
/* 获取线程对象并设置优先级 */
Status = ObReferenceObjectByHandle(hThread,
THREAD_SET_INFORMATION,
PsThreadType,
KernelMode,
(PVOID*)&Thread,
NULL);
if (NT_SUCCESS(Status))
{
KeSetPriorityThread(&Thread->Tcb, Priority);
ObDereferenceObject(Thread);
}
NtClose(hThread);
}
动态线程的优先级根据队列类型设置:
- HyperCriticalWorkQueue:优先级 7(最高)
- CriticalWorkQueue:优先级 5
- DelayedWorkQueue:优先级 4(最低)
这些优先级都低于系统关键线程(如内存管理器),但高于普通用户线程,确保工作项能够及时处理。
工作队列平衡管理器
ReactOS 实现了工作队列平衡管理器(Balance Manager),监控工作队列的状态并动态调整线程数量:
c
/* 工作队列相关的全局变量 */
ULONG ExCriticalWorkerThreads;
ULONG ExDelayedWorkerThreads;
ULONG ExpAdditionalCriticalWorkerThreads;
ULONG ExpAdditionalDelayedWorkerThreads;
/* 平衡管理器事件 */
KEVENT ExpThreadSetManagerEvent;
KEVENT ExpThreadSetManagerShutdownEvent;
PETHREAD ExpWorkerThreadBalanceManagerPtr;
平衡管理器的工作流程:
- 监控队列深度 :定期检查每个队列的
QueueDepth和WorkerCount - 检测饥饿:如果队列深度持续增加而工作线程不足,创建新的动态线程
- 检测过载:如果工作线程过多且空闲,减少动态线程数量
- 响应关机:在系统关机时,禁用所有队列并等待工作线程退出
EX_WORK_QUEUE 结构
c
typedef struct _EX_WORK_QUEUE {
KQUEUE WorkerQueue; // 内核队列对象
LONG WorkItemsProcessed; // 已处理工作项计数
LONG WorkerCount; // 当前工作线程数
LONG DynamicThreadCount; // 动态线程数
EX_QUEUE_WORKER_INFO Info; // 队列信息(位域)
KSPIN_LOCK Lock; // 队列锁
} EX_WORK_QUEUE, *PEX_WORK_QUEUE;
typedef struct _EX_QUEUE_WORKER_INFO {
ULONG WorkerCount : 8; // 工作线程数
ULONG WaitMode : 1; // 等待模式(0=Kernel, 1=User)
ULONG QueueDisabled : 1; // 队列是否禁用
ULONG Reserved : 22; // 保留
} EX_QUEUE_WORKER_INFO, *PEX_QUEUE_WORKER_INFO;
EX_WORK_QUEUE 结构封装了工作队列的所有状态信息。Info 字段使用位域紧凑地存储队列的控制信息。
9.4.B 工作项在驱动中的典型应用模式
模式 1:延迟 IRP 完成
当驱动在 DPC 中完成 IRP,但完成处理需要访问分页内存时,使用工作项:
c
/* DPC 例程(DISPATCH_LEVEL) */
VOID NTAPI
MyDpcRoutine(IN PKDPC Dpc, IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp, IN PVOID Context)
{
PDEVICE_EXTENSION Ext = DeviceObject->DeviceExtension;
PIO_WORKITEM WorkItem;
/* 分配工作项 */
WorkItem = IoAllocateWorkItem(DeviceObject);
if (!WorkItem)
{
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return;
}
/* 将 IRP 存储在工作项上下文中 */
WorkItem->Context = Irp;
/* 排队工作项 */
IoQueueWorkItem(WorkItem, MyWorkItemRoutine, DelayedWorkQueue, Ext);
}
/* 工作项例程(PASSIVE_LEVEL) */
VOID NTAPI
MyWorkItemRoutine(IN PDEVICE_OBJECT DeviceObject, IN PVOID Context)
{
PDEVICE_EXTENSION Ext = DeviceObject->DeviceExtension;
PIRP Irp = (PIRP)Context;
PIO_WORKITEM WorkItem = /* 从某处获取 */;
/* 现在可以访问分页内存 */
PUCHAR Buffer = Ext->PagedBuffer; // 分页内存
/* 复制数据到 IRP 缓冲区 */
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, Buffer, 100);
/* 完成 IRP */
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
/* 释放工作项 */
IoFreeWorkItem(WorkItem);
}
模式 2:设备清理
在驱动卸载时,使用工作项执行可能需要等待的清理操作:
c
VOID NTAPI
MyUnload(PDRIVER_OBJECT DriverObject)
{
PDEVICE_EXTENSION Ext = DriverObject->DeviceObject->DeviceExtension;
KEVENT Event;
/* 初始化事件 */
KeInitializeEvent(&Event, NotificationEvent, FALSE);
/* 排队清理工作项 */
Ext->CleanupEvent = &Event;
IoQueueWorkItem(Ext->WorkItem, MyCleanupRoutine, DelayedWorkQueue, Ext);
/* 等待清理完成 */
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
/* 释放资源 */
IoFreeWorkItem(Ext->WorkItem);
IoDeleteDevice(DriverObject->DeviceObject);
}
VOID NTAPI
MyCleanupRoutine(IN PDEVICE_OBJECT DeviceObject, IN PVOID Context)
{
PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)Context;
/* 执行清理(可能需要等待) */
KeWaitForSingleObject(&Ext->DeviceMutex, Executive, KernelMode, FALSE, NULL);
/* 释放资源 */
ExFreePool(Ext->PagedBuffer);
/* 信号通知完成 */
KeSetEvent(Ext->CleanupEvent, IO_NO_INCREMENT, FALSE);
}
模式 3:文件系统延迟写
文件系统使用 CriticalWorkQueue 处理延迟写操作,确保数据及时刷新到磁盘:
c
/* 延迟写工作项 */
VOID NTAPI
LazyWriteWorkItem(IN PDEVICE_OBJECT DeviceObject, IN PVOID Context)
{
PCACHE_MANAGER CacheMgr = (PCACHE_MANAGER)Context;
PLIST_ENTRY Entry;
PDIRTY_PAGE DirtyPage;
/* 遍历脏页列表 */
while (!IsListEmpty(&CacheMgr->DirtyPageList))
{
Entry = RemoveHeadList(&CacheMgr->DirtyPageList);
DirtyPage = CONTAINING_RECORD(Entry, DIRTY_PAGE, ListEntry);
/* 写入磁盘(可能需要等待) */
WriteDirtyPage(DirtyPage);
/* 释放页面 */
ExFreePool(DirtyPage);
}
}
9.4.C 工作项与 APC 的对比
APC(Asynchronous Procedure Call)
APC 是另一种在特定线程上下文中执行代码的机制:
| 特性 | WorkItem | APC |
|---|---|---|
| 执行上下文 | 系统工作线程 | 目标用户/内核线程 |
| IRQL | PASSIVE_LEVEL | PASSIVE_LEVEL(特殊 APC)或 APC_LEVEL |
| 调度 | 工作队列 | 线程调度器 |
| 适用场景 | 通用后台工作 | 线程特定的异步通知 |
| API | IoQueueWorkItem |
KeInsertQueueApc |
何时使用 APC
APC 适用于需要在特定线程上下文中执行代码的场景:
- I/O 完成通知:当 I/O 操作完成时,在发起线程中调用完成例程
- 线程终止清理:在线程终止前执行清理代码
- 用户模式回调:从内核模式调用用户模式函数
何时使用 WorkItem
WorkItem 适用于不需要特定线程上下文的后台工作:
- 设备驱动的后处理:DPC 无法完成的耗时操作
- 文件系统维护:延迟写、缓存清理
- 系统维护任务:注册表清理、内存管理
9.4.D 与 Windows 实现的对比
工作线程数量的差异
-
Windows XP/2003:
- CriticalWorkQueue:5 个静态线程
- DelayedWorkQueue:3 个静态线程
- HyperCriticalWorkQueue:1 个静态线程
-
Windows Vista+:
- 引入动态线程池(Thread Pool)
- 根据工作负载自动调整线程数量
- 支持工作项优先级继承
-
ReactOS:
- 实现了基本的静态和动态线程
- 平衡管理器正在开发中
- 尚未支持完整的线程池 API
工作项结构的差异
Windows 的 IO_WORKITEM 结构包含更多字段用于:
- 工作项类型 :支持更多的工作项类型(如
DelayedWorkItem、CriticalWorkItem) - 优先级继承:工作项可以继承发起线程的优先级
- 取消支持:支持取消已排队但未执行的工作项
性能优化
Windows 对工作线程进行了多项优化:
- CPU 亲和性:工作线程绑定到特定 CPU,减少缓存失效
- 优先级提升:当队列深度增加时,临时提升工作线程优先级
- 批量处理:一次处理多个工作项,减少锁竞争
ReactOS 实现了基本的工作线程机制,但在性能优化方面还有改进空间。
9.4.E 调试技巧与常见问题
使用 WinDbg 调试工作线程
!thread:显示当前线程信息,检查是否是工作线程!process 0 0:列出所有进程,找到 System 进程(工作线程的宿主)!pool <工作项地址>:查看工作项的内存分配dt nt!_EX_WORK_QUEUE <地址>:查看工作队列的状态
查看工作队列状态
kd> dt nt!_EX_WORK_QUEUE nt!ExWorkerQueue
+0x000 WorkerQueue : KQUEUE
+0x028 WorkItemsProcessed : 0x1234
+0x02c WorkerCount : 5
+0x030 DynamicThreadCount : 2
+0x034 Info : EX_QUEUE_WORKER_INFO
常见工作项错误
-
工作项泄漏 :分配了工作项但未释放。确保在所有路径上调用
IoFreeWorkItem。 -
工作项重用:同一个工作项在未完成前被重新排队。工作项必须等待当前执行完成后才能重新使用。
-
在 DPC 中排队工作项 :DPC 上下文不能直接调用
IoQueueWorkItem(因为它可能等待锁)。应该使用ExInitializeWorkItem初始化,然后延迟到PASSIVE_LEVEL排队。 -
工作例程返回错误 IRQL :工作例程必须在
PASSIVE_LEVEL返回。如果在例程中提升了 IRQL,必须在返回前降低。 -
工作例程禁用 APC :工作例程不能在禁用 APC 的状态下返回。如果调用了
KeEnterCriticalRegion,必须在返回前调用KeLeaveCriticalRegion。 -
设备对象引用泄漏 :使用
IoAllocateWorkItem时会增加设备对象引用计数。确保在IoFreeWorkItem时引用计数正确递减。 -
工作项超时:如果工作例程执行时间过长,会导致队列中的其他工作项延迟。将耗时操作分解为多个小的工作项。
性能监控
可以通过以下方式监控工作线程性能:
- 性能计数器 :
ExWorkerQueue[Type].WorkItemsProcessed提供已处理工作项统计 - WinDbg 扩展 :
!workqueue显示工作队列状态(需要符号) - 事件跟踪:Windows 的 ETW 可以记录工作项事件
总结
内核劳务线程机制的核心要点:
- 三套 API :
- EX 层:
ExQueueWorkItem(最低级) - IO 层:
IoQueueWorkItem(带对象引用) - 设备层:
IoQueueWorkItemToDeviceThread(设备线程)
- EX 层:
- 三种队列 :
CriticalWorkQueue、DelayedWorkQueue、HyperCriticalWorkQueue - 系统工作线程 :
ExpWorkerThread在PASSIVE_LEVEL等待信号量 - DPC vs WorkItem:DPC(DIRQL/DISPATCH_LEVEL)vs WorkItem(PASSIVE_LEVEL)
- 设备线程:每线程一个设备工作队列
- 安全释放 :
IoFreeWorkItem自动管理引用计数 - 典型用途:文件系统延迟写、设备清理、IRP 完成回调、长时间 I/O 处理
下一节 9.5 将以 PCI/PCI-X/ACPI 为例剖析一组 PnP 设备驱动的实例。
本章代码索引
| 文件 | 内容 |
|---|---|
| work.c(file:///d:/reactos/ntoskrnl/ex/work.c) | ExInitializeWorkItem、ExQueueWorkItem、ExQueueWorkItemToFront、ExpWorkerThread、ExpInitializeWorkerThreads |
| io/work.c(file:///d:/reactos/ntoskrnl/io/iomgr/work.c) | IoAllocateWorkItem、IoQueueWorkItem、IoFreeWorkItem、IopWorkItemRoutine |
| devicethread.c(file:///d:/reactos/ntoskrnl/io/iomgr/devicethread.c) | IoQueueWorkItemToDeviceThread、IopDeviceThreadWorker |
| extypes.h(file:///d:/reactos/sdk/include/xdk/extypes.h) | WORK_QUEUE_TYPE、PIO_WORKITEM |
| ps/psmgr.c(file:///d:/reactos/ntoskrnl/ps/psmgr.c) | PsCreateSystemThread(系统线程创建) |
| ketypes.h(file:///d:/reactos/sdk/include/xdk/ketypes.h) | ETHREAD 中的 DeviceThread |
| etypes.h(file:///d:/reactos/sdk/include/xdk/etypes.h) | DEVICE_OBJECT 字段 |