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 字段
相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区12 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜13 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa13 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio