3. 原子信号量说明

Itanium 处理器提供了许多称为“信号量指令”的原子读取修改写入操作。

信号量指令之所以昂贵,是因为它们获取了专有的高速缓存行,然后以原子方式对高速缓存行执行了读取修改写入周期

处理器无法以与其他Itanium指令相同的方式优化内存访问。

信号量指令始终是非推测性(non-speculative)的。

这意味着原子信号指令会导致管道停顿。

所有信号量操作都需要访问存储器所需的全部周期(对于远距离的存储器来说可能是相当大的数目!)加上5个时钟。

非信号量指令引用存储器可能仅消耗1个时钟,甚至可以与另一条指令并行化。

但是,原子信号操作是必需的,以便通过单个处理器实现状态转换,并且这些状态更改是实现锁定的必要元素

如果没有信号量指令,则多个处理器可能会更改存储位置,但是该处理器无法断定这是唯一存储影响更改的唯一处理器。

信号量指令必须具有释放或获取语义,并且始终在写入之前进行读取。

在信号量指令的读取和写入之间将没有其他访问同一存储区的操作。

3.1 比较和交换(Compare and exchange)

比较和交换指令允许指定一个存储位置应具有的内容以及要放入该存储位置的新值。

原子操作按以下方式执行。

首先,获取专用缓存行,并且停止对缓存行的所有状态更改。

然后将存储位置的内容与期望值进行比较。如果内存位置具有期望值,则将新值写入内存位置。

ps: 这里其实就是我们常说的 CAS

只有这样,才允许高速缓存行更改状态或被另一个处理器获取。

这允许对存储位置进行原子状态转换。

CMPXCHG 操作将在存储新值之前返回存储位置的值。

如果返回的值是预期的旧值,则操作成功。

如果返回了期望值,那么我们可以确定没有其他进程与此进程竞争,只有该处理器会影响从旧值到新值的状态转换。

一个处理器的状态转换的唯一性通常用于锁定,因为我们可以确保只有一个处理器在某个内存位置成功完成状态转换。

如果其他处理器等到它们可以进行相同的状态转换,则可以确定只有一个处理器获得对某些资源的独占访问权,而其他处理器则从受此机制保护的资源中排除。

3.2 Fetchadd

Fetchadd 自动向内存位置添加一个值,并返回结果。

Fetchadd 是一种实现计数器而又不受其他锁定机制保护的计数器值的方法。

正常的计数器更新受竞争条件的影响,因为递增计数器意味着加载计数器值,递增寄存器值,然后将结果写回内存。

fetchadd 的另一个用途是实现使用情况计数器。

对于结构的每个用户,使用计数器都会增加。

如果用户不再需要该结构,则使用计数器通过fetchadd递减。

我们可以检查这是否是结构的最后一个用户,因为fetchadd也返回结果。

如果结果为零,则可以释放结构。

ps:这个让我想到了 AtomicInteger 中的原子性操作。

3.3 Xchg

Xchg 指令今天很少使用。

从历史上看,这是第一条原子指令,因为它已经为单处理器系统实现,因此可用于多处理环境中的同步。

xchg的创造性使用造就了(led to)了初始锁定算法。

Xchg可用于原子替换值并检查值后。

如果需要进行状态更改,并且代码随后可以处理状态变量中编码的先前条件,则这很有用。

小结

本文主要介绍了几种常见的指令集,可能我们以前最熟悉的就是 CAS 这种操作模式了。

Fetchadd 也是非常有用的计数器指令。

最后一个指令 Xchg,我们了解即可。

下一节让我们一起学习下自旋锁实现。

参考资料

linux 锁实现