引言

上篇文章讲到的基本原子操作(Guaranteed atomic operations )不但不能保证读-修改-写回的原子性,还不能暴露给操作系统或者应用程序(总体来说是软件)来使用,因为它的原子性作用范围并不是机器指令。这篇文章,我们来看总线锁和缓存锁,它们可以作用在机器指令上,因此可以被软件使用。

总线锁

LOCK#信号、语义和LOCK前缀

《Intel指令参考手册》对总线锁的描述占用了整个8.1.2的内容,这里,我只介绍我们会用到的部分。
Intel 64和IA32处理器提供了一个LOCK#信号,这个信号在特定的内存操作时会自动断言来锁定系统总线和等效链路。当这个信号被断言,其他处理器对总线控制的请求会被阻塞。
锁定总线、阻塞其他处理器对总线的控制请求是LOCK信号的语义,这个语义,我们可以通过在一个指令前面加上LOCK前缀来实现,例如LOCK INC 4(%rsp)意思就是在处理器执行INC 4(%rsp)这条指令(这条指令的意思是将内存地址为%rsp这个寄存器的值加上4位置上的数据加1)时,该处理器将独占总线,其他处理器不能访问该内存位置。
所以你也可以认为总线锁是一种锁,一种硬件提供的锁,相对于操作系统或者应用程序提供的锁,它应该是最轻量的。
既然LOCK前缀是作用在机器指令上的,那么就保证了整个指令的执行是原子性的,没有其他cpu的干扰,而且作用在指令上,软件(操作系统和应用程序)就可以直接使用这个能力来实现原子操作了。
软件怎么使用这个语义呢?任何语言最终都是以机器指令的形式在硬件上执行的,对于c来说,编译器将c源代码翻译成汇编语言程序,对于java来说,虚拟机将字节码翻译成汇编语言程序,编译器和虚拟机就是软件,当需要使用机器提供的原子操作时,它们就可以直接在翻译成汇编语言时加上LOCK前缀。当然,这只是一个很粗略的描述,实际的过程取决于语言的实现,可能是一个很复杂的过程。我们只需要知道通过LOCK前缀,软件可以直接使用硬件提供的原子操作。
下面,我们来看软件能使用哪些指令来实现LOCK语义进而达到原子操作的目的。

软件可以使用的总线锁定

为了明确地强制使用LOCK语义,当一些指令修改一个内存位置时,软件可以在这些指令前面加上LOCK前缀。注意,能加LOCK前缀的指令是固定的,如果LOCK前缀被用在其他指令或者没有进行内存写操作(没有内存写操作,进行锁定就没有意义,因为内存数据不会修改)的指令时,就会出现错误。
能使用LOCK前缀的指令包括:

  • 位测试和修改指令(BTS、BTR、BTC)。(The bit test and modify instructions)。
  • 交换指令(XADD,CMPXCHG和CMPXCHG8B)。(The exchange instructions)。
  • XCHG会自动加上LOCK前缀。
  • 单个操作数的算术和逻辑指令:INC、DEC、NOT和NEG。
  • 两个操作数的算法和逻辑指令:ADD、ADC、SUB、SBB、AND、OR和XOR。

这些指令都有各自的含义,我们这里不会一一解释,但是在java并发编程中会用到的CMPXCHG我们会在后面的文章中重点介绍,这里,你必须知道的是,通过提供总线锁定或者说通过提供LOCK前缀,硬件实现了CPU指令级的原子操作,并且这些原子操作能直接暴露给软件来使用,这个是操作系统和很多语言用来实现信号量、互斥量和锁的基础。

缓存锁定

对于Intel486和Pentium系列的处理器,LOCK#总是对总线起作用,即使要访问的内存位置被缓存在处理器中。
而对于P6和最新的处理器家族,如果LOCK操作期间被锁定的内存区域已经被缓存在执行LOCK操作的处理器缓存中,并且这个内存区域会作为写回的内存区域,并且被完全包含在一个缓存行中,那么处理器可能不会在总线上断言LOCK#信号,它会在自己的缓存内部修改这个内存位置然后利用缓存一致性策略来保证操作的原子性。这个操作被称为缓存锁定。
缓存一致性协议机制自动阻止了缓存相同内存位置的两个或者更多的处理器同时修改这个区域的数据。
这是《Intel指令参考手册》上对缓存锁定的描述。我们不必深究它的原理,不必去研究缓存一致性策略、缓存行等概念,我们只需要知道,缓存锁定也只是实现LOCK语义的一种方式,它与总线锁定都是为了完成这个语义而存在的,只是实现方式不同而已。

小结

关于总线锁定和缓存锁定,我觉得最重要的有两点:一,它提供了什么?在基本原子操作(Guaranteed Automic Operations)的基础上,它保证了整个机器指令(例如INC)操作的原子性。二,它会被怎样使用。既然原子性在机器指令层面得到了保证,那么它就可以以指令的形式暴露给操作系统和应用程序,LOCK前缀就是暴露的方式,软件在机器指令前加上LOCK前缀,就能利用硬件提供的原子操作能力。
下一篇文章, 我们来看两个重要的机器指令,理解它们的含义,然后看一下原子执行这两个指令,我们能达到什么目的。