mm学习笔记_03:物理页管理与PFN数据库

📅 2026/7/19 14:12:14 👁️ 阅读次数 📝 编程学习
mm学习笔记_03:物理页管理与PFN数据库

mm学习笔记_03:物理页管理与PFN数据库

基于 ReactOS ARM3 MM 源码(ntoskrnl/mm/ARM3/pfnlist.c + ntoskrnl/mm/freelist.c)


1. pfnlist.c 概述

文件位置d:\reactos\ntoskrnl\mm\ARM3\pfnlist.c

职责:管理物理页面的 PFN(Page Frame Number)数据库操作,包括页面在空闲/已清零/备用/已修改等列表间的移动、按缓存颜色分配、引用计数/共享计数管理、页面清零等。

核心数据结构——MMPFN条目(每个物理页一个):

typedefstruct_MMPFN{struct{PFN_NUMBER Flink;// 链表前向指针PFN_NUMBER WsIndex;// 工作集索引PVOID Event;// 等待事件PVOID ReadStatus;// 读取状态}u1;struct{PFN_NUMBER Blink;// 链表后向指针SHORT ShareCount;// 共享计数}u2;struct{struct{...}e1;// PageLocation, CacheAttribute, Rom 等标志struct{...}e2;// ReferenceCount, ShortFlags}u3;union{...}u4;// PteFrame, Priority 等PMMPTE PteAddress;// 指向映射到此页的 PTEMMPTE OriginalPte;// 原始 PTE 备份}MMPFN;

2. 物理页8种状态及状态转换图

miarm.h中定义:

typedefenum_MMLISTS{ZeroedPageList=0,// 已清零页面(可直接使用)FreePageList=1,// 空闲页面(内容未知)StandbyPageList=2,// 备用页面(内容有效但无引用,可直接回收)ModifiedPageList=3,// 已修改页面(需写回磁盘后才可回收)ModifiedNoWritePageList=4,// 已修改但不写回(如临时页)BadPageList=5,// 坏页(硬件故障)ActiveAndValid=6,// 活跃有效(正在使用)TransitionPage=7// 转换中(介于有效与无效之间)}MMLISTS;

状态转换图

┌──────────────┐ │ ZeroedPage │◄──────── 零页线程清零 │ (已清零空闲) │ └──────┬───────┘ │ MiRemoveZeroPage() ▼ ┌──────────────┐ ┌─────►│ ActiveAndValid │◄──── MiResolveDemandZeroFault │ │ (活跃有效) │◄──── MiResolvePageFileFault │ └───────┬───────┘◄──── MiResolveTransitionFault │ │ │ │ 页面被替换/释放 │ ▼ │ ┌──────────────┐ │ │ TransitionPage│──► 如果共享计数为0,进入对应列表 │ │ (转换中) │ │ └──────┬───────┘ │ │ │ ┌───────┴────────┬──────────┐ │ ▼ ▼ ▼ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │StandbyPage│ │ModifiedPage│ │ModNoWrite│ │ │ (备用) │ │ (已修改) │ │ (不改写) │ │ └─────┬────┘ └─────┬────┘ └──────────┘ │ │ │ │ │ 写回磁盘 │ │ ▼ │ │ ┌──────────┐ │ │ │ Zeroed │◄─────┘ │ │ 零页 │ │ └────┬─────┘ │ │ 清零 │ ▼ │ ┌──────────┐ └──│ FreePage │ │ (空闲) │ └──────────┘

关键规律

  • 可用页面总和= Free + Zeroed + Standby(即MmAvailablePages
  • ActiveAndValid的页面引用计数 > 0,共享计数 > 0
  • Transition页面保留了物理内容,PTE 被标记为 Transition(位 11 = 1, 位 0 = 0)
  • BadPage从系统初始化时标记,永不回收使用

3. 关键函数分析

3.1 MiRemoveAnyPage——优先空闲页

文件:pfnlist.c:477

功能:从物理页列表中取出任意一页,优先取空闲页(FreePageList),其次取已清零页(ZeroedPageList)。

搜索顺序

  1. 先尝试从MmFreePagesByColor[FreePageList][Color].Flink取(按颜色空闲页)
  2. 若空,尝试MmFreePagesByColor[ZeroedPageList][Color].Flink(按颜色零页)
  3. 若空,尝试MmFreePageListHead.Flink(全局空闲链表)
  4. 若空,尝试MmZeroedPageListHead.Flink(全局零页链表)
PFN_NUMBER NTAPIMiRemoveAnyPage(IN ULONG Color){// 1. 先取对应颜色的空闲页PageIndex=MmFreePagesByColor[FreePageList][Color].Flink;if(PageIndex==LIST_HEAD){// 2. 取对应颜色的零页PageIndex=MmFreePagesByColor[ZeroedPageList][Color].Flink;if(PageIndex==LIST_HEAD){// 3. 取全局空闲链表PageIndex=MmFreePageListHead.Flink;if(PageIndex==LIST_HEAD){// 4. 取全局零页链表PageIndex=MmZeroedPageListHead.Flink;}}}// 从对应颜色列表中移除PageIndex=MiRemovePageByColor(PageIndex,Color);returnPageIndex;}

3.2 MiRemoveZeroPage——优先零页

文件:pfnlist.c:537

功能优先取已清零页,仅在零页为空时回退取空闲页并手动清零。

PFN_NUMBER NTAPIMiRemoveZeroPage(IN ULONG Color){BOOLEAN Zero=FALSE;// 1. 取颜色零页PageIndex=MmFreePagesByColor[ZeroedPageList][Color].Flink;if(PageIndex==LIST_HEAD){// 2. 取全局零页链表PageIndex=MmZeroedPageListHead.Flink;if(PageIndex==LIST_HEAD){Zero=TRUE;// 需要手动清零// 3. 取颜色空闲页PageIndex=MmFreePagesByColor[FreePageList][Color].Flink;if(PageIndex==LIST_HEAD){// 4. 取全局空闲链表PageIndex=MmFreePageListHead.Flink;}}}PageIndex=MiRemovePageByColor(PageIndex,Color);if(Zero)MiZeroPhysicalPage(PageIndex);// 手动清零returnPageIndex;}

3.3 MiInsertPageInFreeList

文件:pfnlist.c:611

功能:将已释放的物理页插回空闲链表尾部,同时更新颜色列表。当空闲列表达到 8 页时唤醒零页线程MmZeroingPageEvent

VOID NTAPIMiInsertPageInFreeList(IN PFN_NUMBER PageFrameIndex){// 设置链表指针(插到尾部)LastPage=ListHead->Blink;Pfn1->u1.Flink=LIST_HEAD;Pfn1->u2.Blink=LastPage;// 设置状态Pfn1->u3.e1.PageLocation=FreePageList;Pfn1->u4.Priority=3;// 更新颜色列表Color=PageFrameIndex&MmSecondaryColorMask;ColorTable=&MmFreePagesByColor[FreePageList][Color];// ...插入到颜色链表尾部// 递增可用页计数MiIncrementAvailablePages();// 如果空闲页 >= 8,唤醒零页线程if(ListHead->Total>=8)KeSetEvent(&MmZeroingPageEvent,IO_NO_INCREMENT,FALSE);}

3.4 MiInsertPageInList

文件:pfnlist.c:779

功能:通用页面插入函数,根据ListHeadListName决定插入策略:

  • ZeroedPageList:插入到链表头部(最新清零的页在最前面)
  • StandbyPageList:按优先级插入到MmStandbyPageListByPriority[Priority]
  • ModifiedPageList:插入到颜色子链表,更新MmTotalPagesForPagingFile
  • ModifiedNoWritePageList:尚未实现(ASSERT(FALSE)

4. 颜色分配算法(Cache Color)

作用:避免 CPU 缓存颠簸(Cache Thrashing),将物理页按其在 CPU 缓存中的位置分组,优先分配相同颜色的页面给同一进程。

核心思想:物理地址的某些低位(MmSecondaryColorMask)决定了该页在 CPU 缓存中的映射位置。当同一进程的连续虚拟页映射到不同颜色的物理页时,可分散缓存压力。

关键宏

// 获取页面颜色(低 N 位)#defineMI_GET_PAGE_COLOR(x)((x)&MmSecondaryColorMask)// 获取下一个系统颜色(轮转)#defineMI_GET_NEXT_COLOR()(InterlockedIncrement(&MmSystemPageColor)&MmSecondaryColorMask)// 获取下一个进程颜色#defineMI_GET_NEXT_PROCESS_COLOR(p)((p)->NextPageColor&MmSecondaryColorMask)

颜色数据结构

typedefstruct_MMCOLOR_TABLES{PFN_NUMBER Flink;// 该颜色的第一个页PVOID Blink;// 该颜色的最后一个页ULONG Count;// 该颜色的页数量}MMCOLOR_TABLES;// 对每种列表(Free/Zeroed)和每种颜色都有一个链表头MMCOLOR_TABLES MmFreePagesByColor[2][MmSecondaryColors];

颜色分配流程

MiResolveDemandZeroFault() → Color = MI_GET_NEXT_PROCESS_COLOR(Process) → MiRemoveZeroPage(Color) // 优先取对应颜色的零页 或 MiRemoveAnyPage(Color) // 优先取对应颜色的空闲页

5. MiInitializePfn 初始化逻辑

文件:pfnlist.c:970

功能:当物理页被分配后,初始化其 PFN 条目。

VOID NTAPIMiInitializePfn(IN PFN_NUMBER PageFrameIndex,IN PMMPTE PointerPte,IN BOOLEAN Modified){Pfn1=MI_PFN_ELEMENT(PageFrameIndex);Pfn1->PteAddress=PointerPte;// 保存映射此页的 PTE 地址if(PointerPte->u.Hard.Valid==1){// 来自 MmCreateProcessAddressSpace:设为 Demand Zero PTEMI_MAKE_SOFTWARE_PTE(&Pfn1->OriginalPte,MM_READWRITE);}else{Pfn1->OriginalPte=*PointerPte;// 保存原始 PTE 内容}Pfn1->u3.e2.ReferenceCount=1;// 初始引用计数 = 1Pfn1->u2.ShareCount=1;// 初始共享计数 = 1Pfn1->u3.e1.PageLocation=ActiveAndValid;// 标记为活跃有效Pfn1->u3.e1.Modified=Modified;// 脏位// 获取页表 PFN,递增其共享计数PointerPtePte=MiAddressToPte(PointerPte);PageFrameIndex=PFN_FROM_PTE(PointerPtePte);Pfn1->u4.PteFrame=PageFrameIndex;MI_PFN_ELEMENT(PageFrameIndex)->u2.ShareCount++;}

6. 引用计数管理

6.1 MiDecrementShareCount

文件:pfnlist.c:1141

功能:递减 PFN 的共享计数(ShareCount)。当共享计数降至 0 时:

  1. 若为原型 PTE,将原型 PTE 标记为 Transition
  2. 将页面状态设为TransitionPage
  3. 若引用计数为 1(最后一引用),检查MI_IS_PFN_DELETED
    • 已标记删除 → 直接插入空闲链表
    • 未标记删除 → 调用MiDecrementReferenceCount
VOID NTAPIMiDecrementShareCount(IN PMMPFN Pfn1,IN PFN_NUMBER PageFrameIndex){ASSERT(Pfn1->u2.ShareCount>0);if(!--Pfn1->u2.ShareCount){// ShareCount 降为 0if(Pfn1->u3.e1.PrototypePte){// 将原型 PTE 标记为 TransitionTempPte.u.Soft.Transition=1;MI_WRITE_INVALID_PTE(PointerPte,TempPte);}Pfn1->u3.e1.PageLocation=TransitionPage;if(Pfn1->u3.e2.ReferenceCount==1){if(MI_IS_PFN_DELETED(Pfn1)){MiInsertPageInFreeList(PageFrameIndex);}else{MiDecrementReferenceCount(Pfn1,PageFrameIndex);}}else{InterlockedDecrement16(&Pfn1->u3.e2.ReferenceCount);}}}

6.2 MiDecrementReferenceCount

文件:pfnlist.c:1236

功能:递减 PFN 的引用计数(ReferenceCount)。当降至 0 时:

  1. 检查是否标记删除 → 是则插入空闲链表
  2. 否则根据Modified位决定插入修改列表(MmModifiedPageListHead)或备用列表(MmStandbyPageListHead
VOID NTAPIMiDecrementReferenceCount(IN PMMPFN Pfn1,IN PFN_NUMBER PageFrameIndex){InterlockedDecrement16(&Pfn1->u3.e2.ReferenceCount);if(Pfn1->u3.e2.ReferenceCount)return;// 引用计数为 0if(MI_IS_PFN_DELETED(Pfn1)){MiInsertPageInFreeList(PageFrameIndex);}else{if(Pfn1->u3.e1.Modified==1)MiInsertPageInList(&MmModifiedPageListHead,PageFrameIndex);elseMiInsertStandbyListAtFront(PageFrameIndex);}}

引用计数与共享计数的区别

计数含义典型值
ReferenceCountPFN 当前被引用的次数(I/O、锁等)释放时降为 0
ShareCount映射到此物理页的 PTE 数量多个进程共享时 > 1

7. 全局统计变量(freelist.c)

文件:d:\reactos\ntoskrnl\mm\freelist.c

// PFN 数据库基址——所有 PFN 条目的数组PMMPFN MmPfnDatabase;// 当前可用物理页数(Free + Zeroed + Standby)PFN_NUMBER MmAvailablePages;// 系统启动时可用的物理页数PFN_NUMBER MmResidentAvailablePages;// 总已提交页面数(commit charge)SIZE_T MmTotalCommittedPages;// 各分类的提交计数SIZE_T MmSharedCommit;// 共享内存提交SIZE_T MmDriverCommit;// 驱动提交SIZE_T MmProcessCommit;// 进程提交SIZE_T MmPagedPoolCommit;// 分页池提交// 峰值提交计数SIZE_T MmPeakCommitment;// 最大提交限制SIZE_T MmtotalCommitLimitMaximum;

MiIncrementAvailablePages/MiDecrementAvailablePagespfnlist.c:69-118):

维护MmAvailablePages的增减,同时检查低/高内存阈值:

  • MmAvailablePages == MmLowMemoryThreshold→ 触发低内存事件
  • MmAvailablePages == MmHighMemoryThreshold→ 触发高内存事件
  • MmAvailablePages < MmMinimumFreePages→ 调用MmRebalanceMemoryConsumers()回收内存

8. LRU 用户页管理(旧版 MM)

文件:d:\reactos\ntoskrnl\mm\freelist.c

旧版 MM 通过双向链表维护用户物理页的 LRU(Least Recently Used)顺序:

PMMPFN FirstUserLRUPfn;// LRU 链表头(最早使用)PMMPFN LastUserLRUPfn;// LRU 链表尾(最近使用)

PFN 条目中的 LRU 指针:

Pfn->PreviousLRU// 前一个用户页Pfn->NextLRU// 下一个用户页

关键函数

  • MmGetLRUFirstUserPage()— 取 LRU 链表第一页并引用
  • MmGetLRUNextUserPage(PreviousPage, MoveToLast)— 取下一页,若MoveToLast为真则将当前页移到链表尾部(避免频繁换出早期映射的 DLL 如 ntdll)
  • MmInsertLRULastUserPage()— 将页插入 LRU 链表尾部
  • MmRemoveLRUUserPage()— 从 LRU 链表中移除

用途:内存平衡器(balance.c)通过 LRU 链表遍历用户页面,选择换出候选页。


附录:Pfnlist.c 函数一览

函数行号功能
MiZeroPhysicalPage122通过超空间映射清零一个物理页
MiUnlinkFreeOrZeroedPage137从空闲/零页链表中拆下一个 PFN(按颜色)
MiUnlinkPageFromList265从备用/修改链表中拆下一个 PFN
MiRemovePageByColor364按颜色从空闲/零页链表中移除一个 PFN
MiRemoveAnyPage477取任意一页(优先空闲)
MiRemoveZeroPage537取零页(优先已清零)
MiInsertPageInFreeList611插入空闲链表
MiInsertStandbyListAtFront718插入备用链表头部
MiInsertPageInList779通用页面插入
MiInitializePfn970初始化 PFN 条目
MiDecrementShareCount1141递减共享计数
MiDecrementReferenceCount1236递减引用计数
MiInitializePfnForOtherProcess1301为其他进程初始化 PFN