Reactos 第 9 章 设备驱动 — 9.4 内核劳务线程

第 9 章 设备驱动 --- 9.4 内核劳务线程

本节深入剖析内核劳务线程(Kernel Worker Threads)机制。 DPC(9.3 节)虽然轻量,但它在 DISPATCH_LEVEL 运行,不能访问分页内存、不能等待。ReactOS 提供 内核劳务线程 用于运行"需要 PASSIVE_LEVEL 上下文但又不属于某个具体用户线程"的工作。关键 API 包括 ExQueueWorkItemIoQueueWorkItemIoAllocateWorkItem,实现位于 ntoskrnl/ex/work.c(file:///d:/reactos/ntoskrnl/ex/work.c) 和 ntoskrnl/io/iomgr/work.c(file:///d:/reactos/ntoskrnl/io/iomgr/work.c)。


概述

内核劳务线程机制由三个层次构成:

  1. EX 工作项层ExQueueWorkItemExQueueWorkItemToFrontExInitializeWorkItem(无状态)
  2. IO 工作项层IoQueueWorkItemIoAllocateWorkItemIoFreeWorkItem(带对象引用)
  3. 设备线程层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 三种延迟机制
  • 理解 IoQueueWorkItemExQueueWorkItem 的差异
  • 知道何时使用工作线程 vs DPC
  • 理解系统工作线程的循环结构

涉及的内核子系统

子系统 头文件/源文件 核心作用
EX 工作项 ntoskrnl/ex/work.c(file:///d:/reactos/ntoskrnl/ex/work.c) ExInitializeWorkItemExQueueWorkItemExpWorkerThread
IO 工作项 ntoskrnl/io/iomgr/work.c(file:///d:/reactos/ntoskrnl/io/iomgr/work.c) IoAllocateWorkItemIoQueueWorkItem
设备线程 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_TYPEPIO_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;
    }
}

关键优势

IoAllocateWorkItemExQueueWorkItem 更安全:

  1. 对象引用:自动管理设备对象引用计数
  2. 关联性:与具体设备对象绑定
  3. 释放安全IoFreeWorkItem 在引用计数归零时安全释放
  4. 异常保护:内核 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]);
        /* ... */
    }
}

关键设计

  1. 每队列一个线程 :每个 WORK_QUEUE_TYPE 至少有一个工作线程
  2. 信号量同步KeWaitForSingleObject 等信号量
  3. 链表取工作:每次取一个
  4. PASSIVE_LEVELExpWorkerThread 是普通内核线程,IRQL = 0
  5. 完整特权:可以访问分页内存、可以调用等待、可以访问所有内核 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 KeInsertQueueDpcIoRequestDpc IoQueueWorkItemExQueueWorkItem

典型选择

  1. 中断后处理:DPC
  2. 必须快速完成的小任务(如唤醒 ISR 排队的 IRP):DPC
  3. 耗时的 I/O 完成处理:WorkItem
  4. 需要等待的事件:WorkItem
  5. 需要访问分页内存的工作:WorkItem
  6. 文件系统延迟写:WorkItem (CriticalWorkQueue)

注意事项

  • WorkItem 在线程上下文运行,可以被抢占
  • WorkItem 不能从 DPC 上下文排队(DPC 中只能用 ExInitializeWorkItem 然后延迟排队)
  • 长期运行的工作项会影响其他工作的延迟

9.4.A ReactOS 工作线程实现的深度剖析

ExpWorkerThreadEntryPoint 的完整实现

ReactOS 的工作线程入口点 ExpWorkerThreadEntryPointntoskrnl/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;
}

这段代码揭示了多个关键设计:

  1. 动态线程超时 :动态工作线程在 10 分钟无工作后自动退出,避免资源浪费。静态线程(EX_DYNAMIC_WORK_THREAD 标志未设置)永不超时。

  2. 错误检测:工作线程在执行完工作例程后检查三个条件:

    • APC 是否被禁用(CombinedApcDisable != 0
    • IRQL 是否为 PASSIVE_LEVEL
    • 是否启用了线程模拟(ActiveImpersonationInfo

    违反这些条件会导致蓝屏(BugCheck),因为工作线程必须在干净的状态下返回。

  3. IRP 挂起检查:动态线程在超时退出前检查是否有挂起的 IRP。如果有,继续工作循环而不是退出,防止 IRP 泄漏。

  4. 队列禁用处理 :如果队列被禁用(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;

平衡管理器的工作流程:

  1. 监控队列深度 :定期检查每个队列的 QueueDepthWorkerCount
  2. 检测饥饿:如果队列深度持续增加而工作线程不足,创建新的动态线程
  3. 检测过载:如果工作线程过多且空闲,减少动态线程数量
  4. 响应关机:在系统关机时,禁用所有队列并等待工作线程退出

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 适用于需要在特定线程上下文中执行代码的场景:

  1. I/O 完成通知:当 I/O 操作完成时,在发起线程中调用完成例程
  2. 线程终止清理:在线程终止前执行清理代码
  3. 用户模式回调:从内核模式调用用户模式函数

何时使用 WorkItem

WorkItem 适用于不需要特定线程上下文的后台工作:

  1. 设备驱动的后处理:DPC 无法完成的耗时操作
  2. 文件系统维护:延迟写、缓存清理
  3. 系统维护任务:注册表清理、内存管理

9.4.D 与 Windows 实现的对比

工作线程数量的差异

  • Windows XP/2003

    • CriticalWorkQueue:5 个静态线程
    • DelayedWorkQueue:3 个静态线程
    • HyperCriticalWorkQueue:1 个静态线程
  • Windows Vista+

    • 引入动态线程池(Thread Pool)
    • 根据工作负载自动调整线程数量
    • 支持工作项优先级继承
  • ReactOS

    • 实现了基本的静态和动态线程
    • 平衡管理器正在开发中
    • 尚未支持完整的线程池 API

工作项结构的差异

Windows 的 IO_WORKITEM 结构包含更多字段用于:

  • 工作项类型 :支持更多的工作项类型(如 DelayedWorkItemCriticalWorkItem
  • 优先级继承:工作项可以继承发起线程的优先级
  • 取消支持:支持取消已排队但未执行的工作项

性能优化

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

常见工作项错误

  1. 工作项泄漏 :分配了工作项但未释放。确保在所有路径上调用 IoFreeWorkItem

  2. 工作项重用:同一个工作项在未完成前被重新排队。工作项必须等待当前执行完成后才能重新使用。

  3. 在 DPC 中排队工作项 :DPC 上下文不能直接调用 IoQueueWorkItem(因为它可能等待锁)。应该使用 ExInitializeWorkItem 初始化,然后延迟到 PASSIVE_LEVEL 排队。

  4. 工作例程返回错误 IRQL :工作例程必须在 PASSIVE_LEVEL 返回。如果在例程中提升了 IRQL,必须在返回前降低。

  5. 工作例程禁用 APC :工作例程不能在禁用 APC 的状态下返回。如果调用了 KeEnterCriticalRegion,必须在返回前调用 KeLeaveCriticalRegion

  6. 设备对象引用泄漏 :使用 IoAllocateWorkItem 时会增加设备对象引用计数。确保在 IoFreeWorkItem 时引用计数正确递减。

  7. 工作项超时:如果工作例程执行时间过长,会导致队列中的其他工作项延迟。将耗时操作分解为多个小的工作项。

性能监控

可以通过以下方式监控工作线程性能:

  1. 性能计数器ExWorkerQueue[Type].WorkItemsProcessed 提供已处理工作项统计
  2. WinDbg 扩展!workqueue 显示工作队列状态(需要符号)
  3. 事件跟踪:Windows 的 ETW 可以记录工作项事件

总结

内核劳务线程机制的核心要点:

  1. 三套 API
    • EX 层:ExQueueWorkItem(最低级)
    • IO 层:IoQueueWorkItem(带对象引用)
    • 设备层:IoQueueWorkItemToDeviceThread(设备线程)
  2. 三种队列CriticalWorkQueueDelayedWorkQueueHyperCriticalWorkQueue
  3. 系统工作线程ExpWorkerThreadPASSIVE_LEVEL 等待信号量
  4. DPC vs WorkItem:DPC(DIRQL/DISPATCH_LEVEL)vs WorkItem(PASSIVE_LEVEL)
  5. 设备线程:每线程一个设备工作队列
  6. 安全释放IoFreeWorkItem 自动管理引用计数
  7. 典型用途:文件系统延迟写、设备清理、IRP 完成回调、长时间 I/O 处理

下一节 9.5 将以 PCI/PCI-X/ACPI 为例剖析一组 PnP 设备驱动的实例。


本章代码索引

文件 内容
work.c(file:///d:/reactos/ntoskrnl/ex/work.c) ExInitializeWorkItemExQueueWorkItemExQueueWorkItemToFrontExpWorkerThreadExpInitializeWorkerThreads
io/work.c(file:///d:/reactos/ntoskrnl/io/iomgr/work.c) IoAllocateWorkItemIoQueueWorkItemIoFreeWorkItemIopWorkItemRoutine
devicethread.c(file:///d:/reactos/ntoskrnl/io/iomgr/devicethread.c) IoQueueWorkItemToDeviceThreadIopDeviceThreadWorker
extypes.h(file:///d:/reactos/sdk/include/xdk/extypes.h) WORK_QUEUE_TYPEPIO_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 字段
相关推荐
Doker 多克1 小时前
Spring AI Alibaba—快速构建ReactAgent
java·开发语言·前端·ai编程
星栈独行1 小时前
Rust + Makepad 应用怎么打包发布:Windows、macOS、Linux 全平台交付
windows·程序人生·macos·ui·rust
张忠琳1 小时前
【Go 1.26.4】Golang Slice 深度解析
开发语言·后端·golang
辣香牛肉面2 小时前
Windows PDF转换工具箱
windows·pdf
daly5202 小时前
PyCharm怎么下载?2026最新版PyCharm安装教程(Windows/macOS/Linux)
windows·macos·pycharm
西凉的悲伤2 小时前
redis-windows 安装 redis 到 windows 电脑
java·windows·redis·redis-windows
码云骑士2 小时前
09-Python模块导入机制-sys.path与循环导入的死锁式排查
开发语言·python
星恒随风2 小时前
C++ 模板初阶:从泛型编程、函数模板到类模板,一篇打通基础概念
开发语言·c++·笔记·学习
郝学胜-神的一滴2 小时前
Qt 高级开发 031:QListWidget图标布局实战
开发语言·c++·qt·程序人生·软件构建·用户界面