【问题标题】:Spinlock with XCHG unlocking带 XCHG 解锁的自旋锁
【发布时间】:2016-08-12 09:15:54
【问题描述】:

维基百科为使用 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.

从这里https://en.wikipedia.org/wiki/Spinlock#Example_implementation

我不明白为什么解锁需要是原子的。怎么了

spin_unlock:
     mov     [locked], 0  

【问题讨论】:

  • 我同意 mov 应该可以工作,特别是考虑到变量中只使用了最低有效位。
  • 我想使用 XCHG 进行解锁会给 spin_unlock 一个返回值,1 表示成功,0 表示错误,因为没有锁定。
  • 问题实际上不是原子性——普通对齐的 32 位存储在 x86 上总是原子的——而是 orderlocked atomics(包括隐含的 locked xchg)在 x86 上具有总顺序,而普通存储仅具有发布一致性。当然,释放语义对于自旋锁来说就足够了,只要获取是使用 locked atomic 完成的。
  • @HansPassant 我在该问题中看不到任何内容,也没有看到支持这一可疑说法的答案。
  • xchg 上旋转是否理想?使用计数锁更多最好仅在负载上旋转,并且仅在您看到锁已解锁时才尝试获取锁。在xchg 上旋转可能会延迟解锁器的xchg 发生。如果您在锁被锁定时根本不写入锁,那么拥有锁的核心在尝试解锁时仍将拥有缓存线,对吧?

标签: assembly x86 synchronization spinlock


【解决方案1】:

重点

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

【讨论】:

  • 如果执行解锁的线程/进程没有持有锁,则行为未定义无论如何
  • 在这种情况下,unlock() 返回 0,这不是最糟糕的选择
  • 但是如果进行解锁的进程没有持有锁,另一个可能有。现在锁被解锁,而另一个进程认为它持有,另一个进程可以获得锁。唯一的好处是你现在可以panic(),但你可能已经有数据损坏了。
  • 取决于您是否将锁定的错误使用定义为锁定机制本身的业务。我更喜欢得到一个返回值,然后自己决定如何处理它:-) ...
  • 如果你想要一个返回值来检测锁定错误,那么你应该只是mov eax, [locked]/mov [locked], 0。当您致电unlock 时,您应该已经持有锁;这仍然会捕获双重解锁错误。我认为这里的重点是使用xchgMFENCE 效果,为商店提供强于发布的语义。正如人们在 cmets 关于这个问题所说的那样,这不应该是必需的,因为 x86 是强排序的。 SFENCE 可以为 NT 存储提供释放语义,但通常这是在执行 NT 存储的代码中完成的,而不是在锁定中。
【解决方案2】:

解锁确实需要have release semantics 才能正确保护关键部分。但它不需要顺序一致性。原子性并不是真正的问题(见下文)。

所以是的,在 x86 上,一个简单的存储是安全的,并且 glibc's pthread_spin_unlock does so:

    movl    $1, (%rdi)
    xorl    %eax, %eax
    retq

另请参阅一个简单但可能可用的x86 spinlock implementation I wrote in this answer,它使用带有pause 指令的只读自旋循环。


可能这段代码是从位域版本改编而来的。

使用btr 解锁位域中的一个标志为零是不安全的,因为它是对包含字节 (or the containing naturally-aligned 4 byte dword or 2 byte word) 的非原子读取-修改-写入。

所以也许写它的人没有意识到simple stores to aligned addresses are atomic on x86, like on most ISAs。但是 x86 具有弱序 ISA 所没有的是每个商店都有 release semantics。释放锁的xchg 使每次解锁都成为一个完整的内存屏障,这超出了正常的锁定语义。 (尽管在 x86 上,获取锁将是一个完整的障碍,因为没有xchg 或其他locked 指令就无法进行原子 RMW 或原子比较和交换,而这些都是像 @ 这样的完整障碍987654334@.)

从技术上讲,解锁存储不需要是原子的,因为我们只存储零或 1,所以只有低字节很重要。例如我认为如果锁未对齐并跨缓存线边界拆分,它仍然可以工作。撕裂可能发生但无关紧要,真正发生的是锁的低字节被原子修改,操作总是将零放入高 3 字节。


如果您想返回旧值以捕获双重解锁错误,更好的实现将分别加载和存储:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-22
    • 2013-09-27
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多