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 <signal.h> 7 #include <stdbool.h> 8 #include <stdio.h> 9 #include <string.h> 10 #include <syscall.h> 11 #include <unistd.h> 12 13 #include <linux/prctl.h> 14 #include <sys/prctl.h> 15 #include <sys/time.h> 16 17 #include "rseq.h" 18 19 #include "../kselftest_harness.h" 20 21 #ifndef __NR_rseq_slice_yield 22 # define __NR_rseq_slice_yield 471 23 #endif 24 25 #define BITS_PER_INT 32 26 #define BITS_PER_BYTE 8 27 28 #ifndef PR_RSEQ_SLICE_EXTENSION 29 # define PR_RSEQ_SLICE_EXTENSION 79 30 # define PR_RSEQ_SLICE_EXTENSION_GET 1 31 # define PR_RSEQ_SLICE_EXTENSION_SET 2 32 # define PR_RSEQ_SLICE_EXT_ENABLE 0x01 33 #endif 34 35 #ifndef RSEQ_SLICE_EXT_REQUEST_BIT 36 # define RSEQ_SLICE_EXT_REQUEST_BIT 0 37 # define RSEQ_SLICE_EXT_GRANTED_BIT 1 38 #endif 39 40 #ifndef asm_inline 41 # define asm_inline asm __inline 42 #endif 43 44 #define NSEC_PER_SEC 1000000000L 45 #define NSEC_PER_USEC 1000L 46 47 struct noise_params { 48 int64_t noise_nsecs; 49 int64_t sleep_nsecs; 50 int64_t run; 51 }; 52 53 FIXTURE(slice_ext) 54 { 55 pthread_t noise_thread; 56 struct noise_params noise_params; 57 }; 58 59 FIXTURE_VARIANT(slice_ext) 60 { 61 int64_t total_nsecs; 62 int64_t slice_nsecs; 63 int64_t noise_nsecs; 64 int64_t sleep_nsecs; 65 bool no_yield; 66 }; 67 68 FIXTURE_VARIANT_ADD(slice_ext, n2_2_50) 69 { 70 .total_nsecs = 5LL * NSEC_PER_SEC, 71 .slice_nsecs = 2LL * NSEC_PER_USEC, 72 .noise_nsecs = 2LL * NSEC_PER_USEC, 73 .sleep_nsecs = 50LL * NSEC_PER_USEC, 74 }; 75 76 FIXTURE_VARIANT_ADD(slice_ext, n50_2_50) 77 { 78 .total_nsecs = 5LL * NSEC_PER_SEC, 79 .slice_nsecs = 50LL * NSEC_PER_USEC, 80 .noise_nsecs = 2LL * NSEC_PER_USEC, 81 .sleep_nsecs = 50LL * NSEC_PER_USEC, 82 }; 83 84 FIXTURE_VARIANT_ADD(slice_ext, n2_2_50_no_yield) 85 { 86 .total_nsecs = 5LL * NSEC_PER_SEC, 87 .slice_nsecs = 2LL * NSEC_PER_USEC, 88 .noise_nsecs = 2LL * NSEC_PER_USEC, 89 .sleep_nsecs = 50LL * NSEC_PER_USEC, 90 .no_yield = true, 91 }; 92 93 94 static inline bool elapsed(struct timespec *start, struct timespec *now, 95 int64_t span) 96 { 97 int64_t delta = now->tv_sec - start->tv_sec; 98 99 delta *= NSEC_PER_SEC; 100 delta += now->tv_nsec - start->tv_nsec; 101 return delta >= span; 102 } 103 104 static void *noise_thread(void *arg) 105 { 106 struct noise_params *p = arg; 107 108 while (RSEQ_READ_ONCE(p->run)) { 109 struct timespec ts_start, ts_now; 110 111 clock_gettime(CLOCK_MONOTONIC, &ts_start); 112 do { 113 clock_gettime(CLOCK_MONOTONIC, &ts_now); 114 } while (!elapsed(&ts_start, &ts_now, p->noise_nsecs)); 115 116 ts_start.tv_sec = 0; 117 ts_start.tv_nsec = p->sleep_nsecs; 118 clock_nanosleep(CLOCK_MONOTONIC, 0, &ts_start, NULL); 119 } 120 return NULL; 121 } 122 123 FIXTURE_SETUP(slice_ext) 124 { 125 cpu_set_t affinity; 126 127 ASSERT_EQ(sched_getaffinity(0, sizeof(affinity), &affinity), 0); 128 129 /* Pin it on a single CPU. Avoid CPU 0 */ 130 for (int i = 1; i < CPU_SETSIZE; i++) { 131 if (!CPU_ISSET(i, &affinity)) 132 continue; 133 134 CPU_ZERO(&affinity); 135 CPU_SET(i, &affinity); 136 ASSERT_EQ(sched_setaffinity(0, sizeof(affinity), &affinity), 0); 137 break; 138 } 139 140 ASSERT_EQ(rseq_register_current_thread(), 0); 141 142 ASSERT_EQ(prctl(PR_RSEQ_SLICE_EXTENSION, PR_RSEQ_SLICE_EXTENSION_SET, 143 PR_RSEQ_SLICE_EXT_ENABLE, 0, 0), 0); 144 145 self->noise_params.noise_nsecs = variant->noise_nsecs; 146 self->noise_params.sleep_nsecs = variant->sleep_nsecs; 147 self->noise_params.run = 1; 148 149 ASSERT_EQ(pthread_create(&self->noise_thread, NULL, noise_thread, &self->noise_params), 0); 150 } 151 152 FIXTURE_TEARDOWN(slice_ext) 153 { 154 self->noise_params.run = 0; 155 pthread_join(self->noise_thread, NULL); 156 } 157 158 TEST_F(slice_ext, slice_test) 159 { 160 unsigned long success = 0, yielded = 0, scheduled = 0, raced = 0; 161 unsigned long total = 0, aborted = 0; 162 struct rseq_abi *rs = rseq_get_abi(); 163 struct timespec ts_start, ts_now; 164 165 ASSERT_NE(rs, NULL); 166 167 clock_gettime(CLOCK_MONOTONIC, &ts_start); 168 do { 169 struct timespec ts_cs; 170 bool req = false; 171 172 clock_gettime(CLOCK_MONOTONIC, &ts_cs); 173 174 total++; 175 RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 1); 176 do { 177 clock_gettime(CLOCK_MONOTONIC, &ts_now); 178 } while (!elapsed(&ts_cs, &ts_now, variant->slice_nsecs)); 179 180 /* 181 * request can be cleared unconditionally, but for making 182 * the stats work this is actually checking it first 183 */ 184 if (RSEQ_READ_ONCE(rs->slice_ctrl.request)) { 185 RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 0); 186 /* Race between check and clear! */ 187 req = true; 188 success++; 189 } 190 191 if (RSEQ_READ_ONCE(rs->slice_ctrl.granted)) { 192 /* The above raced against a late grant */ 193 if (req) 194 success--; 195 if (variant->no_yield) { 196 syscall(__NR_getpid); 197 aborted++; 198 } else { 199 yielded++; 200 if (!syscall(__NR_rseq_slice_yield)) 201 raced++; 202 } 203 } else { 204 if (!req) 205 scheduled++; 206 } 207 208 clock_gettime(CLOCK_MONOTONIC, &ts_now); 209 } while (!elapsed(&ts_start, &ts_now, variant->total_nsecs)); 210 211 printf("# Total %12ld\n", total); 212 printf("# Success %12ld\n", success); 213 printf("# Yielded %12ld\n", yielded); 214 printf("# Aborted %12ld\n", aborted); 215 printf("# Scheduled %12ld\n", scheduled); 216 printf("# Raced %12ld\n", raced); 217 } 218 219 TEST_HARNESS_MAIN 220