[337] | 1 | #include "pthread_impl.h"
|
---|
| 2 | #include <semaphore.h>
|
---|
| 3 | #include <unistd.h>
|
---|
| 4 | #include <dirent.h>
|
---|
| 5 | #include <string.h>
|
---|
| 6 | #include <ctype.h>
|
---|
| 7 | #include "futex.h"
|
---|
| 8 | #include "atomic.h"
|
---|
| 9 | #include "../dirent/__dirent.h"
|
---|
| 10 |
|
---|
| 11 | static struct chain {
|
---|
| 12 | struct chain *next;
|
---|
| 13 | int tid;
|
---|
| 14 | sem_t target_sem, caller_sem;
|
---|
| 15 | } *volatile head;
|
---|
| 16 |
|
---|
| 17 | static volatile int synccall_lock[2];
|
---|
| 18 | static volatile int target_tid;
|
---|
| 19 | static void (*callback)(void *), *context;
|
---|
| 20 | #ifndef __c2__
|
---|
| 21 | static volatile int dummy = 0;
|
---|
| 22 | weak_alias(dummy, __block_new_threads);
|
---|
| 23 | #else
|
---|
| 24 | extern volatile int __block_new_threads;
|
---|
| 25 | #endif
|
---|
| 26 |
|
---|
| 27 | static void handler(int sig)
|
---|
| 28 | {
|
---|
| 29 | struct chain ch;
|
---|
| 30 | int old_errno = errno;
|
---|
| 31 |
|
---|
| 32 | sem_init(&ch.target_sem, 0, 0);
|
---|
| 33 | sem_init(&ch.caller_sem, 0, 0);
|
---|
| 34 |
|
---|
| 35 | ch.tid = __syscall(SYS_gettid);
|
---|
| 36 |
|
---|
| 37 | do ch.next = head;
|
---|
| 38 | while (a_cas_p(&head, ch.next, &ch) != ch.next);
|
---|
| 39 |
|
---|
| 40 | if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000))
|
---|
| 41 | __syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE);
|
---|
| 42 |
|
---|
| 43 | sem_wait(&ch.target_sem);
|
---|
| 44 | callback(context);
|
---|
| 45 | sem_post(&ch.caller_sem);
|
---|
| 46 | sem_wait(&ch.target_sem);
|
---|
| 47 |
|
---|
| 48 | errno = old_errno;
|
---|
| 49 | }
|
---|
| 50 |
|
---|
| 51 | void __synccall(void (*func)(void *), void *ctx)
|
---|
| 52 | {
|
---|
| 53 | sigset_t oldmask;
|
---|
| 54 | int cs, i, r, pid, self;;
|
---|
| 55 | DIR dir = {0};
|
---|
| 56 | struct dirent *de;
|
---|
| 57 | struct sigaction sa = { .sa_flags = SA_RESTART, .sa_handler = handler };
|
---|
| 58 | struct chain *cp, *next;
|
---|
| 59 | struct timespec ts;
|
---|
| 60 |
|
---|
| 61 | /* Blocking signals in two steps, first only app-level signals
|
---|
| 62 | * before taking the lock, then all signals after taking the lock,
|
---|
| 63 | * is necessary to achieve AS-safety. Blocking them all first would
|
---|
| 64 | * deadlock if multiple threads called __synccall. Waiting to block
|
---|
| 65 | * any until after the lock would allow re-entry in the same thread
|
---|
| 66 | * with the lock already held. */
|
---|
| 67 | __block_app_sigs(&oldmask);
|
---|
| 68 | LOCK(synccall_lock);
|
---|
| 69 | __block_all_sigs(0);
|
---|
| 70 | pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
---|
| 71 |
|
---|
| 72 | head = 0;
|
---|
| 73 |
|
---|
| 74 | if (!libc.threaded) goto single_threaded;
|
---|
| 75 |
|
---|
| 76 | callback = func;
|
---|
| 77 | context = ctx;
|
---|
| 78 |
|
---|
| 79 | /* This atomic store ensures that any signaled threads will see the
|
---|
| 80 | * above stores, and prevents more than a bounded number of threads,
|
---|
| 81 | * those already in pthread_create, from creating new threads until
|
---|
| 82 | * the value is cleared to zero again. */
|
---|
| 83 | a_store(&__block_new_threads, 1);
|
---|
| 84 |
|
---|
| 85 | /* Block even implementation-internal signals, so that nothing
|
---|
| 86 | * interrupts the SIGSYNCCALL handlers. The main possible source
|
---|
| 87 | * of trouble is asynchronous cancellation. */
|
---|
| 88 | memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
|
---|
| 89 | __libc_sigaction(SIGSYNCCALL, &sa, 0);
|
---|
| 90 |
|
---|
| 91 | pid = __syscall(SYS_getpid);
|
---|
| 92 | self = __syscall(SYS_gettid);
|
---|
| 93 |
|
---|
| 94 | /* Since opendir is not AS-safe, the DIR needs to be setup manually
|
---|
| 95 | * in automatic storage. Thankfully this is easy. */
|
---|
| 96 | dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
---|
| 97 | if (dir.fd < 0) goto out;
|
---|
| 98 |
|
---|
| 99 | /* Initially send one signal per counted thread. But since we can't
|
---|
| 100 | * synchronize with thread creation/exit here, there could be too
|
---|
| 101 | * few signals. This initial signaling is just an optimization, not
|
---|
| 102 | * part of the logic. */
|
---|
| 103 | for (i=libc.threads_minus_1; i; i--)
|
---|
| 104 | __syscall(SYS_kill, pid, SIGSYNCCALL);
|
---|
| 105 |
|
---|
| 106 | /* Loop scanning the kernel-provided thread list until it shows no
|
---|
| 107 | * threads that have not already replied to the signal. */
|
---|
| 108 | for (;;) {
|
---|
| 109 | int miss_cnt = 0;
|
---|
| 110 | while ((de = readdir(&dir))) {
|
---|
| 111 | if (!isdigit(de->d_name[0])) continue;
|
---|
| 112 | int tid = atoi(de->d_name);
|
---|
| 113 | if (tid == self || !tid) continue;
|
---|
| 114 |
|
---|
| 115 | /* Set the target thread as the PI futex owner before
|
---|
| 116 | * checking if it's in the list of caught threads. If it
|
---|
| 117 | * adds itself to the list after we check for it, then
|
---|
| 118 | * it will see its own tid in the PI futex and perform
|
---|
| 119 | * the unlock operation. */
|
---|
| 120 | a_store(&target_tid, tid);
|
---|
| 121 |
|
---|
| 122 | /* Thread-already-caught is a success condition. */
|
---|
| 123 | for (cp = head; cp && cp->tid != tid; cp=cp->next);
|
---|
| 124 | if (cp) continue;
|
---|
| 125 |
|
---|
| 126 | r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);
|
---|
| 127 |
|
---|
| 128 | /* Target thread exit is a success condition. */
|
---|
| 129 | if (r == ESRCH) continue;
|
---|
| 130 |
|
---|
| 131 | /* The FUTEX_LOCK_PI operation is used to loan priority
|
---|
| 132 | * to the target thread, which otherwise may be unable
|
---|
| 133 | * to run. Timeout is necessary because there is a race
|
---|
| 134 | * condition where the tid may be reused by a different
|
---|
| 135 | * process. */
|
---|
| 136 | clock_gettime(CLOCK_REALTIME, &ts);
|
---|
| 137 | ts.tv_nsec += 10000000;
|
---|
| 138 | if (ts.tv_nsec >= 1000000000) {
|
---|
| 139 | ts.tv_sec++;
|
---|
| 140 | ts.tv_nsec -= 1000000000;
|
---|
| 141 | }
|
---|
| 142 | r = -__syscall(SYS_futex, &target_tid,
|
---|
| 143 | FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts);
|
---|
| 144 |
|
---|
| 145 | /* Obtaining the lock means the thread responded. ESRCH
|
---|
| 146 | * means the target thread exited, which is okay too. */
|
---|
| 147 | if (!r || r == ESRCH) continue;
|
---|
| 148 |
|
---|
| 149 | miss_cnt++;
|
---|
| 150 | }
|
---|
| 151 | if (!miss_cnt) break;
|
---|
| 152 | rewinddir(&dir);
|
---|
| 153 | }
|
---|
| 154 | close(dir.fd);
|
---|
| 155 |
|
---|
| 156 | /* Serialize execution of callback in caught threads. */
|
---|
| 157 | for (cp=head; cp; cp=cp->next) {
|
---|
| 158 | sem_post(&cp->target_sem);
|
---|
| 159 | sem_wait(&cp->caller_sem);
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | sa.sa_handler = SIG_IGN;
|
---|
| 163 | __libc_sigaction(SIGSYNCCALL, &sa, 0);
|
---|
| 164 |
|
---|
| 165 | single_threaded:
|
---|
| 166 | func(ctx);
|
---|
| 167 |
|
---|
| 168 | /* Only release the caught threads once all threads, including the
|
---|
| 169 | * caller, have returned from the callback function. */
|
---|
| 170 | for (cp=head; cp; cp=next) {
|
---|
| 171 | next = cp->next;
|
---|
| 172 | sem_post(&cp->target_sem);
|
---|
| 173 | }
|
---|
| 174 |
|
---|
| 175 | out:
|
---|
| 176 | a_store(&__block_new_threads, 0);
|
---|
| 177 | __wake(&__block_new_threads, -1, 1);
|
---|
| 178 |
|
---|
| 179 | pthread_setcancelstate(cs, 0);
|
---|
| 180 | UNLOCK(synccall_lock);
|
---|
| 181 | __restore_sigs(&oldmask);
|
---|
| 182 | }
|
---|