本文介绍汇编指令的LOCK指令前缀
,需要注意的是,LOCK指令前缀
只是见于常见的汇编指令体系,而并不是存在于所有的汇编指令体系中,即有些汇编指令体系中用等价的设计来获得LOCK指令前缀
提供的功能。而且,在具有LOCK指令前缀
的汇编指令体系中,并不是所有的汇编指令都能被LOCK指令前缀
修饰,不同汇编指令体系下允许被LOCK指令前缀
修饰的汇编指令集不尽相同。
LOCK指令前缀
功能如下:
- 被修饰的汇编指令成为“原子的”
- 与被修饰的汇编指令一起提供内存屏障效果
本文接下来以X86指令体系为上下文语境,在X86指令体系中,具有LOCK指令前缀
,其内允许使用LOCK指令前缀
修饰的汇编指令有:
ADD,ADC,AND,BTC,BTR,BTS,CMPXCHG,CMPXCH8B,DEC,INC,NEG,NOT,OR,SBB,SUB,XOR,XADD以及XCHG等
需要注意的是,“XCHG”和“XADD”汇编指令本身是原子指令,但也允许使用LOCK指令前缀
进行修饰。
一、被修饰的汇编指令成为“原子的”
原子操作的本质描述是:当且仅当操作物理或者逻辑不可中断(不可中断:操作所涉内存不可被读取和修改)时,该操作才是原子的。
原子汇编指令分为两类:
- 本身是原子指令,比如“XCHG”和“XADD”汇编指令
- 本身不是原子指令,但是被
LOCK指令前缀
修饰后成为原子指令,比如LOCK CMPXCHG
汇编指令被LOCK指令前缀
修饰后会成为“原子的”,因为LOCK指令前缀
会带来如下效果:
- 被修饰的汇编指令A在执行期间,会在内存总线上声言一个
#LOCK
信号,该信号导致内存被锁住,此时内存不能再被其他汇编指令存取,直到A执行完成。经过分析可知,A的执行效果与“暂停执行其他所有汇编指令直到A执行完成”等价,因此此时A是原子的 - 上一个方案锁住内存,期间内存不能被存取,代价非常大,因此,现在一般是采用“缓存锁定”的方案,避免降低内存的存取速度,具体是:
在汇编指令A(被LOCK指令前缀修饰的汇编指令)执行期间锁住所涉及到的Cache Line,此时其他CPU核不能存取其内缓存中对应的Cache Line
。需要注意的是,有些情形不能使用“缓存锁定”,而只能使用“内存总线锁定”,比如“所涉及操作数据跨越多个Cache Line”,“CPU不支持缓存锁定”等
二、与被修饰的汇编指令一起提供内存屏障效果
内存屏障的作用(内存屏障可具有下述作用,但不一定需要具备下述所有作用):
- 禁止重排序
- 刷新缓存到内存
- 使缓存失效
内存屏障分为两类:
- 本身是内存屏障,比如“lfence”,“sfence”和“mfence”汇编指令
- 本身不是内存屏障,但是被
LOCK指令前缀
修饰,其组合成为一个内存屏障。在X86指令体系中,其中一类内存屏障常使用“LOCK指令前缀
加上一个空操作”方式实现,比如lock addl $0x0,(%esp)
汇编指令被LOCK指令前缀修饰后,一起提供内存屏障效果,因为LOCK指令前缀带来如下效果:
- 将所有写缓冲区中的数据刷新到内存,并将所有涉及到的Cache Line状态置为无效,使得下次只能从内存读取
- 禁止指令重排序,即“B之前的语句集A禁止被重排序到B之后,B之后的语句集C禁止被重排序到B之前,执行顺序必为
A,B,C
”,需要注意的是,上述叙述中的语句集A和C内部是可能存在重排序的
参考文献
[1]https://blog.csdn.net/qq_30055391/article/details/84892936
[2]https://blog.csdn.net/muxiqingyang/article/details/6615199
[3]https://blog.csdn.net/qq_26222859/article/details/52235930
[4]https://blog.csdn.net/zacklin/article/details/7445442
[5]https://stackoverflow.com/questions/29880015/lock-prefix-vs-mesi-protocol
[6]https://www.infoq.cn/article/cache-coherency-primer
[7]https://zh.wikipedia.org/wiki/%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C
[8]https://stackoverflow.com/questions/8891067/what-does-the-lock-instruction-mean-in-x86-assembly