Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. ; High-level synchronization primitives.
  2.  
  3. ; Mutex: stands for MUTual EXclusion.
  4. ; Allows to enforce that only one thread executes some code at a time.
  5. ; mutex_lock acquires the given mutex, mutex_unlock releases it;
  6. ; if thread 1 holds the mutex and thread 2 calls mutex_lock,
  7. ; thread 2 is blocked until thread 1 calls mutex_unlock.
  8. ; Several threads can wait for the same mutex; when the owner
  9. ; releases the mutex, one of waiting threads grabs the released mutex,
  10. ; but it is unspecified which one.
  11.  
  12. ; If there is no contention, i.e. no one calls mutex_lock
  13. ; while somebody is holding the mutex, then
  14. ; mutex_lock and mutex_unlock use just a few instructions.
  15. ; This is the fast path.
  16. ; Otherwise, mutex_lock and mutex_unlock require a syscall
  17. ; to enter waiting state and wake someone up correspondingly.
  18.  
  19. ; Implementation. We use one dword for status and
  20. ; kernel handle for underlying futex to be able to sleep/wake.
  21. ; Bit 31, the highest bit of status dword,
  22. ; is set if someone holds the mutex and clear otherwise.
  23. ; Bits 0-30 form the number of threads waiting in mutex_lock.
  24. ; All modifications of status dword should be atomic.
  25.  
  26. struct MUTEX
  27. status  dd      ?
  28. handle  dd      ?
  29. ends
  30.  
  31. ; Initialization. Set status dword to zero and
  32. ; open the underlying futex.
  33. ; in: ecx -> MUTEX
  34. proc mutex_init
  35.         mov     [ecx+MUTEX.status], 0
  36.         push    ebx
  37.         mov     eax, 77
  38.         xor     ebx, ebx
  39.         call    FS_SYSCALL_PTR
  40.         pop     ebx
  41.         mov     [ecx+MUTEX.handle], eax
  42.         ret
  43. endp
  44.  
  45. ; Finalization. Close the underlying futex.
  46. ; in: ecx = MUTEX handle
  47. proc mutex_destroy
  48.         push    ebx
  49.         mov     eax, 77
  50.         mov     ebx, 1
  51.         call    FS_SYSCALL_PTR
  52.         pop     ebx
  53.         ret
  54. endp
  55.  
  56. ; Acquire the mutex.
  57. macro mutex_lock mutex
  58. {
  59. local .done
  60. ; Atomically set the locked status bit and get the previous value.
  61.         lock bts [mutex+MUTEX.status], 31
  62. ; Fast path: the mutex was not locked. If so, we are done.
  63.         jnc     .done
  64. if ~(mutex eq ecx)
  65.         mov     ecx, mutex
  66. end if
  67.         call    mutex_lock_slow_path
  68. .done:
  69. }
  70.  
  71. ; Acquire the mutex, slow path.
  72. ; Someone holds the mutex... or has held it a moment ago.
  73. ; in: ecx -> MUTEX
  74. proc mutex_lock_slow_path
  75. ; Atomically increment number of waiters.
  76.         lock inc [ecx+MUTEX.status]
  77. ; When the mutex owner will release the mutex and wake us up,
  78. ; another thread can sneak in and grab the mutex before us.
  79. ; So, the following actions are potentially repeated in a loop.
  80. .wait_loop:
  81.         mov     edx, [ecx+MUTEX.status]
  82. ; The owner could have unlocked the mutex in parallel with us.
  83. ; If so, don't sleep: nobody would wake us up.
  84.         test    edx, edx
  85.         jns     .skip_wait
  86. ; Pass the fetched value to the kernel along with futex handle.
  87. ; If the owner unlocks the mutex while we are here,
  88. ; the kernel will detect mismatch and exit without sleeping.
  89. ; Otherwise, the owner will wake us up explicitly.
  90.         push    ebx ecx esi
  91.         mov     eax, 77
  92.         mov     ebx, 2
  93.         mov     ecx, [ecx+MUTEX.handle]
  94.         xor     esi, esi
  95.         call    FS_SYSCALL_PTR
  96.         pop     esi ecx ebx
  97. .skip_wait:
  98. ; We have woken up.
  99. ; Or we didn't even sleep because status dword has been changed beneath us.
  100. ; Anyway, something may have changed, re-evaluate the situation.
  101. ; Atomically set the locked status bit and get the previous value.
  102.         lock bts [ecx+MUTEX.status], 31
  103. ; If the mutex was locked, someone has grabbed the mutex before us.
  104. ; Repeat the loop.
  105.         jc      .wait_loop
  106. ; The mutex was unlocked and we have just managed to lock it.
  107. ; Our status has changed from a waiter to the owner.
  108. ; Decrease number of waiters and exit.
  109.         lock dec [ecx+MUTEX.status]
  110.         ret
  111. endp
  112.  
  113. ; Release the mutex.
  114. macro mutex_unlock mutex
  115. {
  116. local .done
  117. ; Atomically clear the locked status bit and check whether someone is waiting.
  118.         lock and [mutex+MUTEX.status], 0x7FFFFFFF
  119. ; Fast path: nobody is waiting.
  120.         jz      .done
  121.         mov     ecx, [mutex+MUTEX.handle]
  122.         call    mutex_unlock_slow_path
  123. .done:
  124. }
  125.  
  126. ; Release the mutex, slow path.
  127. ; Someone is sleeping in the kernel, or preparing for the sleep.
  128. ; Wake one of waiters.
  129. ; in: ecx = MUTEX handle
  130. proc mutex_unlock_slow_path
  131.         push    ebx
  132.         mov     eax, 77
  133.         mov     ebx, 3
  134.         mov     edx, 1
  135.         call    FS_SYSCALL_PTR
  136.         pop     ebx
  137.         ret
  138. endp
  139.