`
tomhibolu
  • 浏览: 1387990 次
文章分类
社区版块
存档分类
最新评论

kernel hacker修炼之道之内存管理-分页

 
阅读更多

浅析linux内核内存管理之分页

作者:李万鹏


硬件中的分页


硬件中的分页分常规分页和扩展分页:



常规分页,32位的线性地址被分为3个域:


  • Directory(目录) 最高10位
  • Table(页表) 中间10位
  • Offset(偏移量) 最低12位

扩展分页,32位的线性地址被分为2个域:

  • Directory 最高10位
  • Offset 其余22位

扩展分页和正常分页的页目录项基本相同,除了:

  • Page Size标志必须被设置
  • 20位物理地址字段只有最高10位是有意义的。这是因为每一个物理地址都是在以4MB为边界的地方开始的,故这个地址的最低22位为0

页目录项:

页表项:


Linux中的分页

从linux 2.6.11开始采用了四级分页模型:

  • 页全局目录(Page Global Directory) —— ——>PDT(PAE未开启)或PDPT(PAE开启)
  • 页上级目录(Page Upper Directory)
  • 页中间目录(Page Middle Directory) —— ——>PDT(PAE未开启)
  • 页表(Page Table) —— —— —— —— ——>PT

PTRS_PER_PTE,PTRS_PER_PMD,PTRS_PER_PUD以及PTRS_PER_PGD
用于计算页表,页中间目录,页上级目录和页全局目录表中表项的个数。当PAE被禁止时,它们产生的值分别为1024,1,1和1024。当PAE被激活时,产生的值分别为512,512,1和4。

注意:PAE关闭时,PGD就是PDT,此时不用PUD,PMD。虽然它们两个在线性地址中,但长度为0,2^0=1,也就是说,它们都是有一个数组元素的数组。当PAE启动时,PGD指示四个PDPT entry中的哪一个, 不使用PUD。

那么使用分页有什么好处呢?
就拿二级分页来说吧,如果不用二级分页,用一级,那么4GB需要4MB物理内存存放entry,一个页4KB,entry4B。如果用两级分页,2^10*2^10*4KB=4GB。所以需要两个页就行,每个页1024 entry。4KB+4KB=8KB。实际上,用不上8KB的,并不是所有entry都是存在的,正如下边说的那个函数,有的entry会为空,这样又节省很多空间。所以实际页表与页目录占用的空间小于8KB,多合算阿。

页表处理

pte_t,pmd_t,pud_t和pgd_t分别描述页表项,页中间目录项,页上级目录和页全局目录的格式。当PAE被激活的时他们都是64位的数据类型,否则都是32位数据类型。pgprot_t是另一个64位(PAE激活时)或32位(PAE禁用时)的数据类型,它表示与一个单独表项相关的保护标志。
五个类型转换宏(__pte,__pmd,__pud,__pgd和__pgprot)把一个无符号整数转换成所需的类型。另外的五个类型转换宏(pte_val,pmd_val,pud_val,pgd_val和pgprot_val)执行相反的转换,即把上面提到的四种特殊的类型转换成一个无符号整数。

内核还提供许多宏用于读取或修改页表项:
  • 如果相应的表项值为0,那么,宏pte_none,pmd_none,pud_none和pgd_none产生的值为1,否则产生的值为0。
  • 宏pte_clear,pmd_clear,pud_clear和pgd_clear清除相应页表一个表项,由此禁止进程使用由该页表项映射的线性地址。ptep_get_and_clear()函数清除一个页表项并返回前一个值。
  • set_pte,set_pmd,set_pud和set_pgd向一个页表项中写入指定的值。
  • 如果a和b两个表项页表项指向同一页并且指定相同的访问优先级,那么pte_same(a,b)返回1,否则返回0。
  • 如果页中间目录指向一个大型页(2MB或4MB),那么pmd_large(e)返回1,否则返回0.

如果一个页表项的Present标志或者Page Size标志等于1,则pte_present宏产生的值为1,否则为0。前面讲过页表项的Page Size标志对微处理器的分页单元来讲没有意义,然而,对于当前在主存中却没有读,写或执行权限的页,内核将其Present和Page Size分别标记为0和1。这样,任何试图对此类页的访问都会引起一个缺页异常,因为页的Present标志被清0,而内核可以通过检查Page Size的值来检查到产生异常并不是因为缺页。

读页标志的函数:


设置页标志的函数:


对页表项操作的宏:

这里需要注意的是,如果是32位未开启PAE,则pud_offset,pmd_offset返回的仍是pgd;如果是32位开启PAE,则pud_offset返回的是pgd。pgd_offset,pud_offset,pmd_offset,pte_offset_map等返回的都是线性地址,比如,pmd_offset返回的pmd中相应表项的线性地址,*pmd_offset(pud,addr)才是表项中的值,即PT的地址。还有一个要注意的地方就是pte_offset_map,如果页表被保存在高端内存,那么就建立一个临时内核映射,这样就可以编辑页表了,等编辑完了,在解除临时内核映射。举个例子,do_anonymous_page()函数中有这么一段:

解释一下:
调用handle_mm_fault分配一个新的页框,分配了pud,pmd,实际返回的都是pgd中的表项(如果是x86 32,未开启PAE),调用pte_alloc_map,如果pgd中的表项为空,则新分配一个页表,如果是HIGHPTE就从highmem分,如果不空并且是HIGHPTE则调用kmap_atomic将这个页表映射到临时内核映射区的一个窗口KM_PTE0,这样内核就可以编辑这个页表了,即设置相应表项。do_anonymous_page中从highmem为引起page fault的线性地址分配页框。第一次pte_unmap之后又pte_offset_map是为了防止一旦alloc_page导致进程睡眠,临时内核映射被覆盖。然后用新分配到的页框设置页表项,临时内核很宝贵的,由于编辑完页表了,解除临时内核映射,分配页框任务完成。

页分配函数:

在x86 32位未开启PAE的时候,调用pud_alloc,pmd_alloc其实并未真正分配,上边说了,其实此时pud,pmd只有一项,目的就是在请求pud,pmd的时候返回pgd。如下边map_vm_area()中的pud_alloc,并没有真正分配。而是通过pud,pmd一一转手,目的应该是与64位兼容。


写了一个测试程序:

测试结果:

注意判断是否是大页,好多人没有考虑这个地方,导致各种诡异的问题。




分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics