MMPFN

The MMPFN (Memory Manager Page Frame Number) structure is the key to pretty much everything that the Memory Manager knows about a page of physical memory that is in general use. There can be, and typically is, other physical memory known to Windows but it is treated as device-specific memory in a so-called I/O space and is never used to support allocations of virtual memory: pages of memory in the I/O space are not represented by MMPFN structures.

The MMPFN is arguably the most fundamental of all structures used by the Memory Manager. An array of MMPFN structures, one for each page from physical address zero to the highest possible, is one of the kernel’s largest single uses of memory, and certainly of memory that isn’t ever paged out. A little more than 1% of physical memory—a little less on 32-bit Windows—is lost from general use as overhead just from this MMPFN array.

To inspect an MMPFN or the whole array of them when debugging, use the !pfn command or get the array’s address from the internal variable named MmPfnDatabase. In 64-bit Windows, the MMPFN array has the pre-set address FFFFFA80`00000000. Starting with the 1607 release of Windows 10, apparently as a continuing programme of kernel-mode Address Space Layout Randomization (ASLR), this address is among the several whose (many) hard-coded references throughout the kernel get changed at load time through the Dynamic Value Relocation Table in the kernel’s load configuration.

Variability

With one MMPFN for every physical page, nobody wants this structure to grow as Windows evolves. In all known Windows versions, the MMPFN is:

That the MMPFN doesn’t change in size certainly doesn’t mean that it doesn’t change. The layout has varied greatly as ever more gets packed in ever more intricately. Inevitably, variation occurs even between builds of the one version. Along with the conventional dated names of the frequent Windows 10 releases, this note uses the following shorthands for variations within old versions:

Beware that although Windows XP SP2 is more usual as these notes’ cut-off between early and late for version 5.1, the MMPFN changed at SP1.

Layout

Microsoft’s name for the MMPFN structure itself and the names and types of its members are known from type information in public symbol files, starting with Windows 2000 SP3. What’s shown for earlier versions is inferred from what use these are (yet) known to make of the MMPFN. Where known use corresponds closely with that of a version for which Microsoft’s symbols are available, it seems reasonable to suppose continuity—but this is more than usually uncertain for this structure, given its elaborate substructure. Some use anyway has no correspondence, the code having changed too much. Even where the use hasn’t changed, tracking it down exhaustively would be difficult, if not impossible, even with source code.

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
union {
    /*  see below: Union 1  */
} u1;
3.10 to 6.3
union {
    LIST_ENTRY ListEntry;
    RTL_BALANCED_NODE TreeNode;
    struct {
        /*  see below: ListEntry Overlay  */
    };
};
10.0 and higher
0x04 (3.10 to 5.2) 0x08 (late 5.2)
MMPTE *PteAddress;
3.10 to 5.2
0x08 (3.10 to 5.2);
0x04 (6.0 to 6.3);
0x10
0x10 (late 5.2);
0x08 (6.0 to 6.3);
0x18
union {
    /*  see below: Union 2  */
} u2;
3.10 to 6.3
MIPFNBLINK u2;
10.0 and higher
0x08 (6.0 to 6.3) 0x10 (6.0 to 6.3)
union {
    /*  see below: Union with PteAddress  */
};
6.0 to 6.3
0x0C (3.10 to 3.51)  
USHORT ReferenceCount;
3.10 to 3.50
ULONG ReferenceCount;
3.51 only
0x0E (3.10 to 3.50)   unknown 16-bit count 3.10 to 3.50
0x0C (4.0 to 6.3);
0x14
0x18 (late 5.2 to 6.3);
0x20
union {
    /*  see below: Union 3  */
} u3;
5.0 and higher
  0x1C (late 5.2 to 6.3);
0x24
ULONG UsedPageTableEntries;
late 5.2 only
USHORT UsedPageTableEntries;
6.0 to 6.1
USHORT NodeBlinkLow;
6.2 and higher
  0x1E (6.0 to 6.3);
0x26
UCHAR VaType;
6.0 to 6.1
struct {
    /*  see below: VaType Bits  */
};
6.2 and higher
  0x1F (6.0 to 6.3);
0x27
UCHAR ViewCount;
6.0 to 6.1
union {
    UCHAR ViewCount;
    UCHAR NodeFlinkLow;
};
6.2 and higher
0x10 (3.10 to 6.3) 0x20 (late 5.2 to 6.3)
MMPTE OriginalPte;
3.10 to 5.1
union {
    /*  see below: Union with OriginalPte  */
};
5.2 to 6.1
MMPTE OriginalPte;
6.2 to 6.3
0x14 (non-PAE);
0x18 (PAE)
0x28 unknown MMPFNENTRY 3.10 to 3.51
ULONG PteFrame;
4.0 to 5.0
union {
    /*  see below: Union 4  */
} u4;
5.1 and higher

A little of the elaborate substructuring is simplified if we invent a symbol for the bits to allow for the width of a Page Frame Number (PFN). While 32-bit and 64-bit Windows need deal only with 36-bit and 48-bit physical addresses, pages of physical memory do not need the whole ULONG or ULONGLONG that Microsoft defines for the PFN_NUMBER. Wherever a physical page number is kept in anything that’s similar to a Page Table Entry (PTE), there are always are the low 12 bits for flags. In 64-bit Windows, and in 32-bit Windows with PAE, each PTE is eight bytes and high bits are available too. Throughout the following, the understanding is that PFN_BITS is

Version PFN Bit Width
x86 x64
5.1 to early 5.2 26  
late 5.2 25 57
6.0 to 6.1 25 52
6.2 25 36
6.3 and higher 24 36

See that the reduction in what to allow has been more about the need for creating space somewhere in the MMPFN than about codifying the width that’s implied by 36-bit and 48-bit physical address spaces.

ListEntry Overlay

Windows 10 introduces to the MMPFN the relatively large LIST_ENTRY and RTL_BALANCED_NODE structures. Three old members are made into an unnamed structure that shares the space. Take away the construction and the Windows 10 MMPFN has the following 0x10 or 0x18 bytes at its start:

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
union {
    /*  see below: Union 1  */
} u1;
10.0 and higher
0x04 0x08
union {
    /*  see below: Union with PteAddress  */
};
10.0 and higher
0x08 0x10
MMPTE OriginalPte;
10.0 and higher

Union 1

All known versions of the MMPFN have the u1 member at offset 0x00. As a type, it’s an unnamed union. Members have come and gone and been reordered. That it orginated as a union is certain: even version 3.10 uses the space variously as the Flink, WsIndex and Event (at least).

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
SINGLE_LIST_ENTRY NextSlistPfn;
1709 and higher
0x00 0x00
PVOID Next;
1709 and higher
0x00 0x00
PFN_NUMBER Flink;
3.10 and higher (x86);
late 5.2 to 6.1 (x64)
struct {
    ULONGLONG Flink : 36;
    ULONGLONG NodeFlinkHigh : 28;
};
6.2 and higher (x64)
0x00 0x00
struct {
    ULONG MustNotBeZero : 2;
    ULONG Age : 3;
} PageTableWsle;
1703 only
0x00 0x00
ULONG WsIndex;
3.10 to 6.2
ULONG_PTR WsIndex;
6.3 to 1703
0x00 0x00
KEVENT *Event;
3.10 to 1703
0x00 0x00
NTSTATUS ReadStatus;
5.0 to 5.2
0x00 0x00
PVOID Next;
6.0 to 1703
0x00 0x00
PVOID volatile VolatileNext;
6.0 to 1703
0x00 0x00
KTHREAD *KernelStackOwner;
6.0 to 1703
0x00 0x00
MMPFN *NextStackPfn;
5.0 only
SINGLE_LIST_ENTRY NextStackPfn;
5.1 to 1703
0x00 0x00
MI_ACTIVE_PFN Active;
1709 and higher

The Flink is meaningful when the physical page represented by the MMPFN is on any of several lists, e.g., of free pages. It is specifically the PFN of the next physical page on the same list. As noted above, a PFN does not use the whole of its integral type: excess bits can be used for other purposes concurrently. Starting with Windows 8, pages on a standby list can also be linked in a list just of pages that are on the same NUMA node. The ancient Flink coexists with what might be named NodeFlink except that the latter’s 36 bits are separated into a NodeFlinkHigh and a NodeFlinkLow. Only the high 28 bits are crammed in with the intact 36-bit Flink at offset 0x00. The low 8 overlay the ViewCount further into the MMPFN.  

Union with PteAddress

The PteAddress is a direct MMPFN member before version 6.0, which wraps it in an unnamed union and shifts it deeper into the structure. Version 10.0 then wraps the whole of this union into an unnamed structure within a different unnamed union and brings it back towards the front.

Offset (x86) Offset (x64) Definition Versions
0x08 (6.0 to 6.3);
0x04
0x10 (6.0 to 6.3);
0x08
MMPTE *PteAddress;
6.0 and higher
PVOID volatile VolatilePteAddress;
6.0 and higher
0x08 (6.1 to 6.3) 0x10 (6.1 to 6.3)
LONG volatile Lock;
6.1 to 6.3
0x08 (6.1 to 6.3);
0x04
0x10 (6.1 to 6.3);
0x08
ULONG_PTR PteLong;
6.1 and higher

Union 2

Windows 10 turns the u2 member’s type from an unnamed union to an MIPFNBLINK structure. However, the newly named structure has only the one top-level member, which is anyway a union. Take away the scaffolding:

Offset (x86) Offset (x64) Definition Versions
0x08 (3.10 to 5.2);
0x04 (6.0 to 6.3);
0x10
0x10 (late 5.2);
0x08 (6.0 to 6.3);
0x18
PFN_NUMBER Blink;
3.10 to 6.1
struct {
    /*  see below: Blink Bits  */
};
6.2 and higher
0x04 (6.0 to 6.3) 0x08 (6.0 to 6.3)
MMPTE *ImageProtoPte;
6.0 to 6.3
0x08 (3.10 to 5.2);
0x04 (6.0 to 6.3);
0x10
0x10 (late 5.2);
0x08 (6.0 to 6.3);
0x18
ULONG_PTR ShareCount;
3.10 to 6.3
struct {
    /*  see below: ShareCount Bits  */
};
10.0 and higher
0x08 (5.0)  
ULONG SecondaryColorFlink;
5.0 only
0x10 0x18
ULONG EntireField;
10.0 and higher
0x10 0x18
union {
    LONG volatile Lock;
    struct {
        /*  see below: Lock Bits  */
    };
};
10.0 and higher

Blink Bits

The 64-bit Windows 8 introduces what might be named NodeBlink except that it exists only as high and low parts. The high 20 bits are crammed into the one ULONGLONG with the full Blink and a new 4-bit TbFlushStamp. The low 16 bits replace the UsedPageTableEntries in the MMPFN directly.

Mask (x86) Mask (x64) Definition Versions
0x01FFFFFF (6.2);
0x00FFFFFF
0x0000000F`FFFFFFFF
ULONG_PTR Blink : PFN_BITS;
6.2 and higher
  0x00FFFFF0`00000000
ULONG_PTR NodeBlinkHigh : 20;
6.2 and higher
0x1E000000 (6.2);
0x0F000000
0x0F000000`00000000
ULONG_PTR TbFlushStamp : 4;
6.2 and higher
0xE0000000 (6.2);
0x70000000 (6.3);
0x30000000
0xF0000000`00000000 (6.2 to 6.3);
0x30000000`00000000
ULONG_PTR SpareBlink : 3;
6.2 to 6.3 (x86)
ULONG_PTR SpareBlink : 4;
6.2 to 6.3 (x64)
ULONG Unused : 2;
10.0 and higher
0x40000000 0x40000000`00000000
ULONG PageBlinkDeleteBit : 1;
10.0 and higher
0x80000000 0x80000000`00000000
ULONG PageBlinkLockBit : 1;
10.0 and higher

Note that the narrowing of Blink by one bit for 32-bit Windows 8.1 is not accompanied by a widening of SpareBlink, which then does not account for all the spare bits. The highest two bits become meaningful for the Lock in Windows 10 and are defined in all three sets of bit fields, i.e., in union with Blink, ShareCount and Lock.

ShareCount Bits

The ShareCount in Windows 10 loses the highest two bits to the Lock.

Mask (x86) Mask (x64) Definition Versions
0x3FFFFFFF 0x3FFFFFFF`FFFFFFFF
ULONG ShareCount : 30;
10.0 and higher (x86)
ULONG ShareCount : 62;
10.0 and higher (x64)
0x40000000 0x40000000`00000000
ULONG PageShareCountDeleteBit : 1;
10.0 and higher
0x80000000 0x80000000`00000000
ULONG PageShareCountLockBit : 1;
10.0 and higher

Lock Bits

The new Lock for Windows 10 uses only the highest two bits.

Mask (x86) Mask (x64) Definition Versions
0x3FFFFFFF 0x3FFFFFFF`FFFFFFFF
ULONG LockNotUsed : 30;
10.0 and higher (x86)
ULONG LockNotUsed : 62;
10.0 and higher (x64)
0x40000000 0x40000000`00000000
ULONG DeleteBit : 1;
10.0 and higher
0x80000000 0x80000000`00000000
ULONG LockBit : 1;
10.0 and higher

Union 3

The u3 member combines 16 bits of flags and a 16-bit reference count. Originally, the e1 and e2 branch each lay out the whole four bytes, the first as bit fields, leaving the reference count as a 16-bit DontUse field, the second as 16-bit integers, collecting the flags as ShortFlags. Version 5.2 from Windows Server 2003 SP1 switched the flags and reference count, and changed many details of the construction.

Offset (x86) Offset (x64) Definition Versions
0x0C (5.0 to 6.3);
0x14
0x18 (late 5.2 to 6.3);
0x20
MMPFNENTRY e1;
5.0 to early 5.2
struct {
    /*  see below: Structure with Entry 1  */
};
late 5.2 and higher
0x0C (5.0 to 6.3);
0x14
0x18 (late 5.2 to 6.3);
0x20
struct {
    /*  see below: Entry 2 Structure  */
} e2;
5.0 and higher
0x0C (6.0) 0x18 (6.0)
struct {
    USHORT ReferenceCount;
    UCHAR ByteFlags;
    UCHAR InterlockedByteFlags;
} e3;
6.0 only
0x14 0x20
struct {
    ULONG EntireField;
} e4;
1607 and higher

The u3 union is almost certainly not original. The only known use that the earliest versions make of this space is for two 16-bit reference counts. The flags were instead in union with the PteFrame, which is here thought to be the origin of th ename MMPFNENTRY for the flags as a structure.

Structure with Entry 1

When the version 5.2 from Windows Server 2003 SP1 brought the 16-bit ReferenceCount to the front of u3, the MMPFNENTRY of ULONG bit fields lost its high 16 bits that were labelled as DontUse, and was narrowed to USHORT bit fields. Version 6.0 rearranged further into two sets of UCHAR bit fields, which the 1607 release of Windows 10 formalises as separate structures MMPFNENTRY1 and MMPFNENTRY3.

Offset (x86) Offset (x64) Definition Versions
0x0C (5.0 to 6.3);
0x14
0x18 (late 5.2 to 6.3);
0x20
USHORT ReferenceCount;
late 5.2 and higher
0x0E (5.0 to 6.3);
0x16
0x1A (late 5.2 to 6.3);
0x22
MMPFNENTRY e1;
late 5.2 to 1511
MMPFNENTRY1 e1;
1607 and higher
0x17 0x23
MMPFNENTRY3 e3;
1607 and higher

Entry 2 Structure

The e2 member is in essence a pair of 16-bit integers: the 16 bits of flags that e1 defines as bit fields; and a 16-bit reference count. The order changes and different versions put either or both of the 16-bit integers in union with another that is volatile. Starting with the 1607 release of Windows 10, the flags are lost, which leaves the e2 member as just the ReferenceCount.

Offset (x86) Offset (x64) Definition Versions
0x0C (5.0 to 6.3);
0x14
 
USHORT ShortFlags;
5.0 to early 5.2
USHORT ReferenceCount;
late 5.2 only
union {
    USHORT ReferenceCount;
    SHORT volatile VolatileReferenceCount;
};
6.0 to 6.3
USHORT ReferenceCount;
10.0 and higher
0x0E (5.0 to 6.3);
0x16
 
USHORT ReferenceCount;
5.0 to early 5.2
USHORT ShortFlags;
late 5.2 to 6.1
union {
    USHORT ShortFlags;
    USHORT volatile VolatileShortFlags;
};
6.2 to 1511

VaType Bits

Mask (x64) Definition Versions
0x0F
ULONG Unused : 4;
6.2 and higher
0xF0
ULONG VaType : 4;
6.2 to 1607
ULONG Unused2 : 4;
1703 and higher

Union with OriginalPte

Offset (x86) Offset (x64) Definition Versions
0x10 (5.2 to 6.1) 0x20 (5.2 to 6.1)
MMPTE OriginalPte;
5.2 to 6.1
0x10 (5.2 to 6.1) 0x20 (5.2 to 6.1)
LONG AweReferenceCount;
5.2 only
LONG volatile AweReferenceCount;
6.0 to 6.1

Union 4

Windows XP squeezed ULONG bit fields into what had been the PteFrame at the end of the MMPFN, relabelling the whole as EntireFrame. Versions 6.0 and 6.1 have just the structure of bit fields before version 6.2 restores access to the whole as an integral type but now with the name EntireField.

Offset (x86) Offset (x64) Definition Versions
0x14 (non-PAE);
0x18 (PAE)
0x28
ULONG_PTR EntireFrame;
5.1 to 5.2
0x14 (non-PAE);
0x18 (PAE)
0x28
struct {
    /*  bit fields, see below  */
};
5.1 and higher
0x14 (non-PAE);
0x18 (PAE)
0x28
ULONG_PTR EntireField;
6.2 and higher

The original name, PteFrame, survives through all these changes as the first bit field.

Mask (x86) Mask (x64) Definition Versions
0x03FFFFFF (5.1 to early 5.2);
0x01FFFFFF (late 5.2 to 6.2);
0x00FFFFFF
0x01FFFFFF`FFFFFFFF (late 5.2);
0x000FFFFF`FFFFFFFF (6.0 to 6.1);
0x0000000F`FFFFFFFF
ULONG_PTR PteFrame : PFN_BITS;
5.1 and higher
  0x00000030`00000000
ULONG_PTR Channel : 2;
6.2 and higher
  0x00700000`00000000 (6.0 to 6.1);
0x003FFFC0`00000000 (6.2)
ULONG_PTR Unused : 3;
6.0 to 6.1
ULONG_PTR Unused : 16;
6.2 only
  0x00000040`00000000
ULONG_PTR Unused1 : 1;
6.3 and higher
  0x00000080`00000000
ULONG_PTR Unused2 : 1;
6.3 and higher
  0x001FFF00`00000000
ULONG_PTR Unused3 : 13;
6.3 only
  0x0003FF00`00000000
ULONG_PTR Partition : 10;
10.0 and higher
  0x000C0000`00000000
ULONG_PTR Spare : 2;
10.0 and higher
  0x00100000`00000000
ULONG_PTR FileOnly : 1;
10.0 and higher
  0x00400000`00000000 (6.2);
0x00200000`00000000
ULONG_PTR PfnExists : 1;
6.2 and higher
0x02000000 (6.0 to 6.1) 0x00800000`00000000 (6.0 to 6.1)
ULONG_PTR PfnImageVerified : 1;
6.0 to 6.1
0x06000000 (6.2);
0x07000000
0x01800000`00000000 (6.2);
0x01C00000`00000000
ULONG_PTR PageIdentity : 2;
6.2 only
ULONG_PTR PageIdentity : 3;
6.3 and higher
0x04000000 (5.1 to early 5.2);
0x02000000 (late 5.2)
0x02000000`00000000 (late 5.2)
ULONG_PTR InPageError : 1;
5.1 to 5.2
0x08000000 (5.1 to early 5.2);
0x04000000 (late 5.2)
0x04000000`00000000 (late 5.2)
ULONG_PTR VerifierAllocation : 1;
5.1 to 5.2
0x10000000 (late 5.1 to early 5.2);
0x08000000 (late 5.2);
0x04000000
0x08000000`00000000 (late 5.2);
0x01000000`00000000
ULONG_PTR AweAllocation : 1;
late 5.1 and higher
0x08000000 0x02000000`00000000
ULONG_PTR PrototypePte : 1;
6.0 and higher
0x20000000 (late 5.1 to early 5.2)  
ULONG LockCharged : 1;
late 5.1 to early 5.2
0x40000000 (late 5.1 to early 5.2)  
ULONG KernelStack : 1;
late 5.1 to early 5.2
0x70000000 (late 5.2) 0x70000000`00000000 (late 5.2)
ULONG_PTR Priority : 3;
late 5.2 only
0x80000000 (5.2) 0x80000000`00000000 (late 5.2)
ULONG_PTR MustBeCached : 1;
5.2 only
0xF0000000 0xFC000000`00000000
ULONG PageColor : 4;
6.0 and higher (x86)
ULONG PageColor : 6;
6.0 and higher (x64)
0xF0000000 (early 5.1);
0x80000000 (late 5.1)
 
ULONG Reserved : 4;
early 5.1 only
ULONG Reserved : 1;
late 5.1 only

Several of the bit fields move between this union and the MMPFNENTRY structure: