lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame^] | 1 | /* Linuxthreads - a simple clone()-based implementation of Posix */ |
| 2 | /* threads for Linux. */ |
| 3 | /* Copyright (C) 1998 Xavier Leroy (Xavier.Leroy@inria.fr) */ |
| 4 | /* */ |
| 5 | /* This program is free software; you can redistribute it and/or */ |
| 6 | /* modify it under the terms of the GNU Library General Public License */ |
| 7 | /* as published by the Free Software Foundation; either version 2 */ |
| 8 | /* of the License, or (at your option) any later version. */ |
| 9 | /* */ |
| 10 | /* This program is distributed in the hope that it will be useful, */ |
| 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ |
| 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ |
| 13 | /* GNU Library General Public License for more details. */ |
| 14 | |
| 15 | /* Internal locks */ |
| 16 | |
| 17 | #define __FORCE_GLIBC |
| 18 | #include <features.h> |
| 19 | #include <errno.h> |
| 20 | #include <sched.h> |
| 21 | #include <time.h> |
| 22 | #include <stdlib.h> |
| 23 | #include <limits.h> |
| 24 | #include "pthread.h" |
| 25 | #include "internals.h" |
| 26 | #include "spinlock.h" |
| 27 | #include "restart.h" |
| 28 | |
| 29 | libpthread_hidden_proto(nanosleep) |
| 30 | |
| 31 | static void __pthread_acquire(int * spinlock); |
| 32 | |
| 33 | static __inline__ void __pthread_release(int * spinlock) |
| 34 | { |
| 35 | WRITE_MEMORY_BARRIER(); |
| 36 | *spinlock = __LT_SPINLOCK_INIT; |
| 37 | __asm__ __volatile__ ("" : "=m" (*spinlock) : "m" (*spinlock)); |
| 38 | } |
| 39 | |
| 40 | |
| 41 | /* The status field of a spinlock is a pointer whose least significant |
| 42 | bit is a locked flag. |
| 43 | |
| 44 | Thus the field values have the following meanings: |
| 45 | |
| 46 | status == 0: spinlock is free |
| 47 | status == 1: spinlock is taken; no thread is waiting on it |
| 48 | |
| 49 | (status & 1) == 1: spinlock is taken and (status & ~1L) is a |
| 50 | pointer to the first waiting thread; other |
| 51 | waiting threads are linked via the p_nextlock |
| 52 | field. |
| 53 | (status & 1) == 0: same as above, but spinlock is not taken. |
| 54 | |
| 55 | The waiting list is not sorted by priority order. |
| 56 | Actually, we always insert at top of list (sole insertion mode |
| 57 | that can be performed without locking). |
| 58 | For __pthread_unlock, we perform a linear search in the list |
| 59 | to find the highest-priority, oldest waiting thread. |
| 60 | This is safe because there are no concurrent __pthread_unlock |
| 61 | operations -- only the thread that locked the mutex can unlock it. */ |
| 62 | |
| 63 | |
| 64 | void internal_function __pthread_lock(struct _pthread_fastlock * lock, |
| 65 | pthread_descr self) |
| 66 | { |
| 67 | #if defined HAS_COMPARE_AND_SWAP |
| 68 | long oldstatus, newstatus; |
| 69 | int successful_seizure, spurious_wakeup_count; |
| 70 | int spin_count; |
| 71 | #endif |
| 72 | |
| 73 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 74 | if (!__pthread_has_cas) |
| 75 | #endif |
| 76 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 77 | { |
| 78 | __pthread_acquire(&lock->__spinlock); |
| 79 | return; |
| 80 | } |
| 81 | #endif |
| 82 | |
| 83 | #if defined HAS_COMPARE_AND_SWAP |
| 84 | /* First try it without preparation. Maybe it's a completely |
| 85 | uncontested lock. */ |
| 86 | if (lock->__status == 0 && __compare_and_swap (&lock->__status, 0, 1)) |
| 87 | return; |
| 88 | |
| 89 | spurious_wakeup_count = 0; |
| 90 | spin_count = 0; |
| 91 | |
| 92 | /* On SMP, try spinning to get the lock. */ |
| 93 | #if 0 |
| 94 | if (__pthread_smp_kernel) { |
| 95 | int max_count = lock->__spinlock * 2 + 10; |
| 96 | |
| 97 | if (max_count > MAX_ADAPTIVE_SPIN_COUNT) |
| 98 | max_count = MAX_ADAPTIVE_SPIN_COUNT; |
| 99 | |
| 100 | for (spin_count = 0; spin_count < max_count; spin_count++) { |
| 101 | if (((oldstatus = lock->__status) & 1) == 0) { |
| 102 | if(__compare_and_swap(&lock->__status, oldstatus, oldstatus | 1)) |
| 103 | { |
| 104 | if (spin_count) |
| 105 | lock->__spinlock += (spin_count - lock->__spinlock) / 8; |
| 106 | READ_MEMORY_BARRIER(); |
| 107 | return; |
| 108 | } |
| 109 | } |
| 110 | #ifdef BUSY_WAIT_NOP |
| 111 | BUSY_WAIT_NOP; |
| 112 | #endif |
| 113 | __asm__ __volatile__ ("" : "=m" (lock->__status) : "m" (lock->__status)); |
| 114 | } |
| 115 | |
| 116 | lock->__spinlock += (spin_count - lock->__spinlock) / 8; |
| 117 | } |
| 118 | #endif |
| 119 | |
| 120 | again: |
| 121 | |
| 122 | /* No luck, try once more or suspend. */ |
| 123 | |
| 124 | do { |
| 125 | oldstatus = lock->__status; |
| 126 | successful_seizure = 0; |
| 127 | |
| 128 | if ((oldstatus & 1) == 0) { |
| 129 | newstatus = oldstatus | 1; |
| 130 | successful_seizure = 1; |
| 131 | } else { |
| 132 | if (self == NULL) |
| 133 | self = thread_self(); |
| 134 | newstatus = (long) self | 1; |
| 135 | } |
| 136 | |
| 137 | if (self != NULL) { |
| 138 | THREAD_SETMEM(self, p_nextlock, (pthread_descr) (oldstatus)); |
| 139 | /* Make sure the store in p_nextlock completes before performing |
| 140 | the compare-and-swap */ |
| 141 | MEMORY_BARRIER(); |
| 142 | } |
| 143 | } while(! __compare_and_swap(&lock->__status, oldstatus, newstatus)); |
| 144 | |
| 145 | /* Suspend with guard against spurious wakeup. |
| 146 | This can happen in pthread_cond_timedwait_relative, when the thread |
| 147 | wakes up due to timeout and is still on the condvar queue, and then |
| 148 | locks the queue to remove itself. At that point it may still be on the |
| 149 | queue, and may be resumed by a condition signal. */ |
| 150 | |
| 151 | if (!successful_seizure) { |
| 152 | for (;;) { |
| 153 | suspend(self); |
| 154 | if (self->p_nextlock != NULL) { |
| 155 | /* Count resumes that don't belong to us. */ |
| 156 | spurious_wakeup_count++; |
| 157 | continue; |
| 158 | } |
| 159 | break; |
| 160 | } |
| 161 | goto again; |
| 162 | } |
| 163 | |
| 164 | /* Put back any resumes we caught that don't belong to us. */ |
| 165 | while (spurious_wakeup_count--) |
| 166 | restart(self); |
| 167 | |
| 168 | READ_MEMORY_BARRIER(); |
| 169 | #endif |
| 170 | } |
| 171 | |
| 172 | int __pthread_unlock(struct _pthread_fastlock * lock) |
| 173 | { |
| 174 | #if defined HAS_COMPARE_AND_SWAP |
| 175 | long oldstatus; |
| 176 | pthread_descr thr, * ptr, * maxptr; |
| 177 | int maxprio; |
| 178 | #endif |
| 179 | |
| 180 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 181 | if (!__pthread_has_cas) |
| 182 | #endif |
| 183 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 184 | { |
| 185 | __pthread_release(&lock->__spinlock); |
| 186 | return 0; |
| 187 | } |
| 188 | #endif |
| 189 | |
| 190 | #if defined HAS_COMPARE_AND_SWAP |
| 191 | WRITE_MEMORY_BARRIER(); |
| 192 | |
| 193 | again: |
| 194 | while ((oldstatus = lock->__status) == 1) { |
| 195 | if (__compare_and_swap_with_release_semantics(&lock->__status, |
| 196 | oldstatus, 0)) |
| 197 | return 0; |
| 198 | } |
| 199 | |
| 200 | /* Find thread in waiting queue with maximal priority */ |
| 201 | ptr = (pthread_descr *) &lock->__status; |
| 202 | thr = (pthread_descr) (oldstatus & ~1L); |
| 203 | maxprio = 0; |
| 204 | maxptr = ptr; |
| 205 | |
| 206 | /* Before we iterate over the wait queue, we need to execute |
| 207 | a read barrier, otherwise we may read stale contents of nodes that may |
| 208 | just have been inserted by other processors. One read barrier is enough to |
| 209 | ensure we have a stable list; we don't need one for each pointer chase |
| 210 | through the list, because we are the owner of the lock; other threads |
| 211 | can only add nodes at the front; if a front node is consistent, |
| 212 | the ones behind it must also be. */ |
| 213 | |
| 214 | READ_MEMORY_BARRIER(); |
| 215 | |
| 216 | while (thr != 0) { |
| 217 | if (thr->p_priority >= maxprio) { |
| 218 | maxptr = ptr; |
| 219 | maxprio = thr->p_priority; |
| 220 | } |
| 221 | ptr = &(thr->p_nextlock); |
| 222 | thr = (pthread_descr)((long)(thr->p_nextlock) & ~1L); |
| 223 | } |
| 224 | |
| 225 | /* Remove max prio thread from waiting list. */ |
| 226 | if (maxptr == (pthread_descr *) &lock->__status) { |
| 227 | /* If max prio thread is at head, remove it with compare-and-swap |
| 228 | to guard against concurrent lock operation. This removal |
| 229 | also has the side effect of marking the lock as released |
| 230 | because the new status comes from thr->p_nextlock whose |
| 231 | least significant bit is clear. */ |
| 232 | thr = (pthread_descr) (oldstatus & ~1L); |
| 233 | if (! __compare_and_swap_with_release_semantics |
| 234 | (&lock->__status, oldstatus, (long)(thr->p_nextlock) & ~1L)) |
| 235 | goto again; |
| 236 | } else { |
| 237 | /* No risk of concurrent access, remove max prio thread normally. |
| 238 | But in this case we must also flip the least significant bit |
| 239 | of the status to mark the lock as released. */ |
| 240 | thr = (pthread_descr)((long)*maxptr & ~1L); |
| 241 | *maxptr = thr->p_nextlock; |
| 242 | |
| 243 | /* Ensure deletion from linked list completes before we |
| 244 | release the lock. */ |
| 245 | WRITE_MEMORY_BARRIER(); |
| 246 | |
| 247 | do { |
| 248 | oldstatus = lock->__status; |
| 249 | } while (!__compare_and_swap_with_release_semantics(&lock->__status, |
| 250 | oldstatus, oldstatus & ~1L)); |
| 251 | } |
| 252 | |
| 253 | /* Wake up the selected waiting thread. Woken thread can check |
| 254 | its own p_nextlock field for NULL to detect that it has been removed. No |
| 255 | barrier is needed here, since restart() and suspend() take |
| 256 | care of memory synchronization. */ |
| 257 | |
| 258 | thr->p_nextlock = NULL; |
| 259 | restart(thr); |
| 260 | |
| 261 | return 0; |
| 262 | #endif |
| 263 | } |
| 264 | |
| 265 | /* |
| 266 | * Alternate fastlocks do not queue threads directly. Instead, they queue |
| 267 | * these wait queue node structures. When a timed wait wakes up due to |
| 268 | * a timeout, it can leave its wait node in the queue (because there |
| 269 | * is no safe way to remove from the quue). Some other thread will |
| 270 | * deallocate the abandoned node. |
| 271 | */ |
| 272 | |
| 273 | |
| 274 | struct wait_node { |
| 275 | struct wait_node *next; /* Next node in null terminated linked list */ |
| 276 | pthread_descr thr; /* The thread waiting with this node */ |
| 277 | int abandoned; /* Atomic flag */ |
| 278 | }; |
| 279 | |
| 280 | static long wait_node_free_list; |
| 281 | static int wait_node_free_list_spinlock; |
| 282 | |
| 283 | /* Allocate a new node from the head of the free list using an atomic |
| 284 | operation, or else using malloc if that list is empty. A fundamental |
| 285 | assumption here is that we can safely access wait_node_free_list->next. |
| 286 | That's because we never free nodes once we allocate them, so a pointer to a |
| 287 | node remains valid indefinitely. */ |
| 288 | |
| 289 | static struct wait_node *wait_node_alloc(void) |
| 290 | { |
| 291 | struct wait_node *new_node = 0; |
| 292 | |
| 293 | __pthread_acquire(&wait_node_free_list_spinlock); |
| 294 | if (wait_node_free_list != 0) { |
| 295 | new_node = (struct wait_node *) wait_node_free_list; |
| 296 | wait_node_free_list = (long) new_node->next; |
| 297 | } |
| 298 | WRITE_MEMORY_BARRIER(); |
| 299 | __pthread_release(&wait_node_free_list_spinlock); |
| 300 | |
| 301 | if (new_node == 0) |
| 302 | return malloc(sizeof *wait_node_alloc()); |
| 303 | |
| 304 | return new_node; |
| 305 | } |
| 306 | |
| 307 | /* Return a node to the head of the free list using an atomic |
| 308 | operation. */ |
| 309 | |
| 310 | static void wait_node_free(struct wait_node *wn) |
| 311 | { |
| 312 | __pthread_acquire(&wait_node_free_list_spinlock); |
| 313 | wn->next = (struct wait_node *) wait_node_free_list; |
| 314 | wait_node_free_list = (long) wn; |
| 315 | WRITE_MEMORY_BARRIER(); |
| 316 | __pthread_release(&wait_node_free_list_spinlock); |
| 317 | return; |
| 318 | } |
| 319 | |
| 320 | #if defined HAS_COMPARE_AND_SWAP |
| 321 | |
| 322 | /* Remove a wait node from the specified queue. It is assumed |
| 323 | that the removal takes place concurrently with only atomic insertions at the |
| 324 | head of the queue. */ |
| 325 | |
| 326 | static void wait_node_dequeue(struct wait_node **pp_head, |
| 327 | struct wait_node **pp_node, |
| 328 | struct wait_node *p_node) |
| 329 | { |
| 330 | /* If the node is being deleted from the head of the |
| 331 | list, it must be deleted using atomic compare-and-swap. |
| 332 | Otherwise it can be deleted in the straightforward way. */ |
| 333 | |
| 334 | if (pp_node == pp_head) { |
| 335 | /* We don't need a read barrier between these next two loads, |
| 336 | because it is assumed that the caller has already ensured |
| 337 | the stability of *p_node with respect to p_node. */ |
| 338 | |
| 339 | long oldvalue = (long) p_node; |
| 340 | long newvalue = (long) p_node->next; |
| 341 | |
| 342 | if (__compare_and_swap((long *) pp_node, oldvalue, newvalue)) |
| 343 | return; |
| 344 | |
| 345 | /* Oops! Compare and swap failed, which means the node is |
| 346 | no longer first. We delete it using the ordinary method. But we don't |
| 347 | know the identity of the node which now holds the pointer to the node |
| 348 | being deleted, so we must search from the beginning. */ |
| 349 | |
| 350 | for (pp_node = pp_head; p_node != *pp_node; ) { |
| 351 | pp_node = &(*pp_node)->next; |
| 352 | READ_MEMORY_BARRIER(); /* Stabilize *pp_node for next iteration. */ |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | *pp_node = p_node->next; |
| 357 | return; |
| 358 | } |
| 359 | |
| 360 | #endif |
| 361 | |
| 362 | void __pthread_alt_lock(struct _pthread_fastlock * lock, |
| 363 | pthread_descr self) |
| 364 | { |
| 365 | #if defined HAS_COMPARE_AND_SWAP |
| 366 | long oldstatus, newstatus; |
| 367 | #endif |
| 368 | struct wait_node wait_node; |
| 369 | |
| 370 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 371 | if (!__pthread_has_cas) |
| 372 | #endif |
| 373 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 374 | { |
| 375 | int suspend_needed = 0; |
| 376 | __pthread_acquire(&lock->__spinlock); |
| 377 | |
| 378 | if (lock->__status == 0) |
| 379 | lock->__status = 1; |
| 380 | else { |
| 381 | if (self == NULL) |
| 382 | self = thread_self(); |
| 383 | |
| 384 | wait_node.abandoned = 0; |
| 385 | wait_node.next = (struct wait_node *) lock->__status; |
| 386 | wait_node.thr = self; |
| 387 | lock->__status = (long) &wait_node; |
| 388 | suspend_needed = 1; |
| 389 | } |
| 390 | |
| 391 | __pthread_release(&lock->__spinlock); |
| 392 | |
| 393 | if (suspend_needed) |
| 394 | suspend (self); |
| 395 | return; |
| 396 | } |
| 397 | #endif |
| 398 | |
| 399 | #if defined HAS_COMPARE_AND_SWAP |
| 400 | do { |
| 401 | oldstatus = lock->__status; |
| 402 | if (oldstatus == 0) { |
| 403 | newstatus = 1; |
| 404 | } else { |
| 405 | if (self == NULL) |
| 406 | self = thread_self(); |
| 407 | wait_node.thr = self; |
| 408 | newstatus = (long) &wait_node; |
| 409 | } |
| 410 | wait_node.abandoned = 0; |
| 411 | wait_node.next = (struct wait_node *) oldstatus; |
| 412 | /* Make sure the store in wait_node.next completes before performing |
| 413 | the compare-and-swap */ |
| 414 | MEMORY_BARRIER(); |
| 415 | } while(! __compare_and_swap(&lock->__status, oldstatus, newstatus)); |
| 416 | |
| 417 | /* Suspend. Note that unlike in __pthread_lock, we don't worry |
| 418 | here about spurious wakeup. That's because this lock is not |
| 419 | used in situations where that can happen; the restart can |
| 420 | only come from the previous lock owner. */ |
| 421 | |
| 422 | if (oldstatus != 0) |
| 423 | suspend(self); |
| 424 | |
| 425 | READ_MEMORY_BARRIER(); |
| 426 | #endif |
| 427 | } |
| 428 | |
| 429 | /* Timed-out lock operation; returns 0 to indicate timeout. */ |
| 430 | |
| 431 | int __pthread_alt_timedlock(struct _pthread_fastlock * lock, |
| 432 | pthread_descr self, const struct timespec *abstime) |
| 433 | { |
| 434 | long oldstatus = 0; |
| 435 | #if defined HAS_COMPARE_AND_SWAP |
| 436 | long newstatus; |
| 437 | #endif |
| 438 | struct wait_node *p_wait_node = wait_node_alloc(); |
| 439 | |
| 440 | /* Out of memory, just give up and do ordinary lock. */ |
| 441 | if (p_wait_node == 0) { |
| 442 | __pthread_alt_lock(lock, self); |
| 443 | return 1; |
| 444 | } |
| 445 | |
| 446 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 447 | if (!__pthread_has_cas) |
| 448 | #endif |
| 449 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 450 | { |
| 451 | __pthread_acquire(&lock->__spinlock); |
| 452 | |
| 453 | if (lock->__status == 0) |
| 454 | lock->__status = 1; |
| 455 | else { |
| 456 | if (self == NULL) |
| 457 | self = thread_self(); |
| 458 | |
| 459 | p_wait_node->abandoned = 0; |
| 460 | p_wait_node->next = (struct wait_node *) lock->__status; |
| 461 | p_wait_node->thr = self; |
| 462 | lock->__status = (long) p_wait_node; |
| 463 | oldstatus = 1; /* force suspend */ |
| 464 | } |
| 465 | |
| 466 | __pthread_release(&lock->__spinlock); |
| 467 | goto suspend; |
| 468 | } |
| 469 | #endif |
| 470 | |
| 471 | #if defined HAS_COMPARE_AND_SWAP |
| 472 | do { |
| 473 | oldstatus = lock->__status; |
| 474 | if (oldstatus == 0) { |
| 475 | newstatus = 1; |
| 476 | } else { |
| 477 | if (self == NULL) |
| 478 | self = thread_self(); |
| 479 | p_wait_node->thr = self; |
| 480 | newstatus = (long) p_wait_node; |
| 481 | } |
| 482 | p_wait_node->abandoned = 0; |
| 483 | p_wait_node->next = (struct wait_node *) oldstatus; |
| 484 | /* Make sure the store in wait_node.next completes before performing |
| 485 | the compare-and-swap */ |
| 486 | MEMORY_BARRIER(); |
| 487 | } while(! __compare_and_swap(&lock->__status, oldstatus, newstatus)); |
| 488 | #endif |
| 489 | |
| 490 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 491 | suspend: |
| 492 | #endif |
| 493 | |
| 494 | /* If we did not get the lock, do a timed suspend. If we wake up due |
| 495 | to a timeout, then there is a race; the old lock owner may try |
| 496 | to remove us from the queue. This race is resolved by us and the owner |
| 497 | doing an atomic testandset() to change the state of the wait node from 0 |
| 498 | to 1. If we succeed, then it's a timeout and we abandon the node in the |
| 499 | queue. If we fail, it means the owner gave us the lock. */ |
| 500 | |
| 501 | if (oldstatus != 0) { |
| 502 | if (timedsuspend(self, abstime) == 0) { |
| 503 | if (!testandset(&p_wait_node->abandoned)) |
| 504 | return 0; /* Timeout! */ |
| 505 | |
| 506 | /* Eat oustanding resume from owner, otherwise wait_node_free() below |
| 507 | will race with owner's wait_node_dequeue(). */ |
| 508 | suspend(self); |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | wait_node_free(p_wait_node); |
| 513 | |
| 514 | READ_MEMORY_BARRIER(); |
| 515 | |
| 516 | return 1; /* Got the lock! */ |
| 517 | } |
| 518 | |
| 519 | void __pthread_alt_unlock(struct _pthread_fastlock *lock) |
| 520 | { |
| 521 | struct wait_node *p_node, **pp_node, *p_max_prio, **pp_max_prio; |
| 522 | struct wait_node ** const pp_head = (struct wait_node **) &lock->__status; |
| 523 | int maxprio; |
| 524 | |
| 525 | WRITE_MEMORY_BARRIER(); |
| 526 | |
| 527 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 528 | if (!__pthread_has_cas) |
| 529 | #endif |
| 530 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 531 | { |
| 532 | __pthread_acquire(&lock->__spinlock); |
| 533 | } |
| 534 | #endif |
| 535 | |
| 536 | while (1) { |
| 537 | |
| 538 | /* If no threads are waiting for this lock, try to just |
| 539 | atomically release it. */ |
| 540 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 541 | if (!__pthread_has_cas) |
| 542 | #endif |
| 543 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 544 | { |
| 545 | if (lock->__status == 0 || lock->__status == 1) { |
| 546 | lock->__status = 0; |
| 547 | break; |
| 548 | } |
| 549 | } |
| 550 | #endif |
| 551 | |
| 552 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 553 | else |
| 554 | #endif |
| 555 | |
| 556 | #if defined HAS_COMPARE_AND_SWAP |
| 557 | { |
| 558 | long oldstatus = lock->__status; |
| 559 | if (oldstatus == 0 || oldstatus == 1) { |
| 560 | if (__compare_and_swap_with_release_semantics (&lock->__status, oldstatus, 0)) |
| 561 | break; |
| 562 | else |
| 563 | continue; |
| 564 | } |
| 565 | } |
| 566 | #endif |
| 567 | |
| 568 | /* Process the entire queue of wait nodes. Remove all abandoned |
| 569 | wait nodes and put them into the global free queue, and |
| 570 | remember the one unabandoned node which refers to the thread |
| 571 | having the highest priority. */ |
| 572 | |
| 573 | pp_max_prio = pp_node = pp_head; |
| 574 | p_max_prio = p_node = *pp_head; |
| 575 | maxprio = INT_MIN; |
| 576 | |
| 577 | READ_MEMORY_BARRIER(); /* Prevent access to stale data through p_node */ |
| 578 | |
| 579 | while (p_node != (struct wait_node *) 1) { |
| 580 | int prio; |
| 581 | |
| 582 | if (p_node->abandoned) { |
| 583 | /* Remove abandoned node. */ |
| 584 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 585 | if (!__pthread_has_cas) |
| 586 | #endif |
| 587 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 588 | *pp_node = p_node->next; |
| 589 | #endif |
| 590 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 591 | else |
| 592 | #endif |
| 593 | #if defined HAS_COMPARE_AND_SWAP |
| 594 | wait_node_dequeue(pp_head, pp_node, p_node); |
| 595 | #endif |
| 596 | wait_node_free(p_node); |
| 597 | /* Note that the next assignment may take us to the beginning |
| 598 | of the queue, to newly inserted nodes, if pp_node == pp_head. |
| 599 | In that case we need a memory barrier to stabilize the first of |
| 600 | these new nodes. */ |
| 601 | p_node = *pp_node; |
| 602 | if (pp_node == pp_head) |
| 603 | READ_MEMORY_BARRIER(); /* No stale reads through p_node */ |
| 604 | continue; |
| 605 | } else if ((prio = p_node->thr->p_priority) >= maxprio) { |
| 606 | /* Otherwise remember it if its thread has a higher or equal priority |
| 607 | compared to that of any node seen thus far. */ |
| 608 | maxprio = prio; |
| 609 | pp_max_prio = pp_node; |
| 610 | p_max_prio = p_node; |
| 611 | } |
| 612 | |
| 613 | /* This canno6 jump backward in the list, so no further read |
| 614 | barrier is needed. */ |
| 615 | pp_node = &p_node->next; |
| 616 | p_node = *pp_node; |
| 617 | } |
| 618 | |
| 619 | /* If all threads abandoned, go back to top */ |
| 620 | if (maxprio == INT_MIN) |
| 621 | continue; |
| 622 | |
| 623 | /* Now we want to to remove the max priority thread's wait node from |
| 624 | the list. Before we can do this, we must atomically try to change the |
| 625 | node's abandon state from zero to nonzero. If we succeed, that means we |
| 626 | have the node that we will wake up. If we failed, then it means the |
| 627 | thread timed out and abandoned the node in which case we repeat the |
| 628 | whole unlock operation. */ |
| 629 | |
| 630 | if (!testandset(&p_max_prio->abandoned)) { |
| 631 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 632 | if (!__pthread_has_cas) |
| 633 | #endif |
| 634 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 635 | *pp_max_prio = p_max_prio->next; |
| 636 | #endif |
| 637 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 638 | else |
| 639 | #endif |
| 640 | #if defined HAS_COMPARE_AND_SWAP |
| 641 | wait_node_dequeue(pp_head, pp_max_prio, p_max_prio); |
| 642 | #endif |
| 643 | restart(p_max_prio->thr); |
| 644 | break; |
| 645 | } |
| 646 | } |
| 647 | |
| 648 | #if defined TEST_FOR_COMPARE_AND_SWAP |
| 649 | if (!__pthread_has_cas) |
| 650 | #endif |
| 651 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 652 | { |
| 653 | __pthread_release(&lock->__spinlock); |
| 654 | } |
| 655 | #endif |
| 656 | } |
| 657 | |
| 658 | |
| 659 | /* Compare-and-swap emulation with a spinlock */ |
| 660 | |
| 661 | #ifdef TEST_FOR_COMPARE_AND_SWAP |
| 662 | int __pthread_has_cas = 0; |
| 663 | #endif |
| 664 | |
| 665 | #if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP |
| 666 | |
| 667 | int __pthread_compare_and_swap(long * ptr, long oldval, long newval, |
| 668 | int * spinlock) |
| 669 | { |
| 670 | int res; |
| 671 | |
| 672 | __pthread_acquire(spinlock); |
| 673 | |
| 674 | if (*ptr == oldval) { |
| 675 | *ptr = newval; res = 1; |
| 676 | } else { |
| 677 | res = 0; |
| 678 | } |
| 679 | |
| 680 | __pthread_release(spinlock); |
| 681 | |
| 682 | return res; |
| 683 | } |
| 684 | |
| 685 | #endif |
| 686 | |
| 687 | /* The retry strategy is as follows: |
| 688 | - We test and set the spinlock MAX_SPIN_COUNT times, calling |
| 689 | sched_yield() each time. This gives ample opportunity for other |
| 690 | threads with priority >= our priority to make progress and |
| 691 | release the spinlock. |
| 692 | - If a thread with priority < our priority owns the spinlock, |
| 693 | calling sched_yield() repeatedly is useless, since we're preventing |
| 694 | the owning thread from making progress and releasing the spinlock. |
| 695 | So, after MAX_SPIN_LOCK attemps, we suspend the calling thread |
| 696 | using nanosleep(). This again should give time to the owning thread |
| 697 | for releasing the spinlock. |
| 698 | Notice that the nanosleep() interval must not be too small, |
| 699 | since the kernel does busy-waiting for short intervals in a realtime |
| 700 | process (!). The smallest duration that guarantees thread |
| 701 | suspension is currently 2ms. |
| 702 | - When nanosleep() returns, we try again, doing MAX_SPIN_COUNT |
| 703 | sched_yield(), then sleeping again if needed. */ |
| 704 | |
| 705 | static void __pthread_acquire(int * spinlock) |
| 706 | { |
| 707 | int cnt = 0; |
| 708 | struct timespec tm; |
| 709 | |
| 710 | READ_MEMORY_BARRIER(); |
| 711 | |
| 712 | while (testandset(spinlock)) { |
| 713 | if (cnt < MAX_SPIN_COUNT) { |
| 714 | sched_yield(); |
| 715 | cnt++; |
| 716 | } else { |
| 717 | tm.tv_sec = 0; |
| 718 | tm.tv_nsec = SPIN_SLEEP_DURATION; |
| 719 | nanosleep(&tm, NULL); |
| 720 | cnt = 0; |
| 721 | } |
| 722 | } |
| 723 | } |