您好,欢迎来到测品娱乐。
搜索
您的当前位置:首页Linux0.11内核在X86下的内存管理(MMU)学习笔记

Linux0.11内核在X86下的内存管理(MMU)学习笔记

来源:测品娱乐


最近看了很多关于内存管理的资料,总结异同,记录下近期学习的心得,以后,可能

没这么多时间写心得了。

1 基本概念

1.1物理内存

在Linux0.11内核中,为了有效的使用机器中的物理内存,在系统初始化阶段内存被

划分成为几个功能区域,如下图:

其中,Linux内核程序占据在物理内存的开始部分,接下来是供硬盘或软盘等块设备使用的高速缓冲部分(其中要扣除显卡内存和ROM BIOS所占用的内存地址范围0K—1MB)。当一个进程需要读取块设备中的数据时,系统会首先把数据读到高速缓冲区中;当有数据需要用到块设备上去时,系统也是先将数据放到高速缓冲区中,然后由块设备驱动程序写到相应的设备上。内存的最后部分是供所以程序可以随时申请和使用的主内存区。内核程序在使用主内存区是,也同样首先要向内核内存管理模块提出申请,并在申请成功后方能使用。对于含有RAM虚拟盘的系统,主内存区头部还要划去一部分,供

虚拟盘存放数据。

1.2内存寻址

内存是指一组有序字节组成的数组,每个字节有唯一的内存地址。

内存地址则是指对存储在内存中的某个指定数据对象的地址进行定位。

数据对象是指存储在内存中的一个指定数据类型的数值或字符串。

80X86支持多种数据类型:1字节、2字节(1个字)、或4字节(双字或长字)的无符号整型数或带符号整型数,以及多字节字符串等。对于80X86CPU来说,其地址总线宽度为32位,因此可寻址的地址空间范围是0—2^32(4GB)的物理内存,这是就产生一个冲突,我们实际上使用的物理内存一般没有4GB这么大阿,怎么办,于是就引入了一个关

键的技术:内存管理(MMU)。

1.3 地址转换过程中3个特殊地址的概念

虚拟地址(VA, Virtual Address)是指由程序产生的由段选择符和段内偏移地址两个部分组成的地址。因为这两部分组成的地址并没有直接用来访问物理内存,而是需要通过分段地址变换机制处理或映射后才对应到物理内存地址上,因此这种地址被称为虚拟地址。

VA空间由GDT(Global Descriptor Table)映射的全局地址空间和由LDT(Local Descriptor Table)映射的局部地址空间组成。选择符的索引部分由13个bit表示,加上区分GDT和LDT的1个比特位—TI,(低2个bit为RPL---Requestor’s Privilege Level),因此Intel 80X86 CPU 共可以索引2^14=16384个选择符。若每段的长度都取最大值4G,

则最大虚拟地址空间范围是16384*4G=T。

逻辑地址(Logical Address)是指由程序产生的与段相关的偏移地址部分,即程序员编

程所用的地址以及CPU通过指令访问主存时所产生的地址。

用多道程序设计技术后,往往在主存储器中同时存放多个用户作业,而每个用户不能预先知道自己的作业将被放到主存储器中的什么位置。这样,用户编制程序时就不能使用绝对地址。为了方便用户,每个用户都可认为自己作业的程序和数据存放在一组从“0”地址开始的连续空间中。把用户程序中使用的地址称“逻辑地址”,由逻辑地址对应的存储空间称“逻辑地址空间”。在Intel保护模式下即是指程序执行代码段限长内的偏移地址(假定代码段、数据段完全一样)。应用程序员仅需与逻辑地址打交道,而分段和分页机制对他来说是完全透明的,仅由系统编程人员涉及。不过有些资料并不区分逻辑地址和虚拟

地址的概念,而是将它们统称为逻辑地址。

线形地址(Linear Address)是虚拟地址到物理地址变换之间的中间层,是处理器可寻址的内存空间(统称为线性地址空间)中的地址。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启动了分页机制,那么线性地址可以再经过变换后产生一个物理地址。若没有分页机制,那么line address就直

接是logic address。Intel80386的线性地址空间容量为4G。

物理地址(PA, Physical Address)是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为了物

理地址。

虚拟存储(或虚拟内存)(Virtual Memory)是指计算机呈现出要比实际拥有的内存大得多的内存量。因此它允许程序员编制并运行比实际系统拥有的内存大得多的程序。这使得许多大型项目也能够在有限内存资源的系统上实现。一个很经典的比喻:你不需要很长的轨道就可以让一列火车从上海开到北京,你只需要足够长的铁轨(比如说3公里)就可以完成这个任务。采取的方法就是把后面的铁轨立刻铺到火车的前面,只要你的操作足够快就能满足要求,列车就能象在一条完整的轨道上运行。这也就是虚拟内存管理需要完成的任务。在Linux0.11内核中,给每个程序(进程)都划分了总容量为MB的虚拟内

存空间。因此程序的逻辑地址范围是0x00000000—0x04000000。

1.4 内存地址的基本变换过程

任何完整的内存管理系统都包含两个关键部分:保护和地址变换。

提供保护措施是可以防止一个任务访问另一个任务或操作系统的内存区域。

地址变换能够让操作系统在给任务分配内存时具有灵活性,并且因为我们可以让某些物理地址不被任何逻辑地址所映射,所以在地址变换过程中同时也提供了内存保护功能。

80x86在从逻辑地址到物理地址变换过程中使用了分段和分页两种机制,如上图所示,第一阶段使用分段机制把程序的逻辑地址变换成处理器可寻址的内存空间(称为线性地址空间)中的地址,该阶段的分段机制总是使用(否则cpu如何寻址操作?)。第二阶段使

用分页机制把线性地址转换为物理地址。该阶段的分页机制选用,具体后续。

2 内存分段机制

分段机制可用于实现多种系统设计。这些设计范围从使用分段机制的最小功能来保护程序的平坦模型,到使用分段机制创建一个可同时可靠的运行多个程序(或任务)的具有

稳固操作环境的多段模型。

多段模型能够利用分段机制全部功能提高由硬件增强的代码、数据结构、程序和任务的保护措施。通常,每个程序(或任务)都使用自己的段描述符以及自己的段。对程序来说段能够完全是私有的,或者是程序之间共享的。对所有段以及系统上运行程序各自执行

环境的访问都由硬件控制。

2.1 段的定义

分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存块单元。80386虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分构成。

段是虚拟地址到线性地址转换机制的基础。每个段由下面三个参数定义:

(1)段基地址(Base address),指定段在线性地址空间中的开始地址。基地址时线性

地址,对应于段中偏移0处。

(2)段限长(limit),是虚拟地址空间中段内最大可用偏移位置,它定义了段的长度。在段描述符中有定义,与颗粒度(G)配合使用,在上扩段和下扩段中有区别。

(3)段属性(Attributes),指定段的特性。如是否可读、可写或可作为一个程序执行;

段的级等。段描述符中有定义。

2.2实模式和保护模式的区别

2.2.1 实模式

是CPU启动的时候的模式,这时候就相当于一个速度超快的8086,不能使用多线程,不能实现权限分级 ;实模式下,虚地址到实地址转换,段寄存器左移四位与偏移相加,得

到物理地址,寻址空间1M。

2.2.2 保护模式

操作系统接管CPU后,会使CPU进入保护模式,虚地址到实地址转换经过MMU,也就是分段和分页机制,寻址空间4G。另外,保护有两层含义,一是保护操作系统不被随

意访问和破坏,另外,保护应用程序在各自的地址空间不被随意破坏。

2.2.3 寻址方式的比较

在实模式下,寻址一个内存地址主要是使用段和偏移值,段值被存放在段寄存器中(如ds),并且段的长度被固定为KB。段内偏移地址存放在任意一个可用于寻址的寄存器中

(如si)。根据段寄存器和偏移寄存器中的值,就可以算出实际指向的内存地址。

struct logical_address{

UINT16 seg_base;

UINT16 offset;

};

#define logical_to_phisical(logical) ((logical.seg_base4) + logical.offset)

这样做的缺点是不安全,程序员可以通过修改段寄存器的内容访问到任意一个内存单

元,系统缺乏保护,很脆弱。

80286尝试过渡到保护模式,加入了struct seg_descriptor这个概念。

在保护模式运行方式下,段寄存器中存放的不再是被寻址段的基地址,而是一个段描述符表(Segment Descriptor Table)中某一个描述符项在表中的索引值。索引值指定的段描述符项中含有需要寻址的内存段的基地址、段的长度和段的访问级等信息。如下:

struct logical_address{

struct seg_selector{

UINT16 index:13;

UINT16 tl:1;

UINT16 rpl:2;

};

UINT32 offset;

};

struct seg_descriptor{

UINT32 base:32; /* 基地址 */

UINT32 seg_limit:20; /* 段长度 */

UINT32 g:1; /* 粒度,表段的长度单位,0表示字节,1表示4KB */

UINT32 d_b:1; /* 存取方式,0=16位,1=32位 */

UINT32 unused:1; /* 固定设置成0 */

UINT32 avl:1; /* available,可供系统软件使用 */

UINT32 p:1; /* segment present,为0时表示该段的内容不在内存中 */

UINT32 dpl:2; /* Descriptor Privilege Level,访问本段所需权限 */

UINT32 s:1; /* 描述项类型,1表示系统,0表示代码或数据 */

UINT32 type:4; /* 段的类型,与上面的S标志位一起使用 */

};

UINT32 logical_to_linear(struct logical_address logical_add){

static bool first = true;

UINT32 table_base;

struct seg_descriptor shadow;

if ( first ) {

if(logical_add.seg_selector.tl == 0)

table_base = gdtr; /* GDT */

else

table_base = ldtr; /* LDT */

shadow = (struct seg_descriptor) *(table_base +

logical_add.seg_selector.index*8);

first = false;

}

if ( logical_add.seg_selector.tl > shadow.dpl ) /* 0: highest level; 3: lowest level

*/

thorow access_denied;

else if ( logical_add.offset > (shadow.limit(shadow.g?12:0)) )

thorow over_boundary;

else

return shadow.base + logical_add.offset;

}

寻址的内存位置是由该段描述符项中指定的段基地址与一个段内偏移值组合而成。段

的长度可变,由描述符中的内容指定。

可见,和实模式下的寻址相比,段寄存器值换成了段描述符表中相应段描述符的索引值以及段表选择位和级,称为段选择符(Segment Selector),但偏移值还是使用段描述符表。这是由于在保护模式下访问一个内存段需要的信息比较多,而一个16位的段寄存器放不下这么多内容。注意:如果你不在一个段描述符中定义一个内存线性地址空间区

域,那么该地址区域就完全不能被寻址,CPU将拒绝访问该地址区域。

2.3 保护模式下的分段机制

为了把逻辑地址转换成一个线性抵制,处理器会执行以下操作:

1) 使用段选择符中的偏移值(段索引)在GDT或LDT表中定位相应的段描述符。(仅

当一个新的段选择符加载到段寄存器中时才需要这一步)

2) 利用段选择符检验段的访问权限和范围,以确保该段是可访问的并且偏移量位于

段界限内。

3) 把段描述符中取得的段基址加到偏移量上,最后形成一个线性地址。

如果没有开启分页,那么处理器直接把线性地址映射到物理地址(即线性地址被送到处理器地址总线上)。如果对线性地址空间进行了分页处理,那么就会使用二级地址转换吧

线性地址转换成物理地址。

2.3.1段选择符

80x86为段部分提供了6个存放段选择符的段寄存器:CS,DS,ES,SS,FS和GS。其中CS总是用于寻址代码段,而堆栈段则专门使用SS段寄存器。在任何指定时刻由CS寻址的段称为当前代码段。此时EIP寄存器中包含了当前代码段内下一条要执行指令的段内偏移地址。因此要执行指令的地址可表示成CS:[EIP]。由段寄存器SS寻址的段称为当前堆栈段。栈顶由ESP寄存器内容指定。因此堆栈顶处地址是SS:[ESP]。另外4个寄存器是通用段寄存器,当指令中没有指定所操作数据的段时,那么DS将是默认的数据段寄存器。

保护模式下,CPU要寻址一个段时,就会使用16位的段寄存器的选择符(有些书上

称为选择子)来定位一个段描述符,格式如下:

在80X86 CPU中,段寄存器中的值右移3位即是描述符表中的一个描述符的索引值。13位的索引值最多可定位8192个描述符项。选择符中的bit2(TI)用来指定使用哪个表(0为GDT,1位LDT)。RPL(Requestor’s Privilege Level)为请求者的级(保护机制下

的0-3级)。

2.3.2 段描述符表

段描述符表示段描述符的一个数组,描述符表的长度可变,最多可以包含2^13=8192个8字节描述符。有两种描述符表:全局描述符表(Global Descriptor Table);局部描述

符表(Local Descriptor Table)。

表述符表存储在由操作系统维护着的特殊数据结构中,并且由处理器的内存管理硬件来引用。这些特殊结构应该保存在仅由操作系统软件访问的受保护的内存区域中,以防止应用程序修改其中的地址转换信息。虚拟地址空间被分割成大小相等的两半。一半由GDT来映射变换到线性地址,另一半则由LDT来映射。整个虚拟地址空间共含2^14个段:一半空间(即2^13个段)是由GDT映射到全局虚拟地址空间,另一半是由LDT映射的局

部虚拟地址空间。

每个程序都可以有若干个内存段组成。程序的逻辑(虚拟)地址即是用于寻址这些段和段中具体地址位置。由GDT和LDT映射的全局地址空间和局部地址空间构成了虚拟地

址的空间,如下图:

图中显示了具有两个任务时的情况。每个任务的LDT本身也是由GDT中的描述符定义的一个内存段,该段中存放着对应任务的代码段和数据段描述符,因此LDT段很短,其段限长一般只要大于24个字节即可。同样,每个任务的任务状态段TSS也是由GDT中描述符定义的一个内存段,其段限长也只要满足能够存放一个TSS数据结构就ok了。

当发生任务切换时,LDT会更换成新任务的LDT,但是GDT并不会改变。因此,GDT

所映射的一半虚拟地址空间是系统中所有任务共有的(用于内核程序),但是LDT所映射的另一半则在任务切换时被改变(用于普通任务)。每个任务通过不同的LDT被安全的隔

离开来,这样就无法访问非本任务以为的段空间,达到保护目的。

GDT本身并不是一个段,而是线性地址空间中的一个数据结构。GDT的基线性地址和

长度值必须加载进GDTR寄存器中。

处理器并不适用GDT中的第一个描述符(该符为空描述符),把该段选择符加载进一个数据段寄存器(DS,ES,FS,GS)并不会产生一个异常,但是若使用这些加载了空描述符的短

选择符访问内存时就肯定会产生一般保护性异常。

LDT表存放在LDT类型的系统段中。此时GDT必须含有LDT的段描述符。如果系统支持多个LDT的话,那么每个LDT都必须在GDT中有一个段描述符和一个段选择符。

2.3.3 段描述符

段描述符是GDT和LDT表中的一个数据结构项,用于向处理器提供有关一个段的位置和大小信息以及控制访问的信息状态。每个段描述符长度是8个字节,含有三个主要的字段:段基地址、段限长和段属性。段描述符通常由编译器、链接器、加载器或者操作系

统来创建,但绝不是应用程序。其格式如下:

1)代码和数据段描述符类型

当段描述符中S标志被置位,则该描述符用于代码或数据段。此时类型字段中最高比特位(第2个双字的bit11)用于确定是数据段的描述符(复位)还是代码段的描述符(置

位)。

对于数据段的描述符,类型字的低3位(bit8、9、10)被分别用于表示已访问A(Accessed)、可写W(Write-enable)和扩展方向E(Expansion-direction)。

堆栈段必须是可读/写的数据段。若使用不可写数据段的选择符加载到SS寄存器中,将导致一个一般保护性异常。如果堆栈段的长度需要动态地改变,那么堆栈段可用是一个向下扩展的数据段(扩展方向标志置位,向下扩展是X86架构的默认方式)。这里,动态

的改变段限长将导致栈空间被添加到栈底部。

对于代码段的描述符,类型字的低3位(bit8、9、10)被分别用于表示已访问

A(Accessed)、可读R(Read-enable)和一致的C(Conforming)。

代码段可用是一致性的和非一致性的。向更高级一致性代码段的执行控制转移,允许程序以当前级继续执行。像一个不同级的非一致性代码段的转移将导致一般保护异常,除非使用一个调用门或任务门。不访问保护设施的系统工具以及某些异常类型(例如除出错、溢出)的处理过程可用存放在一致性代码段中。需要防止低级程序或

过程访问的工具应该存放在非一致性代码段中。

所有数据段都是非一致性的,即意味着它们不能被低级的程序或过程访问。然而,与代码段不同,数据段可用被更高级的程序或过程访问,而无须使用特殊的访问门。

如果GDT或LDT中一个段描述符被存放在ROM中,那么若软件或处理器试图更新(写)在ROM中的段描述符是,处理器就会进入一个无限循环。为了防止这个问题,需要存放在ROM中的所有描述符的已访问位应该预先设置成置位状态。同时删除操作系统

中任何试图修改ROM中段描述符的代码。

2)系统描述符类型

当段描述符中的S标志是复位状态的话,那么该描述符是一个系统描述符。处理器能

够识别以下一些类型的系统段描述符:

● 局部描述符表(LDT)的段描述符

● 任务状态段(TSS)描述符

● 调用门描述符

● 中断门描述符

● 陷阱门描述符

● 任务门描述符

这些描述符类型可分为两大类:系统段描述符和门描述符。系统段描述符指向系统段(如LDT和 TSS段),门描述符就是一个“门”,对于调用、中断或陷阱门,其中含有代码

段的选择符和段中程序入口点的指针;对于任务门,其中含有TSS的段选择符。

有关TSS状态段和任务门的使用方法在任务管理中会详细叙述;调用门的使用方法将

放入保护机制中;中断和陷阱门的使用方法属于中断和异常处理的部分。

3 内存管理分页机制

分页机制是80x86内存管理机制的第二部分。它在分段的基础上完成虚拟(逻辑)地址到物理地址的转换过程。分段机制把逻辑地址转换成线性地址,而分页则把线性地址转换成物理地址。分页可以用于任何一种分段模型。处理器分页机制会把线性地址空间划分成页面,然后这些线性地址空间页面被映射到物理地址空间的页面上。分页机制几种页面级保护措施,可和分段机制保护机制合用活替代分段机制的保护措施。如,在基于页面的基础上加强读/写保护。另外,在页面单元上,分页机制还提供了用户-超级用户两级保护

(让我联想到ubuntu里的root用户)。

控制寄存器CR0中的PG位置位可启动分页机制,复位禁止禁用分页机制。

与分段机制类似,分页机制允许我们重新定向(变换)每次内存引用,以适应我们的特殊要求,使用分页机制最普遍的场合是当系统内存实际上被分成很多凌乱的块时,它可

以建立一个大而连续的内存空间映像,好让程序不用操心和管理这些分散的内存块。分页

机制增强了分段机制的性能。

3.1 页表结构

分页转换功能由驻留在内存中的表来描述,该表成为页表(page table),存放在物理地址空间中。页表可以看做简单的2^20物理地址数组。线性到物理地址的映射功能可以简单的看做对数组进行查找。Line address的高20位构成这个数组的索引值,用于选择对应页面的物理(基)地址。线性地址的低12位给出了页面中的偏移量,加上页面的基地址最终形成对应的物理地址。由于页面基地址对齐在4k边界上,因此页面基地址的低12位肯定是0。这意味着高20位的页面基地址和12位的偏移地址连接组合在一起就能得

到相应的物理地址。

3.1.1 两级页表结构

页表含有2^20(1M)个表项,而每个表项占用4字节。如果作为一个表来存放的话,它们最多将占用到4MB的内存。为了减少内存占用量,80x86使用了两级页表。高20位

线性地址到物理地址的转换也被分成两步进行,每笔使用(转换)其中的10位。

第一级页表称为页目录(page directory)。它被存放在1页4KB页面中,具有2^10(1k)个字节长度的表项。这些表项指向对应的二级表。线性地址的高10位(位31-22)用作

一级表(页目录)中的索引值来选择2 ^10个二级表之一。

第二级表成为页表(page table)。它的长度是1个页面,最多含有1K个4字节的表项。每个4字节表项含有相关页面的20位物理基地址。二级页表使用线性地址中间10位(位21-12)作为表项索引值,以获取含有页面20位物理基地址的表项。该20位页面物理基地址和线性地址中的低12位(页内偏移)组合在一起就得到了分页转换过程的输出值,

即对应的最终物理地址。

3.1.2 页表项格式

● 页框地址(Page frame address)指定了一页内存的物理起始地址。因为内存页是位于4K边界上的,所以其低12比特总是0,因此表项的低12比特可作它用。在一个页目录表中,表项的页框地址是一个页表的起始地址;在第二级页表中,表项的页框地址则包

含期望内存操作的物理内存页地址。

● P—bit0存在位(Present)确定了一个页表项是否可以用于地址转换过程,P=1表示可用;反之不可用。当页表项无效(P=0)时,其余位可供程序自由使用,处理器不对这些位进

行测试。

当CPU试图使用一个页表项进行地址转换时,如果此时任意一级页表项的P=0,则处理器就会发出页异常信号。此时缺页中断异常处理程序就可以把所请求的页加入到物理

内存中,并且导致异常的指令会被重新执行。

● R/W—bit1读/写(Read/Write)标志位。等于1则页面可读可写可执行;等于0则页面只读并可执行。当处理器运行在超级用户级(级别0、1、2)时,该位不起作用。

该位对其所映射的所有页面起作用。

● U/S—bit2是用户/超级用户(User/Supervisor)标志。为1表示运行在任何级上的程序都可以访问该页面;为0则表示该页面只能被运行在超级用户级(0、1、2)

上的程序访问。该位对其所映射的所有页面起作用。

● A—bit5是已访问(Accessed)标志。当处理器访问页表项映射的页面时,页表表项的这个标志就会被置为1;当处理器访问页目录表项映射的任何页面时,页目录表项的这个标志就会被置为1。处理器只负责设置该标志位,操作系统可通过定期地复位该标志位

来统计页面的使用情况。

● D—bit6是页面已被修改(Dirty)标志。当处理器对一个页面执行写操作时,就会设

置对应页表表项的D标志。处理器并不会修改页目录项中的D标志。

● AVL—该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不

会。

3.2 分页机制过程

在Intel 80x86的系统中,内存分页管理是通过页目录表和内存页表所组成的二级页表进行的。页目录表中的每个表项(简称页目录项)(4字节)用来寻址一个页表,而每个页表项(4字节)用来指定一页物理内存页。因此,当指定了一个页目录项和一个页表项,我们就可以唯一地确定所对应的物理内存页。也目录表占用一页内存,因此最大可寻址1024个页表,而每个页表也同样占用一页内存,因此一个页表可以寻找1024个物理内存页面,这样,在80386种,一个页目录表所寻址的所有页表为0-----1024x1024x4096=4G的内存空间。在linux内核中,所有的进程都使用一个页目录表,而每个进程都有自己的页表。内核代码和数据段长度是16KB,使用了4个页表(即4个页目录项)。这4个页表直接位于页目录表后面,参加head.s程序line1.9-125。经过分段机制变换,内核代码和数据段位于线性地址空间的头16MB范围内,再经过分页机制变换,它被直接一一对应地映

射到16MB物理内存上。因此对于内核段来讲其线性地址就是物理地址。

对于应用程序或内核其他部分来讲,在申请内存是使用的是线性地址,一个32位的线性地址被分成了三部分:一个页目录项、一个页表项和对应物理内存页上的偏移地址,

从而能间接地寻址到线性地址指定的物理内存位置。

下图给出了线性地址是如何映射到物理内存页上的。对于第一个进程(任务0),其页表是在页目录表给出之后,共4页,对于应用程序的进程,其页表使用的内存是在进程创

建时向内存管理程序申请的,因此是在主内存区中。

一个系统中可以同时存在多个页目录表,而在某个时刻只有一个页目录表可用。当前的页目录表是用CPU的寄存器CR3来确定的,它存储着当前页目录表的物理内存地址。

4 TLB的基本概念

从虚拟地址到物理地址的转换过程可知:使用一级页表进行地址转换时,每次读/写数据需要访问两次内存,第一次访问一级页表获得物理地址,第二次才是真正的读/写数据;使用两级页表时,每次读/写数据需要访问三次内存,访问两次页表(一级页表和二级页表)

获得物理地址,第三次才是真正的读/写数据。

这种地址转换大大降低了CPU的性能,需要怎么样解决此问题呢?程序执行过程中,所用到的指令、数据的地址往往集中在一个很小的范围内,其中地址、数据经常多次使用。由此,通过使用一个高速、容量相对较小的存储器来存储近期使用到的页表条目,以避免

每次地址转换时都到主存去查找,这样可以大幅度提高性能。这个缓存就是TLB。

TLB:Translation lookaside buffer,即旁路转换缓冲,或称为页表缓冲;里面存放的

是一些页表文件(虚拟地址到物理地址的转换表)。

X86保护模式下的寻址方式:段式逻辑地址—〉线形地址—〉页式地址;

页式地址=页面起始地址+业内偏移地址;

对应于虚拟地址:叫page(页面);对应于物理地址:叫frame(页框);

X86体系的系统内存里存放了两级页表,第一级页表称为页目录,第二级称为页表。

TLB和CPU里的一级、二级缓存之间不存在本质的区别,只不过前者缓存页表数据,

而后两个缓存实际数据。

内部组成:(此部分为了解内容,仅为充实资料)

(1)TLB在X86体系的CPU里的实际应用最早是从Intel的486CPU开始的,在X86

体系的CPU里边,一般都设有如下4组TLB:

第一组:缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB);

第二组:缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB);

第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB);

第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Data-TLB);

(2)TLB命中和TLB失败

如果 TLB中正好存放着所需的页表,则称为TLB命中(TLB Hit);如果TLB中没有

所需的页表,则称为TLB失败(TLB Miss)。

(3)TLB条目数

即页表条目数,Entry!

(4)TLB的联合方式

1〉全联合方式:Athlon XP

2〉4路联合方式:P4

当CPU执行机构收到应用程序发来的虚拟地址后,首先到TLB中查找相应的页表数据,如果TLB中正好存放着所需的页表,则称为TLB命中(TLB Hit),接下来CPU再依次看TLB中页表所对应的物理内存地址中的数据是不是已经在一级、二级缓存里了,若没有则到内存中取相应地址所存放的数据。既然说TLB是内存里存放的页表的缓存,那么它里边存放的数据实际上和内存页表区的数据是一致的,在内存的页表区里,每一条记录虚拟页面和物理页框对应关系的记录称之为一个页表条目(Entry),同样地,在TLB里边也缓

存了同样大小的页表条目(Entry)。

5 Cache的作用

同样基于程序访问的局部性,在主存和CPU通用寄存器之间设置一个高速的、容量相

对较小的存储器,把正在执行的指令地址附近的一部分指令或数据从主存调入这个存储器,供CPU在一段频繁使用该指令或数据的时间内调用,这对提高程序的运行速度有很大的作

用。这种介于主存和CPU之间的高速小容量存储器称为高速缓冲器(Cache)。

启动Cache后,CPU读取数据是,如果Cache中有这个数据的副本则直接返回,否则再从主存中读入数据,并存入Cache中,下次再使用(读写)这个数据时,可以直接使用

Cache中的副本。

启动Cache后,CPU写数据时有写穿式和回写式两种方式:

1) 写穿式(Write Through)

任一丛CPU发出的写信号送到Cache的同时,也写入主存,以保证主存的数据能同步更新。它的优点是操作简单,但由于主存的慢速,降低了系统的写速度并占用了总线的

时间。

2) 回写式(Write Back)

为了克服写穿式中每次数据写入时都要访问主存,从而导致系统写速度降低并占用总

线时间,尽量减少对主存的访问次数,出现了回写式。

它是这样工作的:数据一般只写到Cache,这样有可能出现Cache中的数据得到更新而主存中的数据不变(数据陈旧)的情况。但此时可在Cache中设一标志地址及数据陈旧的信息,只有当Cache中的数据被换出或强制进行“清空”操作时,才将原更新的数据写入

主存相应的单元中,这样保证了Cache和主存的数据保持一致。

Cache的两个操作:

1) “清空”(clean):把Cache或Write buffer中已经脏的(修改过,但未写入主存)

数据写入主存。

2) “使无效”(Invalidate):使之不能再使用,并不将脏的数据写入主存。

补充:

翻开MMU资料,各种文章铺天盖地,应有尽有,看其核心,无非保护模式下的分段和分页机制,收集各个知识点,我力求完美,但由于水平,定有遗漏不足之处,所幸此文只是学习交流之用,所有成果皆是前人遗留,整理出来让大家看得更加清晰。最后一部分的TLB为补充内容,完全摘自Paul的《MMU浅析》,也得助于Young的画图功力。

分页机制下ARM的V4架构与X86有些不同,就是其页表项可灵活的分为段(1MB)、粗页表(256个条目)、细页表(1024个条目);页表项可灵活分为大页(KB)、小页(4KB)和极小页(1KB);其原理与X86架构大同小异,若要细化可翻看韦东山的《嵌入式Linux

应用开发完全手册》。

最后,还是那句话,看源代码:memory.c!

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- cepb.cn 版权所有 湘ICP备2022005869号-7

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务