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

玩过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也不能

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
ARM架构的Linux代码实现主要涉及到以下几个文件: 1. `arch/arm/include/asm/pgtable.h`:定义了ARM架构下的页相关宏和数据结构,包括页项(PTE)和页目录项(PMD)的定义。 2. `arch/arm/mm/pgtable.c`:实现了ARM架构下的页相关函数,包括页初始化、页项/页目录项的创建和修改等。 3. `arch/arm/mm/mmu.c`:定义了ARM架构下的内存管理单元(MMU)的初始化和配置函数,包括页的设置和切换等。 在ARM架构下,页使用两级结构,包括页目录(Page Directory Table)和页(Page Table)。每个级别的都有对下一级的指针,最终指向物理内存中的页帧。 ARM架构中的页项(PTE)和页目录项(PMD)由特定位字段组成,用于保存物理地址、标志位和其他控制信息。页项和页目录项的结构可以在`arch/arm/include/asm/pgtable.h`中找到。 在ARM架构下,通过`pgd_offset`、`pmd_offset`、`pte_offset`等函数可以计算出对应虚拟地址在页中的索引,并通过这些索引来获取或设置相应的页项或页目录项。 初始化页时,可以调用`pgd_alloc`、`pmd_alloc`、`pte_alloc`等函数来创建页项和页目录项,并通过`pgd_populate`、`pmd_populate`等函数将物理页框与虚拟地址进行映射。 ARM架构中的MMU初始化和配置主要通过`setup_mm_for_reboot`、`init_mmu`和`__create_page_tables`等函数完成。这些函数会设置页寄存器(TTBR0/TTBR1)和控制寄存器(CRn),从而将页切换到对应的地址空间。 需要注意的是,不同的ARM架构版本和具体的SoC可能会有一些细微的差异,因此具体的代码分析还需要参考相关的架构文档和代码实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值