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