Assembly 带XCHG解锁的自旋锁
Wikipedia为带有x86 XCHG命令的自旋锁提供的示例实现为:Assembly 带XCHG解锁的自旋锁,assembly,x86,synchronization,spinlock,Assembly,X86,Synchronization,Spinlock,Wikipedia为带有x86 XCHG命令的自旋锁提供的示例实现为: ; Intel syntax locked: ; The lock variable. 1 = locked, 0 = unlocked. dd 0 spin_lock: mov eax, 1 ; Set the EAX register to 1. xchg eax, [locked] ; Atomi
; Intel syntax
locked: ; The lock variable. 1 = locked, 0 = unlocked.
dd 0
spin_lock:
mov eax, 1 ; Set the EAX register to 1.
xchg eax, [locked] ; Atomically swap the EAX register with
; the lock variable.
; This will always store 1 to the lock, leaving
; the previous value in the EAX register.
test eax, eax ; Test EAX with itself. Among other things, this will
; set the processor's Zero Flag if EAX is 0.
; If EAX is 0, then the lock was unlocked and
; we just locked it.
; Otherwise, EAX is 1 and we didn't acquire the lock.
jnz spin_lock ; Jump back to the MOV instruction if the Zero Flag is
; not set; the lock was previously locked, and so
; we need to spin until it becomes unlocked.
ret ; The lock has been acquired, return to the calling
; function.
spin_unlock:
mov eax, 0 ; Set the EAX register to 0.
xchg eax, [locked] ; Atomically swap the EAX register with
; the lock variable.
ret ; The lock has been released.
从这里
我不明白的是为什么解锁需要原子化。你怎么了
spin_unlock:
mov [locked], 0
重点
spin_unlock:
mov eax, 0 ; Set the EAX register to 0.
xchg eax, [locked] ; Atomically swap the EAX register with
; the lock variable.
ret ; The lock has been released.
不仅要解锁,还要用正确的返回值填充eax(如果解锁,则为1,否则为0)
如果在调用spin_unlock(在这种情况下,[locked]保持值0)之前未获得锁,spin_unlock应返回0解锁需要正确保护关键部分。但它不需要顺序一致性。原子性并不是真正的问题(见下文)
是的,在x86上,一个简单的存储是安全的,而且:
movl $1, (%rdi)
xorl %eax, %eax
retq
另请参阅一个简单但可能可用的方法,使用带有暂停
指令的只读自旋循环
这段代码可能是根据位域版本改编的 用
btr
解锁位字段中的零一标志是不安全的,因为它是包含字节()的非原子读-修改-写
也许写这篇文章的人没有意识到这一点。但x86的弱有序ISA所没有的是每个商店都有。释放锁的xchg
使每次解锁都成为一个完整的内存屏障,这超出了正常的锁定语义。(虽然在x86上,获取锁将是一个完全的障碍,因为没有xchg
或其他lock
ed指令,无法进行原子RMW或原子比较和交换,而这些都是像mfence
这样的完全障碍)
解锁存储在技术上不需要是原子的,因为我们只存储0或1,所以只有较低的字节才重要。e、 我认为如果锁未对齐并跨缓存线边界拆分,它仍然可以工作。撕裂可能发生,但并不重要,真正发生的是锁的低位字节被原子化修改,操作总是将零放入高位字节
如果希望返回旧值以捕获双重解锁错误,更好的实现将分别加载和存储:
spin_unlock:
;; pre-condition: [locked] is non-zero
mov eax, [locked] ; old value, for debugging
mov dword [locked], 0 ; On x86, this is an atomic store with "release" semantics.
;test eax,eax
;jz double_unlocking_detected ; or leave this to the caller
ret
我同意
mov
应该可以工作,特别是考虑到变量中只使用了最低有效位。我假设使用XCHG进行解锁会得到spin\u unlock
的返回值,1表示成功,0表示错误,因为未持有锁。问题实际上不是原子性-普通对齐的32位存储在x86上始终是原子的-而是顺序lock
ed原子(包括隐式lock
edxchg
)在x86上具有总订单,而普通存储仅具有发布一致性。当然,释放语义对于自旋锁来说已经足够了,只要获取是用lock
ed原子完成的。@HansPassant我看不到这个问题中的任何东西或它的答案支持这个可疑的说法。在xchg
上自旋理想吗?使用计数锁时,最好只在负载上旋转,并且只有在看到锁被解锁时才尝试使用锁。在xchg
上旋转可能会延迟解锁器的xchg
发生。如果在锁被锁定时根本不写入,则拥有锁的内核在尝试解锁时仍将拥有缓存线,对吗?如果执行解锁的线程/进程未持有锁,则行为仍然未定义。在这种情况下,unlock()返回0,这并不是最糟糕的选择,但如果解锁过程没有锁定,另一个可能会锁定。现在锁被解锁了,而另一个进程认为它持有,另一个进程可以获得锁。唯一的优点是现在可以panic()
,但可能已经存在数据损坏。这取决于是否将锁定的错误用法定义为锁定机制本身的业务。我更喜欢获取一个返回值,然后自己决定如何处理它:-)…如果您想要一个返回值来检测锁定错误,那么您应该只mov eax,[locked]
/mov[locked],0
。当您调用解锁
时,您应该已经持有了锁;这仍然会捕获双重解锁错误。我认为这里的要点是使用xchg
的MFENCE
效果,为存储提供比发布更强大的语义。正如人们在对这个问题的评论中所说的那样,这不应该是必需的,因为x86是严格有序的。SFENCE可以为NT存储提供发布语义,但通常在未存储NT的代码中完成,而不是在锁定中完成。