MM 学习笔记 07:池分配器 (Pool Allocator)

发布时间:2026/9/12 7:43:37

MM 学习笔记 07:池分配器 (Pool Allocator) MM 学习笔记 07池分配器 (Pool Allocator)文件ntoskrnl/mm/ARM3/expool.cntoskrnl/mm/ARM3/pool.c日期2026-07-191. 概述池分配器是内核态的堆管理器提供分页池PagedPool和非分页池NonPagedPool两类内存分配。两个核心文件分工如下文件行数职责expool.c~3100 行Executive 池分配器 —ExAllocatePoolWithTag/ExFreePoolWithTag、标签跟踪系统、热标签缓存、大页表管理、配额定级pool.c~1200 行底层页面管理 —MiAllocatePoolPages/MiFreePoolPages、非分页池初始化、已释放池页面保护分配层次结构ExAllocatePoolWithTag (expool.c) │ ▼ POOL_DESCRIPTOR 空闲列表查找 (expool.c) │ ▼ MiAllocatePoolPages (pool.c) ← 底层页面分配 │ ▼ MiRemoveAnyPage / MiRemoveZeroPage (pfnlist.c) ← PFN 数据库2. POOL_DESCRIPTOR 结构typedefstruct_POOL_DESCRIPTOR{POOL_TYPE PoolType;// 池类型分页/非分页ULONG PoolIndex;// 池索引ULONG RunningAllocs;// 累计分配计数ULONG RunningDeAllocs;// 累计释放计数ULONG TotalPages;// 总页数ULONG TotalBigPages;// 大页面数ULONG Threshold;// 阈值决定何时切换到大页面分配PVOID LockAddress;// 锁地址PVOID PendingFrees;// 待释放链表LONG PendingFreeDepth;// 待释放深度SIZE_T TotalBytes;// 总字节数SIZE_T Spare0;LIST_ENTRY ListHeads[POOL_LISTS_PER_PAGE];// 空闲列表头256个}POOL_DESCRIPTOR,*PPOOL_DESCRIPTOR;// miarm.h 中的定义#definePOOL_LISTS_PER_PAGE(PAGE_SIZE/sizeof(LIST_ENTRY))// 32位: 4096/8 512 个桶 (但只用 256 个)// 64位: 4096/16 256 个桶关键理解ListHeads包含256 个空闲列表头每个对应一个固定大小的块范围桶索引计算NumberOfBytes POOL_BLOCK_SHIFTPOOL_BLOCK_SHIFT 4即 16 字节粒度当请求大小 PoolDesc-Threshold时走大页分配路径3. POOL_HEADER — 池头部每个分配块前有一个 8 字节头部用于链表管理和完整性检查typedefstruct_POOL_HEADER{union{struct{#ifdef_WIN64USHORT PreviousSize:8;// 前一个块的大小POOL_BLOCK_SIZE 为单位USHORT PoolIndex:8;// 池索引USHORT BlockSize:8;// 当前块的大小USHORT PoolType:8;// 池类型#elseUSHORT PreviousSize:9;USHORT PoolIndex:7;USHORT BlockSize:9;USHORT PoolType:7;#endifULONG PoolTag;// 4字节标签};ULONGLONG Reserved;};}POOL_HEADER,*PPOOL_HEADER;布局示意32位PreviousSize:9 | PoolIndex:7 | BlockSize:9 | PoolType:7PoolTag:32相关宏#definePOOL_ENTRY(x)(PPOOL_HEADER)((ULONG_PTR)(x)-sizeof(POOL_HEADER))#definePOOL_BLOCK(x,i)(PPOOL_HEADER)((ULONG_PTR)(x)((i)*POOL_BLOCK_SIZE))#definePOOL_NEXT_BLOCK(x)POOL_BLOCK((x),(x)-BlockSize)#definePOOL_PREV_BLOCK(x)POOL_BLOCK((x),-((x)-PreviousSize))4. 全局变量// expool.c - 核心全局变量POOL_DESCRIPTOR NonPagedPoolDescriptor;// 非分页池描述符全局唯一PPOOL_DESCRIPTOR ExpPagedPoolDescriptor[161];// 分页池描述符每CPU或每节点PPOOL_DESCRIPTOR PoolVector[2];// [0]分页池, [1]非分页池KSPIN_LOCK ExpTaggedPoolLock;// 标签跟踪表锁KSPIN_LOCK ExpLargePoolTableLock;// 大页表锁PPOOL_TRACKER_TABLE PoolTrackTable;// 池标签跟踪哈希表PPOOL_TRACKER_BIG_PAGES PoolBigPageTable;// 大页跟踪表ULONG PoolHitTag;// 热标签命中统计ULONG ExpPoolFlags;// 调试标志特殊池、验证器等// pool.c - 池页面管理全局变量LIST_ENTRY MmNonPagedPoolFreeListHead[MI_MAX_FREE_PAGE_LISTS];// 4个空闲页链表PFN_COUNT MmNumberOfFreeNonPagedPool;// 空闲非分页池页数SLIST_HEADER MiNonPagedPoolSListHead;// 非分页池 SListLookasideSLIST_HEADER MiPagedPoolSListHead;// 分页池 SListBOOLEAN MmProtectFreedNonPagedPool;// 释放池页面保护开关5. ExAllocatePoolWithTag 完整分配路径PVOID NTAPIExAllocatePoolWithTag(IN POOL_TYPE PoolType,IN SIZE_T NumberOfBytes,IN ULONG Tag)函数位置expool.c:19045.1 分配流程图ExAllocatePoolWithTag │ ├─ 1. 检查特殊池/验证器标志 │ └─ ExpPoolFlags POOL_FLAG_SPECIAL_POOL → MmAllocateSpecialPool │ ├─ 2. 获取池描述符 │ └─ PoolDesc PoolVector[PoolType BASE_POOL_TYPE_MASK] │ ├─ 3. 检查 Lookaside 缓存快速路径 │ └─ 如果命中直接返回无需遍历空闲列表 │ ├─ 4. 计算桶索引 │ └─ i NumberOfBytes POOL_BLOCK_SHIFT (4) │ ├─ 5. 遍历空闲列表从 i 开始向上搜索 │ │ │ ├─ 找到足够大的块 → 从链表中移除 │ │ │ ├─ 碎片分割Split │ │ └─ 如果剩余大小 ≥ 最小块大小16字节 │ │ 将剩余部分作为自由块插回链表 │ │ │ └─ 未找到 → 调用 MiAllocatePoolPages 获取新页面 │ ├─ 6. 大页分配路径 │ └─ NumberOfBytes POOL_MAX_ALLOC (约 PAGE_SIZE/8) │ → MiAllocatePoolPages(OriginalType, NumberOfBytes) │ └─ 7. 更新标签跟踪表 └─ ExpInsertPoolTrackerTag → 记录到哈希表5.2 Lookaside 缓存优化Lookaside 是每个 CPU 的快速分配缓存使用 SList单向链表// 初始化InitializeSListHead(MiPagedPoolSListHead);InitializeSListHead(MiNonPagedPoolSListHead);// 默认最大值MiNonPagedPoolSListMaximum4;// 非分页池最多缓存4个条目MiPagedPoolSListMaximum8;// 分页池最多缓存8个条目// 在内存较大的系统上可以扩大if(MmNumberOfPhysicalPages(2*_1GB)/PAGE_SIZE){MiNonPagedPoolSListMaximum*8;MiPagedPoolSListMaximum*8;}5.3 碎片分割示例假设分配 48 字节3 × 16从桶索引 3 开始搜索找到的块大小为 80 字节5 × 16[分配前] |←──── 80 字节 (5 块) ────→| [分配后] |← 48 →|←── 32 (剩余) ──→| ↑分配返回 ↑插回空闲列表6. ExFreePoolWithTag 释放路径VOID NTAPIExFreePoolWithTag(IN PVOID P,IN ULONG TagToFree)函数位置expool.c:24946.1 释放流程图ExFreePoolWithTag │ ├─ 1. 检查特殊池 │ └─ MmIsSpecialPoolAddress → MmFreeSpecialPool │ ├─ 2. 检查大页释放 │ └─ PAGE_ALIGN(P) P → 大页释放路径 │ ├─ ExpFindAndRemoveTagBigPages 查找标签和页数 │ └─ MiFreePoolPages 释放页面 │ ├─ 3. 小/中块释放正常路径 │ │ │ ├─ 获取 POOL_HEADER验证标签和完整性 │ │ │ ├─ 尝试前向合并Forward Combine │ │ └─ 检查下一个块是否是空闲块 │ │ 如果是合并两个块 │ │ │ ├─ 尝试后向合并Backward Combine │ │ └─ 检查上一个块是否是空闲块 │ │ 如果是合并两个块 │ │ │ └─ 将合并后的块插入空闲列表 │ ├─ 4. 尝试释放整页 │ └─ 如果合并后的块占满整个页面 │ 从空闲列表移除 → MiFreePoolPages │ └─ 5. 更新标签跟踪表 └─ ExpRemovePoolTrackerTag6.2 合并示例// 合并前假设块 B 刚被释放[块 A:已分配][块 B:刚刚释放][块 C:已分配]↓// 尝试前向合并检查块 C 是否空闲 → 否// 尝试后向合并检查块 A 是否空闲 → 否// 结果块 B 插入空闲列表// 合并前块 A 和块 B 都是空闲[块 A:空闲][块 B:刚刚释放][块 C:已分配]↓// 前向合并检查块 C → 否// 后向合并检查块 A → 是空闲// 合并后[块 AB: 更大的空闲块] [块 C: 已分配]7. 标签跟踪系统池标签跟踪是一个哈希表用于跟踪每个标签的分配统计信息typedefstruct_POOL_TRACKER_TABLE{ULONG Key;// 标签4字节ASCIIULONG NonPagedAllocs;// 非分页分配次数ULONG NonPagedFrees;// 非分页释放次数SIZE_T NonPagedBytes;// 非分页使用字节数ULONG PagedAllocs;// 分页分配次数ULONG PagedFrees;// 分页释放次数SIZE_T PagedBytes;// 分页使用字节数}POOL_TRACKER_TABLE,*PPOOL_TRACKER_TABLE;大页跟踪表类似typedefstruct_POOL_TRACKER_BIG_PAGES{PVOID Va;// 虚拟地址ULONG Key;// 标签ULONG Pages;// 页数}POOL_TRACKER_BIG_PAGES,*PPOOL_TRACKER_BIG_PAGES;查找逻辑// 计算哈希Hash(Tag4)PoolTrackTableMask;// 线性探测while(PoolTrackTable[Hash].Key!Tag){if(HashPoolTrackTableMask)Hash0;}8. 热标签缓存ExpSeedHotTagsExpSeedHotTags在池系统初始化时预填充跟踪表将常用标签预先固定到哈希表的特定位置提高查找效率。VOID NTAPIExpSeedHotTags(VOID){ULONG i,Key,Hash,Index;PPOOL_TRACKER_TABLE TrackTablePoolTrackTable;ULONG TagList[]{ oI, laH,PldM,LooP,tSbO, prI,bdDN,LprI,pOoI, ldM,eliF,aVMC,dSeS,CFtN,looP,rPCT,bNMC,dTeS,sFtN,TPCT,CPCT, yeK,qSbO,mNoI,aEoI,cPCT,aFtN,0ftN,tceS,SprI,ekoT, eS,lCbO,cScC,lFtN,cAeS,mfSF,kWcC,miSF,CdfA,EdfA,orSF,nftN,PRIU,rFpN,RFpN,aPeS,sUeS,FpcA,MpcA,cSeS,mNbO,sFpN,uLeS,DPcS,nevE,vrqR,ldaV, pP,SdaV, daV,LdaV,FdaV, GIB,};// 遍历所有 64 个常用标签for(i0;i64;i){KeyTagList[i];Hash(Key4)PoolTrackTableMask;// 在线性探测过程中插入...}}这些标签涵盖内核中最频繁分配的标签如ldaVVAD、 oI、 laH等。9. MiAllocatePoolPages 底层页面分配// pool.c - 从非分页池预分配页面PVOID NTAPIMiAllocatePoolPages(IN POOL_TYPE PoolType,IN SIZE_T NumberOfBytes)位置pool.c功能检查是否是分页池调用 → 转发到分页池分配器非分页池从MmNonPagedPoolFreeListHead空闲列表中获取页面支持 4 个空闲列表减少多核竞争按颜色分配分配成功后将新页面分割成适当的块插入 POOL_DESCRIPTOR 的空闲列表更新配额统计// 非分页池空闲列表4个减少锁定争用for(i0;iMI_MAX_FREE_PAGE_LISTS;i){InitializeListHead(MmNonPagedPoolFreeListHead[i]);}10. MiFreePoolPages 底层页面释放将整页池内存归还给系统从 POOL_DESCRIPTOR 空闲列表中移除该页上的所有空闲块将页面插入MmNonPagedPoolFreeListHead空闲列表如果启用了MmProtectFreedNonPagedPool对页面进行保护更新MmNumberOfFreeNonPagedPool计数11. 非分页池初始化流程CODE_SEG(INIT)VOID NTAPIMiInitializeNonPagedPool(VOID)位置pool.c:278初始化步骤MiInitializeNonPagedPool │ ├─ 1. 初始化 SListLookaside 缓存 │ └─ 根据物理内存大小调整最大缓存数 │ ├─ 2. 初始化 4 个空闲列表头 │ ├─ 3. 计算初始池大小 │ └─ PoolPages BYTES_TO_PAGES(MmSizeOfNonPagedPoolInBytes) │ ├─ 4. 创建第一个空闲条目 │ └─ FreeEntry-Size PoolPages │ FreeEntry-Owner FirstEntry │ ├─ 5. 插入到空闲列表 │ └─ InsertHeadList(MmNonPagedPoolFreeListHead[MI_MAX_FREE_PAGE_LISTS-1], ...) │ └─ 6. 初始化阈值和事件 └─ MiInitializeNonPagedPoolThresholds() └─ MiInitializePoolEvents()阈值初始化// 低阈值8MB 或非分页池大小的 1/3MiLowNonPagedPoolThreshold(8*_1MB)PAGE_SHIFT;MiLowNonPagedPoolThresholdmin(MiLowNonPagedPoolThreshold,Size/3);// 高阈值20MB 或非分页池大小的 50%MiHighNonPagedPoolThreshold(20*_1MB)PAGE_SHIFT;MiHighNonPagedPoolThresholdmin(MiHighNonPagedPoolThreshold,Size/2);12. 已释放池页面保护机制当MmProtectFreedNonPagedPool TRUE时启用用于检测 Use-After-Free 错误。VOID NTAPIMiProtectFreeNonPagedPool(IN PVOID VirtualAddress,IN ULONG PageCount){PMMPTE PointerPte,LastPte;MMPTE TempPte;// 遍历页面的所有 PTEPointerPteMiAddressToPte(VirtualAddress);LastPtePointerPtePageCount;do{TempPte*PointerPte;// 标记为无效 PTE设置原型位以识别为池页面TempPte.u.Hard.Valid0;TempPte.u.Soft.Prototype1;MI_WRITE_INVALID_PTE(PointerPte,TempPte);}while(PointerPteLastPte);// 刷新 TLBKeFlushEntireTb(TRUE,TRUE);}对应的解除保护函数MiUnProtectFreeNonPagedPool在需要访问被保护的页面时调用。保护下的链表操作// 插入前临时解除保护MiProtectedPoolUnProtectLinks(ListHead,PoolFlink,PoolBlink);// 执行链表操作InsertHeadList(ListHead,Entry);// 重新保护MiProtectedPoolProtectLinks(PoolFlink,PoolBlink);13. 状态图ExAllocatePoolWithTag │ ▼ ┌─────────────────┐ │ 特殊池检查 │───启用──→ MmAllocateSpecialPool └────────┬────────┘ │ 禁用 ▼ ┌─────────────────┐ │ Lookaside 缓存 │───命中──→ 直接返回 └────────┬────────┘ │ 未命中 ▼ ┌─────────────────┐ │ 计算桶索引 │ └────────┬────────┘ ▼ ┌─────────────────┐ │ 遍历空闲列表 │ └────────┬────────┘ │ │ 找到块 未找到 │ │ ▼ ▼ ┌─────────┐ ┌──────────────┐ │ 碎片分割 │ │ MiAllocate │ │ │ │ PoolPages │ └────┬─────┘ └──────┬───────┘ │ │ ▼ ▼ ┌──────────────────────────┐ │ 更新标签跟踪表 │ │ ExpInsertPoolTrackerTag│ └──────────────────────────┘参考链接[miarm.h 中 POOL_HEADER 和 POOL_DESCRIPTOR 定义](file:///d:/reactos/ntoskrnl/mm/ARM3/miarm.h)[expool.c — Executive 池分配器主文件](file:///d:/reactos/ntoskrnl/mm/ARM3/expool.c)[pool.c — 池页面管理](file:///d:/reactos/ntoskrnl/mm/ARM3/pool.c)[special.c — 特殊池和 Driver Verifier](file:///d:/reactos/ntoskrnl/mm/ARM3/special.c)[Windows Internals 7th Part 1, Chapter 10 “Memory Management” — Pool]
延伸阅读

更多相关文章

2026/9/12 7:42:47

基于HarmonyOS的AI春联对联生成——从对齐到评估的全流程技术实践

基于HarmonyOS的AI春联对联生成——从对齐到评估的全流程技术实践 一、项目背景与需求分析(Align) 1.1 场景痛点分析 在现代数字生活中,用户对春联对联生成的需求日益增长。传统的春联对联生成方式存在效率低下、个性化不足等问题。通过AI技术…

2026/8/31 9:59:45

Linear Loops功能详解:简化循环工程操作与自动化工作流

Linear 最近发布了 Loops 功能,这是一个专门为简化循环工程操作而设计的新工具。如果你经常需要处理重复性任务、批量操作或者需要自动化的工作流程,Loops 可能会成为你的新利器。这次我们重点看看它的核心功能、使用门槛、实际效果以及如何快速上手。Lo…

2026/9/12 7:40:05

AI元认知:从技术奇点到伦理困境

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 7:40:05

深入RP2040看门狗:时钟、计数器与寄存器全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 7:40:05

如何用 Bazel 从源码构建 MongoDB 的 mongod 并验证二进制可运行

如何用 Bazel 从源码构建 MongoDB 的 mongod 并验证二进制可运行 【免费下载链接】mongo The MongoDB Database 项目地址: https://gitcode.com/GitHub_Trending/mo/mongo 如果你的目标是把 MongoDB 源码编译出自己的 mongod 数据库服务器(而不是下载预编译包…

2026/9/12 2:05:33

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/12 3:55:12

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/12 6:37:43

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码