使用 XCGH 指令实现 Down 和 Up(信号量)

Implementing Down and Up (semaphore) with XCGH instruction

锁定互斥量的函数可以用汇编语言用 XCHG 指令写成这样:

mutex_lock:
    MOVE REGISTRO,#1    
    XCHG REGISTRO,MUTEX  
    CMP REGISTRO,#0     
    JZE ok          
    CALL thread_yield   
    JMP mutex_lock      
ok: RET          

mutex_unlock:
    MOVE MUTEX,#0       
    RET

¿有没有办法用汇编语言用 XCHG 指令编写 Up 函数和 Down 信号量函数?

我的建议是使用 CMPXCHG 指令。该指令基本上将比较和交换结合在一个操作中。它将源操作数与目标操作数进行比较,相应地设置标志。然后,如果它们相等 (ZF == 1),它将值从源复制到目标。否则 (ZF == 0),它不理会目的地。结合 LOCK 前缀,这对于多线程环境中的原子操作很方便,因为它允许您安全地更新存储在共享内存中的值。

CMPXCHG 唯一需要注意的是它需要奔腾或更高版本的处理器。今天,这显然不是问题,但如果您正在进行逆向编程,您可能需要寻找其他地方。 (当然,复古程序员并不真正需要担心多处理器,而且通常甚至没有多线程。)

这里是二进制信号量的示例实现,以伪 MASM 风格的语法编写。二进制信号量是其值为 0(解锁)或 1(锁定)的信号量。

; This function attempts to obtain the semaphore.
;
; It compares the current value of the semaphore to 0. If it is 0, that means
; the semaphore is not currently locked (i.e., it is available), so this function
; sets it to 1, thus marking it as locked. Otherwise, if the semaphore is already locked,
; this function blocks (waits indefinitely) until it becomes available,
; and then locks it as described above.
;
; The address of the semaphore flag to lock is passed on the stack.
; It is the caller's responsibility to clean the stack (__cdecl).
; 
; Clobbers: EAX, EDX
Lock PROC
    mov  edx, DWORD PTR [esp + 4] ; get address of semaphore (passed as sole parameter on stack)

  WaitUntilAvailable:
    mov  eax, 1
    lock cmpxchg DWORD PTR [edx], eax
    jz   Finished
    ; TODO: Add code to suspend the thread (yield execution) while waiting,
    ;       for example by calling the Win32 Sleep function.
    ;       Currently, this just spins a busy loop.
    jmp  WaitUntilAvailable

  Finished:
    ret
Lock ENDP


; This function releases the semaphore.
; 
; If the semaphore is currently locked (has a value of 1), it is reset to 0.
; 
; The address of the semaphore flag to lock is passed on the stack.
; It is the caller's responsibility to clean the stack (__cdecl).
; 
; Clobbers: EAX, EDX
Unlock PROC
    mov  edx, DWORD PTR [esp + 4]
    xor  eax, eax
    lock cmpxchg DWORD PTR [edx], eax
    ret
Unlock ENDP

如果需要,您可以轻松修改它们以用于计数信号量。在这种情况下,由于您实际上想要 increment/decrement 该值,因此可以使用 XADD 指令。我知道 486 及更高版本支持此功能,而且它比 CMPXCHG 快得多。下面是代码的草图:

; Waits for a semaphore to become available.
; 
; If the value of the semaphore variable is non-zero, decrement it by 1 and return.
; Otherwise, block execution until the semaphore's value is greater than
; or equal to 1 (i.e., add the caller to the semaphore's queue and wait
; until it becomes available).
; 
; Clobbers: EAX, EDX
SemaphoreWait PROC
    mov  edx, DWORD PTR [esp + 4]
    jmp  Check

  WaitUntilAvailable:
    pause   ; (or some other way to yield)

  Check:
    mov  eax, DWORD PTR [edx]
    test eax, eax
    jle  Check
    mov  eax, -1
    lock xadd DWORD PTR [edx], eax
    test eax, eax
    jg   Finished
    lock inc DWORD PTR [edx]
    jmp  WaitUntilAvailable

  Finished:
    ret
SemaphoreWait ENDP


; Signals (releases) a semaphore, incrementing its value by 1.
; 
; Clobbers: EAX
SemaphoreSignal PROC
    mov  eax, DWORD PTR [esp + 4]
    lock inc DWORD PTR [eax]
    ret
SemaphoreSignal ENDP