windows下由于启用了页机制,所有软件层面的地址操作都是VA,通过descriptor(base address(32bit))+offset得到的线性地址并不直接对应物理地址,而是经过页转换机构再做一次转换得到物理地址,这样的转换是硬件提供的能力,转换过程被说的比较多了,大体是通过cr3寄存器得到page direcotry的物理地址,Vitrual Address的高10位就是一个(pde)page direcotry entry的index,或许可以像这样表示cr3[VA>>22],一个pde又指向了一个page table,VA接下来的10bit就是page table中的一个索引,即一条pte(page talbe entry),pte中存放了page的地址,低12位就是在前述转换过程中得到的page中的offset。
需要特别说明的是,pde and pte中存放的是物理帧号(physical frame number)位于pde or pte的高20bit,也就是说可以表示1M个物理帧,每个物理帧大小是4kb,这样就可以表示4GB的内存。
说了半天还没到自映射机制,前面说的都是硬件提供的能力,只要是拿到一个VA硬件层面都进行相同的转换步骤,windows要做内存管理,就需要维护page directory和 page tables,如何通过一个VA得到它所在的页的VA以及所在的页表的VA呢?
微软提供了下面几个宏处理给定的VA
#define MiGetPdeAddress(VA) ((PMMPTE)(((((ULONG)(VA)) >> 22) << 2) + PDE_BASE))//此处PDE_BASE为0xc0300000(这也是一个VA,经页转换后得到的是PD的起始地址,下面就是所谓的自映射了,页转换机构利用VA的高十位(0x300)索引到一个PDE,这个PDE的特别之处是并没有指向一个page table而是指向了page directory的起始地址,一个PD占据一页,一个PT也占据一页),继续把page directory作为一个page table,再用VA的中间10位(还是0x300)又索引到了page directory的起始位置,低12位是0,即offset为0,最终得到的就是Page Directory的起始地址。那么VA先右移22,再左移2(相当于乘以4)加上PDE_BASE经过得到一个VA,经过两次自映射,加上offset后,得到的物理地址就是PDE的物理地址。
#define MiGetPteAddress(va) ((PMMPTE)(((((ULONG)(va)) >> 12) << 2) + PTE_BASE))
这里的PTE_BASE是一个常量,为0xC0000000,也就是说page directory entry的索引(0x300)仍然索引到page directory的起始位置,和上面的宏一样,又是一次自映射。
#define MiGetVirtualAddressMappedByPte(PTE) ((PVOID)((ULONG)(PTE) << 10))//如果是给了一个VA,想要得到所在page的起始地址,只需将VA的低12位清零就可以得到VA所在页的VA,但这里给的是PTE(相当于(*PDE)),因为pte的高十位总是指向PD的起始位置,左移10位之后就相当于取消了这次自映射,当然这里的转换只对利用自映射的va有效。
再说一下,exe载入内存的时候,如何建立描述符,首先根据image的大小分配若干页的内存,这里假设image 在内存中占80kb,基址是0x00400000h,系统共分配20页的内存,起始页的VA假设是1110010000|1101110000|000000000000b(pde index(0x390):pte index(0x370):offset(0x0)),则descriptor.base+00400000h=E4370000h->descriptor.base=E3F70000h,那么code段描述符中的基址就可以根据其相对于00400000h的偏移加上E4370000h得到,比如code段的va是00401000h,那么他的描述符中的基址为E3F71000h,正好是从内存中image的第2页开始。更进一步,将新建code段描述符在GDT中的index(选择子)载入到cs,程序就可以运行了。
总起来说,硬件的转换机制并没有改变,只是windows利用硬件提供的这种能力使自己能方便的维护PD和PT
VA:vitual address
PTE:page table entry
PDE:page directory entry
PD:page directory
PT:page table