; 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