停止运行保护

内核模式驱动程序可以使用运行保护安全地访问由另一个内核模式驱动程序创建和删除的共享系统内存中的对象。

当对象的所有未决访问完成后,且不再授予新的访问请求时,该对象被称为终止。 例如,可能需要逐渐停用共享对象,以便将其删除并替换为新对象。

拥有共享对象的驱动程序可以让其他驱动程序获取和释放该对象的失效保护。 当运行保护生效时,除所有者以外的驱动程序可以访问该对象,而不会有所有者在访问完成之前删除该对象的风险。 在访问开始之前,访问驱动程序请求物件的退出保护。 对于生存期较长的对象,此请求几乎总是被批准。 访问完成后,访问驱动程序会在对象上释放其之前获取的终止保护。

主要停机保护例程

若要开始共享对象,拥有对象的驱动程序调用 ExInitializeRundownProtection 例程来初始化对象的运行保护。 此调用后,访问该对象的其他驱动程序可以获取和释放该对象的运行保护。

访问共享对象的驱动程序调用 ExAcquireRundownProtection 例程来请求对对象执行运行保护。 访问完成后,此驱动程序调用 ExReleaseRundownProtection 例程以释放对象的运行保护。

如果拥有驱动程序确定必须删除共享对象,此驱动程序将等待删除该对象,直到对象的所有未完成访问完成。

在准备移除共享对象时,所属驱动程序调用 ExWaitForRundownProtectionRelease 例程,以等待对象的活动完成。 在此调用期间,ExWaitForRundownProtectionRelease 负责等待对象上以前被授予的所有结束保护实例的释放,同时阻止对对象的新结束保护请求被授予。 最后一个受保护的访问完成后,释放所有撤销保护实例后,ExWaitForRundownProtectionRelease 将返回,相关的驱动程序就可以安全地删除该对象。

ExWaitForRundownProtectionRelease 会阻止调用驱动程序线程的执行,直到在共享对象上保留运行保护的所有驱动程序都会释放此保护。 为了防止 ExWaitForRundownProtectionRelease 长时间阻止执行,访问共享对象的驱动程序线程在对对象保留运行保护时,应避免挂起。 因此,访问驱动程序应在关键区域或受保护的区域中调用 ExAcquireRundownProtectionExReleaseRundownProtection ,或在 IRQL = APC_LEVEL 运行时调用。

防止耗尽保护用途

运行保护可用于提供对几乎始终可用的共享对象的访问,但有时可能需要删除和替换。 访问数据或调用此对象中的例程的驱动程序在删除对象后不得尝试访问该对象。 否则,这些无效的访问可能会导致不可预知的行为、数据损坏甚至系统故障。

例如,在作系统运行时,防病毒驱动程序通常保留在内存中。 有时,可能需要卸载此驱动程序并将其替换为驱动程序的更新版本。 其他驱动程序向防病毒驱动程序发送 I/O 请求,以访问此驱动程序中的数据和例程。 在发送 I/O 请求之前,内核组件(如文件系统筛选器管理器)可以获取运行保护,以防止在处理 I/O 请求时提前卸载防病毒驱动程序。 I/O 请求完成后,可以释放运行保护。

运行保护不会序列化对共享对象的访问。 如果两个或多个访问驱动程序可以同时在对象上保留运行保护,并且必须序列化对对象的访问,则必须使用其他一些机制(例如互斥锁)来序列化访问。

EX_RUNDOWN_REF结构

EX_RUNDOWN_REF结构跟踪共享对象上的运行保护的状态。 此结构对驱动程序不透明。 系统提供的运行保护例程使用此结构来计算当前对对象生效的运行保护实例数。 这些例程还使用此结构来跟踪对象是否已耗尽或正在耗尽。

若要开始共享对象,拥有对象的驱动程序调用 ExInitializeRundownProtection 来初始化与对象关联的 EX_RUNDOWN_REF 结构。 初始化后,拥有驱动程序可以将此结构提供给需要访问该对象的其他驱动程序。 访问驱动程序将此结构作为参数传递给 ExAcquireRundownProtectionExReleaseRundownProtection 调用,用于获取和释放对象的运行保护。 拥有驱动程序将此结构作为参数传递给ExWaitForRundownProtectionRelease调用,该调用等待对象完成清除,以便能够安全删除。

与锁的比较

运行终止保护是保证对共享对象安全访问的几种方法之一。 另一种方法是使用互斥软件锁。 如果驱动程序需要访问当前被另一个驱动程序锁定的对象,则第一个驱动程序必须等待第二个驱动程序释放锁。 但是,获取和释放锁可能会成为性能瓶颈,锁可能会消耗大量内存。 如果错误使用,锁可能导致竞争同一共享对象的驱动程序发生死锁。 检测和避免死锁的努力通常需要转移大量计算资源。

与锁相比,运行保护具有相对轻量级的处理时间和内存要求。 一个简单的引用计数与对象关联,以确保只有在对象的所有未完成访问完成后,才会删除该对象。 使用此方法时,可以使用原子互锁硬件指令,而不是相互排除的软件锁,以确保对对象的安全访问。 获取和发布运行保护的调用通常很快。 使用轻量级机制(如资源释放保护)的好处对于在许多驱动程序之间共享且具有长生命周期的共享对象而言可能具有重大意义。

其他运行保护例程

除了前面提到的保护例程外,还提供其他几个运行结束保护例程。 某些驱动程序可能会使用这些额外的例程。

ExReInitializeRundownProtection 例程使以前使用的EX_RUNDOWN_REF结构能够与新对象相关联,并初始化此对象的运行保护。

ExRundownCompleted 例程更新EX_RUNDOWN_REF结构,以指示关联对象的运行已完成。

ExAcquireRundownProtectionExExReleaseRundownProtectionEx 例程类似于 ExAcquireRundownProtectionExReleaseRundownProtection。 这四个例程递增或递减对共享对象已生效的运行结束保护实例计数。 ExAcquireRundownProtectionExReleaseRundownProtection 增加此计数并减少一个,而 ExAcquireRundownProtectionExExReleaseRundownProtectionEx 以任意数量增加或减少计数。

缓存感知的运行保护

运行引用是一种紧凑且快速的数据结构,但当许多处理器同时尝试获取引用时,它可能会导致缓存争用。 这可能会影响驱动程序的性能和可伸缩性。

若要避免此问题,可以通过使用针对缓存设计的逐出引用来在多个缓存行中分散引用跟踪。 这样可以减少缓存争用,并提高多处理器计算机上的驱动程序性能。

若要使用缓存感知的运行引用,请执行以下步骤:

  1. 通过执行以下作之一创建 EX_RUNDOWN_REF_CACHE_AWARE 对象:
  2. 通过调用 ExAcquireRundownProtectionCacheAware 例程在访问对象之前请求对对象进行运行保护。 如果请求被授予,则此例程返回 TRUE;如果对象正在被撤销,则返回 FALSE。
  3. 通过调用 ExReleaseRundownProtectionCacheAware 例程在访问对象后释放对象的运行保护。
  4. 在通过调用ExWaitForRundownProtectionReleaseCacheAware例程删除对象之前,等待对象运行完。 此例程会阻止当前线程,直到释放对象上所有运行保护实例。
  5. 如果驱动程序之前名为 ExAllocateCacheAwareRundownProtection ,则应调用 ExFreeCacheAwareRundownProtection 以释放运行引用。

若要重复使用缓存感知的运行引用,请执行以下步骤:

  1. 调用 ExWaitForRundownProtectionReleaseCacheAware 后,调用 ExRundownCompletedCacheAware 以指示旧对象的运行已完成。
  2. 调用 ExReInitializeRundownProtectionCacheAware 以在关联对象运行后重新初始化引用。
  3. 现在驱动程序可以再次调用 ExAcquireRundownProtectionCacheAware

缓存感知的运行引用在特定的情况下具有更好的性能和可伸缩性的优势,但它消耗的内存比常规的运行引用要多。 在两种类型的运行引用之间进行选择时,应考虑此权衡。