跳转至

fence.i

缓存一致性问题

当使用 store 指令修改内存中的数据时候, 我们很可能会去修改一个存储着指令的地址, 而改地址若已经 cached, 则会导致缓存中所存储的指令与实际存储在目标地址的指令不一致, 导致程序行为的错误, 无法实现预期的效果

// smc.c
int main() {
  asm volatile("li a0, 0;"
               "li a1, UART_TX;"     // change UART_TX to the correct address
               "li t1, 0x41;"        // 0x41 = 'A'
               "la a2, again;"
               "li t2, 0x00008067;"  // 0x00008067 = ret
               "again:"
               "sb t1, (a1);"
               "sw t2, (a2);"
               "j again;"
              );
  return 0;
}
上面这一段代码的预期结果是通过串口输出一次字母 A 后, 修改标号 again 处的指令为 ret, 实现退出, 而在我所实现的 npc 中的实际运行效果是不断的输出字母 A, 这与代码所描述的行为是不一致的, why? 正是因为我们修改了程序代码, 但在 cache 中的备份却没有被修改, 导致了程序行为的不符合预期. fence.i 指令正是用来解决这样的问题, 在程序访问这个数据块之前, 执行 fence.i, 表明其之后的取指操作都可以看到其之前的 store 指令修改的结果, fence 正是像一个屏障, 让其之后的指令无法跨越个这个屏障来读取到被 store 指令修改之前的旧数据.

fence.i 指令的实现方式

fence.i 指令可以有多种的实现方式, 只要达到了屏障的目的即可. 一种简单的方式是在执行 fence.i 指令时候, 将 cache 中的所有块都标记为 invalid, 并且对流水线进行冲刷, 则下一次访问该指令的时候, 就会触发 cache miss, 重新从存储器中更新缓存 (冲刷缓存与流水线)