INVD指令有什么用?

时间:2017-01-21 03:13:49

标签: assembly x86 cpu-cache

显然,x86 INVD使缓存层次结构无效将内容写回内存。

我很好奇,这样的指示有什么用?鉴于人们几乎无法控制各种缓存级别中的数据,甚至对异步刷新的内容控制得更少,这似乎只是一种确保你不知道的方法。什么数据再次存储在内存中。

2 个答案:

答案 0 :(得分:21)

很棒的问题!

invd这样的钝性指令的一个用例是专门的或非常早期的自举代码,例如当RAM的存在或不存在尚未被验证时。由于我们可能不知道RAM是否存在,它的大小,或者即使它的特定部分正常运行,或者我们可能不想依赖它,它有时对CPU编程自己的部分缓存很有用作为RAM运行并使用它。这称为缓存为RAM(CAR)。在设置CAR期间,在使用CAR时,以及在拆卸CAR模式期间,内核必须确保没有任何内容从该缓存写入内存。

高速缓存作为-RAM

输入CAR

要设置CAR,必须将CPU设置为 No-Fill Cache Mode ,并且必须将用于CAR的内存范围指定为 Write-Back 。这可以通过以下步骤完成:

  1. 设置 MTRR(内存类型范围寄存器),将一块内存指定为 WB(回写)
  2. invd整个缓存,防止任何缓存的写入被写出并导致混乱。
  3. 将缓存模式设置为正常缓存模式cr0.CD=0)。
  4. 正常缓存模式中,"触摸"通过读取内存跨度的所有高速缓存行将用作CAR,从而用它填充高速缓存。填充缓存行只能在正常缓存模式
  5. 中完成
  6. 将缓存模式设置为无填充缓存模式cr0.CD=1)。
  7. 使用CAR

    设置CAR的动机是,一旦设置,CAR区域内的所有访问(读/写)都将达到缓存,命中RAM,但缓存的内容将是可寻址的,就像RAM一样。因此,不是编写只使用寄存器的汇编代码,而是现在可以使用普通的C代码,只要它访问的堆栈和局部/全局变量被限制在CAR区域内。

    退出CAR

    当退出CAR时,对于这个"伪RAM"中的所有内存写入都是一件坏事。突然从缓存中射出并将任何实际内容丢弃在RAM中的同一地址。因此,当退出CAR时,再次invd用于完全删除CAR区域的内容,然后设置正常缓存模式

    Intel 80486手册

    英特尔提到i486 Microprocessor Programmer's Reference Manual中使用Cache-as-RAM。 Intel 80486是首先引入invd指令的CPU。第12.2节阅读:

      

    12.2内部缓存的操作

         

    软件控制缓存的操作模式。可以启用缓存(复位初始化后的状态),缓存可以在存在有效缓存行时禁用(缓存充当快速内部RAM的模式),或者可以完全禁用缓存。

         

    禁用缓存时必须遵循注意事项。只要CD设置为1,如果副本仍在缓存中,i486处理器将无法读取外部存储器。每当NW设置为1时,如果数据在缓存中,i486处理器将不会写入外部存储器。这意味着过时的数据可以在i486 CPU缓存中开发。如果NW稍后设置为0,或者由于高速缓存未命中而稍后覆盖该高速缓存行,则该过时数据将不会写入外部存储器。通常,禁用时应刷新缓存。

         

    通过在设置CD和NW时使用测试寄存器加载数据,可以冻结缓存中的数据。这对于为时间关键的中断代码和数据提供有保证的高速缓存命中非常有用。

         

    请注意,所有段都应从16字节边界开始,以允许程序将缓存行中的代码/数据对齐。

    用法示例

    Libreboot有slide-deck presenting their implementation of CAR,描述了上述过程。幻灯片21上使用了invd指令。

    AMD称其为§2.3.3: Using L2 Cache as General Storage During Boot中的Cache-as-general-storage。

    其他用途

    在某些由于 DMA(直接内存访问)硬件而导致缓存不一致的情况下,invd也可能有用。

答案 1 :(得分:2)

详细说明 IwillnotexistIdonotexists 关于 CAR 的回答:

我认为它实际上是如何完成的

  1. 设置 WB MTRR(而不是 PAT,因为必须禁用寻呼;PAT 在 PMH 中运行,而 MTRR 在加载/存储缓冲区或 L1d 中运行)以覆盖所需的 CAR 空间。我认为 CAR 范围需要正确映射到 SAD 中,并且需要有一个后备存储——在这种情况下,使用映射到 SPI 闪存和代码实际所在的地址范围是有意义的,然后你只需要将它读入缓存。您可以拥有此范围 UC 并将其读入缓存,然后将其写入映射到其他地方的 WB 范围——可能不需要物理设备支持,但确实需要在 SAD 中进行映射,否则您可能会在它到达 L3 时或仅在需要访问后备存储时获取 MCA。在切换到 CAR 模式之前,您确实需要首先从该地址范围引入有效行(除非您可以对该范围执行无 RFO 写入,这样它就不需要引入任何行,并且 {{ 1}} 应该使用像 ItoM 这样的无 RFO 协议,即它只发送一个无效而不是一个 RFO),所以我认为它需要映射到一个实际的设备,比如 SPI 闪存,因为当从一个如果设备在 SAD 中有映射,但是当它到达 IIO 或 IMC 时没有接收设备,我认为你会得到一个 MCA。如果您在执行此操作时实际上得到 0,或者使用 rep stos,那么这将是一个可能的选择。
  2. 除非您知道缓存中的某些内容会占用不必要的空间,否则您不需要 rep stos 缓存,而在内存控制器和 RAM 之前的启动阶段通常不会出现这种情况内存映射已配置。
  3. 将数据读入缓存
  4. CR0.CD = 1。您可能只是禁用了 BSP 上的缓存,因此您这样做只会禁用 L1 和 L2(我不知道它是否仅禁用了该逻辑核心的 L2或两者兼而有之,我认为只是逻辑核心,因为我相信 CD=1 是存储缓冲区中加载/存储本身的属性,并且它不会为该加载/存储驱逐任何内容)。 L3 永远不会发生驱逐,因为缓存未在 CAR 模式下填充,但您可能会遇到在启用 CAR 之前写入期间推送到 L3 的某些内容。 L3 不能被禁用,因为没有 INVD 并且会正常运行,但核心可能会通知 L3 切片 CBo 它在无填充缓存模式下运行,并且不会填充 L3在错过。或者,它可以通过向 Cbo 发出 UC 请求来完全绕过 L3。我不知道是否为写入命中保持缓存一致性,以及是否处理来自其他内核的一致性请求,但是 couple of sources 声称是这种情况,但是当您只知道 BSP 处于活动状态时,这无关紧要。如果它确实在较低的缓存中命中,那么它不会提高它。
  5. 在 CAR 中,读命中将从缓存行中读取,读未命中将从内存中读取但不会填充缓存行。写命中将写入缓存行,写未命中将写入内存而不是像填充缓存那样。这样,就不会发生驱逐(来自 L1/L2)并造成严重破坏。在此状态下使用 ia32_misc_enable 将有效地完全禁用缓存,因为缓存中不会命中任何内容,因此在 CAR 期间不使用 INVD,仅在最后使用。
  6. 完成后,INVD。如果 CAR 区域由 SPI 闪存支持,它将不会 100% 匹配 SPI 闪存的内容。它仍然在写,即一些随机代码上的堆栈,您不想意外地将其写回 SPI 闪存,因此您必须INVD 而不是 INVD,如果 SPI 闪存拒绝写入,您将得到一个马华大概。当第一次提到 CAR 时,该指令是在 486 上引入的,这表明该指令是为此目的而引入的,我认为没有其他用例。
  7. CR0.CD = 0

在 Intel 上,CAR 是通过微码设置来运行 Startup ACM,因此它不需要为此使用特定的宏指令。然而,WBINVD 和 CAR 在内存控制器初始化之前由 ACM 本身和 SEC 内核使用,这当然需要 INVD 宏指令。我必须检查 SEC 内核是否启用了 CAR 或者它是否已经启用,但我知道包含 SEC + PEI 的 IBB 块在 L3 中。

需要提及的重要一点是,当您将代码加载到缓存中时,您需要确保将其从 L1d 中推送到 L2 中,否则指令缓存将无法访问它。这可以通过加载代码然后加载大于 L1d 大小的东西来实现(它是共享的,而不是在线程之间静态分区,因此它需要大于 L1d 的完整大小)。我认为这是因为 L1d 与 L1i 不一致,尽管有一种叫做 SMC 的东西,所以我不确定它在多大程度上是正确的。