如何在Linux 5.10及以上版本arm64架构上禁用L1/L2缓存
问题1:arm64架构Linux内核(≥5.10版本)L1/L2缓存启用代码路径
缓存的启用逻辑集中在内核启动的早期初始化阶段,对应代码路径如下:
- 汇编启动阶段:
arch/arm64/kernel/head.S中的__cpu_setup、__primary_switch函数段,会初始化系统控制寄存器SCTLR_EL1的C位(bit 2,控制数据缓存开关)、I位(bit 12,控制指令缓存开关),默认置1开启缓存,所有主核和副核启动时都会执行该段逻辑。 - 后续缓存管理:
arch/arm64/mm/cache.S实现了缓存刷写、无效化等底层操作接口;arch/arm64/kernel/cacheinfo.c负责枚举L1、L2等缓存层级的硬件信息;cluster共享的L2缓存会在arch/arm64/kernel/smp.c的副核启动流程中同步配置开启状态。
问题2:arm64架构禁用L1/L2缓存的可行方案
你之前尝试的单内核修改单个CPU寄存器、使用arm32专属配置项的方案确实不适用arm64场景,以下是3种可落地的方案:
方案1:内核启动参数修改(无需改代码,优先推荐)
arm64内核原生支持cachepolicy启动参数,直接在bootargs中添加cachepolicy=uncached即可全局禁用所有CPU的L1/L2缓存,修改后重启设备生效。
注意:如果设备的EL3安全固件锁定了SCTLR_EL1的控制位,该配置可能不生效
方案2:内核源码静态修改(稳定性最高)
arm64没有现成的禁用缓存的编译配置项,可直接修改启动阶段的寄存器初始化逻辑,步骤如下:
- 打开
arch/arm64/kernel/head.S,找到__cpu_setup函数中设置SCTLR_EL1的代码段 - 修改配置值清除C位和I位,参考代码:
/* 原有默认配置:开启缓存 */ // mov_q x0, SCTLR_EL1_SET /* 修改后的配置:禁用指令、数据缓存 */ mov_q x0, SCTLR_EL1_SET & ~(SCTLR_EL1_C | SCTLR_EL1_I) msr sctlr_el1, x0 isb
- 重新编译内核刷入设备即可,所有CPU启动时都会默认关闭缓存。
方案3:内核模块动态修改(仅调试用,风险极高)
如果需要动态切换缓存状态,可通过内核模块向所有CPU广播修改SCTLR_EL1寄存器,该方案容易触发缓存一致性问题、系统panic,仅可用于临时调试场景,参考代码:
#include <linux/module.h> #include <linux/smp.h> #include <asm/sysreg.h> static void modify_cache_per_cpu(void *data) { bool disable = *(bool *)data; u64 sctlr; local_irq_disable(); sctlr = read_sysreg(sctlr_el1); if (disable) { sctlr &= ~(SCTLR_EL1_C | SCTLR_EL1_I); } else { sctlr |= (SCTLR_EL1_C | SCTLR_EL1_I); } write_sysreg(sctlr, sctlr_el1); isb(); local_irq_enable(); } static int __init cache_ctrl_init(void) { bool disable = true; on_each_cpu(modify_cache_per_cpu, &disable, 1); pr_info("All CPUs L1/L2 cache disabled\n"); return 0; } static void __exit cache_ctrl_exit(void) { bool disable = false; on_each_cpu(modify_cache_per_cpu, &disable, 1); pr_info("All CPUs L1/L2 cache re-enabled\n"); } module_init(cache_ctrl_init); module_exit(cache_ctrl_exit); MODULE_LICENSE("GPL");
注意:加载该模块后系统性能会下降90%以上,请勿在生产环境使用
内容的提问来源于stack exchange,提问作者Ruifeng Zhang
相关产品推荐
相关产品推荐

