Linux x86 PAT 页属性表机制详解:ioremap、set_memory 与内存类型别名控制
Linux x86 PAT 页属性表机制详解ioremap、set_memory 与内存类型别名控制【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxPATPage Attribute Table页属性表是 x86 平台以页为粒度设置内存属性Cacheability的机制与按物理地址区间配置内存类型的 MTRR 互补。本文基于 Linux 内核文档 pat.rst 并结合arch/x86/mm/pat/下的源码实现完整讲解 PAT 的五种内存属性、各 PAT API 的适用场景与别名aliasing约束、驱动导出物理页到用户态的正确姿势、MTRR 与 PAT 的交互效果、PAT 初始化时序以及利用 debugfs 与debugpat启动参数调试 PAT 的实操方法。读完后你将掌握如何在驱动中正确使用ioremap_*/set_memory_*接口、如何核查系统的 memtype 状态以及规避内存类型别名问题的判断依据。一、PAT 与 MTRR两种内存属性配置机制的关系MTRRMemory Type Range Register允许以物理地址区间为单位设置内存类型但寄存器数量有限、粒度受区间约束PAT 则把属性设置下沉到页表级别粒度PAT 可按 4K 页粒度设置属性而 MTRR 以固定区间寄存器为单位数量PAT 没有硬件条目数量限制MTRR 仅有 7 条可变区间variable MTRR可用代价这种灵活性带来了约束——同一物理内存通过多个虚拟地址映射时必须避免内存类型别名memory type aliasing即同一物理页被映射成不同类型如一处 WB、一处 WC否则行为未定义、可能损坏数据。原文档明确PAT 提供的额外灵活性是以不得对同一物理内存通过多个虚拟地址产生类型别名的行为准则为代价的。内核的 PAT API 在内部正是通过物理地址区间的 memtype 记账reserve_memtype()/free_memtype()系列接口来防止别名发生的。支持的内存属性类型PAT 支持多种内存属性文档列出的常用五类为缩写含义WBWrite-back写回默认 RAM 类型UCUncached非缓存WCWrite-combining写合并WTWrite-through直写UC-Uncached Minus弱于 UC 的非缓存源码中这些名称有直接对应memtype.h 的cattr_name()函数将_PAGE_CACHE_MODE_*枚举翻译为uncached、uncached-minus、write-back、write-combining、write-through等字符串这些字符串正是后文 debugfs 中pat_memtype_list输出的字段。二、PAT API 总表每个接口能用在哪种区域上内核中可以在页级设置内存属性的接口很多为避免别名必须按区域类型选择正确接口。原文档给出了完整的 API 与内存区域RAM / ACPI 等已映射区 / 保留区与地址空洞对照表此处完整保留APIRAMACPI 等已映射区保留区/Holesioremap--UC-UC-ioremap_cache--WBWBioremap_uc--UCUCioremap_wc----WCioremap_wt----WTset_memory_uc / set_memory_wbUC-----set_memory_wc / set_memory_wbWC----set_memory_wt / set_memory_wbWT----pci sysfs resource----UC-pci sysfs resource_wcIORESOURCE_PREFETCH 时----WCpci 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--WBWB/dev/mem mmap !SYNC、无别名且 MTRR 非 WB----UC-表中--表示不建议该 API 用于该类区域。原文档特别强调部分--由内核严格强制执行另一些目前未强制、但未来可能强制执行。关键结论原文档末尾的三条准则驱动访问带 uc/wc 属性的 PCI BAR 时应使用ioremap_uc()/ioremap_wc()驱动修改RAM 范围的访问类型时应使用set_memory_uc()/set_memory_wc()/set_memory_wt()且这些 API必须成对使用先把区域设为 uc/wc/wt用完再set_memory_wb()恢复对 ioremap 和经 /sys、/proc 的 pci 访问实际返回的类型可能比请求的更严格——例如该地址已存在 uncached 映射时新ioremap_wc()会返回 uncached 而非 write-combine这正是防别名机制的体现。源码印证memtype 记账结构防别名的核心数据结构在 memtype.hstruct memtype { u64 start; u64 end; u64 subtree_max_end; enum page_cache_mode type; struct rb_node rb; };每个条目记录一段物理地址区间及其类型以红黑树rb_node组织。配套函数memtype_check_insert()、memtype_erase()、memtype_lookup()分别负责插入前检查冲突并降级类型、删除区间、查询某地址当前类型。可以推断当新映射请求的类型与已有区间冲突时内核会把新映射降级为更严格的类型这正是上表中实际返回类型可能更严格的底层实现路径。在通用层io.h 提供了arch_io_reserve_memtype_wc()/arch_io_free_memtype_wc()这一对 WC 区间保留/释放接口默认实现为空操作x86 覆写驱动可用它在 WC 操作前后显式声明物理区间占用。三、驱动导出物理页给用户态的高级 API文档A. Exporting pages to users一节给出了驱动通过 mmap 把物理页暴露给用户态的标准配方步骤组合pgprot_noncached()PAT 新增的pgprot_writecombine()可作为替代设置页保护位随后调用io_remap_pfn_range()/remap_pfn_range()/vmf_insert_pfn()完成页表映射。使用 PAT 后步骤 1 可在pgprot_noncached()与pgprot_writecombine()之间选择步骤 2 的内部实现会把该区域登记到 memtype 列表中记为 UC 或 WC以确保不与已有映射冲突。重要限制与 RAM 的正确姿势这组 API仅对 IO非 RAM区域有效若驱动要导出的是RAM 页必须在上面步骤之前先执行步骤 0set_memory_uc()或set_memory_wc()修改物理页属性自行跟踪这些页的使用并在页归还 buddy 空闲池之前调用set_memory_wb()恢复——否则页被换出/回收后再以 WB 类型访问会产生别名甚至数据损坏。四、MTRR 对 PAT / 非 PAT 系统的影响当同时存在 WC 类型的 MTRR 与 ioremap 系列调用时Linux 实际设置的 ioremap 值与最终生效的内存类型在 PAT 系统和非 PAT 系统上并不相同。原文档给出了完整对照表PWT/PCD 位组合编码 MTRR 类型MTRRNon-PAT 值PAT 系统 ioremap 值非 PAT 系统生效类型PAT 系统生效类型WC 000WB_PAGE_CACHE_MODE_WBWCWCWC 001WC_PAGE_CACHE_MODE_WCWC*WCWC 010UC-_PAGE_CACHE_MODE_UC_MINUSWC*UCWC 011UC_PAGE_CACHE_MODE_UCUCUC*表示实现定义implementation defined不推荐使用。由此可读出两条工程规则在 PAT 系统上_PAGE_CACHE_MODE_WB的 ioremap 落在 WC MTRR 区间内仍生效为 WC——这是 GPU/显示等驱动依赖的历史行为WC MTRR _PAGE_CACHE_MODE_UC_MINUS在 PAT 系统上生效为 UC 而非 WC非 PAT 上则是实现定义的 WC*因此不要依赖这种组合同时获得 write-combining。原文档给出的迁移方向逐步淘汰mtrr_add()改用arch_phys_wc_add()——在 PAT 启用的系统上它是空操作no-op因为 WC 属性已由 PAT 页表条目提供执行arch_phys_wc_add()的区间应当已经以 WC 属性完成 ioremapioremap_wc()或set_memory_wc()对于同一设备既有希望保持 uncacheable 的 IO 区、又有希望 WC 的区的尺寸受限场景可考虑ioremap_uc()set_memory_wc()白名单式地开启有效 WC 区域——但文档明确此用法不推荐有效内存类型属于实现定义只能作为 MTRR write-combining 无法生效时的最后手段对/proc/mtrr的写入将逐步弃用建议改用上述 PAT 接口。五、PAT 的初始化时序MTRR 状态与启动参数的组合PAT MSRModel-Specific Register决定了页表属性编码与 MTRR PCD/PWT 位的组合语义。Linux 必须更新 PAT MSR 才能支持 WC 和 WT 属性否则 MSR 保留固件写入的值。原文档完整初始化状态表MTRRPAT调用序列PAT 状态PAT MSR 归属EEMTRR - PAT initEnabledOSEDMTRR - PAT initDisabled-DEMTRR - PAT disableDisabledBIOSDDMTRR - PAT disableDisabled--np/EPAT - PAT disableDisabledBIOS-np/DPAT - PAT disableDisabled-E!P/EMTRR - PAT initDisabledBIOSD!P/EMTRR - PAT disableDisabledBIOS!M!P/EMTRR stub - PAT disableDisabledBIOS图例E CPU 支持该特性D CPU 不支持np 指定了nopat启动参数!P 未开启CONFIG_X86_PAT!M 未开启CONFIG_MTRROS/BIOS PAT MSR 由 Linux 初始化还是保留固件设置。要点解读只有MTRR 可用 PAT 可用首行时 PAT 才真正 Enabled 并由 OS 接管 MSRCPU 不支持 MTRR 或显式nopat时一律 Disabled。文档还特别注明Xen 会为 guest 在 PAT MSR 中启用 WC 属性。源码可印证这些开关的存在memtype.c 中通过early_param(nopat, nopat)注册nopat启动参数它早于常规__setup在 early param 阶段即可禁用 PAT并通过pat_enabled()对外导出PAT 是否启用的判断EXPORT_SYMBOL_GPL供 PCI 等子系统查询。六、PAT 调试debugfs 与 debugpat 实战方法一查看 pat_memtype_list需 CONFIG_DEBUG_FS# 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 0x80000000-0x90000000输出即原文档示例每行类型名 物理地址区间展示系统当前所有物理地址区间各自使用的 memtype。排查某地址被映射成了什么类型、是否出现别名时的第一手证据就是它。对应源码在 memtype.c 末尾的pat_memtype_list_init()late_initcall注册仅当pat_enabled()为真时才创建pat_memtype_list节点父目录为arch_debugfs_dir即/sys/kernel/debug/x86/——所以 PAT 被nopat禁用时该文件不会出现这是排查时容易误判的一点。方法二debugpat 启动参数更详细在内核启动命令行加入debugpatdprintk()宏见 memtype.h 的dprintk定义会把所有 PAT 相关调试信息以x86/PAT:前缀打印到 dmesg。该参数由 memtype.c 中的__setup(debugpat, pat_debug_setup)注册设置pat_debug_enable全局标志。适合在问题复现窗口内观察每次 memtype 插入/降级的完整过程。附加CPA 统计同目录的 set_memory.c 还提供cpa_statsdebugfs 文件late_initcall(cpa_stats_init)注册统计 Change Page Attribute 路径上的各类操作次数可与debugpat配合分析set_memory_*的性能与行为。七、实践建议汇总IO 区域PCI BAR 等优先ioremap_wc()/ioremap_uc()需要 WC 且要兼容旧 MTRR 路径时补arch_phys_wc_add()PAT 系统上为空操作安全RAM 区域只能用set_memory_wc()/uc()/wt()且务必成对set_memory_wb()收尾归还内存前必须恢复 WB导出页给用户态IO 区域走pgprot_noncached()/pgprot_writecombine()*_pfn_rangeRAM 页先set_memory_wc()/uc()并自行跟踪生命周期不要依赖 MTRR UC- 的暗号非 PAT 上的 WC*在 PAT 系统上该组合生效为 UC诊断顺序先看/sys/kernel/debug/x86/pat_memtype_list确认区间类型再开debugpat重启复现观察x86/PAT:日志最后用dmesg中的 memtype 降级记录定位别名来源。以上全部行为均可在当前仓库中查证机制描述见 pat.rst实现集中在 arch/x86/mm/pat/memtype.c、arch/x86/mm/pat/memtype.h、arch/x86/mm/pat/set_memory.c 与 include/linux/io.h。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考