linux的页表为什么没有实现自映射

晨曦之光 发布于 2012/04/10 15:06
阅读 638
收藏 0

玩过windows内核的都应该知道windows为了节省4k的内存,实现了自映射,将页目录本身当作了一个页表来寻址4M的虚拟空间(将4M空间的虚 拟内存映射到物理内存),而这4m的虚拟地址空间恰恰就是从0xc0000000到0xc03FFFFF的空间,这一段空间正好是页表页目录的映射区域。下面我来说一下这怎么变成现实。
首先有几点必须遵守的规则:1.一个页表映射的4m虚拟地址肯定是连续的;2.一个页表映射到的1024个4k的物理页不一定是连续的;3.实现自映射必须是页目录和页表结构一样。有了以上的规则,我们要想理解(或者自己实现一个)windows的自映射就必须理解实际上windows的页表虚拟地址都是 连续的,映射到0xc0000000到0xc03FFFFF,表现为一个页表数组,一共1024项,每项4096字节,一共正好4m,页目录作为这4m的 页表。我们反着想,如果页目录成为了这4m的页表,那么肯定映射0xc0000000到0xc03FFFFF的地址,我们将端点的两个地址分 解:11000000000000000000000000000000B和11000000001111111111111111111111B,仔细观察发现高10位是相等的,而这高10位正好是页目录项的索引,我们知道他是768,好了,页目录要作为第768页目录项对应的页表,现在这个页目录可以 作为一个页表插入到这个页表数组的第768项的位置了,它开始身兼两职了,CR3寄存器里面写的是它的物理地址,它作为页目录,此页面偏移768处也写入 了这个物理地址,它作为页表。
现在考虑把这个页目录(页表)的虚拟映射地址换一下会怎样,别的页表不动。比如这个页目录映射到了0xdc000000这个虚拟地址(仅仅据个例子)。然 后分解地址1101110000 0000000000 000000000000,通过计算发现其对应于页目录的880项,那好吧,我把此页面880偏移改成此页面的物理地址,别的偏移处仍然指向 0xc0000000到0xc03FFFFF的虚拟地址对应的物理地址。看看会发生什么事情,如果把这个从0xc0000000到0xc03FFFFF游 离出来的页面当作页表的话,那么它所映射的虚拟地址应该连续,但是它自己是游离出来的,它的虚拟地址和别的页表的虚拟地址根本不连续,所以这样做是不可以的,也就是说对映射地址的连续性要求是一个必要的条件。windows之所以这么简单的实现了自映射,完全归功于它的设计思想,实际上一个设计并没有多么复杂,只要有了完备性,剩下的就是一个自然结果了,比如X系统是复杂的,而且它在unix出现很久以后才出现,但是unix却完美地支持了x系统,这就说 明unix很完备。windows的模块化作的很好,一致性作的也很好,它几乎将任何东西都标准化,每个进程的页表映射到同一个虚拟地址,在 windows看来,为每个进程提供一个一致的视图要比巧妙的杂糅式进程管理重要的多,所以它将虚拟地址空间作了很复杂的划分和规定。
linux正好走了相反的路,它对于虚拟内存空间什么也没有规定,只是说了一下映射规则,提供了一套完备的机制,策略问题根本没有提及,内核虚存空间各个进程完全共享,各个数据结构散落在各处,内核空间仅仅提供了直接映射和高端映射的若干种映射规则和分配规则,至于说每个空间范围干什么用,linux只字 未提,所以说linux的页表可以散落在物理内存的任何地方,因为物理内存和虚拟内存在内核的很大一部分是一一映射的,所以上面的虚拟地址连续的要求就很难满足,比如这可能要求物理内存连续,在linux连续地址空间如此宝贵,浪费当作页表linux觉得这不值得,再说,页表可以随时分配,为什么要连续 呢?连续的话就要先预留4m页面,然后页表没有完全分配前会造成很多空洞,有几个进程能完全用完4m的页表呢?linux保守的回答了这个问题。我们看一 下代码就明白页表的分配了,代码来自最新的2.6.27内核:

int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,

                 unsigned long address, int write_access)

{

         pgd_t *pgd;

         pud_t *pud;

         pmd_t *pmd;

         pte_t *pte;

         __set_current_state(TASK_RUNNING);

         count_vm_event(PGFAULT);

         if (unlikely(is_vm_hugetlb_page(vma)))

                 return hugetlb_fault(mm, vma, address, write_access);

         pgd = pgd_offset(mm, address);

         pud = pud_alloc(mm, pgd, address);

         if (!pud)

                 return VM_FAULT_OOM;

         pmd = pmd_alloc(mm, pud, address);

         if (!pmd)

                 return VM_FAULT_OOM;

         pte = pte_alloc_map(mm, pmd, address);//此处即分配页表

         if (!pte)

                 return VM_FAULT_OOM;

         return handle_pte_fault(mm, vma, address, pte, pmd, write_access);

}

#define pte_alloc_map(mm, pmd, address)                 / 

      ((unlikely(!pmd_present(*(pmd))) && __pte_alloc(mm, pmd, address))? /

                 NULL: pte_offset_map(pmd, address))

分配页表的策略是alloc_pages(GFP_KERNEL|__GFP_HIGHMEM|__GFP_REPEAT|__GFP_ZERO, 0)好家伙,这么猛,你能保证这分配的页面和前一个页面是连续的吗?你不能,我也不能,甚至linus也不能


原文链接:http://blog.csdn.net/dog250/article/details/5302756
加载中
返回顶部
顶部