MM 学习笔记 01:miarm.h 核心头文件分析
概述
miarm.h(ntoskrnl/mm/ARM3/miarm.h)是整个 MM 子系统的核心头文件。它零外部依赖(无 #include),定义了所有 ARM3 源文件共享的常量、宏、类型和全局变量声明。
1. 基础常量
1.1 容量常量
c
#define _1KB (1024u)
#define _1MB (1024 * _1KB)
#define _1GB (1024 * _1MB)
#define _64K (64 * _1KB)
#define PT_SIZE (PTE_PER_PAGE * sizeof(MMPTE)) // 页表大小
#define PD_SIZE (PDE_PER_PAGE * sizeof(MMPDE)) // 页目录大小
#define SYSTEM_PD_SIZE (PPE_PER_PAGE * PD_SIZE) // 进程的完整页目录大小
1.2 内存保护常量(内部保护掩码)
保护值编码为 3 位访问位 + 2 位特殊标志位:
| 宏 | 值 | 含义 |
|---|---|---|
MM_ZERO_ACCESS |
0 | 无访问(请求零页) |
MM_READONLY |
1 | 只读 |
MM_EXECUTE |
2 | 仅执行 |
MM_EXECUTE_READ |
3 | 执行+读 |
MM_READWRITE |
4 | 读写 |
MM_WRITECOPY |
5 | 写时复制 |
MM_EXECUTE_READWRITE |
6 | 执行+读写 |
MM_EXECUTE_WRITECOPY |
7 | 执行+写时复制 |
MM_NOCACHE |
0x08 | 禁止缓存 |
MM_GUARDPAGE |
0x10 | 警戒页 |
MM_WRITECOMBINE |
0x18 | 写合并 |
MM_DECOMMIT |
0x10 | 解除提交 |
MM_NOACCESS |
0x18 | 无访问 |
MM_OUTSWAPPED_KSTACK |
0x17 | 内核栈已换出 |
MM_INVALID_PROTECTION |
0xFFFFFFFF | 无效保护 |
1.3 架构相关的 PTE 标志
x86 32位:
c
#define PTE_VALID 0x1 // 有效位(存在位)
#define PTE_READWRITE 0x2 // 可写
#define PTE_PROTOTYPE 0x400 // 原型 PTE
#define PTE_ACCESSED 0x20 // 已访问
#define PTE_DIRTY 0x40 // 已修改
#define PTE_DISABLE_CACHE 0x10 // 禁用缓存
#define PTE_PROTECT_MASK 0x612 // 保护掩码
x86-64(注意高位的 NX 位):
c
#define PTE_READONLY 0x8000000000000000ULL // NX 位
#define PTE_READWRITE 0x8000000000000002ULL
#define PTE_EXECUTE_READWRITE 0x0000000000000002ULL
ARM:
c
#define PTE_READONLY 0x200
#define PTE_PROTOTYPE 0x400 // 使用共享位
2. 关键结构体(在 miarm.h 中声明,在其他文件中定义)
2.1 POOL_DESCRIPTOR --- 池描述符
定义在 miarm.h 中(非 ARM3 的其他文件也使用此结构):
c
typedef struct _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;
关键理解:
ListHeads有PAGE_SIZE / 8 = 512(32位)或PAGE_SIZE / 16 = 256(64位)个桶- 每个桶对应一个固定大小的块范围(8 或 16 字节粒度)
Threshold决定何时切换到大页面分配模式
2.2 POOL_HEADER --- 池头部
每个分配块前有 8 字节头部:
32位: PreviousSize:9 | PoolIndex:7 | BlockSize:9 | PoolType:7
64位: PreviousSize:8 | PoolIndex:8 | BlockSize:8 | PoolType:8
PreviousSize:前一个块的大小(以 POOL_BLOCK_SIZE 为单位)PoolIndex:所属池的索引BlockSize:当前块的大小(以 POOL_BLOCK_SIZE 为单位)PoolType:池类型
2.3 其余核心结构(在 ntoskrnl/include/internal/mm.h 和 sdk/include/ndk/mmtypes.h 中定义)
| 结构 | 文件 | 关键字段 |
|---|---|---|
MMPFN |
ntoskrnl/include/internal/mm.h:389 |
PteAddress, ReferenceCount, ShareCount, OriginalPte, u4.PteFrame, e1.PageLocation |
MMVAD |
sdk/include/ndk/mmtypes.h:720 |
StartingVpn, EndingVpn, VadFlags, ControlArea, FirstPrototypePte |
MMPTE |
sdk/include/ndk/*/mmtypes.h |
联合体:Hard(硬件PTE) / Proto(原型) / Soft(软件) / Trans(转换) / Subsect(子段) / List(列表) |
CONTROL_AREA |
sdk/include/ndk/mmtypes.h:519 |
Segment, FilePointer, NumberOfMappedViews, Flags |
SEGMENT |
sdk/include/ndk/mmtypes.h:407 |
ControlArea, TotalNumberOfPtes, PrototypePte, ThePtes[1] |
SUBSECTION |
sdk/include/ndk/mmtypes.h:571 |
ControlArea, SubsectionBase, PtesInSubsection, NextSubsection |
MM_AVL_TABLE |
sdk/include/ndk/mmtypes.h:660 |
BalancedRoot, DepthOfTree, NumberGenericTableElements |
MMPFNLIST |
ntoskrnl/include/internal/mm.h:460 |
Total, ListName, Flink, Blink |
MMPFNENTRY |
ntoskrnl/include/internal/mm.h:368 |
Modified, PrototypePte, PageColor(4), PageLocation(3), CacheAttribute(2) |
3. 页面列表枚举(MMLISTS)
c
typedef enum _MMLISTS
{
ZeroedPageList = 0, // 已清零页面
FreePageList = 1, // 空闲页面
StandbyPageList = 2, // 备用页面(内容有效但未引用)
ModifiedPageList = 3, // 已修改页面(需要写回)
ModifiedNoWritePageList = 4,// 已修改但不写回
BadPageList = 5, // 坏页
ActiveAndValid = 6, // 活跃有效
TransitionPage = 7 // 转换中
} MMLISTS;
物理页状态转换:
Free ── 分配 ──→ Active
Active ── 换出 ──→ Standby ── 清零 ──→ Zeroed ── 可用 ──→ Free
Active ── 修改 ──→ Modified ── 写回 ──→ Standby
Active ── 暂时释放 ──→ Transition ── 引用 ──→ Active
4. 关键宏
4.1 地址范围判断
c
MI_IS_SESSION_ADDRESS(Addr) // 是否会话空间地址
MI_IS_PAGE_TABLE_ADDRESS(Addr) // 是否页表区域地址
MI_IS_SYSTEM_PAGE_TABLE_ADDRESS // 是否系统页表区域地址
4.2 PTE 操作
c
MI_MAKE_SOFTWARE_PTE(p, x) // 创建软件 PTE
MI_SET_PFN_DELETED(x) // 标记 PFN 已删除
MI_IS_PFN_DELETED(x) // 检查 PFN 是否已删除
4.3 颜色分配
c
MI_GET_PAGE_COLOR(x) // 获取页面的缓存颜色
MI_GET_NEXT_COLOR() // 下一个系统颜色
MI_GET_NEXT_PROCESS_COLOR(x) // 下一个进程颜色
4.4 会话与系统映射
c
#define MI_IS_SESSION_IMAGE_ADDRESS(Address) ...
#define MI_SYSTEM_VIEW_BUCKET_SIZE _64K // 系统视图以64K分桶
5. 全局变量声明
5.1 PTE 模板
c
extern MMPTE HyperTemplatePte; // 超空间模板 PTE
extern MMPTE ValidKernelPte; // 有效内核 PTE 模板
extern MMPTE ValidKernelPteLocal; // 本地有效内核 PTE
extern MMPTE DemandZeroPte; // 请求零页 PTE(软件 PTE)
extern MMPTE PrototypePte; // 原型 PTE 模板
extern MMPTE MmDecommittedPte; // 解除提交 PTE
5.2 系统 PTE 范围
c
extern PMMPTE MmSystemPtesStart[MaximumPtePoolTypes]; // 各种类型 PTE 池的起始
extern PMMPTE MmSystemPtesEnd[MaximumPtePoolTypes]; // 各种类型 PTE 池的结束
extern PMMPTE MmFirstReservedMappingPte, MmLastReservedMappingPte;
extern PMMPTE MiFirstReservedZeroingPte;
5.3 会话空间
c
extern PMMPTE MiSessionImagePteStart, MiSessionImagePteEnd;
extern PMMPTE MiSessionBasePte, MiSessionLastPte;
5.4 页面列表
c
extern MMPFNLIST MmStandbyPageListByPriority[8]; // 8级优先级的备用列表
5.5 其他
c
extern MM_AVL_TABLE MmSectionBasedRoot; // 基于 Section 的 VAD 树根
extern PMMPTE MiHighestUserPte; // 最高用户 PTE
extern PMMPTE MmSharedUserDataPte; // 共享用户数据 PTE
6. 设计要点
-
零外部依赖 :
miarm.h不包含任何其他头文件,仅依赖编译器内置类型和包含它的上下文(ntoskrnl.h+debug.h)。这是一种"大头文件"设计。 -
架构隔离 :通过
_M_IX86/_M_AMD64/_M_ARM宏在编译时选择架构相关的 PTE 定义。x86-64 通过 NX 位(第63位)实现只读/可执行分离。 -
保护常量 vs 硬件 PTE :
MM_*是逻辑保护常量(与架构无关),PTE_*是硬件 PTE 标志(架构相关)。两者通过MmProtectToPteMask[32]和MmProtectToValue[32]两张表进行映射。 -
颜色分配 :通过
MI_GET_PAGE_COLOR宏支持 L2 Cache 颜色分配,减少缓存抖动。 -
自映射页表 :x86 架构通过
PTE_BASE/PDE_BASE实现页表自映射,使操作系统可以直接通过虚拟地址访问页表项,而无需物理地址转换。