分类: LINUX
2008-10-14 10:38:02
分页单元(paging unit)把线性地址转换成物理地址。 其中的一个关键任务是把所请求的访问类型与线性地址的访问权限相比较,如果这次内存访问是无效的,就产生一个缺页异常(参见第四章和第八章)。
为了效率起见,线性地址被分成以固定长度为单位的组,称为页(page)。页内部连续的线性地址被映射到连续的物理地址中。这样,内核可以指定一个页的物理地址和其存取权限,而不用指定页所包含的全部线性地址的存取权限。我们遵循通常习惯,使用术语“页”既指一组线性地址,又指包含在这组地址中的数据。
分页单元把所有的RAM 分成固定长度的页框(page frame)(有时叫做物理页)。每一个页框包含一个页(page),也就是说一个页框的长度与一个页的长度一致。页框是主存的一部分,因此也是一个存储区域。区分一页和一个页框是很重要的,前者只是一个数据块,可以存放在任何页框或磁盘中。
把线性地址映射到物理地址的数据结构称为页表(page table)。页表存放在主存中,并
在启用分页单元之前必须由内核对页表进行适当的初始化。
从80386 开始,所有的80x86 处理器都支持分页,它通过设置cr0 寄存器的PG 标志启用。当PG=0 时,线性地址就被解释成物理地址。
*常规分页
从80386 起,Intel 处理器的分页单元处理4KB 的页。
32 位的线性地址被分成3 个域:
Directory(目录)
最高10 位
Table(页表)
中间10 位
Offset(偏移量)
最低12 位
线性地址的转换分两步完成,每一步都基于一种转换表,第一种转换表称为页目录表(page directory),第二种转换表称为页表(page table)(在接下来的讨论中,小写的“page table”表示保存线性地址和物理地址之间映射的页,而利用“Page Table”表示在上层页表中的页。)。
使用这种二级模式的目的在于减少每个进程页表所需RAM的数量。如果使用简单的一级页表,那将需要高达220 个表项(也就是,在每项4 个字节时,需要4MB RAM)来表示每个进程的页表(如果进程使用全部4GB线性地址空间),即使一个进程并不使用那个范围内的所有地址。二级模式通过只为进程实际使用的那些虚拟内存区请求页表来减少内存容量。
每个活动进程必须有一个分配给它的页目录。不过,没有必要马上为进程的所有页表都分配RAM。只有在进程实际需要一个页表时才给该页表分配RAM 会更为有效率。
正在使用的页目录的物理地址存放在控制寄存器cr3 中。线性地址内的Directory 字段决定页目录中的目录项,而目录项指向适当的页表。地址的Table 字段依次又决定页表中的表项,而表项含有页所在页框的物理地址。Offset 字段决定页框内的相对位置(见图2-7)。由于它是12 位长,故每一页含有4096 字节的数据。
Directory 字段和Table 字段都是10 位长,因此页目录和页表都可以多达1024 项。那么一个页目录可以寻址到高达1024 × 1024 × 4096=232 个存储单元,这和你对32 位地址所期望的一样。
页目录项和页表项有同样的结构,每项都包含下面的字段:
Present 标志
如果被置为1,所指的页(或页表)就在主存中;如果该标志为0,则这一页不在主存中,此时这个表项剩余的位可由操作系统用于自己的目的。如果执行一个地址转换所需的页表项或页目录项中Present 标志被清0,那么分页单元就把该线性地址存放在控制寄存器cr2中,并产生14 号异常:缺页异常。(我们将在第十七章中看到Linux 如何使用这个字段。)
包含页框物理地址最高20 位的字段
由于每一个页框有4KB的容量,它的物理地址必须是4096 的倍数,因此物理地址的最低12 位总是为0。如果这个字段指向一个页目录,相应的页框就含有一个页表;如果它指向一个页表,相应的页框就含有一页数据。
Accessed 标志
每当分页单元对相应页框进行寻址时就设置这个标志。当选中的页被交换出去时,这一标志就可以由操作系统使用。分页单元从来不重置这个标志,而是必须由操作系统去做。
Dirty 标志
只应用于页表项中。每当对一个页框进行写操作时就设置这个标志。与Accessed标志一样,当选中的页被交换出去时,这一标志就可以由操作系统使用。分页单元从来不重置这个标志,而是必须由操作系统去做。
Read/Write 标志
含有页或页表的存取权限(Read/Write 或Read)(参阅本章后面“硬件保护方案”一节)。
User/Supervisor 标志
含有访问页或页表所需的特权级(参见后面的“硬件保护方案”一节)。
PCD 和 PWT 标志
控制硬件高速缓存处理页或页表的方式(参见本章后面“硬件高速缓存”一节)。
Page Size 标志
只应用于页目录项。如果设置为1,则页目录项指的是2MB 或4MB 的页框(参见下一节)。
Global 标志
只应用于页表项。这个标志是在Pentium Pro中引入的,用来防止常用页从TLB(TLB 的全称为Translation Lookaside Buffer,这是IBM 的叫法,有时也叫联想内存(Associative Memory),俗称“快表”。)高速缓存中刷新出去[参阅本章后面“转换后援缓冲器(TLB)“一节]。只有在cr4 寄存器的页全局启用(Page Global Enable ,PGE)标志置位时这个标志才起作用。
*扩展分页
从Pentium 模型开始, 80x86 微处理器引入了扩展分页(extended paging),它允许页框大小为4MB 而不是4KB(见图2-8)。扩展分页用于把大段连续的线性地址转换成相应的物理地址,在这些情况下,内核可以不用中间页表进行地址转换,从而节省内存并保留TLB 项[参阅“转换后援缓冲器(LTB)”一节]。
正如前面所述,通过设置页目录项的Page Size 标志启用扩展分页功能。在这种情况下,分页单元把32 位线性地址分成两个字段:
Directory
最高10 位
Offset
其余22 位
扩展分页和正常分页的页目录项基本相同,除了:
• Page Size 标志必须被设置。
• 20 位物理地址字段只有最高10 位是有意义的。这是因为每一个物理地址都是在以4MB 为边界的地方开始的,故这个地址的最低22 位为0。
通过设置cr4 处理器寄存器的PSE 标志能使扩展分页与常规分页共存。
*硬件保护方案
分页单元和分段单元的保护方案不同。尽管80x86 处理器允许一个段使用4 种可能的特权级别,但与页和页表相关的特权级只有两个,因为特权由前面 “常规分页”一节中所提到的User/Supervisor 标志所控制。若这个标志为0,只有当CPL 小于3(这意味着对于Linux 而言,处理器处于内核态)时才能对页寻址;若该标志为1,则总能对页寻址。
此外,与段的3 种存取权限(读,写,执行)不同的是,页的存取权限只有两种(读,写)。如果页目录项或页表项的Read/Write 标志等于0,说明相应的页表或页是只读的,否则是可读写的(新的Intel Pentium 4 处理器在每个64位页表项中增加了一个NX(No eXecute)标志[必须激活PAE,参见本章后面的“物理地址扩展(PAE)分页机制”一节]。Linux
常规分页举例
*这个简单的例子将有助于阐明常规分页是如何工作的。我们假定内核已给一个正在运行
的进程分配的线性地址空间范围是0x20000000 到 0x2003ffff(正如我们在后面章节所看到的那样,3GB 线性地址空间是一个上限,但是用户态进程只允许引用其中的一个子集)。这个空间正好由64页组成。我们不必关心包含这些页的页框的物理地址,事实上,其中的一些页甚至可能不在主存中。我们只关注页表项中剩余的字段。
让我们从分配给进程的线性地址的最高10 位(分页单元解释为Directory 字段)开始。这两个地址都以2 开头后面跟着0,因此高10 位有相同的值,即0x080 或十进制的128。因此,这两个地址的Directory 字段都指向进程页目录的第129项。相应的目录项中必须包含分配给该进程的页表的物理地址(见图2-9)。如果没有给这个进程分配其它的线性地址,则页目录的其余1023 项都填为0。
中间10 位的值(即Table 字段的值)范围从0 到0x
假设进程需要读线性地址0x20021406 中的字节。这个地址由分页单元按下面的方法处理:
1. Directory 字段的0x80 用于选择页目录的第0x80 目录项, 此目录项指向和该进程的页相关的页表。
2. Table 字段0x21 用于选择页表的第0x21 表项, 此表项指向包含所需页的页框。
3. 最后,Offet 字段0x406 用于在目标页框中读偏移量为0x406 中的字节。
如果页表第0x21 表项的Present 标志为0,则此页就不在主存中;在这种情况下,分页单元在线性地址转换的同时产生一个缺页异常。无论何时,当进程试图访问限定在0x20000000 到0x2003ffff 范围之外的线性地址时,都将产生一个缺页异常,因为这些页表项都填充了0,尤其是它们的Present 标志都被清0。
*物理地址扩展(PAE)分页机制
处理器所支持的RAM容量受连接到地址总线上的地址管脚数限制。早期Intel处理器从80386 到Pentium 使用32 位物理地址。从理论上讲,这样的系统上可以安装高达4GB的RAM;而实际上,由于用户进程线性地址空间的需要,内核不能直接对1GB以上的RAM
进行寻址,我们将会在后面“Linux 中的分页”一节中看到这一点。
然而,大型服务器需要大于4GB的RAM来同时运行数以千计的进程,近几年这对Intel
造成了压力,所以必须扩展32 位80x86 结构所支持的RAM 容量。
Intel通过在它的处理器上把管脚数从32 增加到36 已经满足了这些需求。从Pentium Pro开始,Intel 所有处理器现在寻址能力达236 =64GB。不过,只有引入一种新的分页机制把32 位线性地址转换为36 位物理地址才能使用所增加的物理地址。
从Pentium Pro 处理器开始,Intel 引入一种叫做物理地址扩展(Physical AddressExtension ,PAE)的机制。另外一种叫做页大小扩展[Page Size Extension (PSE-36)]的机制在Pentium III 处理器中引入,但是Linux 并没有采用这种机制,因而我们在本书中不做进一步讨论。
通过设置cr4 控制寄存器中的物理地址扩展(PAE)标志激活PAE。页目录项中的页大小标志PS 启用大尺寸页(在PAE 启用时为2MB)。
Intel 为了支持PAE 已经改变了分页机制。
• 64GB 的RAM 被分为224 个页框,页表项的物理地址字段从20 位扩展到了24 位。因为PAE页表项必须包含12 个标志位(在前面“常规分页”一节已描述)和24 个物理地址位,总数之和为36,页表项大小从32 位变为64 位增加了一倍。结果,一个4KB 的页表包含512 个表项而不是1024 个表项。
• 引入一个叫做页目录指针表(Page Directory Pointer Table,PDPT)的页表新级别,它由4 个64 位表项组成。
• cr3 控制寄存器包含一个27 位的页目录指针表(PDPT)基地址字段。因为PDPT存放在RAM 的前4GB 中,并在32 字节(25)的倍数上对齐,因此27 位足以表示这种表的基地址。
• 当把线性地址映射到4 KB 的页时(页目录项中的PS 标志清0),32 位线性地址按下列方式解释:
cr3
指向一个 PDPT
位31- 30
指向PDPT 中4 个项中的一个
位29-21
指向页目录中512 个项中的一个
位20- 12
指向页表中512 项中的一个
位11-0
4KB 页中的偏移量
• 当把线性地址映射到2MB 的页时(页目录项中的PS 标志置为1),32 位线性地址
按下列方式解释:
cr3
指向一个 PDPT
位31-30
指向PDPT 中4 个项中的一个
位29-21
指向页目录中512 个项中的一个
位20-0
2MB 页中的偏移量
总之,一旦cr3 被设置,就可能寻址高达4GB RAM。如果我们希望对更多的RAM 寻址,就必须在cr3 中放置一个新值,或改变PDPT 的内容。然而,使用PAE 的主要问题是线性地址仍然是32位长。这就迫使内核编程人员用同一线性地址映射不同的RAM区。在后面的“当RAM大于4096MB 时的最终内核页表”一节中,我们将描述启用PAE 时Linux 如何初始化页表。很明显,PAE 并没有扩大进程的线性地址空间,因为它只处理物理地址。此外,只有内核能够修改进程的页表,所以在用户态下运行的进程不能使用大于4GB 的物理地址空间。另一方面,PAE 允许内核使用容量高达64GB 的RAM,从而显著增加了系统中的进程数量。
*64 位系统中的分页
我们在前面几节已经看到,32位微处理器普遍采用两级分页(注4)。然而两级分页并不适用于采用64 位系统的计算机。让我们用一种思维实验来解释为什么:
首先假设一个大小为4KB 的标准页。因为1KB 覆盖210 个地址的范围,4KB 覆盖212 个地址,所以offset 字段是12 位。这样线性地址就剩下52 位分配给Table 和Directory 字段。如果我们现在决定仅仅使用64 位中的48 位来寻址(这个限制仍然使我们自在地拥
有256TB 的寻址空间!),剩下的48-12 = 36 位将被分配给Table 和Directory 字段。如
果我们现在决定为两个字段各预留18位,那么每个进程的页目录和页表都含有218个项,
即超过256000 个项。由于这个原因,所有64 位处理器的硬件分页系统都使用了额外的分页级别。使用的级别数量取决于处理器的类型。表2-4 总结了一些Linux 所支持64 位平台使用的硬件分页系统的主要特征。对于与平台名称相关的硬件的简要描述请参见第一章的“硬件的依赖性”一节。
表2-4:一些64 位系统的分页级别
平台名称页大小寻址使用的位数 分页级别数线性地址分级
alpha 8 KBa 43 3 10 + 10 + 10 + 13
ia64 4 KBa 39 3 9 + 9 + 9 + 12
表2-4:一些64 位系统的分页级别(续)
平台名称页大小寻址使用的位数 分页级别数线性地址分级
ppc64 4 KB 41 3 10 + 10 + 9 + 12
sh64 4 KB 41 3 10 + 10 + 9 + 12
x86_64 4 KB 48 4 9 + 9 + 9 + 9 + 12
a. 该体系结构支持不同的页大小;我们选择了一种Linux 支持的典型页大小。
稍后我们将会在本章的“Linux 中的分页”一节看到,Linux 成功地提供了一种通用分页模型,它适合于绝大多数所支持的硬件分页系统。
*硬件高速缓存
当今的微处理器时钟频率接近几个GHz,而动态RAM(DRAM)芯片的存取时间是时钟周期的数百倍。这意味着,当从RAM中取操作数或向RAM中存放结果这样的指令执行时,CPU 可能等待很长时间。
为了缩小CPU 和RAM之间的速度不匹配,引入了硬件高速缓存内存(hardware cachememory)。硬件高速缓存基于著名的局部性原理(locality principle),该原理既适用程序结构和也适用数据结构。这表明由于程序的循环结构及相关数组可以组织成线性数组,最近最常用的相邻地址在最近的将来又被用到的可能性极大。因此,引入小而快的内存来存放最近最常使用的代码和数据变得很有意义。为此,80x86 体系结构中引入了一个叫行(line)的新单位。行由几十个连续的字节组成,它们以脉冲突发模式(burst mode)在慢速DRAM和快速的用来实现高速缓存的片上静态RAM(SRAM)之间传送,用来实现高速缓存。
高速缓存再被细分为行的子集。在一种极端的情况下,高速缓存可以是直接映射的(direct mapped),这时主存中的一个行总是存放在高速缓存中完全相同的位置。在另一种极端情况下, 高速缓存是充分关联的(fully associative),这意味着主存中的任意一个行可以存放在高速缓存中的任意位置。但是大多数高速缓存在某种程度上是N-路组关联的(N-way set associative), 意味着主存中的任意一个行可以存放在高速缓存N行中的任意一行中。例如,内存中的一个行可以存放到一个2 路组关联高速缓存两个不同的行中。
如图2-10所示,高速缓存单元插在分页单元和主内存之间。它包含一个硬件高速缓存内存(hardware cache memory)和一个高速缓存控制器(cache controller)。高速缓存内存存放内存中真正的行。 高速缓存控制器存放一个表项数组,每个表项对应高速缓存内存中的一个行。每个表项有一个标签(tag)和描述高速缓存行状态的几个标志(flag)。
这个标签由一些位组成,这些位让高速缓存控制器能够辨别由这个行当前所映射的内存元。这种内存物理地址通常分为3 组:最高几位对应标签,中间几位对应高速缓存控制器的子集索引,最低几位对应行内的偏移量。
当访问一个RAM存储单元时,CPU从物理地址中提取出子集的索引号并把子集中所有
行的标签与物理地址的高几位相比较。如果发现某一个行的标签与这个物理地址的高位相同,则CPU命中一个高速缓存(cache hit);否则,高速缓存没有命中(cache miss)。
当命中一个高速缓存时,高速缓存控制器进行不同的操作,具体取决于存取类型。 对于读操作,控制器从高速缓存行中选择数据并送到CPU寄存器;不需要访问RAM因而节约了CPU 时间,因此,高速缓存系统起到了其应有的作用。对于写操作,控制器可能采用以下两个基本策略之一,分别称之为通写(write-through)和回写(write-back)。在通写中,控制器总是既写RAM 也写高速缓存行,为了提高写操作的效率关闭高速缓存。回写方式只更新高速缓存行,不改变RAM 的内容,提供了更快的功效。当然,回写结束以后,RAM最终必须被更新。只有当CPU执行一条要求刷新高速缓存表项的指令时,或者当一个FLUSH硬件信号产生时(通常在高速缓存不命中之后), 高速缓存控制器才把高速缓存行写回到RAM 中。
当高速缓存没有命中时,高速缓存行被写回到内存中,如果有必要的话,把正确的行从
RAM 中取出放到高速缓存的表项中。
多处理器系统的每一个处理器都有一个单独的硬件高速缓存,因此它们需要额外的硬件电路用于保持高速缓存内容的同步。如图2-11 所示,每个CPU 都有自己的本地硬件高速缓存。但是,现在更新变得更耗时:只要一个CPU修改了它的硬件高速缓存,它就必须检查同样的数据是否包含在其他的硬件高速缓存中;如果是,它必须通知其他CPU用适当的值对其更新。常把这种活动叫做高速缓存侦听(cache snooping)。值得庆幸的是,所有这一切都在硬件级处理,内核无需关心。
高速缓存技术正在快速向前发展。例如,第一代Pentium 芯片包含一颗称为L1-cache 的
片上高速缓存。近期的芯片又包含另外的容量更大、速度较慢,称之为L2-cache,L3-cache等的片上高速缓存。多级高速缓存之间的一致性是由硬件实现的。Linux 忽略这些硬件细节并假定只有一个单独的高速缓存。
处理器的cr0寄存器的CD 标志位用来启用或禁用高速缓存电路。这个寄存器中的NW标
志指明高速缓存是使用通写还是回写策略。
Pentium 处理器高速缓存的另一个有趣的特点是,让操作系统把不同的高速缓存管理策
略与每一个页框相关联。为此,每一个页目录项和每一个页表项都包含两个标志;PCD(Page Cache Disablt)标志指明当访问包含在这个页框中的数据时,高速缓存功能必须被启用还是禁用。PWT(page Write-Through)标志指明当把数据写到页框时,必须使用的策略是回写策略还是通写策略。Linux 清除了所有页目录项和页表项中的PCD 和PWT 标志;结果是:对于所有的页框都启用高速缓存,对于写操作总是采用回写策略。
*转换后援缓冲器(TLB)
除了通用硬件高速缓存之外, 80x86处理器还包含了另一个称为转换后援缓冲器或TLB(Translation Lookaside Buffer)的高速缓存用于加快线性地址的转换。当一个线性地址被第一次使用时,通过慢速访问RAM 中的页表计算出相应的物理地址。同时,物理地址被存放在一个TLB 表项(TLB entry)中,以便以后对同一个线性地址的引用可以快速地得到转换。
在多处理系统中,每个CPU 都有自己的TLB,这叫做该CPU的本地TLB。与硬件高速缓存相反,TLB中的对应项不必同步,这是因为运行在现有CPU上的进程可以使同一线性地址与不同的物理地址发生联系。
当CPU 的cr3 控制寄存器被修改时,硬件自动使本地TLB中的所有项都无效,这是因为新的一组页表被启用而TLB 指向的是旧数据。