使用后备列表
必须动态分配固定大小缓冲区以执行按需 I/O 操作的驱动程序可以使用 ExXxxLookasideListEx 或 ExXxxLookasideList 支持例程。 在此类驱动程序初始化其后备列表后,操作系统将在驱动程序的后备列表中保留一定数量的给定大小的动态分配缓冲区,从而有效地为驱动程序保留一组可重复使用的固定大小缓冲区。 驱动程序后备列表中固定大小缓冲区(也称为条目)的格式和内容由驱动程序决定。
例如,必须为基础 SCSI 端口/微型端口驱动程序设置 SCSI 请求块 (SRB) 的存储类驱动程序会使用后备列表。 此类类驱动程序会根据需要从其后备列表中为 SRB 分配缓冲区,并在完成的 IRP 中 SRB 返回类驱动程序时,将每个 SRB 缓冲区释放回后备列表,以供后备列表重复使用。 由于对驱动器的 I/O 需求时增时减,存储类驱动器无法预先确定在任何时候必须使用多少个 SRB,因此在这种驱动器中,后备列表是管理固定大小 SRB 缓冲区分配和去分配的一种方便而经济的方法。
操作系统会维护当前正在使用的所有分页和非分页后备列表的状态,动态跟踪所有列表中条目分配和取消分配的需求,以及新条目可用的系统池。 当分配需求较高时,操作系统会增加每个后备列表中容纳的条目数量。 当需求再次下降时,就会将多余的后备条目释放回系统池。
后备列表是线程安全的。 后备列表具有内置同步功能,可让驱动程序中多个并发运行的线程共享后备列表。 这些线程可以安全地从共享后备列表中分配缓冲区,并将这些缓冲区释放到列表中,而无需驱动程序显式同步这些操作。 但是,为了避免发生可能的泄漏和数据损坏,共享后备列表的一组线程必须显式同步列表的初始化和删除。
后备列表接口
从 Windows Vista 开始,LOOKASIDE_LIST_EX 结构描述了一个可包含分页或非分页缓冲区的后备列表。 如果驱动程序为该后备列表提供自定义 Allocate 和 Free 例程,则这些例程会接收一个专用上下文作为输入参数。 驱动程序可以使用该上下文为后备列表收集专用数据。 例如,上下文可用于计算列表动态分配和释放的列表条目的数量。 有关如何以这种方式使用上下文的代码示例,请参阅 ExInitializeLookasideListEx。
以下系统提供的例程支持由 LOOKASIDE_LIST_EX 结构描述的后备列表:
从 Windows 2000 开始,PAGED_LOOKASIDE_LIST 结构描述了包含分页缓冲区的后备列表。 如果驱动程序为该后备列表提供自定义 Allocate 和 Free 例程,则这些例程不会接收一个专用上下文作为输入参数。 因此,如果驱动程序只能在 Windows Vista 和更高版本的 Windows 上运行,请考虑使用 LOOKASIDE_LIST_EX 结构而不是 PAGED_LOOKASIDE_LIST 结构来处理后备列表。 以下系统提供的例程支持由 PAGED_LOOKASIDE_LIST 结构描述的后备列表:
ExAllocateFromPagedLookasideList
ExInitializePagedLookasideList
从 Windows 2000 开始,NPAGED_LOOKASIDE_LIST 结构描述了包含非分页缓冲区的后备列表。 如果驱动程序为该后备列表提供自定义 Allocate 和 Free 例程,则这些例程不会接收一个专用上下文作为输入参数。 同样,如果驱动程序只能在 Windows Vista 和更高版本的 Windows 上运行,请考虑使用 LOOKASIDE_LIST_EX 结构而不是 NPAGED_LOOKASIDE_LIST 结构来处理后备列表。 以下系统提供的例程支持由 NPAGED_LOOKASIDE_LIST 结构描述的后备列表:
ExAllocateFromNPagedLookasideList
ExInitializeNPagedLookasideList
实现指南
要实现使用 LOOKASIDE_LIST_EX 结构的后备列表,请遵循以下设计指导方针:
调用 ExInitializeLookasideListEx 以设置后备列表。 在此调用中,指定后备列表中的条目是分页缓冲区还是非分页缓冲区。 如果驱动程序本身或任何传递其后备列表条目的基础驱动程序可能在 IRQL >= DISPATCH_LEVEL 时访问这些条目,则使用非分页缓冲区。 只有当访问驱动程序的后备列表项总是发生在 IRQL <= APC_LEVEL 时,才使用分页缓冲区。
无论列表中的条目是分页还是非分页,用于后备列表的 LOOKASIDE_LIST_EX 结构必须始终驻留在非分页系统内存中。
为了获得更好的性能,请将 Allocate 和 Free 参数的 NULL 指针传递给 ExInitializeLookasideListEx,除非分配和取消分配例程必须做的事情不仅仅是为后备列表条目分配和释放内存。 例如,这些例程可能会记录有关驱动程序使用动态分配的缓冲区的信息。
驱动程序提供的 Allocate 例程可将收到的输入参数(PoolType、Tag 和 Size)直接传递给 ExAllocatePoolWithTag 或 ExAllocatePoolWithQuotaTag 例程,以便分配新的缓冲区。
每次调用 ExAllocateFromLookasideListEx 后,只要不再使用先前分配的条目,就应尽快对 ExFreeToLookasideListEx 进行对等调用。
提供 Allocate 和 Free 例程,除了分别调用 ExAllocatePoolWithTag and ExFreePool 之外不进行任何操作,这样将浪费处理器周期。 当驱动程序将 NULL Allocate 和 Free 指针传递给 ExInitializeLookasideListEx 时,ExAllocateFromLookasideListEx 会自动调用 ExAllocatePoolWithTag 和 ExFreePool。
任何驱动程序提供的 Allocate 例程都不得为分页池中的条目分配内存到非分页后备列表中,反之亦然。 它还必须分配固定大小的条目,因为任何对 ExAllocateFromLookasideListEx 的后续驱动程序调用都会返回当前在后备列表中持有的第一个条目,除非该列表为空。 也就是说,只有在给定的后备列表当前为空的情况下,调用 ExAllocateFromLookasideListEx 才会导致调用驱动程序提供的 Allocate 例程。 因此,在每次调用 ExAllocateFromLookasideListEx 时,只有当后备列表中的所有条目均为固定大小时,返回的条目才会与驱动程序所需的大小完全一致。 驱动程序提供的 Allocate 例程也不应更改驱动程序最初传递给 ExInitializeLookasideListEx 的 Tag 值,因为池标记值的更改会增加调试和跟踪驱动程序内存使用情况的难度。
对 ExFreeToLookasideListEx 的调用会将先前分配的条目存储到后备列表中,除非列表已满(即列表包含系统确定的最大条目数)。 为了获得更好的性能,驱动程序每次调用 ExAllocateFromLookasideListEx 时,都应尽可能快地对 ExFreeToLookasideListEx 进行交互调用。 当驱动程序快速将条目释放回其后备列表时,该驱动程序对 ExAllocateFromLookasideListEx 的下一次调用就不太可能因为为新条目动态分配内存而造成性能损失。
类似的指导方针也适用于使用 PAGED_LOOKASIDE_LIST 或 NPAGED_LOOKASIDE_LIST 结构的后备列表。