Assembly 带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

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]   ; 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
ed
xchg
)在x86上具有总订单,而普通存储仅具有发布一致性。当然,释放语义对于自旋锁来说已经足够了,只要获取是用
lock
ed原子完成的。@HansPassant我看不到这个问题中的任何东西或它的答案支持这个可疑的说法。在
xchg
上自旋理想吗?使用计数锁时,最好只在负载上旋转,并且只有在看到锁被解锁时才尝试使用锁。在
xchg
上旋转可能会延迟解锁器的
xchg
发生。如果在锁被锁定时根本不写入,则拥有锁的内核在尝试解锁时仍将拥有缓存线,对吗?如果执行解锁的线程/进程未持有锁,则行为仍然未定义。在这种情况下,unlock()返回0,这并不是最糟糕的选择,但如果解锁过程没有锁定,另一个可能会锁定。现在锁被解锁了,而另一个进程认为它持有,另一个进程可以获得锁。唯一的优点是现在可以
panic()
,但可能已经存在数据损坏。这取决于是否将锁定的错误用法定义为锁定机制本身的业务。我更喜欢获取一个返回值,然后自己决定如何处理它:-)…如果您想要一个返回值来检测锁定错误,那么您应该只
mov eax,[locked]
/
mov[locked],0
。当您调用
解锁
时,您应该已经持有了锁;这仍然会捕获双重解锁错误。我认为这里的要点是使用
xchg
MFENCE
效果,为存储提供比发布更强大的语义。正如人们在对这个问题的评论中所说的那样,这不应该是必需的,因为x86是严格有序的。SFENCE可以为NT存储提供发布语义,但通常在未存储NT的代码中完成,而不是在锁定中完成。