basic_percpu_ops_test.c 7.8 KB

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