synccall.c 5.1 KB

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