能力值:
(RANK:10 )
2 楼
楼主的心得不错
能力值:
( LV9,RANK:610 )
3 楼
不错,欢迎继续~
能力值:
( LV6,RANK:90 )
4 楼
很感兴趣,继续~~
顺便请教下,0xc0000000-0xc0400000这部分虚拟空间用于页面映射表,它是用的一维数组的形式来建立整个虚拟空间的映射,但落实到物理页面上,对应的页面映射表是采用二级层次结构来完成的.问,系统怎么恰当的设置,将这个一维数组和这个二级层次结构中的每项对应起来?使得 0xc0000000正好对应二级层次结构中的二级页面表项第0项
这里没想太明白
能力值:
( LV4,RANK:50 )
5 楼
下面简单谈一谈页表的初始化,顺便也就回答了你的问题
===============================================
页表的初始化:
我们知道,在启动分页以后,CR3指向的物理地址指向页目录
如果我们已经将页目录映射到了一个确定的地址,下面的工作就不是问题了
也许你会问,我们如何将页目录映射到一个确定的地址呢?这似乎是一个先有鸡还是先有蛋的问题
其实很简单,两句话,子进程的页目录映射由父进程代劳,老祖宗进程的页目录的映射工作由实模式代劳。
对于子进程,父进程会在其hyperspace上将子进程的页表映射构造好,然后将页目录对应的物理地址写道子进程的EProcess里就一切OK了。
而对于第一个进程的页表是在启动分页前设置好的。
其实windows的做法是子进程的页表映射数据是直接从父进程中拷贝而来的,上面已经说过,所有进程的系统空间的映射除了页表和hyperspace两块区块外都是相同的,所以所要做的工作就是申请两个页面,映射到父进程的hyperspace,这样父进程就可以读写这两个页面了,其中一个页面用于保存页目录,其高半部分直接从父进程的pde的高半部分拷贝,一页用于保存hyperspace的页表项,并修改保存目录页面中的两个pte的值,一个是指向其自身的pte(pde),一个是指向hyperspace的pte。至于这两个pte在页目录页面中的偏移是容易计算的,而且是固定不变的。
其实让页目录页面中的某一项的pte值指向其自身也就完成了页目录页面本身的映射,比如要想将页目录页面映射到0xc0300000,只需将偏移0xc00除的pte指向其自身即可,计算公式:0xc0300000/1000*4+0xc0000000-0xc0300000=0xc00。
能力值:
( LV4,RANK:50 )
6 楼
例程MmCreateProcessAddressSpace 包含了windows父进程为子进程构造页表的代码,大家可以在wrk中找到。
能力值:
( LV7,RANK:100 )
7 楼
期待下文~
能力值:
( LV2,RANK:10 )
8 楼
支持LZ,受教了,LZ内存管理研究很透彻啊。
能力值:
( LV6,RANK:90 )
9 楼
多谢楼主,想明白了,PAGETABLE_MAP区域0xc0000000-0xc0400000能够正确的映射到二级层次结构的页目录表的画龙点睛之笔是将0xc0000000-0xc0400000区域对应的PDE表项(下标为 0xc0000000 / 2^22 = 0xc00)即PDE[0xc00]设置为此页目录表自身,0xc0000000-0xc0400000就映射到页目录表对应的4M物理地址空间,分别与各个pte项对应起来.
点睛之笔对应的代码就是......
PageDirectory[ADDR_TO_PTE_OFFSET(PAGETABLE_MAP)] = PFN_TO_PTE(Pfn[0])|PA_PRESENT|PA_READWRITE;
是这样吧,
能力值:
( LV4,RANK:50 )
10 楼
[QUOTE=skypismire;795884]多谢楼主,想明白了,PAGETABLE_MAP区域0xc0000000-0xc0400000能够正确的映射到二级层次结构的页目录表的画龙点睛之笔是将0xc0000000-0xc0400000区域对应的PDE表项(下标为 0xc0000000 / 2^22 = 0xc00)即PDE[0xc00]设置为...[/QUOTE]
对头,不用谢,呵呵!
能力值:
( LV3,RANK:30 )
11 楼
关于AVL 相关代码
http://www.cnblogs.com/hxf829/archive/2009/04/10/1659805.html
能力值:
( LV4,RANK:50 )
12 楼
一块内存在变得可以使用之前,需要经过以下步骤:
1)分配虚拟空间,所有程序只能读写虚拟空间
2)分配物理内存
3)建立虚拟内存和物理之间的映射
下面先说说虚拟空间的管理: 用户空间管理:
=====================================================
Windows将所有已经分配或保留的内存区块记录在一个称为avl的二叉树的结构中,该二叉树定义如下:
typedef struct _MMADDRESS_NODE {
union {
LONG_PTR Balance : 2;
struct _MMADDRESS_NODE *Parent;
} u1;
struct _MMADDRESS_NODE *LeftChild;
struct _MMADDRESS_NODE *RightChild;
ULONG_PTR StartingVpn;
ULONG_PTR EndingVpn;
} MMADDRESS_NODE, *PMMADDRESS_NODE;
使用这种管理策略管理用户空间是一个很自然的事,如果让我写一个这样的模块,可能实现的方法是一样的,就不多说了呵呵。 系统空间的管理:
=======================================================
系统空间的使用者是内核本身(当然也包含驱动程序),所以内核开发人员对系统空间的内存的需求是很清楚的,所以对系统空间的管理策略应该是针对内核的需求特点进行精心考虑的
看了wrk系统空间管理部分的源代码,不得不佩服windows开发人员的智慧
我们知道页表的元素称为pte,整个页表被映射在系统空间的0xc000000到0xc03ffffff的地方,每一页占有四个字节,其定义如下:
typedef struct _MMPTE {
union {
ULONG_PTR Long;
MMPTE_HARDWARE Hard;
MMPTE_HARDWARE_LARGEPAGE HardLarge;
HARDWARE_PTE Flush;
MMPTE_PROTOTYPE Proto;
MMPTE_SOFTWARE Soft;
MMPTE_TRANSITION Trans;
MMPTE_SUBSECTION Subsect;
MMPTE_LIST List;
} u;
} MMPTE;
我们看到windows对mmpte的定义是由多种解释的,硬件对其解释如下:
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;
其最重要的是最低位的valid位,如果对写的地址的pte的valid等于0将引发缺页硬件异常,该地址会通过cr2传给中断处理程序。
其高20位用来保存物理页框架号,还有其他位现的意义就不说了,以上的定义除了高20位和低8位之间的4位保留可以自由定义外,其它为都是由硬件定义的。
windows对pte还有另外的定义,如MMPTE_LIST List;大家知道其用途么?呵呵,这就是用来管理系统空间的。
windows将整个页表被映射在系统空间的0xc000000到0xc03ffffff的地方后,带给我们的最大的好处是什么?
对了,虚拟地址和该地址在页表中的pte的地址的相互转换变得很容易了,所以windows将系统空间的分配和释放就转换成对pte的分配和释放了,这种想想法真是太高明了,呵呵。
windows将未使用的ptes根据用途分别链入一个链表,实现方法就是pte的另一种解释:
typedef struct _MMPTE_LIST {
ULONG Valid : 1;
ULONG OneEntry : 1;
ULONG filler0 : 8;
//
// Note the Prototype bit must not be used for lists like freed nonpaged
// pool because lookaside pops can legitimately reference bogus addresses
// (since the pop is unsynchronized) and the fault handler must be able to
// distinguish lists from protos so a retry status can be returned (vs a
// fatal bugcheck).
//
ULONG Prototype : 1; // MUST BE ZERO as per above comment.
ULONG filler1 : 1;
ULONG NextEntry : 20;
} MMPTE_LIST;
对应空闲ptes的表头用一个全局变量保存,连续空闲区块的第一个pte的高20位指向下一个空闲连续区块的第一个pte,第二个pte的高20位用来保存该连续空间的大小(页数、pte个数)
也许你会问了,假如一个空闲块只有一个页面,那空闲区块的大小保存在那里呢?嘿嘿,这就是OneEntry 的作用!当OneEntry 等于1时,表示该空闲块只有1页。
windows开发人员聪明吧,他直接将管理空闲空间的链表建立在页表上面,既高效,又节省空间,而且该链表的元素还是静态分配的,高明啊,哈哈。
还有,ptes的分配是从一个空闲pte区的后面开始分配的,知道这一点可以帮助你看懂该部分源代码。
详细的请大家看wrk啦,比如非分页内存的分配例程是MiReserveAlignedSystemPtes。
非分页内存其实有两种,除了上面讨论的一种外,还有一种是在初始化时就已经分配好的,该区域已经建立了物理映射,被称为非分页池,该区域是可以直接使用的,Windows以一般“池”的方法管理该区域,类似用户空间的heap的管理,及空闲链表法,由于被管理的页面是可以直接使用的,所以空闲链表就建立在这些页面上,总共有四个空闲链表,分别记录1、2、3、4及以上个页面大小的空闲内存块,没有什么亮点,就不多说了。
除了非分页内存外,还有一种称为分页内存的空间,这些页面是可以交换到磁盘上的,所以不能保证这些页面是有物理映射的,所以windows用一个数据结构来描述这些页面的分配状态:
typedef struct _MM_PAGED_POOL_INFO {
PRTL_BITMAP PagedPoolAllocationMap;
PRTL_BITMAP EndOfPagedPoolBitmap;
PMMPTE FirstPteForPagedPool;
PMMPTE LastPteForPagedPool;
PMMPTE NextPdeForPagedPoolExpansion;
ULONG PagedPoolHint;
SIZE_T PagedPoolCommit;
SIZE_T AllocatedPagedPool;
} MM_PAGED_POOL_INFO, *PMM_PAGED_POOL_INFO;
从 PagedPoolAllocationMap项可以看出,Windows是使用位图法记录各个页面的分配状态,有点类似fat文件系统,呵呵。
也许你要问了,Windows分别使用MMPTE_LIST、位图、空闲链表记录系统空间的分配状态,在释放一块内存时,如何知道使用哪一个数据结构呢?其实很简单,从要释放的地址就可以确定了,windows使用不同的数据结构管理的可是系统不同的区域,地址属于哪个区域可是一目了然啊,呵呵。
能力值:
( LV4,RANK:50 )
13 楼
已经将所有内容整理到1楼了,大家就凑合着看哈
能力值:
( LV4,RANK:50 )
14 楼
杯具啊,写了那么多字,也没人鼓励一下
为什么我的帖子没人回呢?看来这个帖子要TJ了,没兴趣写下去了。
能力值:
( LV2,RANK:10 )
15 楼
好吧,我承认错误,我刚才看过了没回复。lz继续写下去吧,写的不好可是要tjj的,要是tj了就不能tjj了。
能力值:
( LV4,RANK:50 )
16 楼
虽然这是个老掉牙的话题,但是我相信还是会有朋友对这个话题感兴趣的。
没有互动没有意思,欢迎大家交流,指出我的错误,提出你的见解,谢谢大家。
能力值:
( LV2,RANK:10 )
17 楼
我这几天也在看毛老先生的那本书中有关内存管理的代码。
楼主说得相当的好。我也发现楼主理解得比我深。
能力值:
( LV2,RANK:10 )
18 楼
楼主干得好~
能力值:
( LV4,RANK:50 )
19 楼
lkd> dd MmFirstFreeSystemPte l 1
8055f8c0 eb000000
lkd> dd MmSystemPteBase l 1
8055f84c c0000000
奇怪,为何MmSystemPteBase 的值会是c0000000?
如果这样,第一个空闲的pte地址岂不是指向了用户空间?
第一个空闲pte的地址:
PointerPte = MmSystemPteBase + MmFirstFreeSystemPte[0] /1000
= c0000000+eb000 = c00eb000
第一个空闲pte在页表中的偏移:
c00eb000-c0000000 = eb0000
第一个空闲pte对应的虚拟地址:
eb000/4*1000 = 3ac00000
显然有问题:
3ac00000小于0x80000000
MmSystemPteBase 应该是c0200000才合理吧,难道本地内核调试连静态变量都不正确?
谁解释一下?
能力值:
( LV4,RANK:50 )
20 楼
物理内存管理:
================================================================
Windows将物理地址空间分成4k(0x1000)字节大小的页,以页为单位分配物理内存
因为windows以物理页的形式分配物理内存,所有要管理这些页,必须建立这些页的描述,显然对于一个确定的页来说,最重要的信息应该是:
1.起始地址
2.大小
3.是否可用等等
我们来看看Windows对物理内存资源的描述:
typedef struct _MMPFN {
union {
PFN_NUMBER Flink;
WSLE_NUMBER WsIndex;
PKEVENT Event;
NTSTATUS ReadStatus;
struct _MMPFN *NextStackPfn;
} u1;
PMMPTE PteAddress;
union {
PFN_NUMBER Blink;
ULONG ShareCount;
ULONG SecondaryColorFlink;
} u2;
union {
MMPFNENTRY e1;
struct {
USHORT ShortFlags;
USHORT ReferenceCount;
} e2;
} u3;
#if defined (_WIN64)
ULONG UsedPageTableEntries;
#endif
MMPTE OriginalPte;
PFN_NUMBER PteFrame;
} MMPFN, *PMMPFN;
PMMPFN MmPfnDatabase;
上面是windows对一个物理页的描述,MMPFN用来表示一个物理页,MmFfnDatabase就是一个MMPFN的数组;
你也许奇怪,MMPFN中并没有诸如开始地址、页大小等等信息,其实MMPFN中最重要的数据是:
1) u1.Flink;u2.Blink这两个字段其实就是组成一个LIST_ENTRY,用来将该页链入某个链表
2) PteAddress,用来指向一个页表项。
那么如何知道该MMPFN所代表的那块内存的开始地址和页大小呢,其实很简单。
首先,页大小是固定的,就是4k,没必要每个PFN都记录,而开始地址的计算如下:
设x为一个MMPFN,那么对应的开始页号就是&x-MmPfnDatabae,起始地址就是(&x-MmPfnDatabae)*0x1000.
MMPFN数据只存在于数组MmPfnDatabase中,而且按其所代表的页是连续的,即MmPfnDatabase[0]代表0~0xfff,MmPfnDatabase[1]代表0x1000~0x1fff,。。。
而对其使用也就是将其挂入相应的链表,期间除了改变u1.Flink;u2.Blink的指向外,没有数据拷贝,其实这才是其高明之处,它给了我们一个在静态数组之上建立复杂数据结构的范例,这才是我看这部分代码的最大收获(个人认为,使用静态内存就是安心,就是爽,呵呵)。
对于物理内存的管理就是根据不同需求,将存在于MmPfnDatabase数组中的MMPFN数据挂入不同的链表,Windows用于物理内存管理的链表如下,其意义可从其名字上看出,就不多说了:
1) MMPFNLIST MmZeroedPageListHead;
2) MMPFNLIST MmFreePageListHead;
3) MMPFNLIST MmStandbyPageListHead;
4) MMPFNLIST MmModifiedPageListHead;
5) MMPFNLIST MmModifiedNoWritePageListHead;
6) MMPFNLIST MmBadPageListHead;
能力值:
( LV9,RANK:310 )
21 楼
谢谢LZ的科普,虽然还不太懂,为什么我机子上
lkd> !pte
VA 00000000
PDE at 00000000C0600000 PTE at 00000000C0000000
contains 000000002BF2A867 contains 0000000000000000
pfn 2bf2a ---DA--UWEV
pde是c0600000,不是c0300000???
能力值:
( LV4,RANK:50 )
22 楼
PAE模式,pte占8个字节
0xc0000000/0x1000*8 +0xc0000000 =0xc0600000
能力值:
( LV9,RANK:310 )
23 楼
呵呵,谢谢LS,继续研究
能力值:
( LV2,RANK:10 )
24 楼
楼主,你要加油啊,说说工作集是如何管理的!!!越详细越好!!
能力值:
( LV6,RANK:90 )
25 楼
windows对pte还有另外的定义,如MMPTE_LIST List;大家知道其用途么?呵呵,这就是用来管理系统空间的。 windows将整个页表被映射在系统空间的0xc000000到0xc03ffffff的地方后,带给我们的最大的好处是什么? 对了,虚拟地址和该地址在页表中的pte的地址的相互转换变得很容易了,所以windows将系统空间的分配和释放就转换成对pte的分配和释放了,这种想想法真是太高明了,呵呵
不错不错,再次受教,毛老师的书看第一遍过的太快,这些细节没太多考虑。