动机

x86 允许系统任意内存区域被 L1/L2/L3 缓存接管,因此对于每一个页来说都有必要进行缓存方式的控制管理,而这就是内存类型(memory type)的动机。

那么开发者需要了解内存类型的动机?正常来说我们不需要了解,但是要是碰到 MMIO 的话最好了解一下,因为通常涉及缓存就需要考虑内存模型,也就是访问乱序问题和数据不可见问题。

NOTE: 本文不会详细调研内存类型的概念,因为要解决上述 MMIO 问题的话,默认的 ioremap 已经处理好了(历史上出现过问题)。这些就当作是扩展阅读吧。

名词对齐

memory type (Intel/AMD) = type of caching (Intel SDM) = page cache mode (Linux).

这三个名词都是同一概念,本文使用 memory type。

有哪些内存类型

Intel 手册(卷三 §11.3)定义内存类型有以下分类:

  • Strong Uncacheable (UC)
  • Uncacheable (UC-)
  • Write Combining (WC)
  • Write Through (WT)
  • Write Back (WB)
  • Write Protected (WP)

AMD 手册(卷二 §7.4)还额外定义两种特殊的内存类型,仅作展示:

  • Cache Disable (CD)
  • Write-Combining Plus (WC+)

Linux 可以通过 cat /sys/kernel/debug/x86/pat_memtype_list 获取主机的内存类型信息。

性质

内存类型的性质很多,比较重要的有内存访问和缓存策略。

Memory Access Allowed UC/CD WC WP WT WB
Read Out-of-Order no yes yes yes yes
Read Speculative no yes yes yes yes
Read Reorder Before Write no yes yes yes yes
Write Out-of-Order no yes no no no
Write Speculative no no no no no
Write Buffering no yes yes yes yes
Write Combining no yes no yes yes
Caching Policy UC CD WC WP WT WB
Read Cacheable no no no yes yes yes
Write Cacheable no no no no yes yes
Read Allocate no no no yes yes yes
Write Allocate no no no no no yes
Write Hits Update Memory yes yes yes yes yes no

NOTES:

  • 不考虑细节,UC 和 UC- 的性质基本上是相同的。
  • Buffering 指的是使用 store buffer。
  • Combining 指的是写入合并(将多次写操作合并为一次)。

The buffering of writes to WC memory also causes data to be collapsed; that is, multiple writes to the same memory location will leave the last data written in the location and the other writes will be lost.
来源:Intel SDM 卷三 §11.3.1

对于软件开发者来说,这些偏硬件的细节会最终导致不同内存类型之间的内存访问操作需要不同方式的 LFENCE/SFENCE/MFENCE 来保证程序顺序(因为不同的内存访问和缓存策略导致了不同的内存模型)。AMD 手册特意画了一张极其详尽的统计表(表 7-4)来说明不同内存模型的乱序程度,非常复杂,至少有 13 种情况,这里就不贴了。

使用场景

但是如果只遵循使用场景的建议,那会比上面抠内存模型细节简单不少:

  • 默认内存(最佳性能):WB
  • 常规驱动(MMIO 保序):UC/UC-
  • gpu/net/video 驱动(写多读少,写独立优化):WC

NOTES:

  • WT 和 WP 在 Linux 内核中基本用不到。详见 WTWP 的调用统计。
  • WB 就是我们常规理解的 TSO 内存模型,网上文章很多了,不解释。
  • 更加权威的解释见 Intel SDM 卷三 §11.3.2。

怎样指定内存类型

很复杂,我并不想看处理器手册的 PAT 配置说明。这种固定流程还是直接看内核源码/调接口吧:ioremap_change_attr 或者 set_memory_*

TODO

目前查文档的时间不多,后面可能会补充一些细节。

References

Intel® 64 and IA-32 Architectures Software Developer’s Manual
AMD64 Architecture Programmer’s Manual
Linux - MMIO 的映射和访问 – 知乎
Getting a handle on caching – LWN.net