Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1337399
  • 博文数量: 482
  • 博客积分: 13297
  • 博客等级: 上将
  • 技术积分: 2890
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-12 16:25
文章分类

全部博文(482)

文章存档

2012年(9)

2011年(407)

2010年(66)

分类: LINUX

2011-05-11 11:20:03

页表管理(Page table management)

 Linux内核软件架构习惯与分成硬件相关层和硬件无关层。对于页表管理,2.6.10以前(包括2.6.10)在硬件无关层使用了3级页表目录管理的方式,它不管底层硬件是否实现的也是3级的页表管理:

  • Page Global Directory (PGD)

  • Page Middle Directory (PMD)

  • Page Table (PTE)

从2.6.11开始,为了配合64位CPU的体系结构,硬件无关层则使用了4级页表目录管理的方式:

  • Page Global Directory (PGD)

  • Page Upper Directory (PUD)

  • Page Middle Directory (PMD)

  • Page Table (PTE)

PGD每个条目中指向一个PUD,PUD的每个条目指向一个PMD,PMD的每个条目指向一个PTE,PTE的每个条目指向一个页面(Page)的物理首地址。因此一个线性地址被分为了5个部分,如下图:

PGD,PUD,PMD,PTE中到底有几个条目,不同的CPU体系结构有不同的定义。
虽然硬件无关层是这么设计的,但是底层硬件未必也是这样实现的。如x86体系结构,如果不使用PAE(Physical Address Extension)特性,则硬件底层实现的是2级的页表目录管理,事实上,只有PGD,PTE才是真正有意义的。

中间插段广告

Linux文化T恤,淘宝销售,有兴趣的可以购买。

淘宝店面地址:

继续页目录(Page directory)

每个进程所代表的上下文数据结构中都有一个指针(mm_struct->pgd),其指向这个进程所使用的PGD的一个页(page frame)。这个页面中包含了一个类型为pgd_t的数组。pgd的载入到CPU的方式完全和体系结构相关。x86,进程的页表地址从mm_struct->pgd载入到CR3寄存器,载入页表地址的同时,会引起TLB(快表,是对页目录,页表缓存的缓冲区)也被强制刷新。事实上,这也是__flush_tlb()函数,实现的机制。

PGD中的每个条目指向一个页(page frame), 这个页是“由类型为pud_t的条目组成的PUD”。 PUD中的每个条目同样指向一个页,这个页是“由类型为pmd_t的条目组成的PMD”。PMD的每个条目指向一个页,这个页是“由类型为pte_t的条目组成的PTE”。PTE的每个条目就指向了真正的数据或指令所在的页面的首地址的了,这也不是100%的,如果所需要的页面被交换到磁盘空间去后,这个条目就包含的内容是在当page fault发生后,传入需要调用的 do_swap_page()函数,找到包含页面数据的交换空间。

将线性地址转换成物理地址,需要将线性地址分成5个部分,其中4个的值是在各级页表中的索引或者也可以看成是偏移(OFFSET),另外一个是数据在页中的偏移。为了分别析出这5个部分,各级页表和页中偏移都拥有特定的几个宏:SHIFT,SIZE和MASK。SHIFT宏表示各级页表或页中偏移所占用的bit数。

MASK的值和线性地址做AND运算,获得一个各级的高位部分,一般用于页面,页表对齐。SIZE宏表示各级所能管理的内存空间的字节数。

MASK和SIZE都是有SHIFT计算得到,如x86体系结构是这样的:

#define PAGE_SHIFT    12

#define PAGE_SIZE    (1UL << PAGE_SHIFT)

#define PAGE_MASK   (~ (PAGE_SIZE - 1))

PAGE_SHIFT是线性地址中偏移(offset)的位的位数,x86系统是12位。page的字节数计算很简单:2PAGE_SHIFT (和1<

PMD_SHIFT是线性地址中第三级页表的所占的位数,PMD_SIZE和PMD_MARK是由这个宏计算得到的。

PUD_SHIFT是线性地址中第二级页表的所占的位数,PUD_SIZE和PUD_MARK是由这个宏计算得到的。

PGD_SHIFT是线性地址中第一级页表的所占的位数,PGD_SIZE和PGD_MARK是由这个宏计算得到的。

 

最后介绍4个重要的宏:PTRS_PER_PGD,PTRS_PER_PUD, PTRS_PER_PMD,PTRS_PER_PTE。它们用于确定每级页表有多少条目。
不使能PAE特性的x86体系结构这几个宏定义如下:
#define PTRS_PER_PGD  1024
#define PTRS_PER_PUD    1  //这种情况下PUD事实不起作用,为了代码的硬件无关性,设置为1。

                                                        //在include/asm-generic/pgtable-nopud.h中定义
#define PTRS_PER_PMD  1  //这种情况下PMD事实不起作用,为了代码的硬件无关性,设置为1。

                                                    //在include/asm-generic/pgtable-nopmd.h中定义

#define PTRS_PER_PTE  1024

 页表条目(Page table entry)

页表的每个条目都是一个声明为数据结构的对象:pgd_t,pud_t,pmd_t和pte_t分别对应PGD,PUD,PMD和PTE。
虽然这些数据结构常常只有一个无符号整数,它们被定义成数据结构有2个原因:第一,类型保护,防止被不合适的方式使用。第二,容易扩展每个条目所占字节的数量,如x86使能PAE,则需要另外加入4位(原书是说4位,但是我觉得应该是错误的,应该是加入了4个字节),以使得能够访问多余4GB的物理内存。
为了保持一些保护位,定义了pgprot_t数据结构,它保存相关的标志,通常会保持在页表条目的低位区域。

为了类型的计算,在文件asm/page_32.h或者asm/page_64.h中定义了5个宏。传入上述的类型,返回相应的数据结构中的部分数值:pte_val(),pmd_val(),pud_val()和pgprot_val(). 相反的操作的计算的宏:__pte(),__pmd(),__pud(),__pgd()和__pgprot()。

条目中的状态位,完全是和体系结构相关的。下面解释一下不使能PAE的x86体系结构下,各个状态位的含义。
没有使能PAE的x86,pte_t数据结构中只有一个32位的整数。每个PTE中的类型为pte_t的指针指向一个页面的首地址,也就是说指向的地址总是页面对齐的。因此,在这个整数中PAGE_SHIFT指定数目的位数,也就是12位,是给页表条目中的状态位。列表如下:

比较费解的是_PAGE_PROTNONE这个状态位,x86的体系结构上并不存在这个状态位,LINUX内核借用了PAT位作为这个来使用。这里还有一个问题如果有PSE位被设置,则PAT位的位置就会使用另外一个位置,幸运的是,LINUX内核不会在用户页面中使用PSE特性。LINUX内核挪用这个位的目的是:确定一个虚拟内存的页面在物理内存中是存在的,但是用户空间的进程不能访问它,如同对一段内存区域调用mprotect() API函数并传入PROT_NONE标志一样。当一段内存区域被要求保护,_PAGE_PRESENT为被清除,_PAGE_PROTNONE位被置一。pte_present()宏会同时检测这2位的设置情况,让kernel能够自己知道对应的PTE是否可用:
#define pte_present(x)  ((x).pte_low & (_PAGE_PRESENT | _PAGE_PROTNONE))


如果正好是用户空间不能访问的页面,这就相当巧妙了,但是也相当的重要考量。因为硬件状态为_PAGE_PRESENT已经被清除,当试图访问这个页面的时候,会产生一个page fault的异常,LINUX内核强制的保护了页面访问,但是内核还是知道页面是存在的,如果需要交换到磁盘或者进程退出释放页面,能够做出正确的动作。

页表条目的操作

X86体系结构的情况下,在include/asm-x86/pgtable.h文件中,定义了“析出”或者“检查”页表条目中的值的几个宏(在2.6.24版本的内核中,由于体系结构的关系,这几个宏可能分布在几个相关的头文件中)。

通过 4 个宏,把一个线性地址从第一级页目录表 (Page directories) 追巡最后一级页目录表。

pgd_offset: 通过线性地址 ( 其中的一部分指出了需要访问的内存地址在的 PGD 中的索引 ) 和进程的 mm_struct 数据结构对象,返回一个指向 PGD 条目的地址,内容是某个 PUD 页面的首地址。

pud_offset: 通过线性地址(也是索引)和指向 PGD 条目的地址,返回一个指向 PUD 条目的地址,内容是某个 PMD 页面的首地址。如果硬件系统并不支持 PUD  则直接返回指向 PGD 条目的地址。也就是通过巧妙的直接返回 PGD 的方式,使得不同体系结构下,统一的软件架构。

pmd_offset: 通过线性地址(也是索引)和指向 PUD 条目的地址,返回一个指向 PMD 条目的地址,内容是某个 PTE 页面的首地址。如果硬件系统并不支持 PMD  则直接返回指向 PUD 条目的地址。也就是通过巧妙的直接返回 PUD 的方式,使得不同体系结构下,统一的软件架构。

pte_offset: 通过线性地址(也是索引)和指向 PMD 条目的地址(内容是 PMD 页面的首地址),返回一个指向 PTE 条目的地址,内容是某个需要访问的数据内存页面的首地址(物理地址),这个地址和线性地址的低位部分的数据在内存页面中的偏移相加,就获得了数据真正所在的物理地址了。

 

第二组宏,用于检测页表条目是否存在或者是否可用的信息。

  • pte_none(), pmd_none(), pud_none(),pgd_none() ,如果对应的条目不存在,则返回 1 。宏的定义只是检测条目的内容是否全 0.
  •  pte_present(), pmd_present(), pud_present(),pgd_present(), 如果条目中的 PRESENT 位被设置,则返回 1.
  • pte_clear(), pmd_clear(), pud_clean(),pgd_clear(), 对相应条目清零。
====


阅读(1125) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~