13. PAT (Page Attribute Table)

x86 页属性表 (PAT) 允许在页面粒度级别设置内存属性。PAT 是 MTRR 设置的补充,MTRR 允许在物理地址范围内设置内存类型。然而,由于 PAT 具有在页级别设置属性的能力,且对于此类属性设置的数量没有硬件限制,因此它比 MTRR 更灵活。伴随这种灵活性而来的是一些准则,即对于具有多个虚拟地址的相同物理内存,不能产生内存类型别名(aliasing)。

PAT 允许使用不同类型的内存属性。目前支持的最常用的属性有:

WB

写回 (Write-back)

UC

未缓存 (Uncached)

WC

写入合并 (Write-combined)

WT

写直通 (Write-through)

UC-

弱未缓存 (Uncached Minus)

13.1. PAT API

内核中有许多不同的 API 允许在页级别设置内存属性。为了避免别名(aliasing),应当审慎使用这些接口。下面是可用接口、其预期用途及其内存属性关系的表格。在内部,这些 API 在物理地址范围上使用 reserve_memtype()/free_memtype() 接口来避免任何别名冲突。

API

RAM

ACPI,...

保留/空洞 (Reserved/Holes)

ioremap

--

UC-

UC-

ioremap_cache

--

WB

WB

ioremap_uc

--

UC

UC

ioremap_wc

--

--

WC

ioremap_wt

--

--

WT

set_memory_uc, set_memory_wb

UC-

--

--

set_memory_wc, set_memory_wb

WC

--

--

set_memory_wt, set_memory_wb

WT

--

--

pci sysfs 资源 (resource)

--

--

UC-

pci sysfs resource_wc 为 IORESOURCE_PREFETCH

--

--

WC

pci proc !PCIIOC_WRITE_COMBINE

--

--

UC-

pci proc PCIIOC_WRITE_COMBINE

--

--

WC

/dev/mem 读写

--

WB/WC/UC-

WB/WC/UC-

/dev/mem mmap SYNC 标志

--

UC-

UC-

/dev/mem mmap !SYNC 标志及该区域的任何别名

--

WB/WC/UC-

(来自现有别名)

WB/WC/UC-

(来自现有别名)

/dev/mem mmap !SYNC 标志、该区域无别名且 MTRR 为 WB

--

WB

WB

/dev/mem mmap !SYNC 标志、该区域无别名且 MTRR 不为 WB (!WB)

--

--

UC-

13.2. 面向驱动的高级 API

A. 使用 remap_pfn_range、io_remap_pfn_range、vmf_insert_pfn 将页面导出给用户。

想要将某些页面导出到用户空间的驱动程序通过使用 mmap 接口以及以下组合来实现:

  1. pgprot_noncached()

  2. io_remap_pfn_range()remap_pfn_range()vmf_insert_pfn()

随着对 PAT 的支持,新增了一个 API pgprot_writecombine。因此,驱动程序可以继续使用上述序列,在第 1 步中使用 pgprot_noncached()pgprot_writecombine(),然后执行第 2 步。

此外,第 2 步会在内部的 memtype 列表中将该区域跟踪为 UC 或 WC,以确保没有冲突的映射。

请注意,这组 API 仅适用于 IO(非 RAM)区域。如果驱动程序想要导出 RAM 区域,它必须在上述步骤 0 中执行 set_memory_uc()set_memory_wc(),同时还要跟踪这些页面的使用情况,并在页面释放回空闲池之前使用 set_memory_wb()

13.3. MTRR 对 PAT / 非 PAT 系统的工作影响

下表给出了在非 PAT 和 PAT 系统上,在 x86 上使用 ioremap*() 调用时使用写合并 MTRR 的效果。理想情况下,应当逐步淘汰 mtrr_add() 的使用,转而使用 arch_phys_wc_add(),它在启用 PAT 的系统上是一个空操作(no-op)。执行 arch_phys_wc_add() 的区域应该已经使用 WC 属性或 PAT 条目进行了 ioremap,这可以通过使用 ioremap_wc() / set_memory_wc() 来完成。将希望保持不可缓存的 IO 内存区域与需要写合并的区域结合在一起的设备,应考虑使用 ioremap_uc() 随后调用 set_memory_wc() 来将有效的写合并区域加入白名单。尽管如此,仍不鼓励这种用法,因为实际的内存类型被认为是实现定义的,不过对于大小受限的区域且 MTRR 写合并原本无效的设备,该策略可以作为最后手段使用。

====  =======  ===  =========================  =====================
MTRR  Non-PAT  PAT  Linux ioremap value        Effective memory type
====  =======  ===  =========================  =====================
      PAT                                        Non-PAT |  PAT
      |PCD                                               |
      ||PWT                                              |
      |||                                                |
WC    000      WB   _PAGE_CACHE_MODE_WB             WC   |   WC
WC    001      WC   _PAGE_CACHE_MODE_WC             WC*  |   WC
WC    010      UC-  _PAGE_CACHE_MODE_UC_MINUS       WC*  |   UC
WC    011      UC   _PAGE_CACHE_MODE_UC             UC   |   UC
====  =======  ===  =========================  =====================

(*) denotes implementation defined and is discouraged

注意

上表中的 -- 意味着“不建议对该 API 的此种用法”。其中一些 -- 会受到内核的严格执行。其他一些目前并未真正执行,但在未来可能会被强制执行。

对于 ioremap 以及通过 /sys 或 /proc 进行的 pci 访问 —— 如果该地址存在任何现有的别名,返回的实际类型可能会更加严格。例如:如果存在现有的未缓存映射,则新的 ioremap_wc 可能会返回未缓存映射,而不是请求的写合并映射。

set_memory_[uc|wc|wt] 和 set_memory_wb 应当成对使用,驱动程序首先将区域设为 uc、wc 或 wt,并在使用完毕后将其切换回 wb。

随着时间的推移,对 /proc/mtrr 的写入将被废弃,转而使用基于 PAT 的接口。建议向 /proc/mtrr 写入的用户使用上述接口。

驱动程序应当使用 ioremap_[uc|wc] 以 [uc|wc] 访问类型访问 PCI BAR。

驱动程序应当使用 set_memory_[uc|wc|wt] 来设置 RAM 范围的访问类型。

13.4. PAT 调试

启用 CONFIG_DEBUG_FS 后,可以通过以下方式检查 PAT memtype 列表:

# mount -t debugfs debugfs /sys/kernel/debug
# cat /sys/kernel/debug/x86/pat_memtype_list
PAT memtype list:
uncached-minus @ 0x7fadf000-0x7fae0000
uncached-minus @ 0x7fb19000-0x7fb1a000
uncached-minus @ 0x7fb1a000-0x7fb1b000
uncached-minus @ 0x7fb1b000-0x7fb1c000
uncached-minus @ 0x7fb1c000-0x7fb1d000
uncached-minus @ 0x7fb1d000-0x7fb1e000
uncached-minus @ 0x7fb1e000-0x7fb25000
uncached-minus @ 0x7fb25000-0x7fb26000
uncached-minus @ 0x7fb26000-0x7fb27000
uncached-minus @ 0x7fb27000-0x7fb28000
uncached-minus @ 0x7fb28000-0x7fb2e000
uncached-minus @ 0x7fb2e000-0x7fb2f000
uncached-minus @ 0x7fb2f000-0x7fb30000
uncached-minus @ 0x7fb31000-0x7fb32000
uncached-minus @ 0x80000000-0x90000000

此列表显示了物理地址范围以及用于访问这些物理地址范围的各种 PAT 设置。

获取 PAT 相关调试消息的另一种更详细的方法是使用 “debugpat” 启动参数。通过此参数,各种调试消息将被打印到 dmesg 日志中。

13.5. PAT 初始化

下表描述了 PAT 在各种配置下是如何初始化的。Linux 必须更新 PAT MSR 才能支持 WC 和 WT 属性。否则,PAT MSR 具有固件写入其中的值。注意,Xen 会为客户机在 PAT MSR 中启用 WC 属性。

MTRR

PAT

调用序列 (Call Sequence)

PAT 状态 (PAT State)

PAT MSR

E

E

MTRR -> PAT 初始化

已启用

OS

E

D

MTRR -> PAT 初始化

Disabled(禁用)

D

E

MTRR -> PAT 禁用

Disabled(禁用)

BIOS

D

D

MTRR -> PAT 禁用

Disabled(禁用)

np/E

PAT -> PAT 禁用

Disabled(禁用)

BIOS

np/D

PAT -> PAT 禁用

Disabled(禁用)

E

!P/E

MTRR -> PAT 初始化

Disabled(禁用)

BIOS

D

!P/E

MTRR -> PAT 禁用

Disabled(禁用)

BIOS

!M

!P/E

MTRR 残桩 (stub) -> PAT 禁用

Disabled(禁用)

BIOS

图例

E

CPU 中启用了该功能

D

CPU 中禁用/不支持该功能

np

指定了“nopat”引导选项

!P

未设置 CONFIG_X86_PAT 选项

!M

未设置 CONFIG_MTRR 选项

已启用

PAT 状态设为启用

Disabled(禁用)

PAT 状态设为禁用

OS

PAT 使用 OS 设置初始化 PAT MSR

BIOS

PAT 保持 PAT MSR 为 BIOS 设置