MM 学习笔记 09:进程会话与工作集
文件:
ntoskrnl/mm/ARM3/procsup.c+ntoskrnl/mm/ARM3/session.c+ntoskrnl/mm/ARM3/wslist.cpp+ntoskrnl/mm/balance.c日期:2026-07-19
1. 概述
这一组文件管理三个紧密相关的子系统:
| 文件 | 行数 | 职责 |
|---|---|---|
procsup.c |
~1600 行 | 进程地址空间管理:PEB/TEB、内核栈、AWE |
session.c |
~1100 行 | Session 生命周期:创建/删除/附加/分离 |
wslist.cpp |
~800 行 | 工作集列表管理(唯一 C++ 文件) |
balance.c |
~460 行 | 内存平衡器:后台线程页面回收 |
2. procsup.c --- 进程地址空间管理
位置: ntoskrnl/mm/ARM3/procsup.c
2.1 MmCreateProcessAddressSpace
c
NTSTATUS
NTAPI
MmCreateProcessAddressSpace(IN ULONG MinWs,
IN PEPROCESS Process,
OUT PULONG_PTR DirectoryTableBase)
位置: procsup.c:1161
创建新进程的地址空间,分配页目录、超空间和工作集页面:
MmCreateProcessAddressSpace
│
├─ 1. 生成进程颜色(用于缓存优化)
│ └─ Process->NextPageColor = RtlRandom(&MmProcessColorSeed)
│
├─ 2. 初始化超空间锁
│
├─ 3. 分配关键页面(锁定 PFN 数据库)
│ │
│ ├─ TableBaseIndex → 页目录(Page Directory)
│ │ └─ 使用 MiRemoveZeroPageSafe 优先获取零页
│ │
│ ├─ HyperIndex → 超空间页(Hyperspace)
│ │ └─ 用于临时映射物理页
│ │
│ └─ WsListIndex → 工作集列表页(Working Set List)
│ └─ 存储 WSL(Working Set List)
│
├─ 4. 设置基地址
│ ├─ Process->WorkingSetPage = WsListIndex
│ └─ DirectoryTableBase[0/1] 赋值
│
├─ 5. 架构相关初始化
│ └─ MiArchCreateProcessAddressSpace(Process, DirectoryTableBase)
│
└─ 6. 插入进程链表
└─ InsertTailList(&MmProcessList, &Process->MmProcessLinks)
2.2 MmDeleteProcessAddressSpace
c
VOID
NTAPI
MmDeleteProcessAddressSpace(IN PEPROCESS Process)
位置: procsup.c:1357
释放进程地址空间:
MmDeleteProcessAddressSpace
│
├─ 1. 从进程链表移除
│ └─ RemoveEntryList(&Process->MmProcessLinks)
│
├─ 2. 从工作集扩展链表移除
│ └─ RemoveEntryList(&Process->Vm.WorkingSetExpansionLinks)
│
├─ 3. 释放工作集页面
│ ├─ MiGetPfnEntry(Process->WorkingSetPage)
│ ├─ MI_SET_PFN_DELETED
│ └─ MiDecrementShareCount → 两次(PTE 和页面)
│
├─ 4. 释放超空间页面
│ └─ 同样标记 PFN_DELETED 并减少引用
│
├─ 5. 释放页目录
│ └─ PageFrameIndex = Process->Pcb.DirectoryTableBase[0] >> PAGE_SHIFT
│ MI_SET_PFN_DELETED
│ MiDecrementShareCount × 2(自映射 PDE)
│
├─ 6. 释放 Session 引用
│ └─ MiReleaseProcessReferenceToSessionDataPage
│
└─ 7. 清零目录基地址
└─ Process->Pcb.DirectoryTableBase[0/1] = 0
2.3 MmCreatePeb / MmCreateTeb
c
NTSTATUS
NTAPI
MmCreatePeb(IN PEPROCESS Process, OUT PVOID * Peb)
位置: procsup.c:518
c
NTSTATUS
NTAPI
MmCreateTeb(IN PEPROCESS Process, OUT PVOID * Teb)
位置: procsup.c:753
这两个函数类似,都通过 MiCreatePebOrTeb 实现:
MiCreatePebOrTeb(Process, Size, &BaseAddress)
│
├─ 1. 分配配额(PsChargeProcessNonPagedPoolQuota)
│
├─ 2. 分配 VAD(MMVAD_LONG)
│ └─ 标签 'ldaV'
│
├─ 3. 设置 VAD 属性
│ ├─ CommitCharge = BYTES_TO_PAGES(Size)
│ ├─ MemCommit = TRUE
│ ├─ PrivateMemory = TRUE
│ ├─ Protection = MM_READWRITE
│ ├─ NoChange = TRUE
│ └─ VadFlags2.OneSecured = TRUE (防误删)
│
├─ 4. 计算基地址
│ ├─ PEB: 随机选择 MM_HIGHEST_VAD_ADDRESS 之上的一个页面
│ ├─ TEB: 在用户栈附近
│ └─ 使用 MiInsertVadEx 插入 VAD 树
│
└─ 5. 提交页面
└─ MmCommitPageMemory → 分配物理页
PEB vs TEB 的关键差异:
| 特性 | PEB | TEB |
|---|---|---|
| 大小 | sizeof(PEB) |
sizeof(TEB) |
| 位置 | MM_HIGHEST_VAD_ADDRESS 之上的随机地址 |
最高 VAD 地址范围内 |
| 保护 | OneSecured + ReadOnly |
OneSecured |
| 用途 | 进程信息(堆、模块、LDR) | 线程信息(异常处理、TLS) |
2.4 MmCreateKernelStack / MmDeleteKernelStack
c
NTSTATUS
NTAPI
MmCreateKernelStack(IN BOOLEAN GuiStack,
IN ULONG_PTR StackSize,
OUT PVOID *StackBase)
位置: procsup.c:276
c
VOID
NTAPI
MmDeleteKernelStack(IN PVOID StackBase, IN BOOLEAN GuiStack)
位置: procsup.c:187
内核栈管理:
-
每个线程有一个内核栈
-
GUI 线程(Win32k)需要更大的栈
-
栈页面标记为
NoAccess的守卫页用于检测栈溢出MmCreateKernelStack:
├─ 分配连续虚拟地址空间(通过 MiAllocateVad)
├─ 提交物理页面(部分提交)
├─ 设置栈顶的守卫页(MM_GUARDPAGE)
└─ 返回栈基地址
2.5 AWE 系统调用
AWE(Address Windowing Extensions)允许 32 位应用程序访问超过 4GB 的物理内存:
c
NTSTATUS
NTAPI
NtAllocateUserPhysicalPages(IN HANDLE ProcessHandle,
IN OUT PULONG_PTR NumberOfPages,
OUT PULONG_PTR UserPfnArray)
位置: procsup.c:1434
当前实现是存根(stub)状态,尚未完全实现:
NtAllocateUserPhysicalPages
→ DPRINT1("UNIMPLEMENTED\n")
→ return STATUS_NOT_IMPLEMENTED
NtMapUserPhysicalPages
→ DPRINT1("UNIMPLEMENTED\n")
→ return STATUS_NOT_IMPLEMENTED
NtAllocateUserPhysicalPagesEx
→ 同状态
3. session.c --- Session 管理
位置: ntoskrnl/mm/ARM3/session.c
Session 是 Windows 终端服务/多会话支持的核心概念。每个 Session 包含一组进程、一个独立的窗口站和桌面。
3.1 Session 空间地址范围
在 x86 32 位系统中:
Session 空间: 0xA0000000 - 0xC0000000 (512MB)
↓
页表覆盖整个 Session 区域
3.2 核心全局变量
c
PMM_SESSION_SPACE MmSessionSpace; // 当前 Session 空间指针
PFN_NUMBER MiSessionDataPages; // Session 数据页数
PFN_NUMBER MiSessionTagPages; // Session 标签页数
PFN_NUMBER MiSessionCreateCharge; // 创建 Session 的配额
KGUARDED_MUTEX MiSessionIdMutex; // Session ID 位图锁
PRTL_BITMAP MiSessionIdBitmap; // Session ID 位图
volatile LONG MiSessionLeaderExists; // Session 领导者存在标志
LIST_ENTRY MiSessionWsList; // Session 工作集链表
3.3 MmSessionCreate
c
NTSTATUS
NTAPI
MmSessionCreate(OUT PULONG SessionId)
位置: session.c:827
MmSessionCreate
│
├─ 1. 检查进程是否已在 Session 中
│
├─ 2. 设置 Session Leader
│ └─ 原子操作 InterlockedCompareExchange(&MiSessionLeaderExists, 1, 0)
│
├─ 3. 调用 MiSessionCreateInternal 创建 Session
│ │
│ ├─ 设置 PSF_SESSION_CREATION_UNDERWAY_BIT 标志
│ ├─ 分配页表(x86: 512MB 空间的页表)
│ ├─ 从位图分配 Session ID
│ ├─ 保留系统 PTE
│ ├─ 分配数据页(零页)
│ ├─ 分配标签页
│ ├─ 设置 PDE 映射
│ └─ 初始化 Session 空间
│
├─ 4. 初始化 Session 工作集
│ └─ MiSessionInitializeWorkingSetList()
│
└─ 5. 设置完成标志
└─ MmSessionSpace->u.Flags.Initialized = 1
3.4 MmSessionDelete
c
NTSTATUS
NTAPI
MmSessionDelete(IN ULONG SessionId)
位置: session.c:887
3.5 MmAttachSession / MmDetachSession
c
VOID
NTAPI
MmAttachSession(IN PEPROCESS Process, IN PVOID SessionObject)
位置: session.c:917
c
VOID
NTAPI
MmDetachSession(IN PEPROCESS Process)
位置: session.c:985
MmAttachSession:
├─ 增加引用计数
├─ 附加进程到 Session 空间
├─ 设置 Process->Session 指针
├─ 插入 Session 进程链表
└─ 激活 Session 空间(切换 PDE)
MmDetachSession:
├─ 减少引用计数
├─ 从 Session 进程链表移除
├─ 如果引用为 0 → MiDereferenceSessionFinal
│ ├─ 设置 DeletePending 标志
│ ├─ 等待所有进程分离
│ └─ 释放资源
└─ 清除 Process->Session 指针
3.6 Session ID 位图管理
c
void NTAPI MiInitializeSessionIds(VOID)
{
// 计算需要的数据页数
MiSessionDataPages = ROUND_TO_PAGES(sizeof(MM_SESSION_SPACE)) >> PAGE_SHIFT;
// 标签页大小(池标签跟踪)
MiSessionTagSizePages = 2;
MiSessionBigPoolPages = 1;
MiSessionTagPages = MiSessionTagSizePages + MiSessionBigPoolPages;
// 创建 Session 的总配额
MiSessionCreateCharge = 1 + MiSessionDataPages + MiSessionTagPages;
// 初始化位图(初始 64 个 Session ID)
MiSessionIdBitmap = ExAllocatePoolWithTag(PagedPool,
sizeof(RTL_BITMAP) + BitmapSize,
TAG_MM);
RtlInitializeBitMap(MiSessionIdBitmap, (PVOID)(MiSessionIdBitmap + 1), Size);
RtlClearAllBits(MiSessionIdBitmap);
}
3.7 Session 生命周期图
┌────────────┐
│ 系统启动 │
└─────┬──────┘
│
▼
┌──────────────────┐
│ MiInitialize │
│ SessionIds │ ← 初始化 ID 位图
└──────┬───────────┘
│
▼
┌──────────────────┐
│ MmSessionCreate │ ← 创建 Session
│ │ │
│ ├─ 分配 Session │
│ │ ID 和数据页 │
│ ├─ 设置 PDE │
│ └─ 初始化工作集 │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ MmAttachSession │ ← 进程附加到 Session
│ └─ 增加引用计数 │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ MmDetachSession │ ← 进程分离
│ └─ 减少引用计数 │
└──────┬───────────┘
│
▼ (引用计数 = 0)
┌──────────────────────┐
│ MiDereference │ ← 最终清理
│ SessionFinal │
│ ├─ 设置 Delete │
│ │ Pending │
│ ├─ 等待分离完成 │
│ └─ 释放所有资源 │
└──────────────────────┘
4. wslist.cpp --- 工作集列表管理
位置: ntoskrnl/mm/ARM3/wslist.cpp
注意:这是 MM 子系统中唯一的 C++ 文件,使用了 C++ 特性(引用传递等)。
4.1 核心数据结构
c
PMMWSL MmWorkingSetList; // 系统工作集列表
PMMWSL MmSystemCacheWorkingSetList; // 系统缓存工作集列表
KEVENT MmWorkingSetManagerEvent; // 工作集管理器事件
工作集列表条目(WSLE):
c
typedef struct _MMWSLE {
union {
struct {
ULONG_PTR Valid : 1; // 有效位
ULONG_PTR Locked : 1; // 锁定位
ULONG_PTR Cache : 1; // 缓存位
ULONG_PTR Protection : 5; // 保护
ULONG_PTR VirtualPageIndex : 56; // 虚拟页索引
} e1;
struct {
ULONG_PTR NextFree : 28; // 下一个空闲条目
ULONG_PTR PreviousFree : 28; // 上一个空闲条目
ULONG_PTR MustBeZero : 8;
} Free;
LONGLONG Long;
} u1;
} MMWSLE, *PMMWSLE;
4.2 MiInitializeWorkingSetList
c
NTSTATUS
NTAPI
MiInitializeWorkingSetList(PMMWSL WsList, ULONG_PTR WsListPage)
初始化工作集列表:
- 映射工作集列表页面
- 设置 ListHead、LastEntry、FirstFree 等字段
- 初始化分配位图
4.3 MiInsertInWorkingSetList
c
ULONG_PTR
NTAPI
MiInsertInWorkingSetList(PMMWSL WsList, PMMWSLE Wsle)
在 WSL 中插入一个新的工作集条目:
- 从空闲链表获取一个槽位
- 写入 WSLE 数据
- 更新链表指针
4.4 MiRemoveFromWorkingSetList
c
VOID
NTAPI
MiRemoveFromWorkingSetList(PMMWSL WsList, ULONG_PTR Index)
从 WSL 中移除一个条目:
- 标记为无效
- 插入空闲链表
- 如果移除的是最后一个条目,缩小列表
c
static void FreeWsleIndex(PMMWSL WsList, ULONG Index)
{
PMMWSLE Wsle = WsList->Wsle;
ULONG& LastEntry = WsList->LastEntry;
ULONG& FirstFree = WsList->FirstFree;
ULONG& LastInitializedWsle = WsList->LastInitializedWsle;
Wsle[Index].u1.Long = 0; // 擦除条目
if (Index == (LastEntry - 1))
{
// 释放最后一个条目,缩小列表
while (Wsle[Index].u1.e1.Valid == 0) Index--;
LastEntry = Index + 1;
// 收缩已分配的内存
while ((LastInitializedWsle - LastEntry) > (PAGE_SIZE / sizeof(MMWSLE)))
{
// 释放一页
PFN_NUMBER Page = PFN_FROM_PTE(PointerPte);
ntoskrnl::MiPfnLockGuard PfnLock;
PMMPFN Pfn = MiGetPfnEntry(Page);
MI_SET_PFN_DELETED(Pfn);
MiDecrementShareCount(...);
}
}
}
4.5 MmWorkingSetManager 工作集管理器
c
NTSTATUS
NTAPI
MmWorkingSetManager(ULONG_PTR Parameter)
工作集管理器负责在内存压力下修剪工作集:
MmWorkingSetManager
│
├─ 等待事件(MmWorkingSetManagerEvent)
│
├─ 遍历所有进程的工作集
│
├─ 计算需要修剪的页面数
│
├─ 修剪(Trim)
│ └─ 从 WSL 中移除页面,放回备用列表
│
└─ 重复
4.6 TrimWsList 工作集修剪
修剪算法的核心:遍历工作集列表,将最近未访问的页面换出。
TrimWsList(WsList, Target)
│
├─ 遍历 WSLE 条目
│
├─ 检查 Accessed 位
│ ├─ 已访问 → 清除 Accessed 位,跳过
│ └─ 未访问 → 准备换出
│
├─ 锁定页面(防止正在使用)
│
├─ 为页面分配页文件槽位
│
├─ 写入页面内容到交换文件
│
├─ 从工作集移除
│ └─ MiRemoveFromWorkingSetList
│
└─ 更新统计
5. balance.c --- 内存平衡器
位置: ntoskrnl/mm/balance.c
5.1 核心数据结构
c
MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM]; // 消费者数组
static ULONG MiMinimumAvailablePages = 256; // 最小可用页数
static ULONG MiMinimumPagesPerRun = 256; // 每轮最少回收页数
static KEVENT MiBalancerEvent; // 平衡器事件
static KEVENT MiBalancerDoneEvent; // 完成事件
static KTIMER MiBalancerTimer; // 定时器
// 消费者类型
#define MC_USER 0 // 用户进程
#define MC_MAXIMUM 1
5.2 内存消费者
c
typedef struct _MM_MEMORY_CONSUMER {
ULONG PagesUsed; // 已使用页数
ULONG PagesTarget; // 目标页数
NTSTATUS (*Trim)(ULONG Target, ULONG Priority, PULONG NrFreed); // 修剪回调
ULONG MinimumPages; // 最小页数
} MM_MEMORY_CONSUMER;
初始化:
c
CODE_SEG("INIT")
VOID NTAPI MmInitializeBalancer(ULONG NrAvailablePages, ULONG NrSystemPages)
{
memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
MiMinimumAvailablePages = 256;
MiMinimumPagesPerRun = 256;
MiMemoryConsumers[MC_USER].PagesTarget = NrAvailablePages / 2;
}
5.3 MiBalancerThread 平衡器线程主循环
c
MiBalancerThread(PVOID Unused)
{
PVOID WaitObjects[2];
WaitObjects[0] = &MiBalancerEvent; // 手动触发
WaitObjects[1] = &MiBalancerTimer; // 定时触发
while (TRUE)
{
KeSetEvent(&MiBalancerDoneEvent, IO_NO_INCREMENT, FALSE);
// 等待事件或定时器
KeWaitForMultipleObjects(2, WaitObjects, ...);
// 计算需要回收的页面数
Target = 256;
// 遍历消费者
MiTrimMemoryConsumer(MC_USER, Target);
}
}
5.4 MmTrimUserMemory 用户内存修剪
c
NTSTATUS
MmTrimUserMemory(ULONG Target, ULONG Priority, PULONG NrFreedPages)
两种模式:
-
Priority = 0:仅清除 Accessed 位(不实际换出)
-
Priority = 1 :实际换出页面(调用
MmPageOutPhysicalAddress)MmTrimUserMemory
│
├─ Priority = 0 (低优先级)
│ └─ 遍历用户页面的反向映射
│ └─ Pte->u.Hard.Accessed = 0 (清除访问位)
│
└─ Priority = 1 (高优先级)
└─ 调用 MmPageOutPhysicalAddress 换出页面
├─ 获取反向映射条目
├─ 锁定地址空间
├─ 写入交换文件
└─ 释放物理页面
5.5 MmRebalanceMemoryConsumers 重新平衡
c
VOID
NTAPI
MmRebalanceMemoryConsumers(VOID)
{
// 如果平衡器不在运行,触发它
if (InterlockedCompareExchange(&PageOutThreadActive, 1, 0) == 0)
{
KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
}
}
同步版本:
c
VOID
NTAPI
MmRebalanceMemoryConsumersAndWait(VOID)
{
KeResetEvent(&MiBalancerDoneEvent);
MmRebalanceMemoryConsumers();
KeWaitForSingleObject(&MiBalancerDoneEvent, ...);
}
5.6 平衡器流程图
┌──────────────────────────┐
│ 系统初始化 │
│ MmInitializeBalancer │
│ └─ 设置目标页数 │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ 创建平衡器线程 │
│ PsCreateSystemThread │
│ → MiBalancerThread │
└──────────┬───────────────┘
│
▼
┌──────────┐
│ 等待事件 │ ←──────┐
│ (定时/触发)│ │
└─────┬────┘ │
│ │
▼ │
┌──────────┐ │
│ 计算目标 │ │
│ Target=256│ │
└─────┬────┘ │
│ │
▼ │
┌──────────┐ │
│ MiTrim │ │
│ Consumer │ │
└─────┬────┘ │
│ │
▼ │
┌──────────┐ │
│ 设置完成 │─────────┘
│ 事件 │
└──────────┘
6. 三个子系统的关系
┌─────────────────────────────────────────────────────┐
│ balance.c │
│ MiBalancerThread │
│ │ │
│ MmRebalanceMemoryConsumers │
│ │ │
└────────────────────┼─────────────────────────────────┘
│ 触发
▼
┌─────────────────────────────────────────────────────┐
│ wslist.cpp │
│ MmWorkingSetManager │
│ │ │
│ Trim → MiRemoveFromWorkingSetList │
│ │ │
└────────────────────┼─────────────────────────────────┘
│ 页面回收
▼
┌─────────────────────────────────────────────────────┐
│ procsup.c │
│ MmCreateProcessAddressSpace │
│ MmCreatePeb / MmCreateTeb │
│ MmCreateKernelStack │
│ │ │
│ ▼ │
│ MmDeleteProcessAddressSpace │
│ │ │
└────────────────────┼─────────────────────────────────┘
│ Session 管理
▼
┌─────────────────────────────────────────────────────┐
│ session.c │
│ MmSessionCreate → MmAttachSession │
│ MmSessionDelete → MmDetachSession │
│ MiSessionCreateInternal │
└─────────────────────────────────────────────────────┘
关键交互路径:
- 进程创建 →
MmCreateProcessAddressSpace→ 分配页目录/工作集页 - Session 创建 →
MmSessionCreate→MiSessionCreateInternal→ 分配 Session 空间 - 内存压力 →
MmRebalanceMemoryConsumers→MiBalancerThread→Trim→ 页面回收 - 工作集修剪 →
MmWorkingSetManager→TrimWsList→ 页面换出
参考链接
- procsup.c --- 进程地址空间管理(file:///d:/reactos/ntoskrnl/mm/ARM3/procsup.c)
- session.c --- Session 管理(file:///d:/reactos/ntoskrnl/mm/ARM3/session.c)
- wslist.cpp --- 工作集列表(file:///d:/reactos/ntoskrnl/mm/ARM3/wslist.cpp)
- balance.c --- 内存平衡器(file:///d:/reactos/ntoskrnl/mm/balance.c)
-
Windows Internals 7th Part 1, Chapter 10 "Memory Management"