WDF驱动开发-特定于KMDF的技术(四)

在 KMDF 驱动程序中创建可分页的代码

可分页代码 是在不使用代码时可以写入计算机的分页文件的代码。 你可以使驱动程序的一部分可分页,以减少其加载映像和初始加载时间,并减少使用计算机有限非分页内存池的驱动程序代码量。

为了帮助你确定可分页代码或数据是否适合驱动程序,请执行以下操作:

1. 标识驱动程序中的可分页部分,在需要可分页分区之前,不会将其加载到内存中。

2.确保分页驱动程序代码不会妨碍计算机从低功耗状态快速唤醒的能力。

驱动程序提供的所有设备对象回调函数均在 IRQL = PASSIVE_LEVEL中调用,这使你可以使其代码 可分页 。

但是,如果框架在设备离开低功耗状态并返回到其工作 (D0) 状态时调用回调函数,则不应使回调函数的代码可分页。

如果此类代码可分页,则代码可能会在计算机进入睡眠状态之前写入分页文件。 因此,计算机唤醒速度会变慢,因为无法 重新加载代码,因此在恢复分页磁盘电源之前,设备无法完全正常运行 。

因此, “设备返回到其工作状态 ”这一行为涉及的回调函数不应是可分页的。

3. 确定驱动程序是否需要在电源转换期间访问驱动程序外部的可分页数据,例如文件、注册表或分页池:

a. 如果功能驱动程序或总线驱动程序调用 WdfDeviceInitSetPowerPageable,则当驱动程序的设备 进入低功耗 状态或 返回到工作状态时,系统将确保分页文件的设备处于其工作 (D0) 状态。 这样,驱动程序就可以在设备的电源转换期间访问文件、注册表和分页池。

默认情况下,框架允许访问功能驱动程序的可分页数据。 框架对总线驱动程序枚举的每个子设备使用父设备的设置,除非总线驱动程序为子设备调用 WdfDeviceInitSetPowerPageable 或 WdfDeviceInitSetPowerNotPageable 。 如果为子设备编写调用 WdfDeviceInitSetPowerPageable 的 总线驱动程序,则子设备堆栈中没有任何驱动程序可以调用 WdfDeviceInitSetPowerNotPageable。

从Filter驱动程序调用 WdfDeviceInitSetPowerPageable 或 WdfDeviceInitSetPowerNotPageable 不起作用。 框架使用下一个下级驱动程序指定的设置。

大多数驱动程序不需要调用 WdfDeviceInitSetPowerPageable 或 WdfDeviceInitSetPowerNotPageable。 相反,你应该让框架使用适合你的驱动程序的默认设置。 但是,如果驱动程序是驱动程序堆栈的一部分,该驱动程序不得在电源转换 (期间访问可分页数据,例如存储堆栈或视频堆栈) ,或者驱动程序是枚举可能是存储或视频设备的总线驱动程序,则驱动程序应调用 WdfDeviceInitSetPowerNotPageable 。

如果驱动程序调用 WdfDeviceInitSetPowerInrush,则它不得调用 WdfDeviceInitSetPowerPageable。

驱动程序必须先调用 WdfDeviceInitSetPowerPageable ,然后才能调用 WdfDeviceCreate。

b. 如果功能驱动程序或总线驱动程序调用 WdfDeviceInitSetPowerNotPageable,则当驱动程序的设备 进入低功耗 状态或返回到工作状态时,系统分页文件的设备可能未处于其工作 (D0) 状态。 因此,在设备的电源转换期间,驱动程序不得执行可能导致操作系统访问分页文件的任何操作。 此类操作包括访问文件、注册表或分页池。

默认情况下,框架允许访问功能驱动程序的可分页数据。 框架对总线驱动程序枚举的每个子设备使用父设备的设置,除非总线驱动程序为子设备调用 WdfDeviceInitSetPowerPageable 或 WdfDeviceInitSetPowerNotPageable 。 如果为子设备编写调用 WdfDeviceInitSetPowerPageable 的总线驱动程序,则子设备的堆栈中没有任何驱动程序可以调用 WdfDeviceInitSetPowerNotPageable。

从Filter驱动程序调用 WdfDeviceInitSetPowerPageable 或 WdfDeviceInitSetPowerNotPageable 不起作用。 框架使用下一个较低级别的驱动程序指定的设置。

大多数驱动程序不需要调用 WdfDeviceInitSetPowerPageable 或 WdfDeviceInitSetPowerNotPageable。 相反,应让框架使用适合驱动程序的默认设置。 但是,如果驱动程序是驱动程序堆栈的一部分,该堆栈在电源转换期间不得访问可分页数据,例如存储堆栈或视频堆栈 () ,或者驱动程序是枚举可能是存储或视频设备的总线驱动程序,则驱动程序应调用 WdfDeviceInitSetPowerNotPageable 。

如果驱动程序调用 WdfDeviceInitSetPowerNotPageable,则必须在调用 WdfDeviceCreate 之前执行此操作。

获取当前是否可以分页

若要获取框架电源状态机的当前状态,驱动程序可以从 PnP 或电源回调函数中调用 WdfDeviceGetDevicePowerState 。 若要获取框架的电源策略状态机的当前状态,驱动程序可以从电源策略回调函数中调用 WdfDeviceGetDevicePowerPolicyState 。 驱动程序调用 WdfDeviceGetDevicePowerState 或 WdfDeviceGetDevicePowerPolicyState 后,它可以调用 WdfDevStateIsNP 以确定返回的状态是表示可分页状态还是不可分页状态。 如果框架的状态机处于不可分页状态,则驱动程序不可分页,并且不得执行可能导致操作系统访问分页文件的任何操作。 此类操作包括访问文件、注册表或分页池。

使驱动程序可分页

默认情况下,链接器将“.text”和“.data”等名称分配给驱动程序映像文件的代码和数据部分。 加载驱动程序时,I/O 管理器会将这些部分设置为非分页。 非分页部分始终驻留在内存中。

驱动程序开发人员可以选择使驱动程序的指定部分可分页,以便 Windows 可以在这些部件未使用时将这些部件移动到分页文件。 若要使代码或数据节可分页,驱动程序开发人员会向节分配以“PAGE”开头的名称。 I/O 管理器在加载驱动程序时检查部分的名称。 如果节名称以“PAGE”开头,则 I/O 管理器使分区可分页。

在 IRQL >= DISPATCH_LEVEL 下运行的代码必须驻留在内存中。 也就是说,此代码必须位于不可分页的段中,或位于内存中锁定的可分页段中。 如果在 IRQL >= DISPATCH_LEVEL 下运行的代码导致页面错误,则会发生 bug 检查。 驱动程序可以使用 PAGED_CODE 宏来验证是否仅在适当的 IRQL 中调用可分页函数。

如果代码或数据节可分页,则驱动程序可以通过调用 MmLockPagableCodeSection 或 MmLockPagableDataSection 例程将分区锁定在内存中。 在驱动程序调用 MmUnlockPagableImageSection 例程来解锁它之前,节将保持锁定状态。 当可分页节被锁定时,它的行为与非分页节相同。

代码和数据何时应可分页?

你可以使驱动程序的全部或部分可分页。 分页驱动程序代码可以减小驱动程序的加载映像的大小,从而释放系统空间供其他用途使用。 它最适用于零星使用设备(如调制解调器和 CD-ROM)的驱动程序,或很少调用的部分驱动程序。

执行以下任一操作的驱动程序代码必须驻留在内存中。 也就是说,此代码必须位于非分页节中,或者在代码运行时锁定在内存中的分页节中。

  • 在 IRQL = DISPATCH_LEVEL 或更高位置运行。
  • 获取旋转锁。
  • 调用内核的任何对象支持例程,例如 KeReleaseMutex 或 KeReleaseSemaphore,其中 Wait 参数设置为 TRUE。 如果在 将 Wait 设置为 TRUE 的情况下调用内核,调用将在 DISPATCH_LEVEL 返回 IRQL

当代码执行可能导致页面错误的任何操作时,驱动程序代码必须在 IRQL < DISPATCH_LEVEL上运行。 如果代码执行以下任一操作,则可能会导致页面错误:

  • 访问未在内存中锁定的分页池。
  • 调用可分页例程。
  • 在用户线程的上下文中访问未锁定的用户缓冲区。

通常,如果所有可分页代码或数据的总量至少为 4 KB (KB) ,则应将分区设置为分页。 应尽可能将纯可分页代码或数据隔离到代码或数据中,这些部分有时可以分页,但有时必须锁定。 例如,将纯可分页代码和锁定的按需代码组合在一起会导致锁定组合部分的系统空间超出必要。 但是,如果驱动程序的可分页代码或数据 少于 4 KB,则可以将代码或数据与锁定的按需代码或数据合并到一个部分,从而节省系统空间。

检测可分页的代码

若要检测在 IRQL >= DISPATCH_LEVEL 下运行的代码,请使用 PAGED_CODE 宏。 在调试模式下,如果代码在 IRQL >= DISPATCH_LEVEL 运行,则此宏将生成一条消息。 将宏添加为例程中的第一个语句,以将整个例程标记为分页代码,如以下示例所示:

NTSTATUS 
MyDriverXxx( 
    IN OUT PVOID ParseContext OPTIONAL, 
    OUT PHANDLE Handle 
    ) 
{ 
    NTSTATUS Status; 
 
    PAGED_CODE(); 
. 
. 
. 
}

 若要确保正确执行此操作,请在启用“强制 IRQL 检查”选项的情况下,针对已完成的驱动程序运行驱动程序验证程序。 此选项会导致每次驱动程序将 IRQL 引发到 DISPATCH_LEVEL 或更高版本时,系统都会自动分页出所有可分页代码。 使用驱动程序验证程序,可以快速找到此区域中的任何驱动程序 bug。 否则,通常只有客户才能发现这些 bug,并且它们通常很难重现。

  • 33
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值