WDF可以使用框架的内置计时器支持。 它适用于 Kernel-Mode Driver Framework (KMDF) 驱动程序,以及从版本 2 开始的 User-Mode Driver Framework (UMDF) 驱动程序。
框架提供了一个 计时器对象 ,使驱动程序能够创建计时器。 在驱动程序创建计时器对象并启动计时器的时钟后,框架会在经过指定时间量后调用驱动程序提供的回调函数。 (可选)驱动程序可以设置计时器,以便框架在经过指定时间时重复调用回调函数。
若要创建框架计时器对象,驱动程序必须调用 WdfTimerCreate 方法。 此方法注册 EvtTimerFunc 回调函数和定期时间间隔。 如果希望框架只调用回调函数一次,驱动程序会为定期时间间隔指定零。
通常,你将知道驱动程序需要为每个设备使用的计时器数。 因此,驱动程序可以通过在其 EvtDriverDeviceAdd 回调函数中调用 WdfTimerCreate 来创建计时器对象,并且可以将计时器对象句柄存储在设备或队列对象的上下文空间中。
若要启动计时器,驱动程序会调用 WdfTimerStart,传递“到期时间”。 框架启动计时器的时钟,并在指定的时间量已过时调用 EvtTimerFunc 回调函数。
如果驱动程序在调用 WdfTimerCreate 时提供了定期时间间隔,则计时器称为 定期计时器。 在初始“到期时间”过后,定期计时器的时钟将继续运行,并且每当经过定期时间间隔时,框架会重复调用驱动程序的回调函数。 定期计时器不会自动启动。 与非定期计时器一样,驱动程序在创建计时器后仍必须调用 WdfTimerStart ,以便第一次启动它。
驱动程序可能从其 EvtTimerFunc 回调函数调用 WdfTimerStart,以便在非定期计时器过期后重新启动它。
若要停止计时器,驱动程序可以调用 WdfTimerStop。 驱动程序可以通过重复启动和停止计时器来重复使用计时器。
驱动程序创建计时器对象时,必须指定父对象。 框架在删除父级时停止计时器并删除计时器对象。 若要获取计时器对象的父对象,驱动程序可以调用 WdfTimerGetParentObject。
在版本 1.9 之前的 KMDF 版本中,如果希望驱动程序的所有回调函数在 IRQL = PASSIVE_LEVEL 运行,则无法轻松使用计时器对象。 框架将计时器对象的 EvtTimerFunc 回调函数实现为延迟过程调用, 在 IRQL = DISPATCH_LEVEL调用的 DPC。 因此,如果希望计时器过期代码在PASSIVE_LEVEL 则 EvtTimerFunc 回调函数必须将在 PASSIVE_LEVEL 运行 的工作项 排队。
在 KMDF 版本 1.9 及更高版本中,可以创建 被动级别计时器,即在 PASSIVE_LEVEL 运行的计时器。 若要创建被动级别计时器,请在驱动程序调用 WdfTimerCreate 时指定 WdfExecutionLevelPassive 执行级别。 因此,框架将 EvtTimerFunc 回调函数实现为在PASSIVE_LEVEL运行的工作项。 请注意,被动级别计时器不能是定期计时器。
从 UMDF 版本 2.0 开始,框架将计时器对象的 EvtTimerFunc 回调函数实现为用户模式线程池中的工作线程。 因此,UMDF 驱动程序的计时器回调函数始终在PASSIVE_LEVEL运行。
无唤醒计时器
反复导致系统从低功耗状态恢复的计时器会降低系统电源效率。 延长电池使用时间的一种方法是延迟非关键定期操作,而不是唤醒系统。 从 Windows 8.1 开始,可以使用任何唤醒计时器在 KMDF 或 UMDF 驱动程序中执行此类非关键操作。 当系统处于低功耗状态时,无唤醒计时器不会唤醒系统。 相反,当系统下次完全处于 S0 状态时,框架会调用驱动程序的 EvtTimerFunc 回调函数。
从 KMDF 版本 1.13 和 UMDF 版本 2.0 开始,没有可用的唤醒计时器。
若要创建无唤醒计时器,请将 WDF_TIMER_CONFIG 的 TolerableDelay 成员设置为 TolerableDelayUnlimited。
高分辨率计时器
标准框架计时器的准确度与系统时钟计时周期间隔匹配,默认情况下为 15.6 毫秒。 从 Windows 8.1 开始,可以创建高分辨率计时器。 高分辨率计时器的准确度为 1 毫秒。 对于需要精确、可预测的过期时间的关键操作,可以使用高分辨率计时器。 由于需要频繁维护,高分辨率计时器可能会导致电池使用时间缩短。
高分辨率计时器仅适用于 KMDF 驱动程序,从 KMDF 版本 1.13 开始。
若要创建高分辨率计时器,请将 WDF_TIMER_CONFIG 的 UseHighResolutionTimer 成员设置为 WdfTrue,然后将 Period 值调整为所需的分辨率。
下表显示了基于驱动程序为 Period 提供的不同值的计时器行为示例。 这些示例假定系统时钟计时周期间隔为 15 毫秒:
更准确的描述见本文的下半部分描述。
计时器准确性
系统计时器例程通常允许调用方指定计时器的绝对或相对过期时间。 例如 KeWaitForSingleObject、 KeSetTimer 或 KeDelayExecutionThread。 操作系统测量过期时间的准确度受系统时钟粒度的限制。
系统时间在系统时钟的每个时钟周期上更新,并且仅精确到最新的时钟周期。 如果调用方指定绝对过期时间,则会在处理指定时间后发生的第一个系统时钟时钟周期期间检测到计时器的过期时间。 因此,计时器可以比指定的绝对过期时间晚一个系统时钟周期。 如果指定了计时器间隔或相对过期时间,则到期时间可能早于指定时间或晚于指定时间,具体取决于此间隔的开始和结束时间在系统时钟周期之间的确切位置。 无论指定的是绝对时间还是相对时间,如果系统时钟的中断处理因其他设备的中断处理而延迟,则直到以后才检测到计时器过期。
当调用方指定相对过期时间时,计时器例程会将当前系统时钟时间添加到指定的相对过期时间,以计算计时器使用的绝对过期时间。 由于系统时间仅精确到系统时钟的最新时钟周期,因此计算的过期时间最多可以早于调用方预期的过期时间的系统时钟周期。 如果指定的相对过期时间接近或小于系统时钟周期,计时器可能会立即过期,不会延迟。
更准确地支持较短过期时间的一种可能方法是缩短系统时钟周期之间的时间,但这样做可能会增加功耗。 此外,缩短系统时钟周期可能无法可靠地实现更精细的系统时钟粒度,除非可以保证平台中其他设备的中断处理不会延迟系统时钟中断的处理。
从Windows 8开始,KeDelayExecutionThread 使用更精确的技术从调用方指定的相对过期时间计算绝对过期时间。 首先,为了更准确地估计当前系统时间,例程使用系统性能计数器来测量自上次系统时钟计时周期以来经过的时间。 接下来,例程将系统时间的此更精确的估计值添加到相对过期时间,以计算绝对过期时间。 此方法计算的绝对过期时间精确到微秒内。 因此,计时器不会在指定的相对过期时间之前过期。 计时器仍可能过期到系统时钟周期晚于指定时间,并且即使系统时钟中断的处理因其他设备的中断处理而延迟,也可能在以后过期。
如果系统时间在计时器过期之前发生更改,则相对计时器不受影响,但系统会调整每个绝对计时器。 相对计时器始终在指定的时间单位数过后过期,而不考虑绝对系统时间。 绝对计时器在特定系统时间过期,因此系统时间的更改会更改绝对计时器的等待持续时间。
高解析度计时器
从 Windows 8.1 开始,驱动程序可以使用 ExXxx计时器例程来管理高分辨率计时器。 高分辨率计时器的准确性仅受系统时钟支持的最大分辨率的限制。 相比之下,限制为默认系统时钟分辨率的计时器的准确度明显较低。
但是,高分辨率计时器需要系统时钟中断(至少暂时)以更高的速率发生,这往往会增加功耗。 因此,驱动程序应仅在计时器准确性至关重要时才使用高分辨率计时器,并在所有其他情况下使用默认分辨率计时器。
为了创建高分辨率计时器,WDM 驱动程序调用 ExAllocateTimer 例程并在 Attributes 参数中设置EX_TIMER_HIGH_RESOLUTION标志。 当驱动程序调用 ExSetTimer 例程来设置高分辨率计时器时,操作系统会根据需要增加系统时钟的分辨率,以便计时器过期的时间更准确地对应于 DueTime 和 Period 参数中指定的名义过期时间。
Kernel-Mode驱动程序框架 (KMDF) 驱动程序可以调用 WdfTimerCreate 方法来创建高分辨率计时器。 在此调用中,驱动程序将指向 WDF_TIMER_CONFIG 结构的指针作为参数传递。 若要创建高分辨率计时器,驱动程序会将此结构的 UseHighResolutionTimer 成员设置为 TRUE。 从 Windows 8.1 和 KMDF 版本 1.13 开始,此成员是结构的一部分。
控制计时器准确性
例如,对于在 x86 处理器上运行的 Windows,系统时钟周期之间的默认间隔通常约为 15 毫秒,系统时钟周期之间的最小间隔约为 1 毫秒。 因此,如果未设置EX_TIMER_HIGH_RESOLUTION标志 (, 则 exAllocateTimer 创建的默认分辨率计时器的过期时间) 只能控制在大约 15 毫秒内,但高分辨率计时器的过期时间可以控制在一毫秒内。
如果驱动程序为默认分辨率计时器指定相对过期时间,则计时器最多可以早于或晚于指定的过期时间约 15 毫秒。 如果驱动程序为高分辨率计时器指定相对过期时间,则计时器的过期时间可能晚于指定过期时间后大约一毫秒,但它永远不会提前过期。
如果未设置高分辨率计时器,操作系统通常按其默认速率运行系统时钟。 但是,如果设置了一个或多个高分辨率计时器,则操作系统可能需要在其最大速率下运行系统时钟至少一部分时间,然后这些计时器才会过期。
为了避免不必要地增加功耗,操作系统仅在需要满足高分辨率计时器的计时要求时才以最大速率运行系统时钟。 例如,如果高分辨率计时器是周期性的,并且其周期跨越多个默认的系统时钟时钟周期,则操作系统可能仅在每次到期前的计时器时间段内以最大速率运行系统时钟。 在计时器的剩余时间内,系统时钟按其默认速率运行。
为防止过度耗电量,驱动程序应避免将长时间运行的高分辨率计时器的时间段设置为小于系统时钟周期之间的默认间隔的值。 否则,操作系统将被迫以其最大速率连续运行系统时钟。
从 Windows 8 开始,驱动程序可以调用 ExQueryTimerResolution 例程来获取系统时钟支持的计时器分辨率范围。
与 ExSetTimerResolution 的比较
从 Windows 2000 开始,驱动程序可以调用 ExSetTimerResolution 例程来更改连续系统时钟中断之间的时间间隔。 例如,驱动程序可以调用此例程,将系统时钟从默认速率更改为最大速率,以提高计时器准确性。 但是,与使用 ExAllocateTimer 创建的高分辨率计时器相比,使用 ExSetTimerResolution 有几个缺点。
首先,在调用 ExSetTimerResolution 以暂时提高系统时钟速率后,驱动程序必须再次调用 ExSetTimerResolution 才能将系统时钟还原到其默认速率。 否则,系统时钟计时器会以最大速率持续生成中断,这可能会导致过度的功耗。
其次,使用 ExSetTimerResolution 例程 的驱动程序无法像操作系统对高分辨率计时器那样有效地优化对更高系统时钟速率的临时使用。 因此,系统时钟以最大速率运行的时间比严格必要的时间要多。
第三,如果多个驱动程序同时使用 ExSetTimerResolution 来提高计时器准确性,则系统时钟可能会长时间以最大速率运行。 相比之下,操作系统全局协调多个高分辨率计时器的操作,以便系统时钟仅在需要满足这些计时器的计时要求时才以最大速率运行。
最后,使用 ExSetTimerResolution 本质上不如使用高分辨率计时器准确。 在驱动程序调用 ExSetTimerResolution 将系统时钟增加到其最大速率(通常为每毫秒一个时钟周期)后,驱动程序可能会调用 KeSetTimerEx 等例程来设置计时器。 如果在此调用中,驱动程序指定相对过期时间,则计时器最多可以早于或晚于指定的过期时间约一毫秒。 但是,如果为高分辨率计时器指定了相对过期时间,则计时器可能比指定的过期时间晚大约一毫秒,但它永远不会提前过期。