basic_percpu_ops_test.c 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. // SPDX-License-Identifier: LGPL-2.1
  2. #define _GNU_SOURCE
  3. #include <assert.h>
  4. #include <pthread.h>
  5. #include <sched.h>
  6. #include <stdint.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #include <stddef.h>
  11. #include "rseq.h"
  12. #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
  13. struct percpu_lock_entry {
  14. intptr_t v;
  15. } __attribute__((aligned(128)));
  16. struct percpu_lock {
  17. struct percpu_lock_entry c[CPU_SETSIZE];
  18. };
  19. struct test_data_entry {
  20. intptr_t count;
  21. } __attribute__((aligned(128)));
  22. struct spinlock_test_data {
  23. struct percpu_lock lock;
  24. struct test_data_entry c[CPU_SETSIZE];
  25. int reps;
  26. };
  27. struct percpu_list_node {
  28. intptr_t data;
  29. struct percpu_list_node *next;
  30. };
  31. struct percpu_list_entry {
  32. struct percpu_list_node *head;
  33. } __attribute__((aligned(128)));
  34. struct percpu_list {
  35. struct percpu_list_entry c[CPU_SETSIZE];
  36. };
  37. /* A simple percpu spinlock. Returns the cpu lock was acquired on. */
  38. int rseq_this_cpu_lock(struct percpu_lock *lock)
  39. {
  40. int cpu;
  41. for (;;) {
  42. int ret;
  43. cpu = rseq_cpu_start();
  44. ret = rseq_cmpeqv_storev(&lock->c[cpu].v,
  45. 0, 1, cpu);
  46. if (rseq_likely(!ret))
  47. break;
  48. /* Retry if comparison fails or rseq aborts. */
  49. }
  50. /*
  51. * Acquire semantic when taking lock after control dependency.
  52. * Matches rseq_smp_store_release().
  53. */
  54. rseq_smp_acquire__after_ctrl_dep();
  55. return cpu;
  56. }
  57. void rseq_percpu_unlock(struct percpu_lock *lock, int cpu)
  58. {
  59. assert(lock->c[cpu].v == 1);
  60. /*
  61. * Release lock, with release semantic. Matches
  62. * rseq_smp_acquire__after_ctrl_dep().
  63. */
  64. rseq_smp_store_release(&lock->c[cpu].v, 0);
  65. }
  66. void *test_percpu_spinlock_thread(void *arg)
  67. {
  68. struct spinlock_test_data *data = arg;
  69. int i, cpu;
  70. if (rseq_register_current_thread()) {
  71. fprintf(stderr, "Error: rseq_register_current_thread(...) failed(%d): %s\n",
  72. errno, strerror(errno));
  73. abort();
  74. }
  75. for (i = 0; i < data->reps; i++) {
  76. cpu = rseq_this_cpu_lock(&data->lock);
  77. data->c[cpu].count++;
  78. rseq_percpu_unlock(&data->lock, cpu);
  79. }
  80. if (rseq_unregister_current_thread()) {
  81. fprintf(stderr, "Error: rseq_unregister_current_thread(...) failed(%d): %s\n",
  82. errno, strerror(errno));
  83. abort();
  84. }
  85. return NULL;
  86. }
  87. /*
  88. * A simple test which implements a sharded counter using a per-cpu
  89. * lock. Obviously real applications might prefer to simply use a
  90. * per-cpu increment; however, this is reasonable for a test and the
  91. * lock can be extended to synchronize more complicated operations.
  92. */
  93. void test_percpu_spinlock(void)
  94. {
  95. const int num_threads = 200;
  96. int i;
  97. uint64_t sum;
  98. pthread_t test_threads[num_threads];
  99. struct spinlock_test_data data;
  100. memset(&data, 0, sizeof(data));
  101. data.reps = 5000;
  102. for (i = 0; i < num_threads; i++)
  103. pthread_create(&test_threads[i], NULL,
  104. test_percpu_spinlock_thread, &data);
  105. for (i = 0; i < num_threads; i++)
  106. pthread_join(test_threads[i], NULL);
  107. sum = 0;
  108. for (i = 0; i < CPU_SETSIZE; i++)
  109. sum += data.c[i].count;
  110. assert(sum == (uint64_t)data.reps * num_threads);
  111. }
  112. void this_cpu_list_push(struct percpu_list *list,
  113. struct percpu_list_node *node,
  114. int *_cpu)
  115. {
  116. int cpu;
  117. for (;;) {
  118. intptr_t *targetptr, newval, expect;
  119. int ret;
  120. cpu = rseq_cpu_start();
  121. /* Load list->c[cpu].head with single-copy atomicity. */
  122. expect = (intptr_t)RSEQ_READ_ONCE(list->c[cpu].head);
  123. newval = (intptr_t)node;
  124. targetptr = (intptr_t *)&list->c[cpu].head;
  125. node->next = (struct percpu_list_node *)expect;
  126. ret = rseq_cmpeqv_storev(targetptr, expect, newval, cpu);
  127. if (rseq_likely(!ret))
  128. break;
  129. /* Retry if comparison fails or rseq aborts. */
  130. }
  131. if (_cpu)
  132. *_cpu = cpu;
  133. }
  134. /*
  135. * Unlike a traditional lock-less linked list; the availability of a
  136. * rseq primitive allows us to implement pop without concerns over
  137. * ABA-type races.
  138. */
  139. struct percpu_list_node *this_cpu_list_pop(struct percpu_list *list,
  140. int *_cpu)
  141. {
  142. for (;;) {
  143. struct percpu_list_node *head;
  144. intptr_t *targetptr, expectnot, *load;
  145. off_t offset;
  146. int ret, cpu;
  147. cpu = rseq_cpu_start();
  148. targetptr = (intptr_t *)&list->c[cpu].head;
  149. expectnot = (intptr_t)NULL;
  150. offset = offsetof(struct percpu_list_node, next);
  151. load = (intptr_t *)&head;
  152. ret = rseq_cmpnev_storeoffp_load(targetptr, expectnot,
  153. offset, load, cpu);
  154. if (rseq_likely(!ret)) {
  155. if (_cpu)
  156. *_cpu = cpu;
  157. return head;
  158. }
  159. if (ret > 0)
  160. return NULL;
  161. /* Retry if rseq aborts. */
  162. }
  163. }
  164. /*
  165. * __percpu_list_pop is not safe against concurrent accesses. Should
  166. * only be used on lists that are not concurrently modified.
  167. */
  168. struct percpu_list_node *__percpu_list_pop(struct percpu_list *list, int cpu)
  169. {
  170. struct percpu_list_node *node;
  171. node = list->c[cpu].head;
  172. if (!node)
  173. return NULL;
  174. list->c[cpu].head = node->next;
  175. return node;
  176. }
  177. void *test_percpu_list_thread(void *arg)
  178. {
  179. int i;
  180. struct percpu_list *list = (struct percpu_list *)arg;
  181. if (rseq_register_current_thread()) {
  182. fprintf(stderr, "Error: rseq_register_current_thread(...) failed(%d): %s\n",
  183. errno, strerror(errno));
  184. abort();
  185. }
  186. for (i = 0; i < 100000; i++) {
  187. struct percpu_list_node *node;
  188. node = this_cpu_list_pop(list, NULL);
  189. sched_yield(); /* encourage shuffling */
  190. if (node)
  191. this_cpu_list_push(list, node, NULL);
  192. }
  193. if (rseq_unregister_current_thread()) {
  194. fprintf(stderr, "Error: rseq_unregister_current_thread(...) failed(%d): %s\n",
  195. errno, strerror(errno));
  196. abort();
  197. }
  198. return NULL;
  199. }
  200. /* Simultaneous modification to a per-cpu linked list from many threads. */
  201. void test_percpu_list(void)
  202. {
  203. int i, j;
  204. uint64_t sum = 0, expected_sum = 0;
  205. struct percpu_list list;
  206. pthread_t test_threads[200];
  207. cpu_set_t allowed_cpus;
  208. memset(&list, 0, sizeof(list));
  209. /* Generate list entries for every usable cpu. */
  210. sched_getaffinity(0, sizeof(allowed_cpus), &allowed_cpus);
  211. for (i = 0; i < CPU_SETSIZE; i++) {
  212. if (!CPU_ISSET(i, &allowed_cpus))
  213. continue;
  214. for (j = 1; j <= 100; j++) {
  215. struct percpu_list_node *node;
  216. expected_sum += j;
  217. node = malloc(sizeof(*node));
  218. assert(node);
  219. node->data = j;
  220. node->next = list.c[i].head;
  221. list.c[i].head = node;
  222. }
  223. }
  224. for (i = 0; i < 200; i++)
  225. pthread_create(&test_threads[i], NULL,
  226. test_percpu_list_thread, &list);
  227. for (i = 0; i < 200; i++)
  228. pthread_join(test_threads[i], NULL);
  229. for (i = 0; i < CPU_SETSIZE; i++) {
  230. struct percpu_list_node *node;
  231. if (!CPU_ISSET(i, &allowed_cpus))
  232. continue;
  233. while ((node = __percpu_list_pop(&list, i))) {
  234. sum += node->data;
  235. free(node);
  236. }
  237. }
  238. /*
  239. * All entries should now be accounted for (unless some external
  240. * actor is interfering with our allowed affinity while this
  241. * test is running).
  242. */
  243. assert(sum == expected_sum);
  244. }
  245. int main(int argc, char **argv)
  246. {
  247. if (rseq_register_current_thread()) {
  248. fprintf(stderr, "Error: rseq_register_current_thread(...) failed(%d): %s\n",
  249. errno, strerror(errno));
  250. goto error;
  251. }
  252. printf("spinlock\n");
  253. test_percpu_spinlock();
  254. printf("percpu_list\n");
  255. test_percpu_list();
  256. if (rseq_unregister_current_thread()) {
  257. fprintf(stderr, "Error: rseq_unregister_current_thread(...) failed(%d): %s\n",
  258. errno, strerror(errno));
  259. goto error;
  260. }
  261. return 0;
  262. error:
  263. return -1;
  264. }