工作项、线程和定时器是内核中用于执行长期任务的三种形态,其中工作项和线程本身没什么区别,只不过工作项对处理器的亲和性更好一些。工作项是驱动程序在 EvtWorkItem 事件回调函数中执行的任务。 这些函数在系统工作线程的上下文中以 IRQL = PASSIVE_LEVEL 异步运行。
如果以 IRQL = DISPATCH_LEVEL 运行的 EvtInterruptDpc 或 EvtDpcFunc 函数必须在 IRQL = PASSIVE_LEVEL 执行其他处理,则基于框架的驱动程序通常使用工作项。
换句话说,如果以 IRQL = DISPATCH_LEVEL 运行的函数必须调用只能在 IRQL = PASSIVE_LEVEL 调用的函数,驱动程序可以使用工作项。
通常,驱动程序的 EvtInterruptDpc 或 EvtDpcFunc 回调函数会创建一个工作项对象,并将其添加到系统的工作项队列中。 随后系统工作线程将对象取消排队,并调用工作项的 EvtWorkItem 回调函数。
设置工作项
若要设置工作项,驱动程序必须:
- 创建工作项:驱动程序调用 WdfWorkItemCreate 来创建工作项对象并标识将处理工作项的 EvtWorkItem 回调函数;
- 存储有关工作项的信息:通常,驱动程序使用工作项对象的上下文内存来存储 有关 EvtWorkItem 回调函数应执行的任务的信息。 调用 EvtWorkItem 回调函数时,它可以通过访问此上下文内存来检索信息;
- 将工作项添加到系统的工作项队列:驱动程序调用 WdfWorkItemEnqueue,它将驱动程序的工作项添加到工作项队列;
当驱动程序调用 WdfWorkItemCreate 时,它必须提供框架设备对象或框架队列对象的句柄。 当系统删除该对象时,它还会删除与该对象关联的任何现有工作项。 在调用父对象的 EvtCleanupCallback 回调之前,将释放工作项对象并清理其关联的工作项回调。
使用Work-Item回调函数
将工作项添加到工作项队列后,它将保留在队列中,直到系统工作线程变得可用。 系统工作线程从队列中删除工作项,然后调用驱动程序的 EvtWorkItem 回调函数,将工作项对象作为输入传递。
通常, EvtWorkItem 回调函数执行以下步骤:
- 通过访问工作项对象的上下文内存,获取驱动程序提供的有关工作项的信息;
- 执行指定的任务。 如有必要,回调函数可以调用 WdfWorkItemGetParentObject 来确定工作项的父对象;
- 调用 WdfObjectDelete 以删除工作项对象,或者,如果驱动程序将重新排队工作项,则指示工作项的句柄现在可以重复使用;
每个工作项的回调函数执行的任务必须相对较短。 操作系统提供有限数量的系统工作线程,因此,如果驱动程序使用工作项回调函数执行耗时的任务,则可能会妨碍系统性能。
创建和删除工作项
驱动程序可以使用以下两种技术之一来创建和删除工作项:
1. 使用每个工作项一次:在需要时创建工作项,并在使用后立即将其删除。
此方法适用于需要很少使用 (频率低于每分钟一次的驱动程序) 的少量工作项。
例如,驱动程序的 EvtInterruptDpc 回调函数可以调用 WdfWorkItemCreate, 然后 WdfWorkItemEnqueue,工作项的 EvtWorkItem 回调函数可以调用 WdfObjectDelete。
如果驱动程序遵循此方案,并且其 EvtInterruptDpc 回调函数从 WdfWorkItemCreate 收到STATUS_INSUFFICIENT_RESOURCES返回值,则驱动程序必须能够推迟所需的工作,直到系统资源 (通常内存) 可用。
2. 创建驱动程序根据需要重新排队的一个或多个工作项。
对于使用工作项频繁 (每分钟) 一次以上的驱动程序,或者驱动程序的 EvtInterruptDpc 回调函数无法轻松处理 来自 WdfWorkItemCreate 的STATUS_INSUFFICIENT_RESOURCES返回值,此方法非常有用。
在驱动程序调用 WdfWorkItemEnqueue 之前,系统不会将工作线程分配给工作项。 因此,即使系统工作线程是有限的资源,在初始化设备时创建工作项会消耗少量内存,但不会影响系统性能。
以下步骤描述了一种可能的方案:
- 驱动程序的 EvtDriverDeviceAdd 回调函数调用 WdfWorkItemCreate 以获取工作项句柄;
- 驱动程序的 EvtInterruptDpc 回调函数创建 EvtWorkItem 回调函数必须执行的操作列表,然后使用步骤 1 中的句柄调用 WdfWorkItemEnqueue;
- 驱动程序的 EvtWorkItem 回调函数执行操作列表,并设置一个标志以指示回调函数已运行;
随后,每次调用驱动程序的 EvtInterruptDpc 回调函数时,它都必须确定 EvtWorkItem 回调函数是否已运行。 如果 EvtWorkItem 回调函数尚未运行, 则 EvtInterruptDpc 回调函数不会调用 WdfWorkItemEnqueue,因为工作项仍处于排队状态。 在这种情况下, EvtInterruptDpc 回调函数仅更新 EvtWorkItem 回调函数的操作列表。
每个工作项都与设备或队列相关联。 删除关联的设备或队列时,框架会删除所有关联的工作项,因此,如果使用此技术,驱动程序不必调用 WdfObjectDelete。
一些驱动程序可能需要调用 WdfWorkItemFlush 以从工作项队列中刷新其工作项。
如果驱动程序对未完成的工作项调用 WdfObjectDelete ,则结果取决于工作项的状态:
工作项状态 | 结果 |
已创建但未排队 | 立即清理工作项 |
已排队 | 对 WdfObjectDelete 的调用将等待工作项完成执行,然后清理工作项 |
执行 | 如果驱动程序从同一线程上的 EvtWorkItem 调用 WdfObjectDelete,则 WdfObjectDelete 将立即返回。 EvtWorkItem 完成后,将清理工作项。 否则, WdfObjectDelete 将等待 EvtWorkItem 完成。 |