内存加速的秘密武器:TLB缓存原理和应用全解析

内存加速的秘密武器:TLB缓存原理和应用全解析

在计算机程序的运行过程中,内存访问是一个极其基础且关键的操作。程序的每一次数据读取与写入,都离不开内存的支持。然而,内存访问延迟问题却如同一只拦路虎,横亘在程序高效运行的道路上。为了应对内存访问延迟这一挑战,计算机科学家们可谓绞尽脑汁,提出了各种各样的优化策略和技术。缓存技术便是其中一种极为重要且广泛应用的手段。

缓存就像是一个数据的 “高速中转站”,它位于 CPU 和内存之间,存储着 CPU 近期可能会频繁访问的数据。当 CPU 需要访问数据时,会首先在缓存中查找,如果能够在缓存中找到所需的数据,就可以直接从缓存中读取,从而大大减少了内存访问的时间。缓存技术的出现,在一定程度上缓解了内存访问延迟的问题,但它并没有从根本上解决这一难题。

在缓存技术中,有一种特殊的缓存 ——TLB(Translation Lookaside Buffer)缓存,它在优化内存访问效率方面发挥着举足轻重的作用 。TLB 缓存主要用于存储虚拟地址到物理地址的映射关系,能够显著加速地址转换的过程。那么,TLB 缓存究竟是如何工作的?它的内部结构和工作原理又是怎样的呢?在实际的应用中,我们又该如何充分利用 TLB 缓存来提高内存访问效率呢?接下来,就让我们一同深入探索 TLB 缓存的奥秘。

一、TLB缓存是什么?

TLB,即 Translation Lookaside Buffer,中文名为地址转换后备缓冲器 ,是一种高速缓存,在内存管理中扮演着关键角色。在现代计算机系统中,为了更有效地管理内存和提供内存保护,引入了虚拟内存机制。在这种机制下,程序使用的是虚拟地址,而实际的数据存储在物理内存中,这就需要进行虚拟地址到物理地址的转换。而TLB正是用于存储虚拟地址到物理地址的映射关系,它就像是页表的缓存。

页表是存储虚拟地址与物理地址映射关系的数据结构,通常存放在内存中。由于内存访问速度相对较慢,如果每次地址转换都要访问内存中的页表,会极大地影响系统性能。而 TLB 作为页表的缓存,存储了近期最常访问的页表项,当 CPU 需要进行地址转换时,会首先在 TLB 中查找,从而大大提高了地址转换的速度 。

一个程序是运行在虚拟存储器空间的,它的大小由处理器位数决定,对于一个32位处理器来说,其地址范围就是0~0xFFFF FFFF,也就是4GB;而对于一个64位处理器,其地址范围是0~0xFFFF FFFF FFFF FFFF,这个范围就是程序能够产生的地址范围,其中的某一个地址就称为虚拟地址。

和虚拟存储器相对应的就是物理存储器,它是在现实世界中能直接使用的存储器,其中的某一个地址就是物理地址。物理存储器的大小不能够超过处理器最大可以寻址的空间,例如,对于32位的x86 PC来说,它的物理存储器(一般简称为内存)可以是256MB,即PM的范围是0~0xFFF FFFF,当然,也可以将物理内存增加到4GB,此时虚拟存储器和物理存储器的地址空间的大小就是相同的。

使用了虚拟地址,则处理器输出的地址就是虚拟地址,这个地址不会被直接送到物理存储器中,而要先进行地址转换,因为虚拟地址没有办法直接寻址物理存储器,负责地址转换的部件称为内存管理单元(Memory Manage Unit, MMU),如下图所示。

使用虚拟存储器不仅便于程序在处理器中运行,还给程序编写带来好处,在直接使用物理存储器的处理器中,如果要同时运行多个程序,需要为每个程序都分配一块地址空间,每个程序都需要在这个地址空间内运行,这样极大地限制了程序的编写,而且不能够使处理器随便地运行程序。

通过操作系统动态地将每个程序的虚拟地址转化为物理地址,还可以实现程序的保护,即使两个程序使用了同一个虚拟地址,它们也会对应到不同的物理地址,因此可以保证每个程序的内容不会被其他的程序随便改写。

而且通过这样的方式,还可以实现程序间的共享,例如操作系统内核提供了打印(printf)函数,第一个程序在地址A使用了printf函数,第二个程序在地址B使用了printf函数,操作系统在地址转换的时候,会将地址A和B都转换为同样的物理地址,这个物理地址就是printf函数在物理存储器中的实际地址,这样就实现了程序的共享,虽然两个程序都使用了printf函数,但是没必要使printf函数占用物理存储器的两个地方,因此,使用虚拟存储器不仅可以降低物理存储器的容量需求,还可以带来另外的好处,如保护(protect)和共享(share)。

一个处理器要支持现代的操作系统,就必须支持虚拟存储器,它是操作系统一个非常重要的内容。

二、地址转换

目前最通用的虚拟存储器实现方式是基于分页(page)的虚拟存储器。虚拟地址空间以页为单位划分,典型的页大小为4KB,相应的物理地址空间也进行同样大小的划分,由于历史原因,在物理地址空间中不叫做页,而称为frame,它和页的大小必须相等。当程序开始运行时,会将当前需要的部分内容从硬盘中搬移到物理内存中,每次搬移的单位就是一个页的大小。由于只有在需要的时候才将一个页的内容放到物理内存中,这种方式就称为demand page,它是处理器可以运行比物理内存更大的程序。

对于一个虚拟地址(Virtual Address)来说,VA[11:0]表示页内的位置,称为page offset,VA剩余的部分用来表示哪个页,也称为VPN(Virtual Page Number)。相应的,对于一个物理地址PA(Physical Address)来说,PA[11:0]表示frame内的位置,称为frame offset,而PA剩余的部分用来表示哪个frame,也称为PFN(Physical Frame Number)。由于页和frame的大小是一样的,所以从VA到PA的转化实际上也就是从VPN到PFN的转化,offset的部分是不需要变化的。

2.1 单级页表

在使用虚拟存储器的系统中,都是使用一张表格来存储从虚拟地址到物理地址(实际上是VPN到PFN)的对应关系,这个表格称为页表(Page Table,PT),也称为转换表(translation table)。这个表格一般是放在物理内存中,使用虚拟地址来寻址,表格中被寻址到的内容就是这个虚拟地址对应的物理地址。

每个程序都有自己的页表,用来将这个程序中的虚拟地址映射到物理内存中的某个地址,为了指示一个程序的页表在物理内存中的位置,在处理器中一般都会包括一个寄存器,用来存放当前运行程序的页表在物理内存中的起始地址,这个寄存器称为页表寄存器(Page Table Register,PTR),每次操作系统将一个程序调入物理内存中执行的时候,就会将寄存器PTR设置好,当然,上面的这种机制可以工作的前提是页表位于物理内存中一片连续的地址空间内。

下图表示了如何使用PTR从物理内存中定位到一个页表,并使用虚拟地址来寻址页表,从而找到对应的物理地址的过程。其实,使用PTR和虚拟地址共同来寻址页表,这就相当于使用它们两个共同组成一个地址,使用这个地址来寻址物理内存。图中仍然假设每个页的大小是4KB,使用PTR和虚拟地址共同来寻址页表,找到对应的表项(entry),当这个表项对应的有效位(valid)为1时,就表示这个虚拟地址所在的4KB空间已经被操作系统映射到了物理内存中,可以直接从物理内存中找到这个虚拟地址对应的数据,其实,这时候访问当前页内任意的地址,就是访问物理内存中被映射的那个4KB的空间了。

相反,如果页表中这个被寻址的表项的有效位是0,则表示这个虚拟地址对应的4KB空间还没有被操作系统映射到物理内存中,此时就产生了Page Fault类型的异常,需要操作系统从更下一级的存储器(例如硬盘或闪存)将这个页对应的4KB内容搬移到物理内存中。

图中使用了32位的虚拟地址,页表在物理内存中的起始地址用PTR来指示。虚拟地址的寻址空间是2^32字节,也就是4GB;物理地址的寻址空间是2^30字节,也就是1GB,这对应着实际物理地址的寻址空间。在页表中的一个表项(entry)能够映射4KB的大小,为了能够映射整个4GB的空间,需要表项的个数应该是4GB/4KB=1M,也就是2^20,因此需要20位来寻址。也就是说32位的虚拟地址分成两部分,低12bit用来寻址一个页内的内容,高20bit用来寻址哪个页,因此真正寻址页表只需要VPN就够了。从页表中找到的内容也不是整个物理地址,而只是PFN。

需要注意的是,页表的结构是不同于cache的,在页表中包括了所有VPN的映射关系,所以可以直接使用VPN对页表进行寻址,而不需要使用Tag。

可以采用很多方法来减少一个进程的页表对于存储空间的需求,最常用的是多级页表(Hierarchical Page Table),这种方法可以减少页表对于物理存储空间的占用,而且非常容易使用硬件实现,与之对应的,本节所讲述的页表就称为单级页表(Single Page Table),也被称为线性页表(Linear Page Table)。

2.2 多级页表

将一个4MB的线性页表划分为若干个更小的页表,称它们为子页表,处理器在执行进程的时候,不需要一下子把整个线性页表都放入物理内存中,而是根据需求逐步地放入这些子页表。而且,这些子页表不需要占用连续的物理内存空间。也就是说,相邻的子页表可以放在物理内存中不连续的位置,这样也提高了物理内存的利用效率。但是,由于所有的子页表是不连续地放在物理内存中,所以依旧需要一个表格,来记录每个子页表在物理内存中存储的位置,称这个表格为第一级页表(Level1 Page Table),而那些子页表为第二级页表(Level2 Page Table)。

这样,要得到一个虚拟地址对应的数据,首先访问第一级页表,得到第二级页表的基地址,然后再去第二级页表才可以得到这个虚拟地址对应的物理地址,然后就可以在物理内存中取出相应的数据了。

例如,对于一个32位虚拟地址、页大小为4KB的系统来说,如果采用线性页表,则页表中表项个数为2^20,将其等分为2^10等份,每个等份就是一个第二级页表,共有1024个第二级页表,对应着第一级页表的1024个表项。也就是说,第一级页表需要10位地址来进行寻址。每个二级页表中,表项个数为1024,也需要10位地址来寻址。

下图中,一个页表中的表项简称为PTE,当操作系统创建一个进程时,就在物理内存中为这个进程找到一个连续的4KB空间,存在这个进程的第一级页表,并且将第一级页表在物理内存中起始地址放到PTR寄存器中,在ARM是TTB寄存器,X86是CR3寄存器等。随着这个进程的进行,操作系统会逐步在物理内存中创建第二级页表,每次创建一个第二级页表,操作系统就要将它的起始地址放到第一级页表对应的表项中。

在很多硬件实现Page Table Walk的处理器中,都采用了多级页表的结构。Page Table Walk是指当发生TLB缺失时,需要从页表中找到对应的映射关系并将其写回到TLB的过程。

使用这种多级页表结构,每一级的页表都需要存储在物理内存中,因此要得到一个虚拟地址对应的数据,需要多次访问物理内存。显然,这个过程消耗的时间是很长。对于一个二级页表,需要访问两次物理内存,才能得到虚拟地址对应的物理地址,然后还需要访问一次物理内存得到数据,因此要得到虚拟地址对应的数据,共需要访问三次物理内存。

使用虚拟存储器的优点总结:

(1)让每个程序都有独立的地址空间。

(2)引入虚拟地址到物理地址的映射,为物理内存的管理带来了方便,可以更灵活地对其进行分配和释放,在虚拟存储器上连续的地址空间可以映射到物理内存上不连续的空间。

(3)在处理中如果存在多个进程,为这些进程分配的物理内存之和可能大于实际可用的物理内存,虚拟存储器的管理使得这种情况下各个进程仍能够正常运行,此时为各个进程分配的只是虚拟存储器的页,这些页可能存在物理内存中,也可能临时存在于更下一级的硬盘中,在硬盘中这部分空间被称为swap空间。当物理内存不够用时,将物理内存中一些不常用的页保存到硬盘上的swap空间。因此处理器等效可以使用的物理内存的总量是物理内存的大小 + 硬盘中swap空间的大小。

(4)利用虚拟存储器,可以管理每一个页的访问权限。从硬件的角度来看,单纯的物理内存本身不具有各种权限的属性,它的任何地址都可以被读写,而操作系统则要求在物理内存中实现不同的访问权限。例如一个进程的代码段(text)一般不能被修改,而数据段(data)一般是可读可写的。这些权限的管理是通过页表来实现的,在页表中设置每个页的属性,操作系统和内存管理单元MMU可以控制每个页的访问权限。

三、TLB工作原理

首先,我们知道MMU的作用是把虚拟地址转换成物理地址。虚拟地址和物理地址的映射关系存储在页表中,而现在页表又是分级的。64位系统一般都是3~5级。常见的配置是4级页表,就以4级页表为例说明。分别是PGD、PUD、PMD、PTE四级页表。在硬件上会有一个叫做页表基地址寄存器,它存储PGD页表的首地址。MMU就是根据页表基地址寄存器从PGD页表一路查到PTE,最终找到物理地址(PTE页表中存储物理地址)。

这就像在地图上显示你的家在哪一样,我为了找到你家的地址,先确定你是中国,再确定你是某个省,继续往下某个市,最后找到你家是一样的原理。一级一级找下去。这个过程你也看到了,非常繁琐。如果第一次查到你家的具体位置,我如果记下来你的姓名和你家的地址。下次查找时,是不是只需要跟我说你的姓名是什么,我就直接能够告诉你地址,而不需要一级一级查找。四级页表查找过程需要四次内存访问。延时可想而知,非常影响性能。

3.1地址转换流程

当 CPU 访问内存时,首先会生成一个虚拟地址,这个虚拟地址会被发送到 TLB 中进行查找。如果 TLB 中存储了该虚拟地址对应的物理地址,这就是所谓的 “TLB 命中”。此时,CPU 可以直接从 TLB 中获取物理地址,并使用该物理地址去访问内存,这大大缩短了内存访问的时间。

但如果 TLB 中没有找到对应的映射关系,即发生了 “TLB 缺失”,CPU 就需要访问内存中的页表。页表通常是多级结构,以 x86 架构的 4 级页表为例,CPU 需要从页表基地址寄存器开始,一级一级地查找,通过索引找到下一级页表的物理地址,最终找到包含虚拟地址到物理地址映射关系的页表项,获取物理地址。这个过程需要多次访问内存,非常耗时。

在通过页表获取到物理地址后,CPU 会将这次的虚拟地址到物理地址的映射关系存入 TLB 中,以便下次访问相同虚拟地址时能够直接在 TLB 中命中,提高访问效率。

3.2TLB原理

当cpu要访问一个虚拟地址/线性地址时,CPU会首先根据虚拟地址的高20位(20是x86特定的,不同架构有不同的值)在TLB中查找。如果是表中没有相应的表项,称为TLB miss,需要通过访问慢速RAM中的页表计算出相应的物理地址。同时,物理地址被存放在一个TLB表项中,以后对同一线性地址的访问,直接从TLB表项中获取物理地址即可,称为TLB hit。

想像一下x86_32架构下没有TLB的存在时的情况,对线性地址的访问,首先从PGD中获取PTE(第一次内存访问),在PTE中获取页框地址(第二次内存访问),最后访问物理地址,总共需要3次RAM的访问。如果有TLB存在,并且TLB hit,那么只需要一次RAM访问即可。

⑴TLB的特殊

虚拟地址映射物理地址的最小单位是4KB。所以TLB其实不需要存储虚拟地址和物理地址的低12位(因为低12位是一样的,根本没必要存储)。另外,我们如果命中cache,肯定是一次性从cache中拿出整个数据。所以虚拟地址不需要offset域。index域是否需要呢?这取决于cache的组织形式。如果是全相连高速缓存。那么就不需要index。如果使用多路组相连高速缓存,依然需要index。下图就是一个四路组相连TLB的例子。现如今64位CPU寻址范围并没有扩大到64位。64位地址空间很大,现如今还用不到那么大。因此硬件为了设计简单或者解决成本,实际虚拟地址位数只使用了一部分。这里以48位地址总线为了例说明。

⑵TLB的别名问题

我先来思考第一个问题,别名是否存在。我们知道PIPT的数据cache不存在别名问题。物理地址是唯一的,一个物理地址一定对应一个数据。但是不同的物理地址可能存储相同的数据。也就是说,物理地址对应数据是一对一关系,反过来是多对一关系。由于TLB的特殊性,存储的是虚拟地址和物理地址的对应关系。

因此,对于单个进程来说,同一时间一个虚拟地址对应一个物理地址,一个物理地址可以被多个虚拟地址映射。将PIPT数据cache类比TLB,我们可以知道TLB不存在别名问题。而VIVT Cache存在别名问题,原因是VA需要转换成PA,PA里面才存储着数据。中间多经传一手,所以引入了些问题。

⑶TLB的歧义问题

我们知道不同的进程之间看到的虚拟地址范围是一样的,所以多个进程下,不同进程的相同的虚拟地址可以映射不同的物理地址。这就会造成歧义问题。例如,进程A将地址0x2000映射物理地址0x4000。进程B将地址0x2000映射物理地0x5000。

当进程A执行的时候将0x2000对应0x4000的映射关系缓存到TLB中。当切换B进程的时候,B进程访问0x2000的数据,会由于命中TLB从物理地址0x4000取数据。这就造成了歧义。如何消除这种歧义,我们可以借鉴VIVT数据cache的处理方式,在进程切换时将整个TLB无效。切换后的进程都不会命中TLB,但是会导致性能损失。

⑷TLB表项

TLB内部存放的基本单位是页表条目,对应着RAM中存放的页表条目。页表条目的大小固定不变的,所以TLB容量越大,所能存放的页表条目越多,TLB hit的几率也越大。但是TLB容量毕竟是有限的,因此RAM页表和TLB页表条目无法做到一一对应。因此CPU收到一个线性地址,那么必须快速做两个判断:

所需的也表示否已经缓存在TLB内部(TLB miss或者TLB hit)

所需的页表在TLB的哪个条目内

为了尽量减少CPU做出这些判断所需的时间,那么就必须在TLB页表条目和内存页表条目之间的对应方式做足功夫。

⑸全相连 - full associative

在这种组织方式下,TLB cache中的表项和线性地址之间没有任何关系,也就是说,一个TLB表项可以和任意线性地址的页表项关联。这种关联方式使得TLB表项空间的利用率最大。但是延迟也可能相当的大,因为每次CPU请求,TLB硬件都把线性地址和TLB的表项逐一比较,直到TLB hit或者所有TLB表项比较完成。特别是随着CPU缓存越来越大,需要比较大量的TLB表项,所以这种组织方式只适合小容量TLB。

⑹直接匹配

每一个线性地址块都可通过模运算对应到唯一的TLB表项,这样只需进行一次比较,降低了TLB内比较的延迟。但是这个方式产生冲突的几率非常高,导致TLB miss的发生,降低了命中率。

比如,我们假定TLB cache共包含16个表项,CPU顺序访问以下线性地址块:1, 17 , 1, 33。当CPU访问地址块1时,1 mod 16 = 1,TLB查看它的第一个页表项是否包含指定的线性地址块1,包含则命中,否则从RAM装入;然后CPU方位地址块17,17 mod 16 = 1,TLB发现它的第一个页表项对应的不是线性地址块17,TLB miss发生,TLB访问RAM把地址块17的页表项装入TLB;CPU接下来访问地址块1,此时又发生了miss,TLB只好访问RAM重新装入地址块1对应的页表项。因此在某些特定访问模式下,直接匹配的性能差到了极点

⑺组相连 - set-associative

为了解决全相连内部比较效率低和直接匹配的冲突,引入了组相连。这种方式把所有的TLB表项分成多个组,每个线性地址块对应的不再是一个TLB表项,而是一个TLB表项组。CPU做地址转换时,首先计算线性地址块对应哪个TLB表项组,然后在这个TLB表项组顺序比对。按照组长度,我们可以称之为2路,4路,8路。

经过长期的工程实践,发现8路组相连是一个性能分界点。8路组相连的命中率几乎和全相连命中率几乎一样,超过8路,组内对比延迟带来的缺点就超过命中率提高带来的好处了。

这三种方式各有优缺点,组相连是个折衷的选择,适合大部分应用环境。当然针对不同的领域,也可以采用其他的cache组织形式。

⑻LB表项更新

TLB表项更新可以有TLB硬件自动发起,也可以有软件主动更新

1. TLB miss发生后,CPU从RAM获取页表项,会自动更新TLB表项

2. TLB中的表项在某些情况下是无效的,比如进程切换,更改内核页表等,此时CPU硬件不知道哪些TLB表项是无效的,只能由软件在这些场景下,刷新TLB。

在linux kernel软件层,提供了丰富的TLB表项刷新方法,但是不同的体系结构提供的硬件接口不同。比如x86_32仅提供了两种硬件接口来刷新TLB表项:

1. 向cr3寄存器写入值时,会导致处理器自动刷新非全局页的TLB表项

2. 在Pentium Pro以后,invlpg汇编指令用来无效指定线性地址的单个TLB表项无效。

3.3映射方式剖析

虚地址与 TLB 中项的映射方式主要有以下三种:

全关联映射:在全关联映射方式下,TLB 中的任何一个表项都可以存储任何虚拟地址的映射关系。这种映射方式的优点是灵活性高,TLB 表项空间的利用率最大,只要 TLB 中还有空闲表项,就可以存储新的映射关系 。但缺点也很明显,每次查找时,TLB 硬件都需要把虚拟地址和 TLB 中的所有表项逐一进行比较,直到找到匹配的表项或者所有表项都比较完。

随着 CPU 缓存越来越大,TLB 中的表项数量也可能增多,这种比较操作的延迟会相当大,所以全关联映射方式只适合小容量的 TLB。例如,一个只有 8 个表项的小容量 TLB,采用全关联映射时,虽然查找时需要逐个比较,但由于表项数量少,延迟还在可接受范围内,且能充分利用表项空间。

直接映射:直接映射方式是指每一个虚拟地址只能映射到 TLB 中唯一的一个表项。这种映射方式的优点是查找速度快,只需进行一次比较。例如,假设内存页大小是 8KB,TLB 中有 64 项,虚拟地址的 13 - 18bit 作为 TLB 表项的索引。当虚拟地址的 13 - 18bit 确定后,就可以直接定位到 TLB 中的某一个表项进行比较。但直接映射方式产生冲突的几率非常高,导致 TLB miss 的发生,降低了命中率。比如,CPU 顺序访问线性地址块 1 和 17,由于 1 mod 64 和 17 mod 64 得到的索引相同,当访问完 1 后再访问 17 时,就会发生冲突,导致 TLB miss,需要重新从内存中加载页表项。

分组关联映射:分组关联映射方式结合了全关联映射和直接映射的优点,它把 TLB 中的所有表项分成多个组,每个虚拟地址对应的不再是一个 TLB 表项,而是一个 TLB 表项组。CPU 进行地址转换时,首先计算虚拟地址对应哪个 TLB 表项组,然后在这个组内顺序比对。按照组长度,可以分为 2 路、4 路、8 路等。经过长期的工程实践发现,8 路组关联是一个性能分界点,8 路组关联的命中率几乎和全关联命中率一样,超过 8 路,组内对比延迟带来的缺点就会超过命中率提高带来的好处 。例如,采用 4 路组关联时,将 TLB 表项分为多个 4 个表项一组的组,虚拟地址通过计算索引确定对应的组,然后在组内 4 个表项中查找,这样既减少了冲突的发生,又在一定程度上控制了查找延迟。

四、TLB缓存机制

4.1MMU

MMU:memory management unit,称为内存管理单元,或者是存储器管理单元,MMU是硬件设备,它被保存在主存(main memory)的两级也表控制,并且是由协处理器CP15的寄存器1的M位来决定是enabled还是disabled。MMU的主要作用是负责从CPU内核发出的虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。

MMU使得每个用户进程拥有自己的地址空间(对于WINCE5.0,每个进程是32MB;而对于WINCE6.0,每个进程的独占的虚拟空间是2GB),并通过内存访问权限的检查保护每个进程所用的内存不被其他进程破坏,它提供了以下功能和特征:

虚拟内存管理:MMU将程序使用的虚拟地址转换为物理地址,实现了对虚拟内存空间的管理。它允许每个程序有自己独立的地址空间,并且可以在不同程序之间共享物理内存。

地址映射:MMU通过页表或段表来实现虚拟地址到物理地址的映射。它根据页表或段表中的映射规则,将虚拟地址转换为对应的物理地址。

内存保护:MMU可以设置页面级别或段级别的权限控制,以保护操作系统和应用程序不被非法访问。例如,可以限制某些页面只读、只执行或禁止访问。

虚拟化支持:在虚拟化环境中,MMU可以实现虚拟机与物理机之间的隔离和映射。

缓存控制:MMU可以进行缓存一致性控制,确保数据在主存与缓存之间的一致性。

TLB(Translation Lookaside Buffer):MMU通常会包含一个TLB,用于缓存最近的地址映射结果,以加速地址转换过程。

(1)VA和PA

VA(Virtual Address)是程序在运行时使用的虚拟地址。它由MMU(Memory Management Unit,内存管理单元)根据页表或段表的映射规则进行转换,将其映射为对应的PA。

PA(Physical Address)是实际存在于物理内存中的物理地址。它指向计算机硬件中实际存储单元的位置。

通过使用虚拟内存管理技术,操作系统将程序使用的虚拟地址空间与物理内存进行映射。这样,每个程序都有自己独立的虚拟地址空间,而不需要关心真正分配给它们的物理内存位置。

当程序访问某个虚拟地址时,MMU会将其转换为对应的物理地址,并且确保访问权限合法。这样,程序可以以统一的方式访问内存,并且操作系统可以更好地管理和保护整个系统中的内存资源。

CPU通过地址来访问内存中的单元,如果CPU没有MMU,或者有MMU但没有启动,那么CPU内核在取指令或者访问内存时发出的地址(此时必须是物理地址,假如是虚拟地址,那么当前的动作无效)将直接传到CPU芯片的外部地址引脚上,直接被内存芯片(物理内存)接收,这时候的地址就是物理地址。

如果CPU启用了MMU(一般是在bootloader中的eboot阶段的进入main()函数的时候启用),CPU内核发出的地址将被MMU截获,这时候从CPU到MMU的地址称为虚拟地址,而MMU将这个VA翻译成为PA发到CPU芯片的外部地址引脚上,也就是将VA映射到PA中。

MMU将VA映射到PA是以页(page)为单位的,对于32位的CPU,通常一页为4k,物理内存中的一个物理页面称页为一个页框(page frame)。虚拟地址空间划分成称为页(page)的单位,而相应的物理地址空间也被进行划分,单位是页框(frame).页和页框的大小必须相同。

(2)VA到PA的映射过程

首先将CPU内核发送过来的32位VA[31:0]分成三段,前两段VA[31:20]和VA[19:12]作为两次查表的索引,第三段VA[11:0]作为页内的偏移,查表的步骤如下:

⑴从协处理器CP15的寄存器2(TTB寄存器,translation table base register)中取出保存在其中的第一级页表(translation table)的基地址,这个基地址指的是PA,也就是说页表是直接按照这个地址保存在物理内存中的。

⑵以TTB中的内容为基地址,以VA[31:20]为索引值在一级页表中查找出一项(2^12=4096项),这个页表项(也称为一个描述符,descriptor)保存着第二级页表(coarse page table)的基地址,这同样是物理地址,也就是说第二级页表也是直接按这个地址存储在物理内存中的。

⑶以VA[19:12]为索引值在第二级页表中查出一项(2^8=256),这个表项中就保存着物理页面的基地址,我们知道虚拟内存管理是以页为单位的,一个虚拟内存的页映射到一个物理内存的页框,从这里就可以得到印证,因为查表是以页为单位来查的。

⑷有了物理页面的基地址之后,加上VA[11:0]这个偏移量(2^12=4KB)就可以取出相应地址上的数据了。

这个过程称为Translation Table Walk,Walk这个词用得非常形象。从TTB走到一级页表,又走到二级页表,又走到物理页面,一次寻址其实是三次访问物理内存。注意这个“走”的过程完全是硬件做的,每次CPU寻址时MMU就自动完成以上四步,不需要编写指令指示MMU去做,前提是操作系统要维护页表项的正确性,每次分配内存时填写相应的页表项,每次释放内存时清除相应的页表项,在必要的时候分配或释放整个页表。

(3)CPU访问内存时的硬件操作顺序

CPU访问内存时的硬件操作顺序,各步骤在图中有对应的标号:

1.CPU内核(图中的ARM)发出VA请求读数据,TLB(translation lookaside buffer)接收到该地址,那为什么是TLB先接收到该地址呢?因为TLB是MMU中的一块高速缓存(也是一种cache,是CPU内核和物理内存之间的cache),它缓存最近查找过的VA对应的页表项,如果TLB里缓存了当前VA的页表项就不必做translation table walk了,否则就去物理内存中读出页表项保存在TLB中,TLB缓存可以减少访问物理内存的次数。

2.页表项中不仅保存着物理页面的基地址,还保存着权限和是否允许cache的标志。MMU首先检查权限位,如果没有访问权限,就引发一个异常给CPU内核。然后检查是否允许cache,如果允许cache就启动cache和CPU内核互操作。

3.如果不允许cache,那直接发出PA从物理内存中读取数据到CPU内核。

4.如果允许cache,则以VA为索引到cache中查找是否缓存了要读取的数据

如果cache中已经缓存了该数据(称为cache hit)则直接返回给CPU内核,如果cache中没有缓存该数据(称为cache miss),则发出PA从物理内存中读取数据并缓存到cache中,同时返回给CPU内核。但是cache并不是只去CPU内核所需要的数据,而是把相邻的数据都去上来缓存,这称为一个cache line。ARM920T的cache line是32个字节,例如CPU内核要读取地址0x30000134~0x3000137的4个字节数据,cache会把地址0x30000120~0x3000137(对齐到32字节地址边界)的32字节都取上来缓存。

(4)ARM920T支持多种尺寸规格的页表

ARM体系结构最多使用两级页表来进行转换,页表由一个个条目组成,每个条目存储一段虚拟地址对应的物理地址及访问权限,或者下一级页表的地址。S3C2443最多会用到两级页表,已段(section,大小为1M)的方式进行转换时只用到一级页表,以页(page)的方式进行转换时用到两级页表。而页的大小有3种:大页(large pages,64KB),小页(small pages,4KB)和极小页(tiny pages,1KB)。条目也成为描述符,有段描述符、大页描述符、小页描述符和极小页描述符,分别保存段、大页、小页和极小页的起始物理地址。

MMU的查表过程,首先从CP15的寄存器TTB找到一级页表的基地址,再把VA[31:20]作为索引值从表中找出一项,这个表项称为一级页描述符(level one descriptor),一个这样的表项占4个字节,那么一级页表需要保存的物理内存的大小是4*4096=16KB,表项可以是以下四种格式之一:

⑴如果描述符的最低位是00,属于fault格式,表示该范围的VA没有映射到PA。

⑵如果描述符的最低位是10,属于section格式,这种格式没有二级页表而是直接映射到物理页面,一个色彩体哦你是1M的大页面,描述符中的VA[31:20]就是这个页面的基地址,基地址的VA[19:0]低位全为0,对齐到1M地址边界,描述符中的domain和AP位控制访问权限,C、B两位控制缓存。

⑶如果描述符的最低两位是01或11,则分别对应两种不同规格的二级页表。根据地址对齐的规律想一下,这两种页表分别是多大?从一级描述符中取出二级页表的基地址,再把VA的一部分作为索引去查二级描述符(level two descriptor),如果是coarse page,则VA[19:12](2^8=256)作为查找二级页表表项的索引;如果是fine page,则VA[19:10](2^10=024)。

(5)二级描述符可以是下面四种格式之一:

二级描述符最低两位是00是属于fault格式,其它三种情况分别对应三种不同规格的物理页面,分别是large page(64KB)、small page(4KB)和tiny page(1KB),其中large page和small page有4组AP权限位,每组两个bit,这样可以为每1/4个物理页面分别设置不同的权限,也就是说large page可以为每16KB设置不同的权限,small page可以为每1KB设置不同的权限。

ARM920T提供了多种页表和页面规格,但操作系统只采用其中一种,WINCE采用的就是一级描述符是coarse page table格式(也即由VA[19:12]来作为查找二级页表项的索引),二级描述符是small page格式(也即是VA[11:0]来作为查找物理页面偏移量的索引),每个物理页面大小是4KB。

Translation table walk是指在虚拟地址转换为物理地址的过程中,操作系统或硬件通过遍历页表或段表来完成地址映射的过程。

具体步骤如下:

获取虚拟地址:首先,根据程序的执行情况,获取需要进行地址转换的虚拟地址。

访问页表(或段表)项:根据操作系统或硬件的设计,从页表(或段表)中找到对应的页表项(或段描述符)。这一步通常涉及多级页表结构中的索引计算和查找。

检查权限位:在访问到页表项(或段描述符)后,需要检查相关权限位以确保访问合法。比如读写执行权限、特权级等。

转换为物理地址:如果权限检查通过,则根据页表项中存储的物理页面框号或段基址与虚拟地址中的偏移量进行计算,得到相应的物理地址。

返回结果:将最终得到的物理地址返回给CPU或其他需要使用该地址的部件,使其能够正确地访问所需数据。

这个过程可以由操作系统内核中的内存管理单元(MMU)硬件自动完成,也可以由软件实现。不同操作系统和处理器架构可能会有不同实现方式和细节。但总体思路是通过页表或段表的遍历,将虚拟地址转换为物理地址,以实现内存访问的正确性和安全性。

4.2 Cache的设计

TLB只是加速了虚拟地址到物理地址的转换,可以很快地得到所需要的数据或指令在物理内存中的位置,也就是得到了物理地址,但如果直接从物理内存中取数据或指令,显然也是很慢的。因此可以用Cache来缓存物理地址到数据的转换过程。这种Cache使用物理地址进行寻址,因此称为物理Cache。使用TLB和物理cache一起工作的过程如下图:

如果不使用虚拟存储器,处理器送出的地址会直接访问物理Cache,而现在要先经过TLB才能再访问物理Cache,因此必然会增加流水线的延迟,如果还想获得和以前一样的运行频率,就需要将访问TLB的过程单独使用一级流水线,但是这样就增加了分支预测失败时的惩罚(penalty),也增加了load指令的延迟,不是一个很好的做法。

为什么不使用Cache来直接缓存从虚拟地址到数据的关系呢?当然是可以的,因为这个Cache使用虚拟地址来寻址,称之为虚拟Cache。既然使用虚拟Cache,可以直接从虚拟地址得到对应的数据,那么是不是就不使用TLB了呢?当然不是这样,因为一旦Cache发生miss,仍然需要将对应的虚拟地址转换为物理地址,然后去物理内存中获得对应的数据,因此还需要TLB来加速从虚拟地址到物理地址的转换过程。

如果使用虚拟地址,从虚拟Cache中找到了需要的数据,就不需要再访问TLB和物理内存。在流水线中使用这种虚拟Cache,不会对处理器的时钟产生明显的负面影响,不过它会引起一些新的问题,需要耗费额外的硬件进行解决。

MMU(Memory Management Unit):MMU是一种硬件或软件模块,负责处理虚拟地址到物理地址的转换。它在操作系统的支持下,通过页表或段表来实现地址映射。主要功能包括:

地址转换:将虚拟地址映射为对应的物理地址。

内存保护:根据权限位检查访问权限,防止非法访问。

内存共享:实现多个进程之间的内存共享。

页面置换:当页面不在内存中时,触发页面置换机制将其加载到内存中。

Cache:Cache是一种高速缓存,在CPU与主内存之间起到缓冲作用。其目的是提高数据访问速度和性能。主要功能包括:

缓存命中:当CPU请求读取数据时,先在Cache中查找是否存在该数据,如果存在则直接返回给CPU,称为命中;否则称为缺失。

缓存替换:当Cache已满且需要新的数据时,会使用某种策略选择一个被替换出去的缓存行。

缓存写策略:控制数据在何时写回主内存,可以是写回(write-back)或直写(write-through)策略。

缓存一致性:确保多级缓存中的数据一致性,避免数据不一致问题。

Cache利用了局部性原理,即数据访问往往具有时间和空间上的局部性。因此,通过将频繁访问的数据存储在高速缓存中,可以大大减少对主内存的访问次数,从而提高计算机系统的整体性能。

五、TLB 缓存的独特之处

5.1与普通缓存的区别

在计算机的缓存体系中,TLB 缓存和普通数据缓存虽然都起着加速数据访问的作用,但它们之间存在着显著的区别 。

普通数据缓存,如 L1、L2 和 L3 缓存,主要用于缓存实际的数据。这些缓存存储着 CPU 近期可能会访问的数据,当 CPU 需要访问数据时,首先会在这些普通数据缓存中查找。如果能够在缓存中找到所需的数据,就可以直接从缓存中读取,避免了对速度较慢的内存的访问,从而大大提高了数据访问的速度。例如,在一个简单的整数加法运算中,CPU 需要读取参与运算的两个整数。如果这两个整数已经被缓存在 L1 缓存中,CPU 就可以直接从 L1 缓存中获取它们,而无需访问内存,这使得运算能够快速完成。

而 TLB 缓存则有着不同的使命,它主要缓存的是页表数据,其目的是加速虚拟地址到物理地址的转换过程 。在现代计算机系统中,由于采用了虚拟内存机制,程序使用的是虚拟地址,而实际的数据存储在物理内存中。为了访问物理内存中的数据,需要将虚拟地址转换为物理地址,这个转换过程依赖于页表。页表存储了虚拟地址与物理地址的映射关系,但由于页表通常存储在内存中,访问内存中的页表会带来较大的延迟。

TLB 缓存作为页表的高速缓存,存储了近期最常访问的页表项。当 CPU 需要进行地址转换时,会首先在 TLB 缓存中查找,如果能够在 TLB 缓存中找到对应的映射关系,就可以快速完成地址转换,大大减少了地址转换的时间。比如,当一个程序需要访问某个虚拟地址的数据时,CPU 会先将该虚拟地址发送到 TLB 缓存中进行查找。如果 TLB 缓存命中,CPU 就可以立即获取到对应的物理地址,并使用该物理地址去访问内存中的数据;如果 TLB 缓存未命中,CPU 才会去访问内存中的页表,进行地址转换,这个过程会花费更多的时间。

从缓存的数据类型和作用来看,普通数据缓存关注的是数据本身的快速访问,而 TLB 缓存关注的是地址转换的加速,它们在计算机系统中扮演着不同但又相互配合的角色,共同为提高计算机系统的性能而努力 。

5.2特殊的虚拟地址映射

TLB 缓存中的虚拟地址和物理地址映射关系具有一些独特的特点。

首先,内存映射的最小单位是页,通常一页的大小为 4KB 。这就意味着 TLB 在存储虚拟地址和物理地址的映射关系时,是以页为单位进行的。由于页的大小是固定的,所以 TLB 实际上不需要存储虚拟地址和物理地址的低 12 位。因为对于同一页内的不同地址,其低 12 位表示的是页内偏移,在虚拟地址和物理地址中是相同的,所以没有必要存储这部分信息。

例如,对于一个虚拟地址 0x12345678,其中低 12 位 0x678 表示的是页内偏移,在对应的物理地址中,这部分偏移也是相同的,TLB 只需要存储虚拟地址的高 32 位(假设是 32 位系统)中与页表相关的部分,以及对应的物理地址的高 32 位中与页框相关的部分,就可以完整地表示这个虚拟地址到物理地址的映射关系 。

其次,当 TLB 命中时,会一次性从缓存中拿出整个物理地址,这与数据缓存操作的最小单位不同 。在数据缓存中,通常是以缓存行(cache line)为单位进行数据的读取和写入,缓存行的大小一般比一页要小得多。而 TLB 操作的最小单位是一个物理地址,这是因为 TLB 的主要作用是提供地址转换服务,一旦命中,就需要提供完整的物理地址,以便 CPU 能够准确地访问内存中的数据。

另外,TLB 中虚拟地址是否需要 index 域,取决于 cache 的组织形式 。如果是全相连高速缓存,那么 TLB 中不需要 index 域。在全相连高速缓存中,任何一个虚拟地址都可以映射到 TLB 中的任何一个表项,查找时需要将虚拟地址与 TLB 中的所有表项进行比较,所以不需要 index 域来确定查找的位置。而如果使用多路组相连高速缓存,TLB 中依然需要 index 域。

例如,在一个 4 路组相连的 TLB 中,虚拟地址的一部分会被用来计算 index 值,通过这个 index 值可以确定该虚拟地址对应的 TLB 表项组,然后在组内的 4 个表项中进行查找,这样可以在一定程度上提高查找的效率,同时也减少了冲突的发生。

六、实际应用与优化策略

6.1在操作系统中的角色

在操作系统中,TLB 扮演着至关重要的角色,是内存管理的关键环节 。

进程切换是操作系统中常见的操作,当从一个进程切换到另一个进程时,地址空间也会发生变化。由于不同进程拥有独立的虚拟地址空间,相同的虚拟地址在不同进程中可能会映射到不同的物理地址。例如,进程 A 中虚拟地址 0x1000 可能映射到物理地址 0x20000,而在进程 B 中,虚拟地址 0x1000 却映射到物理地址 0x30000 。如果在进程切换时不进行相应处理,TLB 中缓存的前一个进程的地址映射关系就会影响新进程的地址转换,导致错误的内存访问。

为了解决这个问题,早期的做法是在进程切换时直接清空整个 TLB。这样虽然保证了新进程不会使用到旧进程错误的地址映射,但也带来了性能问题。因为清空 TLB 后,新进程开始执行时,TLB 中没有任何有效的映射关系,会导致大量的 TLB 缺失,CPU 需要频繁地访问内存中的页表来进行地址转换,这大大增加了内存访问的延迟,降低了程序的运行效率 。

后来,ASID(地址空间标识符)的引入有效地减少了进程切换时清空 TLB 的需求 。每个进程都被分配一个唯一的 ASID,TLB 中的每个表项除了包含虚拟地址和物理地址的映射关系外,还包含所属进程的 ASID。当进行进程切换时,如果新进程的 ASID 与旧进程不同,TLB 会识别出这是不同进程的地址映射,不会使用旧进程的映射关系,从而避免了地址混淆。而如果新进程的 ASID 与之前某个进程相同,并且之前该 ASID 对应的 TLB 条目未被覆盖或刷新,那么这些映射仍然有效,可以直接使用,无需重新加载页表项,这就大大提高了进程切换时的效率,减少了因 TLB 清空和重新加载带来的性能损耗 。

6.2优化内存访问效率的方法

(1)代码编写技巧

在代码编写过程中,有一些技巧可以提高 TLB 命中率,进而优化内存访问效率 。

连续访问内存:充分利用程序访问的局部性原理,尽量保证对内存的访问是连续的。例如,在遍历数组时,按照数组元素在内存中的存储顺序依次访问。假设有一个整型数组int array[100];,如果顺序访问for (int i = 0; i < 100; i++) { int value = array[i]; },这样内存访问具有良好的空间局部性,当访问一个内存页中的元素时,该页中的其他元素很可能也会被访问到,从而提高了 TLB 的命中率。相反,如果随机访问数组元素,如for (int i = 0; i < 100; i++) { int index = rand() % 100; int value = array[index]; },会导致频繁的 TLB 缺失,因为每次访问的内存页可能都不同,TLB 很难缓存到有效的映射关系。

合理安排数据结构:根据数据的访问模式来设计数据结构。对于经常一起访问的数据,尽量将它们放在相邻的内存位置。比如,在设计一个游戏角色的数据结构时,如果角色的位置、生命值、攻击力等属性经常被同时访问,就可以将这些属性紧密排列在结构体中。struct GameCharacter { int positionX; int positionY; int health; int attackPower; };,这样在访问角色的某个属性时,其他相关属性很可能也在同一个内存页中,提高了 TLB 命中率。而如果将这些属性分散在不同的结构体或内存位置,就会增加 TLB 缺失的概率 。

(2)硬件与软件协同

硬件设计和操作系统管理对 TLB 性能的协同优化作用至关重要 。

在硬件方面,增加 TLB 的容量可以缓存更多的虚拟地址到物理地址的映射关系,从而提高命中率。例如,将 TLB 的条目数从 64 个增加到 128 个,就能存储更多近期使用过的页表项,减少访问内存中页表的次数 。优化 TLB 的结构,采用更先进的缓存组织方式,如多级 TLB 结构或采用关联性更强的缓存结构,也可提升查找效率和命中率。例如,使用组相联缓存结构,相比直接映射缓存,能在同一组内存储多个页表项,降低因地址冲突导致的缓存未命中情况 。

操作系统层面也有很多优化措施,合理选择页面大小可以减少页表项的数量,从而提高页表缓存的命中率。较大的页面可以减少页表的大小,使更多的页表项能够放入缓存中,但可能会导致内存碎片增加;较小的页面则可以更精细地分配内存,但会增加页表的大小。操作系统需要根据具体的硬件环境和应用场景来选择合适的页面大小 。

采用高效的内存分配算法,使进程的内存分配更具局部性,让相关的页表项更有可能被连续访问,从而提高页表缓存命中率。像伙伴系统算法、slab分配器等,能根据进程的需求合理分配内存,减少内存碎片,提高内存访问的局部性 。

只有硬件和软件相互配合,共同优化,才能充分发挥 TLB 的作用,最大程度地提高内存访问效率,提升计算机系统的整体性能 。

相关推荐

外贸订单哪里来?多平台接单全攻略 – 跨境品牌帮
印度電影影評《 宿敵 》評價心得、解釋、結局、劇情梳理:法律和正義不總是一樣的
在线时钟 - 简化世界时间
office365打不开doc文件

在线时钟 - 简化世界时间

📅 08-06 👁️ 2828