内存管理(4)页面错误异常处理&物理内存管理&其他组件

这篇是Mm的最后一篇~18号po上来就一直设为私有状态,没时间修正...

忙得都是一些恶心事情,比如说恶心的物理实验。。就是在不知原理的情况下拿着些古怪的仪器去测出一堆即使自己瞎编也不会被发现的实现数据,然后老师签字,附带大篇幅手抄的预习报告和数据处理还有课后习题,一套流程下来需要10~15个小时,真装B啊~~~

额,这学期开始的时候非常有兴趣将内核一些关键组件详细的研究一下,一直最搞不明白的就是内存管理和缓存管理的细节,所以记录详细点,其他的东西(仅指基本组件)理解还可以。。以备忘的形式慢慢po上来~

页面交换



首先是PTE的类型

X86硬件PTE

V位如果为1,转译正常进行,V位如果为0 硬件将发生页面错误 交由系统处理


typedef struct _MMPTE_HARDWARE {
ULONG Valid : 1;
#if defined(NT_UP)
ULONG Write : 1;       // UP version
#else
ULONG Writable : 1;    // changed for MP version
#endif
ULONG Owner : 1;
ULONG WriteThrough : 1;
ULONG CacheDisable : 1;
ULONG Accessed : 1;
ULONG Dirty : 1;
ULONG LargePage : 1;
ULONG Global : 1;
ULONG CopyOnWrite : 1; // software field
ULONG Prototype : 1;   // software field
#if defined(NT_UP)
ULONG reserved : 1;    // software field
#else
ULONG Write : 1;       // software field - MP change
#endif
ULONG PageFrameNumber : 20;
} MMPTE_HARDWARE, *PMMPTE_HARDWARE;

以上是PTE有效时的定义 也就是CPU直接寻到物理地址时的定义

无效PTE 有四种  在辅存中、要求0页面、页面转移中、未知

1.在辅存中

typedef struct _MMPTE_SOFTWARE {
ULONG Valid : 1;
ULONG PageFileLow : 4;        用于索引在哪个页面文件中,页面文件的结构MMPAGING_FILE 存储在一个MmPagingFile的数组中 一共可以支持16个页面文件
ULONG Protection : 5;
ULONG Prototype : 1;
ULONG Transition : 1;
ULONG PageFileHigh : 20;    在页面文件中的偏移
} MMPTE_SOFTWARE;

2.要求0页面
跟第一种是一类,但有待分配成全为0的页面

3.页面转移中,位于物理页面中,存在一个PFN 并且正准备转出到辅存(注意:这里指一个物理页面进入备份链表,没有真正进行IO)

typedef struct _MMPTE_TRANSITION {
ULONG Valid : 1;
ULONG Write : 1;
ULONG Owner : 1;
ULONG WriteThrough : 1;
ULONG CacheDisable : 1;
ULONG Protection : 5;
ULONG Prototype : 1;
ULONG Transition : 1;
ULONG PageFrameNumber : 20;
} MMPTE_TRANSITION;

4.最后一种全为0 需要检查VAD 这可能是一个尚未递交的页面

原型PTE

在SEGMENT中有原型PTE的阵列,原型PTE也并不用来参与转译,有无效的PTE指向,作为一种内存管理手段(性质类似于系统PTE区)

当进程第一次引用到原型PTE区域的映射时,内存管理器利用原型PTE填充有效的物理PTE

下面是指向原型PTE的无效PTE的结构定义

typedef struct _MMPTE_PROTOTYPE {
ULONG Valid : 1;
ULONG ProtoAddressLow : 7;
ULONG ReadOnly : 1;  // if set allow read only access.
ULONG WhichPool : 1;                再哪个换页内存池中
ULONG Prototype : 1;
ULONG ProtoAddressHigh : 21;        原型PTE所在偏移
} MMPTE_PROTOTYPE;

原型PTE有多种状态

1.    有效 同硬件PTE
2.    位于页面文件 同第一种无效PTE
3.    位于映射文件中12~31位 是映射文件中的偏移
4.    转移中 同无效的第三种情况
5.    修改但不写出 格式同无效的第三种情况

PTE的完整类型定义是

typedef struct _MMPTE {
union  {
ULONG Long;
HARDWARE_PTE Flush;
MMPTE_HARDWARE Hard;
MMPTE_PROTOTYPE Proto;
MMPTE_SOFTWARE Soft;
MMPTE_TRANSITION Trans;
MMPTE_SUBSECTION Subsect;
MMPTE_LIST List;                系统PTE区用的链表
} u;
} MMPTE;

页面错误处理


Kitrap0E 处理页面错误

MmAccessFault主要用于错误类型的判断,只要不是有效的硬件PTE,CPU都会抛出一个异常,到这里来进行判断的页面错误因素较多。

大体上,错误类型如下:

——无效PTE 
1.    在磁盘的页面文件上,需要通过IO操作换回物理内存中
2.    在内存中,正准备转移,还在内存中
3.    尚未提交的页面,访问为例
4.    要求0页面

——User mode访问内核地址

——写一个只读页面

——守护页面(啥意思?)

——执行不可执行的区域

——Copy-on-write

MmAccessFault的逻辑:

先处理APC_LEVEL以上的irql错误 因为这个时候不允许换页 直接报错

相对于系统范围内的内存:


1.如果是用户模式,直接报错

2.检查PDE,更新进程页目录与系统进程的系统空间部分相一致

3.检查PTE,处理不一致问题,如果PTE有效,写的话检查写权限,无权限则bug check

PTE无效 可能是原型PTE也可能是其他的无效PTE类型

相对于用户模式的内存:

1.PDE检查 如无效,0 PDE将转化成需求0的PDE

2.PTE检查 如果PTE有效 写操作 检查是否是写时复制

3.如果是需求0页面 申请一个0页面

4.0 PTE :检查进程的VAD,如果存在,利用VAD构造PTE信息

5.原型PTE或者其他无效PTE

MiDispatchFault 用来真正处理已经识别出的问题(写时复制和要求0页面的情况则有专人负责):

1.原型PTE,检查内存地址相连续的一系列内存,如果原型PTE还在内存中(有效或者正在转移)就把原本的PTE设置好,返回

2.针对正在转移,从转移的链表中移除,设置PTE有效。

2.针对需求0,申请一个内存页,设置PFN,设置PTE

3.针对辅存中的页面,相连续的页面都会被换进来,只是填充好参数,设置申请的物理页面为正在转移,设置PTE为正在转移,但并不真正发起IO

关于辅存页面的读取,页面文件的组织,详情可见这篇文章http://blog.csdn.net/freexploit/archive/2005/01/31/275480.aspx《浅议Windows 2000/XP Pagefile组织管理》,作者是WebCrazy~ (在这个博客上有WebCrazy一系列的有关于WIN内存管理的文章~非常值得一读~) 

4.原型PTE 找到指向的PTE 根据不同情况作出处理,更新原来的PTE


5.最后如果需要换页,则调用IoPageRead 处理PFN等内容,最后更新PTE


物理内存管理

Pfn数据库         
是一个数组,记录了物理内存的使用状态。是MmPfnDatabase,页帧编号是索引

根据六种不同的状态,MmPfnDatabase的一项内容也被解释为不同的信息

活动状态 MMPFN定义如下


typedef struct _MMPFN {
union {
WSLE_NUMBER WsIndex;  活动页面处在一个工作集中,为进程或者系统所用的物理页面,这个是信息索引。如果是多进程引用的页面,只是第一个进程的工作集索引
} u1;
PMMPTE PteAddress;                指向PTE所在虚拟地址
union {
ULONG_PTR ShareCount;    对应的PTE的数量 只要大于0就不会被移除
} u2;
union {
struct {
USHORT ReferenceCount;    第一次加入工作集合IO锁定后引用+1,sharecount-1或者IO解锁一次 引用-1
MMPFNENTRY e1;            所有PFN共享的一个结构 描述物理页面的状态,详见下文
};
} u3;
MMPTE OriginalPte;
union {
ULONG_PTR EntireFrame;
struct {
ULONG_PTR PteFrame: 25; 帧编号和标志位
ULONG_PTR InPageError : 1;
ULONG_PTR VerifierAllocation : 1;
ULONG_PTR AweAllocation : 1;
ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
ULONG_PTR MustBeCached : 1;
};
} u4;
} MMPFN, *PMMPFN;

typedef struct _MMPFNENTRY {
USHORT Modified : 1;        是否已经修改 如果修改,移除这一页之前要先写入辅存
USHORT ReadInProgress : 1;    正在读
USHORT WriteInProgress : 1;    正在写
USHORT PrototypePte: 1;        所引用的PTE是一个原型PTE
USHORT PageColor : 4;        颜色
USHORT PageLocation : 3;    MMLISTS 的一个枚举值 指明在那个链表中
USHORT RemovalRequested : 1;    
USHORT CacheAttribute : 2;
USHORT Rom : 1;
USHORT ParityError : 1;
} MMPFNENTRY;

备用状态 页面曾属于原来的工作集,现在从工作集中移除,但页面数据仍然有效,PTE仍指向页面,但标记为正在转移的无效PTE,将来可以被系统回收作他用或者被弄回原来的工作集。
已修改状态 跟备用状态类似,唯一不同是页面已被写脏,被回收之前需要写入页面文件

typedef struct _MMPFN {
union {
PFN_NUMBER Flink;
} u1;
PMMPTE PteAddress;
union {
PFN_NUMBER Blink;
} u2;
union {
struct {
USHORT ReferenceCount;
MMPFNENTRY e1;
};
} u3;
union {
MMPTE OriginalPte;
};
union {
ULONG_PTR EntireFrame;
struct {
ULONG_PTR PteFrame: 25;
ULONG_PTR InPageError : 1;
ULONG_PTR VerifierAllocation : 1;
ULONG_PTR AweAllocation : 1;
ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
ULONG_PTR MustBeCached : 1;
};
} u4;

} MMPFN, *PMMPFN;

修改但不写出 
和已经修改类似,但是不会写出

转移状态
这个页面正在进行IO,(无效PTE的转移状态是转移到备用链表和已经修改链表)
typedef struct _MMPFN {
union {
PKEVENT Event;    2选一 IO在进行中,这是一个事件,IO完成之后将获得通知。
NTSTATUS ReadStatus;      如果IO错误,这是一个错误码
} u1;
PMMPTE PteAddress;
union {
ULONG_PTR ShareCount;
} u2;
union {
struct {
USHORT ReferenceCount;
MMPFNENTRY e1;
};
} u3;
union {
MMPTE OriginalPte;
LONG AweReferenceCount;
};
union {
ULONG_PTR EntireFrame;
struct {
ULONG_PTR PteFrame: 25;
ULONG_PTR InPageError : 1;
ULONG_PTR VerifierAllocation : 1;
ULONG_PTR AweAllocation : 1;
ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
ULONG_PTR MustBeCached : 1;
};
} u4;

} MMPFN, *PMMPFN;

空闲&0化状态

不属于任何工作集,空闲的包含了未清除的数据,系统会定期进行清0


typedef struct _MMPFN {
union {
PFN_NUMBER Flink;
} u1;
PMMPTE PteAddress;
union {
PFN_NUMBER Blink;
} u2;
union {
struct {
USHORT ReferenceCount;
MMPFNENTRY e1;
};
} u3;
union {
MMPTE OriginalPte;
LONG AweReferenceCount;
};
union {
ULONG_PTR EntireFrame;
struct {
ULONG_PTR PteFrame: 25;
ULONG_PTR InPageError : 1;
ULONG_PTR VerifierAllocation : 1;
ULONG_PTR AweAllocation : 1;
ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
ULONG_PTR MustBeCached : 1;
};
} u4;

} MMPFN, *PMMPFN;

坏状态
就是产生硬件错误的页面,系统不会再用了。

Win把物理页面分门别类,归在不同的链表中


typedef enum _MMLISTS {
ZeroedPageList,
FreePageList,
StandbyPageList,  //this list and before make up available pages.
ModifiedPageList,
ModifiedNoWritePageList,
BadPageList,
ActiveAndValid,
TransitionPage
} MMLISTS;

typedef struct _MMPFNLIST {
PFN_NUMBER Total;
MMLISTS ListName;
PFN_NUMBER Flink;
PFN_NUMBER Blink;
} MMPFNLIST;

接下来是物理页面在各个链表里面的转移

由页面错误引起

如果发现无效PTE的状态是转移中,就从备用或者已修改链表中移入工作集

如果需要页面文件IO,从空闲/0化/备用三个链表中找到合适的页面,加入工作集之中

如果需要0页面,从0化或者空闲再不行从备用中找到页面并0化,加入工作集
  

0页面线程 :定期或者受到事件通知去0化一些空闲页面

修改页面写出器 :将修改的页面转至备用页面,IO


工作集管理器 修剪或者一段内存的删除有可能导致备用的页面移向空闲链表。

对于链表操作的管理


1.    备用链表 MmStandbyPageListByPriority 是存放备用页面的链表 有八个优先级

2 对于零化页面(空闲页面类似),MmZeroedPageListHead 是存放这些页面的链表,另外系统还维护一个表MmFreePagesByColor

typedef struct _MMCOLOR_TABLES {
PFN_NUMBER Flink;
PVOID Blink;
PFN_NUMBER Count;
} MMCOLOR_TABLES, *PMMCOLOR_TABLES;

MMPFNENTRY 中有四位代表颜色,表示的是页面在处理器中的缓存位置,颜色可以理解为缓存的索引号。

extern PMMCOLOR_TABLES MmFreePagesByColor[2]; 第一个MmFreePagesByColor用于零化 第二个用于空闲,每个表有64种颜色MmFreePagesByColor[0][25]

中的25代表一种颜色

3 修改链表 

有对应的页面文件(OriginalPTE 的prototype为0 非原型PTE)MmModifiedPageListByColor 对应的是一个页面文件,通过颜色来管理。其余放在MmModifiedPageListHead 中,这些页面对应的是一个映射文件。

4.修改但不写出,换页面 都是单独链表


MDL
MDL应该是系统提供的另外一种使用物理内存的方法。MDL描述一组物理页面,PFN值就在MDL之后。

修改该页面写出器
两个线程 一个负责写页面文件。 另一个负责写映射文件。对于页面文件,在修改页面过多或者定期事件中被唤醒

进程/内核栈切换器

负责换入换出进程的相关资源和内核栈,每次换出一个cpu的等待线程的内核栈页面;换出进程页面之前要确保工作集已经修剪到最小(仅保留四个最基本的页面),

工作集管理器

工作集用来描述一个进程使用的物理页面总的集合,系统计算每个页面的年龄已决定是否需要修剪,是控制页面换出的关键。

系统为每个进程工作集分配单独的页面并且存放于链表中

PMMWSL MmWorkingSetList存放了每一个进程的工作集的页面(包括系统工作集?感觉这个应该是系统工作集结构啊,每个进程自己的工作集不是应该在VmSupport->VmWorkingSetList
中么?应该是进程固定地址上的啊)


typedef struct _MMWSL {
WSLE_NUMBER FirstFree;                第一个空闲项
WSLE_NUMBER FirstDynamic;            指向第一个可以被修剪的页面
WSLE_NUMBER LastEntry;                最后一项
WSLE_NUMBER NextSlot;               // The next slot to trim
PMMWSLE Wsle;                        一个数组,每个数组描述一个有效地页面,进程内存空间的每一个页面?
WSLE_NUMBER LastInitializedWsle;
WSLE_NUMBER NonDirectCount;
PMMWSLE_HASH HashTable;            散列表
ULONG HashTableSize;
ULONG NumberOfCommittedPageTables;
PVOID HashTableStart;
PVOID HighestPermittedHashAddress;
ULONG NumberOfImageWaiters;
ULONG VadBitMapHint;

USHORT UsedPageTableEntries[MM_USER_PAGE_TABLE_PAGES];

ULONG CommittedPageTables[MM_USER_PAGE_TABLE_PAGES/(sizeof(ULONG)*8)];

} MMWSL, *PMMWSL;


typedef struct _MMWSLENTRY {
ULONG_PTR Valid : 1;
ULONG_PTR LockedInWs : 1;
ULONG_PTR LockedInMemory : 1;

ULONG_PTR Protection : 5;

ULONG_PTR Hashed : 1;
ULONG_PTR Direct : 1;
ULONG_PTR Age : 2;
ULONG_PTR VirtualPageNumber : MM_VIRTUAL_PAGE_SIZE;  页面的虚拟地址所在
} MMWSLENTRY;

typedef struct _MMWSLE {
union {
PVOID VirtualAddress;
ULONG_PTR Long;
MMWSLENTRY e1;
} u1;
} MMWSLE;

EPORCESS 中的vm定义了MMSUPPORT的结构描述的工作集的一些信息


typedef struct _MMSUPPORT {
LIST_ENTRY WorkingSetExpansionLinks; 允许修剪的进程将会挂入一个链表MmWorkingSetExpansionHead

LARGE_INTEGER LastTrimTime;

MMSUPPORT_FLAGS Flags;
ULONG PageFaultCount;
WSLE_NUMBER PeakWorkingSetSize;
WSLE_NUMBER GrowthSinceLastEstimate;

WSLE_NUMBER MinimumWorkingSetSize;
WSLE_NUMBER MaximumWorkingSetSize;
struct _MMWSL *VmWorkingSetList;
WSLE_NUMBER Claim;                    修剪优先级

WSLE_NUMBER NextEstimationSlot;
WSLE_NUMBER NextAgingSlot;
WSLE_NUMBER EstimatedAvailable;
WSLE_NUMBER WorkingSetSize;

EX_PUSH_LOCK WorkingSetMutex;

} MMSUPPORT, *PMMSUPPORT;

似乎EPROCESS中有一个PFN_NUMBER WorkingSetPage;


MmWorkingSetManager 是工作集管理的主函数,每一秒或者内存资源紧缺的时候调用,修剪算法略~

你可能感兴趣的:(内核研究)