某些内核模式驱动程序(例如串行驱动程序和并行驱动程序)不需要是内存驻留的,除非它们管理的设备处于打开状态。 但是,只要存在活动连接或端口,管理该端口的驱动程序代码的某些部分必须驻留才能为设备提供服务。 如果未使用端口或连接,则不需要驱动程序代码。 相比之下,包含系统代码、应用程序代码或系统分页文件的磁盘的驱动程序必须始终是内存驻留的,因为驱动程序在其设备和系统之间不断传输数据。
当它管理的设备未处于活动状态时,零星使用的设备的驱动程序(例如调制解调器)可以释放系统空间。 如果在单个部分中放置了必须驻留在活动设备中的代码,并且驱动程序在使用设备时将代码锁定在内存中,则可以将此部分指定为可分页。 打开驱动程序的设备后,作系统会将可分页部分引入内存,驱动程序将它锁定到不再需要为止。
系统 CD 音频驱动程序代码使用此技术。 驱动程序的代码根据 CD 设备的制造商分组到可分页部分。 某些品牌可能永远不会出现在给定的系统上。 此外,即使系统中存在 CD-ROM,也可能不会经常访问它,因此按 CD 类型将代码分组到可分页部分可确保不会加载特定计算机上不存在的设备的代码。 但是,当访问设备时,系统将加载相应 CD 设备的代码。 Then the driver calls the MmLockPagableCodeSection routine, as described later in this article, to lock its code into memory while its device is being used.
若要将可分页代码隔离到命名部分,请使用以下编译器指令对其进行标记:
#pragma alloc_text(PAGE*Xxx***, *RoutineName***)
The name of a pageable code section must start with the four letters "PAGE" and can be followed by up to four characters (represented here as Xxx) to uniquely identify the section. 节名称的前四个字母(即“PAGE”)必须大写。 The RoutineName identifies an entry point to be included in the pageable section.
驱动程序文件中可分页代码节的最短有效名称只是 PAGE。 例如,以下代码示例中的杂注指令标识 RdrCreateConnection 为名为 PAGE 的可分页代码部分中的入口点。
#ifdef  ALLOC_PRAGMA 
#pragma alloc_text(PAGE, RdrCreateConnection) 
#endif 
To make pageable driver code resident and locked in memory, a driver calls MmLockPagableCodeSection, passing an address (typically the entry point of a driver routine) that's in the pageable code section. MmLockPagableCodeSection locks in the whole contents of the section that contains the routine referenced in the call. In other words, this call makes every routine associated with the same PAGEXxx identifier resident and locked in memory.
MmLockPagableCodeSection returns a handle to be used when unlocking the section (by calling the MmUnlockPagableImageSection routine) or when the driver must lock the section from additional locations in its code.
驱动程序还可以将很少使用的数据视为可分页数据,以便也可以分页,直到它支持的设备处于活动状态。 例如,系统混音器驱动程序使用可分页数据。 混音器设备没有与之关联的异步 I/O,因此此驱动程序可以使其数据可分页。
可分页数据节的名称必须以四个字母“PAGE”开头,后跟最多四个字符才能唯一标识节。 节名称的前四个字母(即“PAGE”)必须大写。
避免为代码和数据部分分配相同的名称。 为了使源代码更具可读性,驱动程序开发人员通常会将名称 PAGE 分配给可分页代码部分,因为此名称较短,并且它可能出现在许多alloc_text杂注指令中。 然后,将较长的名称分配给任何可分页的数据部分(例如,适用于data_seg的 PAGEDATA、bss_seg 的 PAGEBSS 等),驱动程序可能需要这些名称。
例如,以下代码示例中的前两个杂注指令定义两个可分页的数据部分:PAGEDATA 和 PAGEBSS。 PAGEDATA 是使用data_seg杂注指令声明的,其中包含初始化的数据。 PAGEBSS 使用bss_seg杂注指令声明,并包含未初始化的数据。
#pragma data_seg("PAGEDATA")
#pragma bss_seg("PAGEBSS")
INT Variable1 = 1;
INT Variable2;
CHAR Array1[64*1024] = { 0 };
CHAR Array2[64*1024];
#pragma data_seg()
#pragma bss_seg()
在此代码示例中, Variable1 显式 Array1 初始化并因此放置在 PAGEDATA 节中。 
              Variable2 并且 Array2 是隐式零初始化的,并放置在 PAGEBSS 节中。
将全局变量隐式初始化为零可减小磁盘可执行文件的大小,并优先于显式初始化为零。 应避免显式零初始化,除非需要显式零初始化,以便在特定数据节中放置变量。
To make a data section memory-resident and lock it in memory, a driver calls MmLockPagableDataSection, passing a data item that appears in the pageable data section. MmLockPagableDataSection returns a handle to be used in subsequent locking or unlocking requests.
To restore a locked section's pageable status, call MmUnlockPagableImageSection, passing the handle value returned by MmLockPagableCodeSection or MmLockPagableDataSection, as appropriate. A driver's Unload routine must call MmUnlockPagableImageSection to release each handle it obtains for lockable code and data sections.
锁定分区是一项昂贵的作,因为内存管理器必须在将页面锁定到内存之前搜索其加载的模块列表。 If a driver locks a section from many locations in its code, it should use the more efficient MmLockPagableSectionByHandle after its initial call to MmLockPagableXxxSection.
The handle passed to MmLockPagableSectionByHandle is the handle returned by the earlier call to MmLockPagableCodeSection or MmLockPagableDataSection.
The memory manager maintains a count for each section handle and increments this count every time that a driver calls MmLockPagableXxx for that section. A call to MmUnlockPagableImageSection decrements the count. 虽然任何节句柄的计数器都是非零的,但该分区仍被锁定在内存中。
只要加载节的驱动程序,节的句柄就有效。 Therefore, a driver should call MmLockPagableXxxSection only one time. If the driver requires more locking calls, it should use MmLockPagableSectionByHandle.
If a driver calls any MmLockPagableXxx routine for a section that's already locked, the memory manager increments the reference count for the section. 如果在调用锁例程时将分区分页,则分区中的内存管理器页并将其引用计数设置为 1。
使用此方法可最大程度地减少驱动程序对系统资源的影响。 驱动程序运行时,它可以锁定必须驻留的代码和数据。 当设备没有未完成的 I/O 请求(即设备关闭或设备从未打开时),驱动程序可以解锁相同的代码或数据,使其可供分页。
但是,在驱动程序连接中断后,在中断处理期间可以调用的任何驱动程序代码始终必须是内存驻留。 虽然某些设备驱动程序可以按需进行分页或锁定到内存中,但此类驱动程序代码和数据的某些核心集必须永久驻留在系统空间中。
请考虑以下用于锁定代码或数据部分的实现准则。
- The primary use of the Mm(Un)LockXxx routines is to enable normally nonpaged code or data to be made pageable and brought in as nonpaged code or data. 串行驱动程序和并行驱动程序等驱动程序是一个很好的示例:如果设备没有打开句柄(例如驱动程序管理),则不需要部分代码,并且可以保持分页。重定向程序和服务器也是可以使用此方法的驱动程序的良好示例。 如果没有活动连接,这两个组件都可以分页。 
- 整个可分页分区被锁定在内存中。 
- 代码的一个部分,每个驱动程序一个用于数据的部分是高效的。 许多命名的可分页节通常效率低下。 
- 保留纯可分页的分区和分页,但锁定的按需分区分开。 
- Remember that MmLockPagableCodeSection and MmLockPagableDataSection shouldn't be frequently called. 当内存管理器加载分区时,这些例程可能会导致大量 I/O 活动。 If a driver must lock a section from several locations in its code, it should use MmLockPagableSectionByHandle.