在多核 x86 系统上,互斥体是否使用 LOCK'd 指令实现?



在x86程序集中,有一个LOCK前缀可以应用于指令,使其成为原子。这是所有核心的原子性吗?通常的延误是什么?对于一个普通的互斥体,被锁定的指令是什么?

谢谢。PS:我被教导,在缺乏这样一条指令的系统上,互斥仍然可以完成,但这更费力。我想知道是否还有人再这样做了。

在x86上,锁前缀锁定所有内核并允许原子性。为了在没有LOCK的其他系统上实现这一点,使用CMPXCHG循环或具有重试逻辑的紧密循环,尝试将某个值设置为预期值。正如你所看到的,在大多数情况下,第二种方法更有害,因为它只是不断地循环尝试设置值(并一直这样做,直到完成为止(。对于x86,延迟是最小的,可能包括停止指令管道或刷新它,然后原子地执行该指令(通常是几个周期(,第二种方法不能真正估计,因为它取决于需要原子地修改的值有多少争用。对于互斥锁,我认为它是一个必须获取的标志值(检查互斥锁是否未被获取,并持续等待,直到互斥锁被获取,然后尝试原子性地更改标志以获取它(。

AFAIK-IBM处理器使用第二种方法,因为在linux内核上工作时,我发现原子增量函数使用了它(也许它只适用于较旧的处理器(。x86平台仍然使用

lock addl ...;

不过我承认,我已经一年没有在内核的这一部分工作了,所以我可能在某些方面错了。

在x86程序集中,有一个LOCK前缀,可以应用于指令以使其成为原子。这是所有核心的原子性吗?

是的。

通常的延误是什么?

成本取决于多个因素(CPU型号、CPU速度、总线速度、RAM速度、数据当时的实际位置以及试图使用总线的其他内容(。对于非常旧的CPU(8086808868028680386(,没有缓存,LOCK锁定了总线以确保没有其他东西可以干扰。这不会比没有LOCK的同一条指令花费太多,只是在指令持续时间内尝试使用的所有其他指令都必须等待(它比代码本身慢得多(。

对于所有现代CPU(我猜是"Pentium III或更高版本"(,它被更改为尽可能依赖于MESI缓存一致性协议。具体来说,将缓存线放入缓存并更改为"独占"状态,然后在不锁定总线的情况下单独使用本地缓存执行指令。这样做的成本取决于数据在哪里——例如,如果它已经在同一CPU的L1数据缓存中(而不是在其他CPU的缓存中(,那么LOCK可以不花费任何成本。然而,(由于多CPU代码的性质(缓存线通常在不同的CPU缓存中,需要从一个缓存转移到另一个缓存和/或在其他CPU缓存中无效(通过MESI缓存一致性协议中称为"读取所有权"的东西(,因此成本更高(但仍然没有锁定总线那么高(。

对于一个普通的互斥体,被锁定的指令是什么?

多年来,我已经看到互斥实现了大约20种不同的方式。在所有不同的实现中,没有一条指令是相同的。

请注意,当您无法获取互斥对象时,内核会被告知在释放互斥对象之前不要给您的任务任何CPU时间;然后当互斥被释放时,内核需要被告知该任务可以再次消耗CPU时间。这有竞争条件,最终是内核调度程序中的原子"检查是否可以获取互斥,然后更改任务的状态"。

还要注意,这相当昂贵,因此大多数实现都试图在用户空间中尽可能乐观地进行;这样,在没有争用的情况下,获取互斥对象时就不必涉及内核;因此,如果没有任何东西被阻止等待互斥,那么内核就不会被告知取消阻止不存在的等待任务。如果互斥被争用以增加内核不参与的机会,也有一些变体会在短时间内旋转。

换句话说;获取和释放互斥体的代码通常甚至不在一个地方——它是两部分,其中一部分在用户空间,另一部分在内核中。

最新更新