test_futex.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*
  2. * Copyright (c) 2023 Agustina Arzille.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. * This module aims to test the full futex API. It is composed of 4
  18. * sub-tests, each one dedicated to testing particular features:
  19. *
  20. * - The first sub-test checks the basic futex API: waiting, waiting
  21. * and requeuing using regular futexes (local, non-robust, non-PI).
  22. *
  23. * - The second sub-test checks that shared futexes work correctly. It
  24. * creates a second task and a thread in it. This new thread will map
  25. * memory from the same object as the initial thread. This way, the
  26. * shared memory mapping can be used to place a futex there and check
  27. * that it works correctly.
  28. *
  29. * - The third sub-test checks that PI futexes allow waiting threads to
  30. * propagate their priorities to lower-priority threads. The initial
  31. * thread creates a futex, acquires it by writing its TID to it and
  32. * then creates 2 real-time threads that will wait on the futex. The
  33. * first thread then checks that it has inherited their priority and
  34. * is running boosted.
  35. *
  36. * - The fourth sub-test checks that robust futexes are handled correctly
  37. * when a thread exits while holding one or more of them. It creates the
  38. * linked list of futexes (plus its 'pending' one), sets the futex word
  39. * to its TID and the FUTEX_WAITERS bit and then exits without unlocking
  40. * them. Another thread waits for it to exit and then waits on the futexes,
  41. * checking that the FUTEX_OWNER_DIED bit has been set.
  42. */
  43. #include <stdio.h>
  44. #include <kern/futex.h>
  45. #include <kern/task.h>
  46. #include <kern/user.h>
  47. #include <test/test.h>
  48. #include <vm/map.h>
  49. #include <vm/object.h>
  50. struct test_futex_obj
  51. {
  52. struct futex_robust_list head;
  53. uint64_t prev; // Unused.
  54. };
  55. struct test_futex_data
  56. {
  57. struct thread *thr;
  58. struct futex_td td;
  59. struct test_futex_obj objs[3];
  60. };
  61. static void
  62. test_futex_helper (void *arg)
  63. {
  64. int error = futex_wait (arg, 0, 0, 0);
  65. assert (!error || error == EAGAIN);
  66. }
  67. static void
  68. test_futex_local (void *arg __unused)
  69. {
  70. void *addr;
  71. int error = vm_map_anon_alloc (&addr, vm_map_self (), 1);
  72. assert (! error);
  73. // Don't touch the address' contents so we may test page faults here.
  74. error = futex_wait (addr, 1, 0, 0);
  75. assert (error == EAGAIN);
  76. error = futex_wait (addr, 0, FUTEX_TIMED, 10);
  77. assert (error == ETIMEDOUT);
  78. struct thread *thread;
  79. struct thread_attr attr;
  80. thread_attr_init (&attr, "futex/1");
  81. thread_create (&thread, &attr, test_futex_helper, addr);
  82. test_thread_wait_state (thread, THREAD_SLEEPING);
  83. futex_wake (addr, FUTEX_MUTATE, 1);
  84. assert (*(int *)addr == 1);
  85. thread_join (thread);
  86. *(int *)addr = 0;
  87. struct thread *thrs[4];
  88. for (size_t i = 0; i < ARRAY_SIZE (thrs); ++i)
  89. {
  90. char name[16];
  91. sprintf (name, "futex-L/%d", (int)(i + 1));
  92. thread_attr_init (&attr, name);
  93. error = thread_create (&thrs[i], &attr, test_futex_helper,
  94. (int *)addr + (i & 1));
  95. assert (! error);
  96. test_thread_wait_state (thrs[i], THREAD_SLEEPING);
  97. }
  98. error = futex_requeue (addr, (int *)addr + 1, 0, FUTEX_BROADCAST);
  99. assert (! error);
  100. *(int *)addr = 1;
  101. error = futex_wake (addr, FUTEX_BROADCAST, 1);
  102. assert (! error);
  103. for (size_t i = 0; i < ARRAY_SIZE (thrs); ++i)
  104. thread_join (thrs[i]);
  105. *(int *)addr = 0;
  106. for (size_t i = 0; i < ARRAY_SIZE (thrs); ++i)
  107. {
  108. char name[16];
  109. sprintf (name, "futex-M/%d", (int)(i + 1));
  110. thread_attr_init (&attr, name);
  111. thread_create (&thrs[i], &attr, test_futex_helper,
  112. (int *)addr + (i & 1));
  113. test_thread_wait_state (thrs[i], THREAD_SLEEPING);
  114. }
  115. *(int *)addr = 1;
  116. error = futex_requeue (addr, (int *)addr + 1, 1, 0);
  117. assert (! error);
  118. error = futex_wake (addr, FUTEX_BROADCAST, 0);
  119. assert (! error);
  120. // Wake the remaining thread.
  121. error = futex_wake ((int *)addr + 1, 0, 0);
  122. assert (! error);
  123. for (size_t i = 0; i < ARRAY_SIZE (thrs); ++i)
  124. thread_join (thrs[i]);
  125. }
  126. static void
  127. test_futex_shared_helper (void *arg)
  128. {
  129. struct vm_map_entry *entry = arg;
  130. uintptr_t start = PAGE_SIZE * 100;
  131. int flags = VM_MAP_FLAGS (VM_PROT_RDWR, VM_PROT_RDWR, VM_INHERIT_SHARE,
  132. VM_ADV_DEFAULT, 0);
  133. int error = vm_map_enter (vm_map_self (), &start, PAGE_SIZE,
  134. flags, entry->object, entry->offset);
  135. assert (! error);
  136. void *addr = (void *)start;
  137. error = futex_wait (addr, 0, FUTEX_SHARED, 0);
  138. assert (!error || error == EAGAIN);
  139. assert (*(int *)addr == 1);
  140. }
  141. static void
  142. test_futex_shared (void *arg __unused)
  143. {
  144. void *addr;
  145. int error = vm_map_anon_alloc (&addr, vm_map_self (), 1);
  146. assert (! error);
  147. _Auto entry = vm_map_find (vm_map_self (), (uintptr_t)addr);
  148. assert (entry);
  149. struct thread *thr;
  150. error = test_util_create_thr (&thr, test_futex_shared_helper,
  151. entry, "futex-sh-fork");
  152. test_thread_wait_state (thr, THREAD_SLEEPING);
  153. error = futex_wake (addr, FUTEX_MUTATE | FUTEX_SHARED, 1);
  154. thread_join (thr);
  155. vm_map_entry_put (entry);
  156. }
  157. static void
  158. test_futex_pi_helper (void *arg)
  159. {
  160. int *futex = arg;
  161. int error = futex_wait (futex, (*futex & FUTEX_TID_MASK), FUTEX_PI, 0);
  162. if (! error)
  163. assert ((*futex & FUTEX_TID_MASK) == (uint32_t)thread_id (thread_self ()));
  164. else
  165. assert (error == EAGAIN);
  166. }
  167. static void
  168. test_futex_pi (void *arg __unused)
  169. {
  170. void *addr;
  171. int error = vm_map_anon_alloc (&addr, vm_map_self (), 1);
  172. assert (! error);
  173. int *futex = addr;
  174. *futex = thread_id (thread_self ());
  175. struct thread *thrs[2];
  176. struct thread_attr attr;
  177. thread_attr_init (&attr, "futex-pi/1");
  178. thread_attr_set_policy (&attr, THREAD_SCHED_POLICY_FIFO);
  179. thread_attr_set_priority (&attr, THREAD_SCHED_RT_PRIO_MAX / 2);
  180. error = thread_create (&thrs[0], &attr, test_futex_pi_helper, futex);
  181. assert (! error);
  182. thread_attr_init (&attr, "futex-pi/2");
  183. thread_attr_set_policy (&attr, THREAD_SCHED_POLICY_FIFO);
  184. thread_attr_set_priority (&attr, THREAD_SCHED_RT_PRIO_MAX / 2);
  185. error = thread_create (&thrs[1], &attr, test_futex_pi_helper, futex);
  186. assert (! error);
  187. test_thread_wait_state (thrs[0], THREAD_SLEEPING);
  188. test_thread_wait_state (thrs[1], THREAD_SLEEPING);
  189. assert (thread_real_sched_policy (thread_self ()) ==
  190. THREAD_SCHED_POLICY_FIFO);
  191. error = futex_wake (futex, FUTEX_PI | FUTEX_BROADCAST | FUTEX_MUTATE, 0);
  192. assert (! error);
  193. thread_join (thrs[0]);
  194. thread_join (thrs[1]);
  195. }
  196. static void
  197. test_futex_robust_helper (void *arg)
  198. {
  199. struct test_futex_data *data = arg;
  200. _Auto td = &data->td;
  201. int val = thread_id (thread_self ()) | FUTEX_WAITERS;
  202. futex_td_init (td);
  203. for (size_t i = 0; i < ARRAY_SIZE (data->objs); ++i)
  204. data->objs[i].head.futex = val;
  205. td->pending = &data->objs[0].head;
  206. data->objs[1].head.next = (uint64_t)(uintptr_t)&data->objs[2].head;
  207. data->objs[2].head.next = 0;
  208. td->list = &data->objs[1].head;
  209. test_thread_wait_state (data->thr, THREAD_SLEEPING);
  210. thread_self()->futex_td = td;
  211. }
  212. static void
  213. test_futex_robust (void *arg __unused)
  214. {
  215. void *addr;
  216. int error = vm_map_anon_alloc (&addr, vm_map_self (), 1);
  217. assert (! error);
  218. struct test_futex_data *data = addr;
  219. data->thr = thread_self ();
  220. struct thread *thr;
  221. struct thread_attr attr;
  222. thread_attr_init (&attr, "futex-robust/1");
  223. error = thread_create (&thr, &attr, test_futex_robust_helper, addr);
  224. assert (! error);
  225. error = futex_wait (&data->objs[2].head.futex, FUTEX_WAITERS |
  226. thread_id (thr), 0, 0);
  227. assert (!error || error == EAGAIN);
  228. thread_join (thr);
  229. for (size_t i = 0; i < ARRAY_SIZE (data->objs); ++i)
  230. assert ((data->objs[i].head.futex & FUTEX_OWNER_DIED) != 0);
  231. }
  232. TEST_DEFERRED (futex)
  233. {
  234. struct thread *thread;
  235. int error;
  236. error = test_util_create_thr (&thread, test_futex_local, NULL, "futex");
  237. assert (! error);
  238. thread_join (thread);
  239. error = test_util_create_thr (&thread, test_futex_shared, NULL, "futex-sh");
  240. assert (! error);
  241. thread_join (thread);
  242. error = test_util_create_thr (&thread, test_futex_pi, NULL, "futex-pi");
  243. assert (! error);
  244. thread_join (thread);
  245. error = test_util_create_thr (&thread, test_futex_robust,
  246. NULL, "futex-robust");
  247. assert (! error);
  248. thread_join (thread);
  249. return (TEST_OK);
  250. }