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

发布时间:2026/11/5 20:48:41

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/11/5 20:48:14

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

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

2026/11/5 15:19:43

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

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

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

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

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

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

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

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

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

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

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

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