在ARMv8-A Linux上禁用CPU缓存(L1/L2)

4
我想在运行Linux的ARMv8-A平台上禁用低级缓存,以测量经过优化的代码的性能,独立于缓存访问。对于英特尔系统,我找到了以下资源 ( Is there a way to disable CPU cache (L1/L2) on a Linux system? ),但由于指令集不同,无法直接应用。目前,我有一个内核模块,它改变相应的系统寄存器来禁用指令和数据缓存。
#include <linux/module.h>

int init_module(void)
{
  int64_t value;

  asm volatile("\
    MRS %0, SCTLR_EL1     // Read SCTLR_EL1 into Xt\n\
    BIC %0, %0, (1<<2)    // clear bit 2, SCTLR_EL1.C\n\
    BIC %0, %0, (1<<12)   // clear bit 12, SCTLR_EL1.I\n\
    MSR SCTLR_EL1, %0     // Write Xt to SCTLR_EL1\n\
  " : "+r" (value));

  return 0;
}

void cleanup_module(void)
{
  int64_t value;

  asm volatile("\
    MRS %0, SCTLR_EL1    // Read SCTLR_EL1 into Xt\n\
    ORR %0, %0, (1<<2)   // set bit 2, SCTLR_EL1.C\n\
    ORR %0, %0, (1<<12)  // set bit 12, SCTLR_EL1.I\n\
    MSR SCTLR_EL1, %0    // Write Xt to SCTLR_EL1\n\
  ": "+r" (value));
}

MODULE_LICENSE("GPL");

然而,当加载时(当我在系统寄存器中设置相应的位)会导致完整系统冻结。 我猜我仍然需要某种缓存清除,但我在ARM手册中没有找到任何有用的信息。
请问有什么帮助提示可以让我成功禁用ARM上的缓存或者我错过了什么?谢谢。

你看过这个了吗?http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0438f/BABHEJFF.html - HRgiger
请参见此链接:https://devtalk.nvidia.com/default/topic/973472/how-to-write-bits-to-registers-in-order-to-disable-cache-/?offset=5 - HRgiger
我曾经在armv6上看到过这样的操作,它非常麻烦,我们遇到了ldrex/strex问题(你需要深入芯片供应商的内存控制器而不是ARM的,因此如果实现了独占访问或正确性,则与芯片相关)。我不知道AXI/AMBA对于armv8是否有太多变化,以知道在缓存禁用时是否生成了所需的保护和mmu总线访问。 - old_timer
你需要在各个地方添加许多内存屏障,清空缓存等等,你不能简单地关闭这些位。最好从删除首次打开它们的代码的角度进行攻击。 - old_timer
@HRgiger 谢谢你提供的链接,它们是针对旧版本架构的,但似乎恰好展示了我遇到的问题。 - silvan
@old_timer 谢谢你的提示,也许我可以找到它们被启用的地方。 - silvan
2个回答

6
一般来说,由于几个原因,这是行不通的。
首先,清除SCTLR.C位只会使所有数据访问非缓存,并阻止任何缓存分配。缓存中的任何数据仍然在缓存中,特别是从最近写入的任何脏行;考虑当您的函数返回并且调用者尝试恢复一个栈帧时,它甚至不存在于它现在访问的内存中会发生什么。
其次,很少有单处理器ARMv8系统;假设您正在运行SMP Linux,并突然禁用了某个CPU上的缓存,则即使忽略第一点,事情也会迅速恶化。Linux期望所有CPU彼此协调,并且如果该假设被违反,通常会非常快速地变得非常糟糕。请注意,甚至不值得为此进行SMP交叉调用;可以说唯一安全的尝试在未启用缓存的情况下运行Linux的方法是确保它们从未启用,除非...
第三,不能保证Linux即使在缓存禁用的情况下也会运行。在当前硬件上,内核中的所有锁定和原子操作(更不用说用户空间)都依赖于独占访问指令。虽然CPU集群将实现可缓存内存的架构所需的本地和全局独占监视器(通常作为缓存机制本身的一部分),但它取决于系统是否实现了用于非缓存访问的全局独占监视器,因此这样的东西必须是外部的CPU(通常在互连或内存控制器中)。许多系统不实现这样的全局监视器,在这种情况下,对外部内存的独占访问可能会导致故障,无动于衷或其他各种实现定义的行为,这将导致Linux崩溃或死锁。在这样的系统上禁用缓存以运行Linux实际上是不可能的-仅使UP arm64内核工作(SMP将完全不可能)的大量黑客攻击就足够不切实际了;祝你好运。
事实上,最糟糕的问题不是这些,而是这个:
“...为了测量优化代码的性能,独立于缓存访问。”
如果代码旨在在禁用缓存的情况下部署运行,则逻辑上不能打算在Linux下运行,因此,花费精力来hack Linux将更好地花在在更现实的执行环境中进行基准测试,以便结果实际上具有代表性。另一方面,如果它旨在启用缓存(在Linux或任何其他操作系统下),则禁用缓存进行基准测试将给出毫无意义的结果,并且是浪费时间的行为。“优化”例如指令获取带宽瓶颈,这在实践中并不存在,不会引导您朝着正确的方向前进。

感谢您提供详细的答案,它确实展示了为什么禁用缓存没有成功的原因。所以最后一次尝试可能是在内核源代码中禁用缓存启用部分,但我也看到了您指出的这样做也会很棘手的原因。 - silvan
目标基本上是要有固定的运行时间,拥有优化的、恒定时间的代码。因此,在测试环境中使用缓存将会对证明恒定时间执行造成不利影响。 - silvan
如果你真的想绕过D-cache,那么更简单的方法是通过黑客手段修改mmap/mprotect函数,让你在进程中设置一些非缓存内存来操作。禁用I-cache除了会使所有性能都变得很差之外,任何“优化”都可能会使实际性能变得更糟。在这种情况下,稍微减少一些速度的优化措施可能会使现实世界的性能变得更差。 - Notlikethat
这可能基本上是我的问题的解决方案。你可以加一些关于如何操作的详细信息吗?我也在ARM架构参考手册中看到了有关缓存提示的内容,但没有找到有关如何使用它们的信息。它们是否与您的方法相关? - silvan
通常,“常数时间”意味着“不依赖于数据”,任何体面的基准测试工具都应该考虑缓存效应。在现代硬件上完全以恒定的“挂钟”时间执行是不可能的,特别是在Linux下 - 您必须摆脱操作系统、外部I/O、中断等,此时您必须开始思考任何结果的意义。 - Notlikethat
我并不确切地“知道”如何做到这一点 - 我只是猜测,比如说,一个匿名的mmap将会给你一些使用pgprot_cached内存映射,因此应该很容易地塞入一些标志,使其使用pgprot_writecombine - Notlikethat

0

我已经在armv8-a linux上完成了它。我这样做不是为了衡量性能,而是为了验证xilinx zcu104平台可能存在一些相干性错误。结果是,xilinx提供的pynq镜像在pl和ps通信期间必须存在一些相干性错误。

以下是我的解决方法:

  1. 我的平台是cortex-a53,ubuntu18从EL2启动并切换到EL1,并支持四个CPU核心的SMP。因此,我需要关闭多核以确保L2缓存一致性。由于cpu-hot-plug功能,我只需运行:

    echo '0' > /sys/devices/system/cpu/cpu1/online,

    echo '0' > /sys/devices/system/cpu/cpu2/online,

    echo '0' > /sys/devices/system/cpu/cpu3/online

    然后我运行dmesg来验证已经关闭了多核。

  2. 我构建了内核源代码树,因为我在我的Linux中找不到它。您可以运行uname -r来查看您的内核版本。并在/usr/src中查找是否已经有它。

  3. 我构建了Linux模块。使用gcc inline asm,我刷新了所有缓存并设置了sctlr_el1.c 0。

  4. 我insmod了模块。我首先用正确的结果运行了我的程序,尽管它比多核和D-cache开启慢20倍。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接