【转自ShareHub的博客】在上一篇Linux内存寻址的分段机制中,我们了解了通过分段机制将逻辑地址转换为线性地址的过程。接下来,我们来看一下比较重要和复杂的分页机制。分页机制在段机制之后进行,完成线性-物理地址转换过程。段机制将逻辑地址转换为线性地址,分页机制进一步将线性地址转换为物理地址。分页硬件中的分页机制由CR0中的PG位启用。如果PG=1,开启分页机制,使用本节介绍的机制将线性地址转换为物理地址。如果PG=0,则禁用分页机制,直接使用段机制产生的线性地址作为物理地址。分页机制管理的对象是一个固定大小的存储块,称为页。分页机制把整个线性地址空间和整个物理地址空间都看作是由页组成的。线性地址空间中的任意页都可以映射到物理地址空间中的任意页(我们称物理空间中的页为页)。页或页框)。80386使用4K字节页面。每页的长度为4K字节,对齐在4K字节的边界上,即每页的起始地址可以被4K整除。因此,80386将4G字节的线性地址空间划分为1G页,每页大小为4K字节。分页机制是通过将线性地址空间中的页重定位到物理地址空间来管理的,因为每个页的整个4K字节被映射为一个单元,每个页都对齐在4K字节边界上,因此,低12位通过分页机制直接将线性地址的低12位作为物理地址的低12位。为什么要使用两级页表假设每个进程占用4G线性地址空间,页表一共包含1M个表项,每个表项占用4个字节,那么每个进程的页表占用4M内存空间。为了节省页表占用的空间,我们使用二级页表。每个进程都会分配一个页目录,但只有实际使用的页表才会分配到内存中。一级页表需要一次性分配所有的页表空间,二级页表可以在需要的时候重新分配页表空间。二级页表结构二级页表结构的顶层称为页目录,存放在一个4K字节的页中。页目录表共有1K表项,每表项4字节,指向二级表。线性地址的最后10位(bit31~bit32)用于生成一级索引,从索引得到的表项中指定选择1K个二级表中的一张。二级表结构的第二层称为页表,也只是存放在一个4K字节的页中,包含1K字节的表项,每个表项包含一页的物理基址。二级页表通过线性地址的中间10位(即第21~12位)进行索引,得到包含该页物理地址的页表项。物理地址的高20位和线性地址的低12位组成一个***的物理地址,即页转换过程输出的物理地址。二级页表结构Pagedirectoryentry页目录项结构第31~12位为20位页表地址,由于页表地址的低12位一直为0,所以用高20位指出32位页表地址。向上。因此,一个页目录最多包含1024个页表地址。◆Bit0为存在位。如果P=1,说明页表地址指向的页在内存中。如果P=0,表示不在内存中。◆第一位为读/写位,第二位为用户/管理员位。这两位为页面目录条目提供硬件保护。当特权级为3的进程想要访问一个页面时,需要通过页面保护检查,而特权级为0的进程可以绕过页面保护。◆第三位为PWT(PageWrite-Through)位,表示是否使用直写模式。write-through模式是同时写入内存(RAM)和缓存。该位为1,表示采用直写方式。第4位是PCD(PageCacheDisable)位,表示是否启用缓存,该位为1表示启用缓存。◆第五位为访问位,当访问页目录项时,A位=1。◆第7位为PageSize标志,仅适用于页目录项。如果设置为1,页目录项指的是一个4MB的页,请看后面的扩展分页。◆第9至11位为操作系统专用,Linux无特殊用途。页项结构80386的每一个页目录项都指向一个页表,页表最多包含1024个页项,每个页项为4字节,包括页的起始地址和页的相关信息。页的起始地址也是4K的整数倍,所以页的低12位也留作他用。第31~12位为20位物理页地址。除第6位外,第0~5位和第9~11位与页目录项的作用相同。第6位对于页面条目是唯一的。当页面涉及到写操作时,D位被置1。4GB内存只有一个页目录,页目录最多有1024个页目录项,每个页目录项包含1024个页项。因此,总内存可以分为1024×1024=1M页。由于每页为4K字节,因此内存的大小恰好最多为4GB。#p#线性地址到物理地址的转换32位线性地址到物理地址的转换1.CR3包含页目录的起始地址,使用32位线性地址的***10位A31~A22作为pagesofthepagedirectory目录项的索引,乘以4,加上CR3中页目录的起始地址,形成对应页表的地址。2、从指定地址取出32位的页目录项,它的低12位为0,这32位就是页表的起始地址。将32位线性地址中的A21~A12位作为页在页表中的索引,乘以4,加上页表的起始地址,就构成了一个32位的页地址。3、将A11~A0作为相对页地址的偏移量,与32位页地址相加,组成32位物理地址。扩展分页从奔腾处理器开始,英特尔微处理器引入了扩展分页,它允许页面大小为4MB。扩展分页在扩展分页的情况下,分页机制将32位线性地址分为两个字段:第一个10位目录字段和剩余的22位偏移量。PagecachePagecache因为在分页的情况下,每次访问内存都要访问两级页表,大大降低了访问速度。因此,为了提高速度,在386中设置了最近访问页面的缓存硬件机制,自动保留处理器最近使用的32个页面地址,因此可以覆盖128K字节的内存地址。进行内存访问时,首先检查要访问的页面是否在缓存中,如果在,则无需经过两级访问,如果不在,则进行两级访问。平均而言,pagecache的命中率在98%左右,也就是说每次访问内存时,只有2%的时间必须访问二级分页机制。这大大加快了速度。Linux中的分页机制Linux使用了适用于32位和64位系统的分页机制。Linux分页模型◆页全局目录◆页***目录◆页中间目录◆页表页全局目录包含若干页上层目录的地址,页上层目录包含若干页中间目录的地址反过来,页面中间目录包含几个页表的地址。每个页表条目都指向一个页框。线性地址因此分为五个部分。图中没有显示位数,因为每个部分的大小取决于具体的计算机体系结构。对于没有启用物理地址扩展的32位系统,两级页表就足够了。本质上,Linux通过将“页父目录”位和“页中间目录”位全0,彻底取消了页上层目录和页中目录字段。但是,页父目录和页中的位置保留指针序列中的目录,以便可以在32位和64位系统上使用相同的代码。内核通过将页面目录条目计数设置为1并将这两个目录条目映射到页面全局目录中的适当目录条目,为页面上级目录和页面中间目录保留一个位置。启用物理地址扩展的32位系统使用三级页表。Linux的页全局目录对应80×86页目录指针表(PDPT),取消页上层目录,页中目录对应80×86页目录,Linux页表对应80×86页表。最后,64位系统是采用三级还是四级分页取决于硬件如何划分线性地址的位。总结这里不讨论代码实现,只关注原理。从上面的讨论可以看出,分页机制主要依赖于硬件的实现。Linux使用的四级页表只是为了最大程度地兼容不同的硬件实现。就IA32架构的CPU而言,有各种分页实现,常规的分页机制,PAE机制等,虽然我们讨论的是Linux的分页机制,但实际上大部分篇幅都花在讨论IntelCPU的分页机制实现上了.因为Linux的分页机制是基于硬件的,所以不同的平台需要不同的实现。Linux在软件层面构建的虚拟地址,最终会通过MMU转换成物理地址。也就是说不管Linux的分页机制是如何实现的,CPU只是根据其分页实现来解释线性地址,所以Linux将其传递给CPU。线性地址必须满足硬件实现。例如,如果Linux在32位CPU上,它的四级页表结构将与硬件的两级页表结构兼容。可以看出,Linux在软件层面做了一层抽象,使用四级页表来兼容32位和64位CPU内存寻址的不同硬件实现。参考《深入理解Linux内核》《深入分析Linux内核源码》
