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- (来自现有别名) |
/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 接口以及以下组合来实现:
pgprot_noncached()
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 设置