139 lines
4.3 KiB
PHP
139 lines
4.3 KiB
PHP
|
; High-level synchronization primitives.
|
||
|
|
||
|
; Mutex: stands for MUTual EXclusion.
|
||
|
; Allows to enforce that only one thread executes some code at a time.
|
||
|
; mutex_lock acquires the given mutex, mutex_unlock releases it;
|
||
|
; if thread 1 holds the mutex and thread 2 calls mutex_lock,
|
||
|
; thread 2 is blocked until thread 1 calls mutex_unlock.
|
||
|
; Several threads can wait for the same mutex; when the owner
|
||
|
; releases the mutex, one of waiting threads grabs the released mutex,
|
||
|
; but it is unspecified which one.
|
||
|
|
||
|
; If there is no contention, i.e. no one calls mutex_lock
|
||
|
; while somebody is holding the mutex, then
|
||
|
; mutex_lock and mutex_unlock use just a few instructions.
|
||
|
; This is the fast path.
|
||
|
; Otherwise, mutex_lock and mutex_unlock require a syscall
|
||
|
; to enter waiting state and wake someone up correspondingly.
|
||
|
|
||
|
; Implementation. We use one dword for status and
|
||
|
; kernel handle for underlying futex to be able to sleep/wake.
|
||
|
; Bit 31, the highest bit of status dword,
|
||
|
; is set if someone holds the mutex and clear otherwise.
|
||
|
; Bits 0-30 form the number of threads waiting in mutex_lock.
|
||
|
; All modifications of status dword should be atomic.
|
||
|
|
||
|
struct MUTEX
|
||
|
status dd ?
|
||
|
handle dd ?
|
||
|
ends
|
||
|
|
||
|
; Initialization. Set status dword to zero and
|
||
|
; open the underlying futex.
|
||
|
; in: ecx -> MUTEX
|
||
|
proc mutex_init
|
||
|
mov [ecx+MUTEX.status], 0
|
||
|
push ebx
|
||
|
mov eax, 77
|
||
|
xor ebx, ebx
|
||
|
call FS_SYSCALL_PTR
|
||
|
pop ebx
|
||
|
mov [ecx+MUTEX.handle], eax
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; Finalization. Close the underlying futex.
|
||
|
; in: ecx = MUTEX handle
|
||
|
proc mutex_destroy
|
||
|
push ebx
|
||
|
mov eax, 77
|
||
|
mov ebx, 1
|
||
|
call FS_SYSCALL_PTR
|
||
|
pop ebx
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; Acquire the mutex.
|
||
|
macro mutex_lock mutex
|
||
|
{
|
||
|
local .done
|
||
|
; Atomically set the locked status bit and get the previous value.
|
||
|
lock bts [mutex+MUTEX.status], 31
|
||
|
; Fast path: the mutex was not locked. If so, we are done.
|
||
|
jnc .done
|
||
|
if ~(mutex eq ecx)
|
||
|
mov ecx, mutex
|
||
|
end if
|
||
|
call mutex_lock_slow_path
|
||
|
.done:
|
||
|
}
|
||
|
|
||
|
; Acquire the mutex, slow path.
|
||
|
; Someone holds the mutex... or has held it a moment ago.
|
||
|
; in: ecx -> MUTEX
|
||
|
proc mutex_lock_slow_path
|
||
|
; Atomically increment number of waiters.
|
||
|
lock inc [ecx+MUTEX.status]
|
||
|
; When the mutex owner will release the mutex and wake us up,
|
||
|
; another thread can sneak in and grab the mutex before us.
|
||
|
; So, the following actions are potentially repeated in a loop.
|
||
|
.wait_loop:
|
||
|
mov edx, [ecx+MUTEX.status]
|
||
|
; The owner could have unlocked the mutex in parallel with us.
|
||
|
; If so, don't sleep: nobody would wake us up.
|
||
|
test edx, edx
|
||
|
jns .skip_wait
|
||
|
; Pass the fetched value to the kernel along with futex handle.
|
||
|
; If the owner unlocks the mutex while we are here,
|
||
|
; the kernel will detect mismatch and exit without sleeping.
|
||
|
; Otherwise, the owner will wake us up explicitly.
|
||
|
push ebx ecx esi
|
||
|
mov eax, 77
|
||
|
mov ebx, 2
|
||
|
mov ecx, [ecx+MUTEX.handle]
|
||
|
xor esi, esi
|
||
|
call FS_SYSCALL_PTR
|
||
|
pop esi ecx ebx
|
||
|
.skip_wait:
|
||
|
; We have woken up.
|
||
|
; Or we didn't even sleep because status dword has been changed beneath us.
|
||
|
; Anyway, something may have changed, re-evaluate the situation.
|
||
|
; Atomically set the locked status bit and get the previous value.
|
||
|
lock bts [ecx+MUTEX.status], 31
|
||
|
; If the mutex was locked, someone has grabbed the mutex before us.
|
||
|
; Repeat the loop.
|
||
|
jc .wait_loop
|
||
|
; The mutex was unlocked and we have just managed to lock it.
|
||
|
; Our status has changed from a waiter to the owner.
|
||
|
; Decrease number of waiters and exit.
|
||
|
lock dec [ecx+MUTEX.status]
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; Release the mutex.
|
||
|
macro mutex_unlock mutex
|
||
|
{
|
||
|
local .done
|
||
|
; Atomically clear the locked status bit and check whether someone is waiting.
|
||
|
lock and [mutex+MUTEX.status], 0x7FFFFFFF
|
||
|
; Fast path: nobody is waiting.
|
||
|
jz .done
|
||
|
mov ecx, [mutex+MUTEX.handle]
|
||
|
call mutex_unlock_slow_path
|
||
|
.done:
|
||
|
}
|
||
|
|
||
|
; Release the mutex, slow path.
|
||
|
; Someone is sleeping in the kernel, or preparing for the sleep.
|
||
|
; Wake one of waiters.
|
||
|
; in: ecx = MUTEX handle
|
||
|
proc mutex_unlock_slow_path
|
||
|
push ebx
|
||
|
mov eax, 77
|
||
|
mov ebx, 3
|
||
|
mov edx, 1
|
||
|
call FS_SYSCALL_PTR
|
||
|
pop ebx
|
||
|
ret
|
||
|
endp
|