首页 文章

预取指令行为

提问于
浏览
0

为了满足某些安全属性,我想确保一个重要数据在语句访问时已经在缓存中(因此不会有缓存未命中) . 例如,对于此代码

...
a += 2;
...

我想确保 a 在执行 a += 2 之前就在缓存中 .

我正在考虑使用x86的 PREFETCHh 指令来实现这个目的:

...
__prefetch(&a);     /* pseudocode */
a += 2;
...

但是,我已经读过,在 a += 2 之前插入预取指令可能为时已晚,以确保执行 a += 2a 在缓存中 . 这个说法是真的吗?如果是,我可以通过在预取之后插入 CPUID 指令来修复它,以确保执行了prefectch指令(因为英特尔手册说 PREFETCHh 是针对 CPUID 订购的)?

1 回答

  • 5

    是的,您需要预先获取大约内存延迟的提前期,以使其达到最佳状态 . Ulrich Drepper的What Every Programmer Should Know About Memory讲述了很多关于预取的内容 .

    实现这一目标对于单一访问来说将是非常重要的 . 太快了,你的数据可能会在你关心的insn之前被驱逐出去 . 太晚了,它可能会减少一些访问时间 . 调整它将取决于编译器版本/选项,以及您正在运行的硬件 . (更高的每周期指令意味着您需要提前预取 . 更高的内存延迟也意味着您需要提前预取) .

    由于您要对 a 执行读取 - 修改 - 写入,因此应使用 PREFETCHW (如果可用) . 其他预取指令只是预取用于读取,因此RMW的读取部分可能会命中,但我认为存储部分可能会被MOSI缓存一致性延迟,从而获得缓存行的写入所有权 .

    如果 a 不是原子的,您也可以提前加载 a 并在寄存器中使用该副本 . 在这种情况下,回到全球的商店很容易错过,最终可能会停止执行 .

    你可能很难用编译器做一些可靠的事情,而不是自己编写asm . 任何其他想法也需要检查编译器输出,以确保编译器做了你希望的 .

    预取指令不一定预取任何内容 . 它们是“提示”,当未完成的负载数量接近最大值(即几乎没有负载缓冲区)时,可能会被忽略 .


    另一种选择是加载它(不仅仅是预取)然后使用 CPUID 进行序列化 . (抛出结果的负载就像预取一样) . 在序列化指令之前必须完成加载,并且序列化insn之后的指令在此之前不能开始解码 . 我认为预取可以在数据到达之前退出,这通常是一个优点,但在这种情况下我们不关心一个操作以牺牲整体性能为代价 .

    来自英特尔的insn参考手册(参见x86标签wiki) CPUID 的条目:

    序列化指令执行保证在获取和执行下一条指令之前完成对先前指令的标志,寄存器和存储器的任何修改 .

    我认为这样的序列相当不错(但在先发制人的多任务系统中仍然无法保证):

    add [mem], 0        # can't retire until the store completes, requiring that our core owns the cache line for writing
    CPUID               # later insns can't start until the prev add retires
    add [mem], 2        # a += 2   Can't miss in cache unless an interrupt or the other hyper-thread evicts the cache line before this insn can execute
    

    这里我们使用 add [mem], 0 作为写预取,否则就是近无操作 . (这是一种非原子读 - 修改 - 重写) . 我不确定如果你做 PREFETCHW / CPUID / add [mem], 2PREFETCHW 是否真的能确保缓存行准备就绪 . insn是wrt订购的 . CPUID,但手册并未说明预取效果是否已订购 .


    如果 avolatile ,则 (void)a; 将获得gcc或clang以发出加载insn . 我假设大多数其他编译器(MSVC?)是相同的 . 您可以执行 (void) *(volatile something*)&a 取消引用指向 volatile 的指针并强制从 a 的地址加载 .


    To guarantee that a memory access will hit in cache, you'd need to be running at realtime priority pinned to a core that doesn't receive interrupts . 根据操作系统的不同,定时器中断处理程序可能足够轻,以至于从缓存中驱逐数据的可能性足够低 .

    如果您的进程在执行预取insn和执行实际访问之间进行了调度,则数据可能已从至少L1缓存中逐出 .

    所以你不可能击败一个决心对你的代码进行定时攻击的攻击者,除非以实时优先级运行是现实的 . 攻击者可以运行许多内存密集型代码的线程......

相关问题