2010-4-25 18: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使用不同的数据结构管理的可是系统不同的区域,地址属于哪个区域可是一目了然啊,呵呵。