天天看点

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

目录

0、回顾

1、TLB

1.1、TLB coherency

2、MMU

2.1、TTBR0、TTBR1、TTBCR

2.1.1、TTBCR

2.1.2、TTBR0、TTBR1

2.2、Translation Table

2.2.1、L1 Address Translation

2.2.2、L2 Address Translation

3、OS Usage Of Translation Tables

3.1、Address Space ID(ASID)

3.2、TTBR0、TTBR1

之前写过 MMU 的一些入门和基础的分析《初探 MMU》和《ARMv7-A 的 MMU 浅析》,有基于概念掌握和基本入门的一些理解,这里打算在针对 ARMv7-A 的处理器再次稍微深入一点研究一下他的 MMU 和 TLB;

这一版同样基于 ARM 官方文档:

ARMv7-A_and_R_Architecture_Reference_Manual

DEN0013D_cortex_a_series_PG

的一个是完整版的 ARMv7-A 的处理器架构文档,第二个是 Cortex-A 系列的 Programmer Guide;

由于有前面的两篇文章垫底(《初探 MMU》和《ARMv7-A 的 MMU 浅析》),这里就不再介绍一些基础的内容了,直接单刀直入;

0、回顾

前面知道,MMU 用作虚拟地址和物理地址的相互转换,是为了能够给 OS 提供统一视角的虚拟地址空间;

TLB 的作用是作为 MMU 的 Cache,以提高 MMU 的性能,他们之间的关系如下:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

1、ARM 处理器发出地址访问(虚拟地址),首先过 MMU 地址翻译单元的 TLB,如果 TLB 命中,那么直接返回真实的物理地址;

2、如果 TLB Miss,那么就要靠 Table Walk 单元去主存中查找表,以获取物理地址,然后通过 Cache,去访问;

3、Cache 如果命中,那么直接返回实际物理地址的数据,否则,也就是最糟糕的情况,会去访问主存的数据;

上面的过程呢,软件要做的,只有配置并放好这个 Transliation Tables,其他的过程,全部是硬件行为;下面马上仔细的过这部分的细节;

使能 MMU 的参考代码(因为是 CP15 的系统控制寄存器,所以使用 MRC/MCR 指令):

MRC p15, 0, R1, c1, C0, 0 ;Read control register
ORR R1, #0x1 ;Set M bit
MCR p15, 0,R1,C1, C0,0 ;Write control register and enable MMU
           

这里要注意的一点是,可能要用到内存屏障指令,因为这里就开启了 MMU,即将进入虚拟内存的世界,要确保在这之前,流水线干净,所以执行已经完毕;

1、TLB

TLB 的全称是:Translation Lookaside Buffer;从第一节的那个图可以看出来,MMU 做 Table Walk 的这个 Transliation Tables 是放到主存中,主存访问速度很慢(加 Cache 的根本原因),所以,这里每次都去再主存中做 Table Walk,显然效率非常低,所以,这里就为这个 Table Walk 定制了一个属于他的 “Cache”,称之为 TLB;

但是与 真是的 Cache 不一样(详见《ARMv7-A 处理器窥探(4) —— Cache》),这个 TLB 是专门缓存 Transliation Tables 的,典型的情况,他的组成如下:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

由 VA、ASID、PA、Attributes 组成,即:

VA:虚拟地址;

PA:物理地址;

ASID:Address Space ID;

Attributes:属性;

1.1、TLB coherency

TLB 既然扮演的 Transliation Tables Cache 的角色,那么也会有一致性问题,最典型的就是再 OS 中,上下文切换的时候,上一个进程的虚拟地址对应的物理地址表,肯定是和另一个不一样,导致 TLB 一致性问题;此刻,OS 必须处理这种情况,使得上一个进程的 TLB 对下一个失效,也可以直接通过 CP15 控制寄存器,来 flush 掉 TLB(代价太大);

2、MMU

这里,抛开大物理地址扩展和 section 和 supersection 的分析,暂时就看最最常用的两段查找;两段页表查找,我们称第一级页表为 L1,第二级为 L2;

2.1、TTBR0、TTBR1、TTBCR

前面知道,软件需要负责构建这个虚拟地址到物理地址的转换表:Transliation Tables,当软件构件完毕这个表后,只需要告诉硬件,这个 Transliation Tables 放到了那个首地址即可,这个配置通过写 ARM 的 TTBR 寄存器实现(Translation Table Base Address );这里其实有两个 TTRB 寄存器,分别叫 TTBR0 和 TTBR1,为啥两个,后面解释;

2.1.1、TTBCR

和这个 TTBR0、TTBR1 勾肩搭背的,还有一个 TTBCR 寄存器,他们直接什么关系呢,看寄存器说明:

TTBCR:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

PD0 和 PD1 是和 Security Extensions 相关的,不管他;

EAE 是和 Large Physical Address Extension  相关的,不管他;

主要关注这里的 N[2:0],指示TTBR0页表基址寄存器基址位宽,同时指示使用 TTBR0 还是 TTBR1 作为页表基址寄存器,以及 TTBR0 页表尺寸:

如果 N = 0 的话,则在做 Table Walk 的时候使用 TTBR0 指定的基地址作为 Transliation Tables 入口的地址;

如果 N > 0 的话:指示TTBR0页表基址寄存器基址位宽,同时指示使用 TTBR0 还是 TTBR1 作为页表基址寄存器,以及 TTBR0 页表尺寸;

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables
  • N==0,使用 TTBR0。
  • N>0,如果虚拟地址[31:32-N]为0,则使用 TTBR0;其他情况使用TTRB1。这种情况下,N 指示了TTBR1的页表地址,也指示了 TTBR0 的页表大小。
  • TTRB0的页表大小由TTBCR.N控制,TTRB1的页表大小为16KB。

我换句话来说,当 N>0 的时候,比如 N=1,那么按照这种说法,VA [31:31] 也就是 VA 的 bit[31] 为 0 的时候,使用 TTBR0 否则使用 TTRB1,按照地址空间来划分,即,32bits 地址,当最高位为 0,即虚拟地址为 0x0000_0000 ~ 0x7FFF_FFFF  这个区间的时候,使用 TTBR0 作为 Transliation Tables 入口的地址,从 0x8000_000 ~ 0xFFFF_FFFF 的虚拟地址空间,使用 TTBR1;

ARM 官方举了个例子,当 TTBCR.N=3‘b111 的时候,VA [31:25] 全部为 0 的时候,使用 TTBR0,按照地址空间来划分就是,虚拟地址为:0x0000_0000 ~ 0x01FF_FFFF 这段区间使用 TTBR0 作为 Transliation Tables 入口的地址;

0x0200_0000 ~ 0xFFFF_FFFF 的虚拟地址空间,使用 TTBR1;

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

OK,现在可以理解为,配置 TTBCR.N 这个值,可以实现将虚拟地址切割成为两部分,一部分使用 TTBR0 指定的 Transliation Tables 进行 Table Walk,另一部分使用 TTBR1 指定的 Transliation Tables 进行 Table Walk,这个有什么好处呢?比如,内核的页表,是不会改变的,而进程上下文的页表是会改变的,有了这个的话,就可以考虑用一个 TTBR 来专门为内核服务,另一个 TTBR 为进程服务,这样避免进程和内核使用同一个页表,每次都要进行内核页表的拷贝;

由于 TTBCR 是 CP15 的寄存器,访问 TTBCR 的指令为:

MRC p15, 0, <Rt>, c2, c0, 2 ; Read TTBCR into Rt
MCR p15, 0, <Rt>, c2, c0, 2 ; Write RT to TTBCR
           

2.1.2、TTBR0、TTBR1

上面说了 TTBCR,下面来看 TTRB0、TTRB1 寄存器描述:

TTBR0

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

在带有多核处理器扩展的情况下 TTBR0 由一个可变的长度构成 Transliation Tables Base Address,这个 x 就是上面的 (14 - (TTBCR.N));

Bits[31:x]:x=(14-(TTBCR.N))。一级页表地址;

Bits[x-1:7]:Reserved;

NOS:Not Outer Shareable bit,指示了做 Table walk 的那个内存的属性,是 Outer Shareable 还是 Inner Shareable.

  • 0  Outer Shareable.
  • 1  Inner Shareable.

TTBR0.S == 0 时,该bit无效;

S:Shareable bit. 指示内存共享属性与页表转换的关系;

  • 0  Non-shareable.
  • 1  Shareable.

RNG:Region bits,指示 Outer Cache 属性与页表转换的关系;

  • 0b00 Normal memory, Outer Non-cacheable.
  • 0b01 Normal memory, Outer Write-Back Write-Allocate Cacheable.
  • 0b10 Normal memory, Outer Write-Through Cacheable.
  • 0b11 Normal memory, Outer Write-Back no Write-Allocate Cacheable.

IRGN[6,0]:Inner region bits,指示 Inner Cache 属性与页表转换的关系;

  • 0b00 Normal memory, Inner Non-cacheable.
  • 0b01 Normal memory, Inner Write-Back Write-Allocate Cacheable.
  • 0b10 Normal memory, Inner Write-Through Cacheable.
  • 0b11 Normal memory, Inner Write-Back no Write-Allocate Cacheable.

访问 TTBR0 的指令为:

MRC p15, 0, <Rt>, c2, c0, 0 ; Read 32-bit TTBR0 into Rt
MCR p15, 0, <Rt>, c2, c0, 0 ; Write Rt to 32-bit TTBR0
           

TTBR1

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

它的位域和 TTBR0 几乎一样,唯一不一样的地方在于,配置的地址区间在于 bit[31:14],这意味着,配置进 TTBR1 的 Transliation Tables Base Address 的物理地址,必须 16KB 对齐;

访问 TTBR1 的指令为:

MRC p15, 0, <Rt>, c2, c0, 1 ; Read 32-bit TTBR1 into Rt
MCR p15, 0, <Rt>, c2, c0, 1 ; Write Rt to 32-bit TTBR1
           

2.2、Translation Table

现在我们知道了合理的配置 TTBCR/TTBR0/TTBR1 可以分配并指定 Transliation Tables,而这个 Transliation Tables 位于内存中,用作 MMU 来做 Table Walk;那么接下来我们需要知道页表的结构,这样我们才能够在内存中创建页表,并将页表配置给 TTBR 寄存器,完成 MMU 的配置;

不考虑大地址扩展和 SuperSection 以及 Section 的情况下,针对 Transliation Tables,ARMv7-A 的手册给出了如下的图解

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

图中,我们暂时只考虑 Page Table 的情况,即红色部分(其余的可以照着推);

蓝色的部分,可以理解为之前寄存器里面配置的那个 N 值;这里为了说明情况,我们暂时将 N 定为 0;

2.2.1、L1 Address Translation

我们先暂时不管使用 TTBR0 还是 TTBR1,其实过程是一样的;此刻当 N = 0 的时候,一级页表以虚拟地址(后面简称 VA,即 Virtual Address)VA[31:20] 作为 L1 Index,一共 12bits,最大能够表征 4K 的 Index:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

每个入口是 4 Bytes 也就是一个 Word,32bits 的入口,L1 Index 从 0~4095,一共 4K,在内存上,每个入口 4Bytes,那么 L1 页表占用内存 4K x 4 Bytes = 16KB;

每一个入口是什么样子的呢,我们放大来看:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

可以看到,这个入口,根据不同的配置,内容有所区别,一共有 4 种类型,这 4 种类型,通过 32bits 的尾部 2 bits 来区分,即,绿色部分(Section 和 SuperSection 的区分,还靠 bit[18]);

这里我们暂时不关心 Section 和 SuperSection,关注于红色部分和那个 Fault;

Level 2 Descriptor Base Address:指向的是 L2 页表的物理地址的基地址;可以看到他是 bit[31:10],是 1KB 边界对齐的;

这个 Domain 指的是 ARM 支持将内存标记为最多 16 个 domain,并以 Domain ID 作为区分,每个 Domain 可以支持配置成为不同的访问权限(通过配置 CP15 的 C3 的 Domain Access Control Register (DACR) 寄存器):

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

配置指令为:

MRC p15, 0, <Rt>, c3, c0, 0 ; Read DACR into Rt
MCR p15, 0, <Rt>, c3, c0, 0 ; Write Rt to DACR
           

针对这个 DACR 寄存器,ARM 官方建议配置成为 Client;

The use of domains is deprecated in the ARMv7 architecture, and will eventually be removed,

but in order for access permissions to be enforced, it is still necessary to assign a domain number

to a section and to ensure that the permission bits for that domain are set to client. Typically, you

would set all domain ID fields to 0 and set all fields in the DACR to ‘Client’.

2.2.2、L2 Address Translation

介绍完 L1 Address Translation 后,下面是二级页表!与 L1 页表不一样,二级页表不和 N 值挂钩,它直接采用 VA[19:12]  作为 L2 Index 索引,一共 8 bits,最大能够表征 256 的 L2 Index;

加入 L2 页表后结合 L1,通过一个给定的 VA 进行索引的第二步为(图中表示的 N 值为 0):

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

这样,一个 VA 通过高地址部分[31:20] 索引到了 L1,再从 L1 指向的 L2 加上 VA[19:12] 作为 L2 Index,索引到 L2 表的固定位置;

L2 也是每条由 4 Bytes 构成,即一个 32bits 的数,那么一个 L2 表大小为 256 x 4 Bytes = 1024 Bytes = 1KB;一共有 4096 个这样的 L2,那么 L2 表总的大小为:4096 x 1KB = 4MB;

我们放大每一条 L2 的入口:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

我们只关心红色部分!可以看到,这个 Small Page Base Address 有 bit[31:12] 也就是 4KB 边界对齐!接下来我们看剩余几个位的含义:

AP/APX:Access Permission 即访问权限,每个内存区域 都有自己的权限,不符合访问权限的 内存访问都会引发 异常。如果是 数据访问 则引发 数据异常。如果是 指令访问,且该指令在执行前没有被 flush,将引发 预取指异常。引发的 异常原因将会被设置在 CP15 的 the fault address and fault status registers;

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

内存区域类型 可以通过 TEX字段、C字段 和 B字段 来进行设置:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

XN:指的是 Execute Never,不允许执行,如果往这里取地址执行,那么会导致异常发生;通常,Device memory 类型的区域会配置成为 XN;

S:指的是是否具有 Shareable 属性;

nG:non-Global,这个标记告诉 MMU,这个页表是否是一个全局的,什么意思呢?看下面:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

当 nG 为 0 的时候,说明此区域是全局可见的,换句话来说,就是任何时候都生效;

当 nG 为 1 的时候,说明此区域不是全局的,要联合这个 ASID 来确认;

每一个 nG=1 的区域,都会和 ASID 来关联,ASID (Address Space Identifier),这代表,TLB 可以存在多个不同进程的页表缓存,后面说 ASID 的时候会仔细说;

自此,L1/L2 分析完毕,那么整个 Table Walk 的流程为:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

VA 的 4K 页内偏移,直接对应到 PA 的 4KB 页内偏移;

3、OS Usage Of Translation Tables

通常情况下,在使用 Cortex-A 系列处理器的时候,典型场景是跑多任务 OS;每一个任务(或者成为应用),都有它独立的虚拟地址空间,以及他的独立的 Translation Table;但是对于 OS 来说,Kernel 的 Translation Tables 其实是固定的,只是进程之间的 Translation Tables 不一样而已;

当一个进程启动的时候,OS 负责为他 code 和 data 段建立映射表(Translation Tables);当进程调用诸如 malloc 之类分配内存的行为,OS 负责修改 Translation Tables(Linux 中,实际访问分配的内存的时候,才去修改页表),进程生命周期消亡,OS 负责回收它的资源和页表,并可以为下一个新的进程提供资源;每一个进程都有自己的独立的页表,这便可以保证进程之间不会相互干扰;

3.1、Address Space ID(ASID)

在操作系统中,多进程是一种常态。那么多进程 的情况下,每次切换进程都需要进行 TLB 清理。这样会导致切换的效率变低。为了解决问题,TLB 引入了 ASID(Address Space ID) 。ASID 的范围是 0-255。ASID 由操作系统分配,当前进程的ASID值 被写在 ASID 寄存器 (使用CP15 c3访问)。TLB 在更新页表项时也会将 ASID 写入 TLB。

如果设置了如果 当前进程的ASID,那么 MMU 在查找 TLB 时, 只会查找 TLB 中具有 相同ASID值 的 TLB行。且在切换进程是,TLB 中被设置了 ASID 的 TLB行 不会被清理掉,当下次切换回来的时候还在。所以ASID 的出现使得切换进程时不需要清理 TLB 中的所有数据,可以大大减少切换开销。

有了这个 ASID + nG 的机制,那么 TLB 中就可以缓存不同进程的页表,不用每次都去 flush TLB,导致性能的损失:

ARMv7-A 处理器窥探(5) —— MMU/TLB0、回顾1、TLB2、MMU3、OS Usage Of Translation Tables

3.2、TTBR0、TTBR1

前面我们说了 TTBR0、TTBR1 是根据 TTBCR.N 来进行划分的,典型场景下 OS 跑多任务,如果处理器只能够支持一个 TTBR 的话,也就意味着用户空间和内核空间使用同一个 TTBR,由于内核空间的 code 和 data 几乎是不变的,但是多任务的用户空间都是不一样的,这样就会存在两个问题:

1、多个任务的页表里面,都有同样一部分内核映射的拷贝副本;

2、要修改内核映射的时候,所有任务的页表都要修改;

加入两个 TTBR 的原因,是因为希望内核和用户空间使用两套 TTBR,这样就可以避免上面的尴尬;内核空间固定使用一组,用户空间不断的切换(比如,配合 TTBR0 + ASID 进行性能的提升)

参考文献:

https://www.jianshu.com/p/ef1e93e9d65b

https://www.cs.rutgers.edu/~pxk/416/notes/10-paging.html

https://blog.csdn.net/liyuewuwunaile/article/details/106773630?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242

继续阅读