Locked Atomic Operations
32位的IA-32处理器支持对系统内存中的位置执行“加锁的原子操作”。这些操作通常用于管理共享的数据结构(例如信号量,段描述符,系统段,或者页表)。两个或多个处理器可能会同时修改其中的某些域或者标志。处理器使用3种互相依赖的机制来实现加锁的原子操作:
受保障的原子操作总线封锁,使用LOCK#信号和LOCK指令前缀缓存一致性协议,确保可以对缓存中的数据结构执行原子操作,被称为缓存封锁(cache lock);这种机制适用于P6,Pentium 4,Intel Xeon(及其以后)系列处理器
这些操作主要在以下几个方面互相依赖。
某些基本的访存事务(例如从存储器中读或写一个字节)总是受保障的原子操作。换句话说,一旦事务启动,处理器保证在 本次事务完成之前,不会有其他的处理器或者总线代理(bus agent)被允许访问同一个存储器地址。
处理器还支持对某些选中的访存操作(例如对存储器中的共享区域执行读-改-写操作)执行“带总线封锁”的原子操作。从业务逻辑上看,对这些区域的访问应该是原子性的,但是处理器并不会自动地执行原子操作。所以需要在指令代码中明确指出(即使用lock指令前缀)。
在现代处理器上,频繁使用的存储器数据通常会被缓存在处理器的L1或者L2缓存中,原子操作通常直接作用在处理器的缓存中,无需断言总线封锁(assert bus lock,注:这里指不需要使用CPU的LOCK#引脚,在指令代码中还是需要lock前缀的。)
注意:
当有竞争的加锁访问时,软件需要实现自己的算法,确保对资源的公平访问,以避免锁饥饿或饿死。硬件没有提供任何资源用于确保参与者之间的公平竞争。管理信号量和互斥锁功能的公平性是软件的责任。
随着IA-32处理器复杂性增加,加锁的原子操作机制也相应的跟着演化。相比起早期的处理器,比较新的IA-32处理器(P6,Pentium 4,Intel Xeon系列)和Intel 64处理器提供了更加精细化的加锁机制。下面详细的讨论这些机制。
Guaranteed Atomic Operation
Intel486及其后代处理器确保如下的基本操作总是自动原子性(即无需程序员编写任何指令)的:
读/写单个字节读/写按16位对齐的单字读/写按32位对齐的双字
Pentium及其后代处理器确保如下的访存操作总是原子性的:
读/写按64位对齐的四字访问非缓存类型存储器(uncached memory)中16位宽的数据且数据的16个比特位必须能够容纳在32位的数据总线中。参看下图
P6及其后代处理器确保如下的访存操作总是原子性的:
访问缓存的存储器(cached memory)中非对齐的16-,32-,和64-位宽的数据且数据的比特位必须能够容纳在同一个缓存行中。
在Intel Core 2 Duo,Intel Atom™,Intel Core Duo,Pentium M,Pentium 4,Intel Xeon,P6 family,Pentium,和Intel486处理器上,对于可缓存的存储器区域,访问跨缓存行或者跨页面边界的数据不保证是原子性的。在Intel Core 2 Duo,Intel Atom,Intel Core Duo, Pentium, Pentium 4, Intel Xeon,和P6 family处理器上,通过总线控制信号可以允许外部内存子系统将跨界访问(split access)变成原子性的;但是,非对齐的数据访问会严重的影响处理器性能,应该要避免。
x87指令或者SSE指令都有可能访问超过4字(即8字节)宽度的数据,这种访问有可能需要多次存储器访问操作。如果将如此长的数据写入内存,某次操作可能已经完成内存写入,但是后续写存操作可能由于架构原因(例如页表入口被标记为“不存在”,即内存缺页)还未完成。在这种情况下,尽管整条指令出现故障,尚未执行完毕,但是部分数据(写存完成的那次操作)可能已经对软件可见了。如果TLB的无效化invalidation操作由于某种原因被延迟的,即便是同一个存储器页面访问,也可能发生缺页故障。
如果觉得《Intel 64/x86_64/IA-32/x86处理器 - 锁原子操作(1) - 处理器保证的原子操作》对你有帮助,请点赞、收藏,并留下你的观点哦!