什么是处理器Lock#信号及其工作原理?
我正在读一本关于汇编(中级)的书,它提到了一些指令,比如xchg自动断言处理器 LOCK# 信号。在网上搜索它发现它赋予处理器任何共享内存的专有权,但没有具体细节。这让我想知道这个权利是如何运作的。
- 这是否意味着任何其他计算机设备(如 GPU 或其他设备)无法访问内存。实际上,其他设备可以直接与 RAM 通信,而无需先通过 CPU。
- 处理器如何知道它处于这种锁定状态,例如它是否保存在控制或 rflags 寄存器中,或者因为我在拥有多核 CPU 时无法看到此操作是如何工作的。
- 我访问的网站说锁定任何共享内存。这是否意味着在此锁定期间,整个 RAM 被锁定,或者仅锁定执行指令的内存页(或部分内存而不是全部)。
回答
基本问题是一些指令读取内存,修改读取的值,然后写入新值;如果内存的内容在读取和写入之间发生变化,(一些)并行代码可能会以不一致的状态结束。
一个很好的例子是一个 CPU 做inc dword [foo]而另一个 CPU 做dec dword [foo]。两条指令(在两个 CPU 上)执行后,值应该和原来的一样;但是两个 CPU 都可以读取旧值,然后两个 CPU 都可以修改它,然后两个 CPU 都可以写入它们的新值;导致该值比您预期的高 1 或低 1。
解决方案是使用#lock信号来防止其他任何东西同时访问同一块内存。例如,第一个 CPU 会断言,#lock然后执行读取/修改/写入,然后取消断言#lock;和其他任何事情都会看到#lock被断言并且必须等到#lock被取消断言才能进行任何内存访问。换句话说,它是一种简单的互斥形式(就像自旋锁,但在硬件中)。
当然,“其他一切都必须等待”有性能成本;所以它主要只在软件显式请求时完成(例如lock inc dword [foo]而不是inc dword [foo]),但有一些情况下它是隐式完成的 -xchg操作数使用内存时的指令,以及更新 CPU 使用的某些表中的脏/访问/忙标志(用于分页和 GDT/LDT/IDT 条目)。还; 后来(我认为是 Pentium Pro?),该行为被优化为与缓存一致性协议一起使用,因此#lock如果缓存行可以置于独占状态,则不会断言。
注意:在过去有 2 个 CPU 错误(Intel Pentium "0xF00F" 错误和 Cyrix "Coma" 错误),其中 CPU 可以被欺骗来断言#lock信号并且永远不会取消断言它;导致整个系统锁定,因为没有任何东西可以访问任何内存。
- 这是否意味着任何其他计算机设备(如 GPU 或其他设备)无法访问内存。实际上,其他设备可以直接与 RAM 通信,而无需先通过 CPU。
是的。如果#lock被断言(不包括较新的 CPU 可以将缓存行置于独占状态的情况);任何访问内存的东西都必须等待#lock被取消断言。
注意:大多数现代设备可以/确实可以直接访问内存(将数据传输到 RAM 或从 RAM 传输数据,而无需使用 CPU 传输数据)。
- 处理器如何知道它处于这种锁定状态,例如它是否保存在控制或 rflags 寄存器中,或者因为我在拥有多核 CPU 时无法看到此操作是如何工作的。
它不保存在任何寄存器的内容中。它实际上是总线或链路上的电子信号。对于一个极其简单的例子;假设总线有 32 条“地址”线、32 条“数据”线和一条#lock线;其中“断言#lock”表示该#lock线上的电压从 0 伏升至 3.3 伏。当任何东西想要读取或写入内存时(在尝试更改“地址”线或“数据”线上的电压之前),它会检查线上的电压#lock是否为 0 伏。
注意:真正的总线要复杂得多,还需要一些其他信号(例如,传输方向、避免冲突、“I/O 端口或物理内存”等);现代总线使用串行通道而不是并行线路;现代系统使用“点对点链接”而不是“所有事物共享的公共总线”。
- 我访问的网站说锁定任何共享内存。这是否意味着在此锁定期间,整个 RAM 被锁定,或者仅锁定执行指令的内存页(或部分内存而不是全部)。
不如说公交车上锁了;一切都必须使用总线来访问内存(并且当总线被锁定时,没有其他任何东西可以使用总线,即使其他东西试图将总线用于与内存无关的事情 - 例如发送 IRQ 到一个 CPU)。
当然(由于积极的性能优化 - 主要是“如果可以将缓存行置于独占状态”优化)甚至更好的是说硬件可以做任何事情,只要结果表现得好像有被锁定的共享总线(即使没有共享总线并且实际上没有任何东西被锁定)。
注意:80x86 支持未对齐的访问(例如,您可以 lock inc dword [address]其中访问可以跨越边界),如果内存访问确实跨越边界,则 CPU 需要组合 2 个或更多部分(例如,一个缓存行末尾的几个字节和下一个缓存开始的几个字节线)。现代虚拟内存意味着,如果虚拟地址跨越页面边界,CPU 需要访问 2 个不同的虚拟页面,这些虚拟页面可能具有“非常不相关”的物理地址。如果理论上的 CPU 试图实现独立的锁(每个内存区域不同的锁),那么它也需要支持断言多个锁信号。这可能会导致死锁——例如,一个 CPU 锁定“内存页面 1”,然后尝试锁定“内存页面 2”(并且不能,因为它被锁定了);而另一个 CPU 锁定“内存页面 2”,然后尝试锁定“内存页面 1” (并且不能因为它被锁定)。为了解决理论上的 CPU 必须使用“全局锁排序”的问题 - 始终以特定顺序断言锁。最终结果将是大量的复杂性(在这种情况下,增加的复杂性可能比节省的性能成本更高)。
- Specifically, as https://patchwork.kernel.org/project/kvm/cover/1555536851-17462-1-git-send-email-fenghua.yu@intel.com/ points out, Intel Tremont and other future CPUs will have a feature that allows triggering a #AC (alignment check) exception on split `lock`ed accesses. So it's like setting the AC flag but only for `lock`ed operations, not breaking normal code. (And working in kernel mode, where AC was overloaded to mean something different.)