Intel x86_64 Architecture Background 3

时间:2022-12-01 03:15:11

  多层次的cache结构解决了CPU和DRAM之间处理速度不一致的问题,在Intel体系架构下,CPU核心和主存DRAM之间有着三层的cache。其中一层缓存L1和二层缓存L2在CPU核心(core)中,第三层在核外。一般每个核心都有一个私有的L1级和L2级Cache,同一个物理CPU上的多个核心共享一个L3级缓存,这样的设计是出于提高内存访问性能的考虑。但是这样就有一个问题了,每个核心之间的私有L1,L2级缓存之间需要同步啊。比如,核心1上的线程A对一个共享变量global_counter进行了加1操作,这个被写入的新值存到核心1的L1缓存里了;此时另一个核心2上的线程B要读global_counter了,但是核心2的L1缓存里的global_counter的值还是旧值,最新被写入的值现在还在核心1上。这就需要CPU有一个模块来保证,同一个内存的数据在同一时刻对任何对其可见的核心看来,数据是一致的,由第二章缓存图知道,这种专门的组件就是缓存控制器(Cbox,Bbox)。

缓存一致性 - MESI:

  处理器上有一套完整的协议,来保证Cache一致性。比较经典的Cache一致性协议当属MESI协议,奔腾处理器有使用它,很多其他的处理器都是使用它的变种。单核Cache中每个Cache line有2个标志:dirty和valid标志,它们很好的描述了Cache和Memory(内存)之间的数据关系(数据是否有效,数据是否被修改),而在多核处理器中,多个核会共享一些数据,MESI协议就包含了描述共享的状态。

  在MESI协议中,每个Cache line有4个状态,可用2个bit表示,它们分别是:

  M(Modified)和E(Exclusive)状态的Cache line,数据是独有的,不同点在于M状态的数据是dirty的(和内存的不一致)。

  E状态的数据是clean的(和内存的一致)。

  S(Shared)状态的Cache line,数据和其他Core的Cache共享。只有clean的数据才能被多个Cache共享。

  I(Invalid)表示这个Cache line无效。

在MESI协议中,每个Cache的Cache控制器不仅知道自己的读写操作,而且也监听(snoop)其它Cache的读写操作。每个Cache line所处的状态根据本核和其它核的读写操作在4个状态间进行迁移。

详细了解参考 : Cache一致性协议之MESI 

 

Cache 类型:

Intel体系架构中,Cache 类型一共有 6 种:

Strong Uncacheable (UC):

  这种 cache 类型的 memory,任何读写操作都不经过 cache。一般是 memory-map 的 IO 地址可以使用这种类型。一般的 ram 强烈推荐不使用这种 cache,否则效率会非常低。

Uncacheable (UC-):

  特性与 UC(Strong uncacheable) 相同,唯一不同的是,这种类型的 memory,可以通过修改 MTRR 来把它改变成 WC

Write Combining (WC):

  这种类型的 cache,特性与 UC 相似,不同的地方是它可以被 speculative read(什么叫 speculative read?)每次 write 都可能被 delay,write 的内容会 buffer 到一个叫 “write combining buffer” 的地方。可以通过 对 MTRR 编程来设置 WC,也可以通过设置 PAT 来设置 WC(pat 是什么?)

Write – through (WT):

  这个很好理解,每次 write,都要 write 到 memory,同时 write 到对应的 cache(if write hits)。WT 方式保证了 cache 与 memory 是一致的。

Write – back (WB):

  这种类型的 memory,read 和 write,都跟一般的 cache 一样。只是 write 的时候,当写到了 cache 中,不会立即 write 到 memory 里(这个就跟 WT 不一样了)。CPU 会等到适当的时候再 write 到 memory 里—比如当 cache 满了。 这种类型是效率最高的类型,

Write-protected (WP):

  Read 跟 wb 一样,但每次 write,都会引起 cache invalidate

 详细了解参考 :Cache学习

 

标记cache类型 - MTRR 和 PAT:

  MTRR (Memory Type Range Register)

  MTRR提供了这样一个机制:让不同范围的物理地址空间,具有不同的 cache 方法(UC,UC-,WC,WB,WT,WP 之类的)。MTRR 允许定义最多 96 个物理内存范围,通过 MSR 来指定不同范围的 MTRR 具有哪些 cache 方法。 

  注意,在 variable-range 的 MTRR 中,是有对齐原则的:最小的范围是 4K,base 地址也至少是 4KB 对齐。如果范围大于 4K,那么范围必须是 2 的 N 次方(N 大于 12),base 地址对齐也必须是 2 的 N 次方。

对于 MTRR 的使用,有如下的一些规则:

1.) 如果 MTRR 没有被 enable(MSR IA32_MTRR_DEF_TYPE 中的 E flag 被 clear),那么所有的内存访问都使用 UC 类型。

2.) 如果 MTRR 被 enable 了:

  访问的地址在 0-1M 之间的话,并且 fix-ranges MTRR 是 enable 的,那么就使用 fix-range 的 MTRR 指定的 cache type。

  访问的地址在 1M 以上,那么就通过 variable-ranges MTRR 指定的 type 来决定:

     如果只有一个 variable-range MTRR 包含了这个地址,那么就使用该 MTRR 的 type;

     如果有一个以上 variable-range MTRR 包含了这个地址,并且这些 MTRR 的 type 都一样,那也使用这个 type;

     如果有一个以上 variable-range MTRR 包含了这个地址,并且其中一个 MRTT 的 type 是 UC,那就使用 UC;

     如果有一个以上 variable-range MTRR 包含了这个地址,并且 type 是 WT 和 WB,那么就使用 WT(WT 优先);

          如果以上规则都没满足,那么这块地址的访问的 cache type 是 undefine;

  3. ) 如果访问的地址没有 fix 或者 variable range 的 MTRR match 到,那么就使用系统默认的 cache type。

   MTRR 初始化: 在系统 hardware reset 时,CPU 会自动清除 variable-ranged MTRR 的 flag,并且会清除掉 MSR IA32_MTRR_DEF_TYPE 的 E 标志(表示 disable 所有的 MTRR). 在初始化 MTRR 之前,软件(bios 或者 OS)必须初始化所有的 fix ranged 和 variable ranged MTRR 为 0。然后软件再根据系统需求的实际情况去设置各个 MTRR。

  PAT (Page Attribute Table)

  MTRR 是基于物理地址空间来决定某块物理地址具有什么样子的 cache 属性。而 PAT 是对 MTRR 的补充,PAT 允许 CPU 基于线性地址来决定某个 page(线性地址页)具有什么样子的 cache 属性。PAT 是在页表项或者页目录项里指定的,并且 PAT 要与页表项或者页目录项里的 PCD 和 PWT 这两个 bit 一起来决定某个 page 具有什么样子的 cache 属性(UC,UC-,WB,WT,WC)。

  这些 cache 属性在 PAT 概念里的编码如下:可以通过 CPUID(EAX=1)来判断当前 CPU 是否支持 PAT。软件可以通过编程 MSR IA32_PAT(277H) 来设置 PAT 的相关属性。注意,只要是支持 PAT 的 CPU,PAT 就一定是打开的,也就是说,CPU 没有 control bit 可以控制 PAT 的开和关。那么,系统怎样来指定某个页具有什么样子的 cache 属性呢?页表中的 PAT,PCD,PWT 占 3 个 bit,这 3 个 bit 组合起来,可以索引到 MSR IA32_PAT(277H) 的某个 PA,这个 PA 的值,也就是那 5 种 cache 的 encoding 值。

  或许你有这样一个疑问:既然 PAT 是基于线性地址的,那么是否存在这样的情况:当一个物理页表被 MMU 映射到了不同的两个 page,并且这两个 page entry 的 pat 属性都是不一样的,那怎么办? 这个问题,从 intel 的角度来看,是 undefine 的。即 INTEL 不建议 OS 这样做,如果非要这样做,导致的后果 unknow,也就是后果自负。

 Cache地址映射:

  cache 是体系结构中很重要的一个设计,也是有关存储体系中的一个重要环节,考虑到现实的应用中,由于开发了虚拟地址这个概念,即每个进程都可以拥有一个完整的虚拟地址空间,这样,CPU 在执行两道不同的进程,而进程的指令访问都是基于虚拟地址的,因此,可能出现的情况是:进程 A 在被执行一段时间后,由于进程调度,被切换出去,需要执行进程 B,而进程 A 的 PCA(进程 A 的取指地址指针)所指向的一段代码正在 cache 中,而进程 B 的 PCB,由于是虚拟地址概念,有可能 PCB=PCA(经过虚实地址转换后,物理 PCB 绝对不会等于物理 PCA),如果在 cache 中利用 PCB 去访问 PCA,则会导致 cache hit,但这个时候 cache hit 其实是错误的,因为 cache 中存放的是进程 A 的指令。 

  Cache到物理地址的映射按照工作原理来分有physical index physical tagged, virtual index virtual tagged, physical index virtual tagged等几种分法: 

  Physical index physical tagged是一种最容易理解的操作方式,cache针对物理地址进行操作,简单粗暴,而且不会有歧义。但是这种方式的缺陷也很明显,在多进程操作系统中,每个进程拥有自己独立的地址空间,指令和代码都是以虚拟地址的方式存在,cpu发出的memory access的指令都是以虚拟地址的方式发出,这样的话,对于每一个memory access的操作,都要先等待MMU将虚拟地址翻译为物理地址,这是一种串行的方式,效率较低。 

  Virtual index virtual tagged是纯粹用虚拟地址来寻址,这种方式带来了更多的问题,每一行数据在原有tag的基础上都要将进程标识加上以区分多个进程之间的相同地址,而在处理共享内存时也比较麻烦,共享内存在不同的进程中的虚拟地址不相同,如何同步是个问题。 
  现在比较多的是采用virtual index physical tagged的方式,virtual index的含义是当cpu发出一个地址请求之后,低位地址去和cache中的index匹配, physical tagged是指虚拟地址的高位地址去和mmu中的页表匹配以拿到物理地址(index和取物理地址这两个过程是并行的),然后用从mmu中取到的物理地址作为tag(或者tag的一部分)去和cache line的tag位匹配,这样既保证了同一地址在cache中的唯一性(有个例外,cache alias)又能将mmu和cache并行工作,提高了效率。

详细了解参考 :Cache浅析