【问题标题】:What use is the INVD instruction?INVD 指令有什么用?
【发布时间】:2017-06-06 03:13:20
【问题描述】:

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

我很好奇,这样的指令有什么用?鉴于人们几乎无法控制各种缓存级别中的数据,甚至更少控制可能已经异步刷新的数据,这似乎只是一种确保您不知道哪些数据的方法不再保留在内存中。

【问题讨论】:

  • 好吧,如果你想知道 INVD 指令后内存中的内容,你所要做的就是阅读它。 INVD 适用于当您不再关心内存中的内容时。英特尔的 INVD 手册将“临时内存、测试或故障恢复”列为您可能使用它的情况。
  • 我很震惊:不知何故,SO 设法在这个 x86 指令invd 存在的所有时间里都没有问一个关于它的目的的问题(一直追溯到 1989 年的 80486),所以这不是一个骗局。它也是 Google 上“invd”的第 4 名。
  • @IwillnotexistIdonotexist 经常发生在我身上,当我搜索首字母缩写词时,我的答案是唯一的逐字搜索 Google 结果,我意识到除非来自英特尔的人,否则我永远不会知道它是什么建筑设计告诉我商品

标签: assembly x86 cpu-cache


【解决方案1】:

很好的问题!

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

缓存为 RAM

进入汽车

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

  1. 设置一个MTRR(内存类型范围寄存器),将一块内存指定为WB(Write-Back)
  2. invd 整个缓存,防止任何缓存的写入被写出并造成混乱。
  3. 将缓存模式设置为普通缓存模式 (cr0.CD=0)。
  4. 普通缓存模式中,“触摸”内存跨度的所有缓存行以用作 CAR 通过读取它,从而用它填充缓存。填充缓存线只能在普通缓存模式下完成。
  5. 将缓存模式设置为No-Fill Cache Mode (cr0.CD=1)。

使用汽车

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

退出汽车

当 CAR 退出时,对于这个“伪 RAM”中发生的所有内存写入突然从缓存中射出并丢弃 RAM 中同一地址的任何实际内容,这将是一件坏事。所以在退出CAR时,再次使用invd将CAR区域的内容彻底删除,然后设置Normal Cache Mode

英特尔 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 字节边界开始,以允许程序对齐高速缓存行中的代码/数据。

使用示例

coreboot 有一个slide-deck presenting their implementation of CAR,它描述了上述过程。 invd 指令用于幻灯片 21。

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

其他用途

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

【讨论】:

  • 不一定希望您知道,但 INVD 指令自 486 以来就已存在。当时缓存即 RAM 是否也存在?我相当确定RAM配置当时仍然由芯片组在硬件中完成,但我可能是错的。如果它不用于 486 上的缓存即 RAM 用途,那么它可能是什么?
  • @Dolda2000 好吧,我确实知道它可以追溯到 486,因为我在您的问题下方的评论中对此进行了说明。 AFAIK 386 没有板载缓存,因此它不需要刷新/无效指令(这将是主板的责任)。但事实上,从一开始就提到了 Cache-as-RAM 技巧,在 80486 的用户手册中,§12.2 OPERATION OF THE INTERNAL CACHE
  • @IwillnotexistIdonotexist:很棒的发现和有趣的阅读。
  • @Iwillnotexistidonotexist 另外,您肯定必须确保在使用 CAR 时不要将任何东西推出 L3 吗? IE。通过饱和一组。除非 cr0.NW 阻止这个
  • 我还假设它必须在内存映射的 I/O 区域上完成,这样才能实际读取一行,所以它可以被写入
【解决方案2】:

详细说明 IwillnotexistIdonotexists 对 CAR 的回答:

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

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

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

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

【讨论】:

  • PMH = 页面小姐处理程序?这不是一个众所周知的首字母缩写词,您使用的许多其他缩写词也不是。 :/ 使第一次使用超链接到定义可以涵盖这一点。
  • @PeterCordes 我很确定如果您将英特尔 放入 Google 并启用逐字搜索,这一切都会出现。例如,英特尔 PMH 的第 5 个结果是关于它的专利,并且架构中没有类似的首字母缩略词。我将添加更多参考。我慢慢开始明白,我真的不知道为什么你根本不需要填充缓存/CAR 模式......我错过了一些东西。驱逐仅在需要将某些内容带入缓存并且没有空间时才会发生。除非它投机驱逐或其他什么,否则 CAR 没有意义,因为尽管它不驱逐,它仍然会
  • 将导致驱逐的写入直接写入后备存储。我认为它只是按需驱逐,作为 PLRU 的一部分
  • 我的意思是,我不明白为什么需要 CR0.CD=1 来有效地将区域设置为 WB 并将缓存用作 RAM。如果您从不访问范围之外的 WB 区域,则应该没问题
  • 我怀疑 evict 提前,但肯定 write-back (没有驱逐,清理脏行并从修改到独占)是合理的。这可以利用空闲的写入带宽,并使未来的驱逐更便宜/更快。
猜你喜欢
  • 2021-05-14
  • 2021-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
  • 2011-12-13
相关资源
最近更新 更多