blob: 24c81d47e7b88d6a220b047223720ac0d2fc4ec7 [file] [log] [blame]
yuezonghe824eb0c2024-06-27 02:32:26 -07001/* 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
29libpthread_hidden_proto(nanosleep)
30
31static void __pthread_acquire(int * spinlock);
32
33static __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
64void 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
120again:
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
172int __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
193again:
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
274struct 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
280static long wait_node_free_list;
281static 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
289static 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
310static 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
326static 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
362void __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
431int __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
519void __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
662int __pthread_has_cas = 0;
663#endif
664
665#if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP
666
667int __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
705static 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}