cyclic_kick_wait.c 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /* SPDX-License-Identifier: GPL-2.0 */
  2. /*
  3. * Test SCX_KICK_WAIT forward progress under cyclic wait pressure.
  4. *
  5. * SCX_KICK_WAIT busy-waits until the target CPU enters the scheduling path.
  6. * If multiple CPUs form a wait cycle (A waits for B, B waits for C, C waits
  7. * for A), all CPUs deadlock unless the implementation breaks the cycle.
  8. *
  9. * This test creates that scenario: three CPUs are arranged in a ring. The BPF
  10. * scheduler's ops.enqueue() kicks the next CPU in the ring with SCX_KICK_WAIT
  11. * on every enqueue. Userspace pins 4 worker threads per CPU that loop calling
  12. * sched_yield(), generating a steady stream of enqueues and thus sustained
  13. * A->B->C->A kick_wait cycle pressure. The test passes if the system remains
  14. * responsive for 5 seconds without the scheduler being killed by the watchdog.
  15. */
  16. #define _GNU_SOURCE
  17. #include <bpf/bpf.h>
  18. #include <errno.h>
  19. #include <pthread.h>
  20. #include <sched.h>
  21. #include <scx/common.h>
  22. #include <stdint.h>
  23. #include <string.h>
  24. #include <time.h>
  25. #include <unistd.h>
  26. #include "scx_test.h"
  27. #include "cyclic_kick_wait.bpf.skel.h"
  28. #define WORKERS_PER_CPU 4
  29. #define NR_TEST_CPUS 3
  30. #define NR_WORKERS (NR_TEST_CPUS * WORKERS_PER_CPU)
  31. struct worker_ctx {
  32. pthread_t tid;
  33. int cpu;
  34. volatile bool stop;
  35. volatile __u64 iters;
  36. bool started;
  37. };
  38. static void *worker_fn(void *arg)
  39. {
  40. struct worker_ctx *worker = arg;
  41. cpu_set_t mask;
  42. CPU_ZERO(&mask);
  43. CPU_SET(worker->cpu, &mask);
  44. if (sched_setaffinity(0, sizeof(mask), &mask))
  45. return (void *)(uintptr_t)errno;
  46. while (!worker->stop) {
  47. sched_yield();
  48. worker->iters++;
  49. }
  50. return NULL;
  51. }
  52. static int join_worker(struct worker_ctx *worker)
  53. {
  54. void *ret;
  55. struct timespec ts;
  56. int err;
  57. if (!worker->started)
  58. return 0;
  59. if (clock_gettime(CLOCK_REALTIME, &ts))
  60. return -errno;
  61. ts.tv_sec += 2;
  62. err = pthread_timedjoin_np(worker->tid, &ret, &ts);
  63. if (err == ETIMEDOUT)
  64. pthread_detach(worker->tid);
  65. if (err)
  66. return -err;
  67. if ((uintptr_t)ret)
  68. return -(int)(uintptr_t)ret;
  69. return 0;
  70. }
  71. static enum scx_test_status setup(void **ctx)
  72. {
  73. struct cyclic_kick_wait *skel;
  74. skel = cyclic_kick_wait__open();
  75. SCX_FAIL_IF(!skel, "Failed to open skel");
  76. SCX_ENUM_INIT(skel);
  77. *ctx = skel;
  78. return SCX_TEST_PASS;
  79. }
  80. static enum scx_test_status run(void *ctx)
  81. {
  82. struct cyclic_kick_wait *skel = ctx;
  83. struct worker_ctx workers[NR_WORKERS] = {};
  84. struct bpf_link *link = NULL;
  85. enum scx_test_status status = SCX_TEST_PASS;
  86. int test_cpus[NR_TEST_CPUS];
  87. int nr_cpus = 0;
  88. cpu_set_t mask;
  89. int ret, i;
  90. if (sched_getaffinity(0, sizeof(mask), &mask)) {
  91. SCX_ERR("Failed to get affinity (%d)", errno);
  92. return SCX_TEST_FAIL;
  93. }
  94. for (i = 0; i < CPU_SETSIZE; i++) {
  95. if (CPU_ISSET(i, &mask))
  96. test_cpus[nr_cpus++] = i;
  97. if (nr_cpus == NR_TEST_CPUS)
  98. break;
  99. }
  100. if (nr_cpus < NR_TEST_CPUS)
  101. return SCX_TEST_SKIP;
  102. skel->rodata->test_cpu_a = test_cpus[0];
  103. skel->rodata->test_cpu_b = test_cpus[1];
  104. skel->rodata->test_cpu_c = test_cpus[2];
  105. if (cyclic_kick_wait__load(skel)) {
  106. SCX_ERR("Failed to load skel");
  107. return SCX_TEST_FAIL;
  108. }
  109. link = bpf_map__attach_struct_ops(skel->maps.cyclic_kick_wait_ops);
  110. if (!link) {
  111. SCX_ERR("Failed to attach scheduler");
  112. return SCX_TEST_FAIL;
  113. }
  114. for (i = 0; i < NR_WORKERS; i++)
  115. workers[i].cpu = test_cpus[i / WORKERS_PER_CPU];
  116. for (i = 0; i < NR_WORKERS; i++) {
  117. ret = pthread_create(&workers[i].tid, NULL, worker_fn, &workers[i]);
  118. if (ret) {
  119. SCX_ERR("Failed to create worker thread %d (%d)", i, ret);
  120. status = SCX_TEST_FAIL;
  121. goto out;
  122. }
  123. workers[i].started = true;
  124. }
  125. sleep(5);
  126. if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) {
  127. SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)",
  128. (unsigned long long)skel->data->uei.kind,
  129. (long long)skel->data->uei.exit_code);
  130. status = SCX_TEST_FAIL;
  131. }
  132. out:
  133. for (i = 0; i < NR_WORKERS; i++)
  134. workers[i].stop = true;
  135. for (i = 0; i < NR_WORKERS; i++) {
  136. ret = join_worker(&workers[i]);
  137. if (ret && status == SCX_TEST_PASS) {
  138. SCX_ERR("Failed to join worker thread %d (%d)", i, ret);
  139. status = SCX_TEST_FAIL;
  140. }
  141. }
  142. if (link)
  143. bpf_link__destroy(link);
  144. return status;
  145. }
  146. static void cleanup(void *ctx)
  147. {
  148. struct cyclic_kick_wait *skel = ctx;
  149. cyclic_kick_wait__destroy(skel);
  150. }
  151. struct scx_test cyclic_kick_wait = {
  152. .name = "cyclic_kick_wait",
  153. .description = "Verify SCX_KICK_WAIT forward progress under a 3-CPU wait cycle",
  154. .setup = setup,
  155. .run = run,
  156. .cleanup = cleanup,
  157. };
  158. REGISTER_SCX_TEST(&cyclic_kick_wait)