| // SPDX-License-Identifier: LGPL-2.1 | |
| #define _GNU_SOURCE | |
| #include <assert.h> | |
| #include <pthread.h> | |
| #include <sched.h> | |
| #include <signal.h> | |
| #include <stdbool.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <syscall.h> | |
| #include <unistd.h> | |
| #include <linux/prctl.h> | |
| #include <sys/prctl.h> | |
| #include <sys/time.h> | |
| #include "rseq.h" | |
| #include "../kselftest_harness.h" | |
| #ifndef __NR_rseq_slice_yield | |
| # define __NR_rseq_slice_yield 471 | |
| #endif | |
| #define BITS_PER_INT 32 | |
| #define BITS_PER_BYTE 8 | |
| #ifndef PR_RSEQ_SLICE_EXTENSION | |
| # define PR_RSEQ_SLICE_EXTENSION 79 | |
| # define PR_RSEQ_SLICE_EXTENSION_GET 1 | |
| # define PR_RSEQ_SLICE_EXTENSION_SET 2 | |
| # define PR_RSEQ_SLICE_EXT_ENABLE 0x01 | |
| #endif | |
| #ifndef RSEQ_SLICE_EXT_REQUEST_BIT | |
| # define RSEQ_SLICE_EXT_REQUEST_BIT 0 | |
| # define RSEQ_SLICE_EXT_GRANTED_BIT 1 | |
| #endif | |
| #ifndef asm_inline | |
| # define asm_inline asm __inline | |
| #endif | |
| #define NSEC_PER_SEC 1000000000L | |
| #define NSEC_PER_USEC 1000L | |
| struct noise_params { | |
| int64_t noise_nsecs; | |
| int64_t sleep_nsecs; | |
| int64_t run; | |
| }; | |
| FIXTURE(slice_ext) | |
| { | |
| pthread_t noise_thread; | |
| struct noise_params noise_params; | |
| }; | |
| FIXTURE_VARIANT(slice_ext) | |
| { | |
| int64_t total_nsecs; | |
| int64_t slice_nsecs; | |
| int64_t noise_nsecs; | |
| int64_t sleep_nsecs; | |
| bool no_yield; | |
| }; | |
| FIXTURE_VARIANT_ADD(slice_ext, n2_2_50) | |
| { | |
| .total_nsecs = 5LL * NSEC_PER_SEC, | |
| .slice_nsecs = 2LL * NSEC_PER_USEC, | |
| .noise_nsecs = 2LL * NSEC_PER_USEC, | |
| .sleep_nsecs = 50LL * NSEC_PER_USEC, | |
| }; | |
| FIXTURE_VARIANT_ADD(slice_ext, n50_2_50) | |
| { | |
| .total_nsecs = 5LL * NSEC_PER_SEC, | |
| .slice_nsecs = 50LL * NSEC_PER_USEC, | |
| .noise_nsecs = 2LL * NSEC_PER_USEC, | |
| .sleep_nsecs = 50LL * NSEC_PER_USEC, | |
| }; | |
| FIXTURE_VARIANT_ADD(slice_ext, n2_2_50_no_yield) | |
| { | |
| .total_nsecs = 5LL * NSEC_PER_SEC, | |
| .slice_nsecs = 2LL * NSEC_PER_USEC, | |
| .noise_nsecs = 2LL * NSEC_PER_USEC, | |
| .sleep_nsecs = 50LL * NSEC_PER_USEC, | |
| .no_yield = true, | |
| }; | |
| static inline bool elapsed(struct timespec *start, struct timespec *now, | |
| int64_t span) | |
| { | |
| int64_t delta = now->tv_sec - start->tv_sec; | |
| delta *= NSEC_PER_SEC; | |
| delta += now->tv_nsec - start->tv_nsec; | |
| return delta >= span; | |
| } | |
| static void *noise_thread(void *arg) | |
| { | |
| struct noise_params *p = arg; | |
| while (RSEQ_READ_ONCE(p->run)) { | |
| struct timespec ts_start, ts_now; | |
| clock_gettime(CLOCK_MONOTONIC, &ts_start); | |
| do { | |
| clock_gettime(CLOCK_MONOTONIC, &ts_now); | |
| } while (!elapsed(&ts_start, &ts_now, p->noise_nsecs)); | |
| ts_start.tv_sec = 0; | |
| ts_start.tv_nsec = p->sleep_nsecs; | |
| clock_nanosleep(CLOCK_MONOTONIC, 0, &ts_start, NULL); | |
| } | |
| return NULL; | |
| } | |
| FIXTURE_SETUP(slice_ext) | |
| { | |
| cpu_set_t affinity; | |
| if (__rseq_register_current_thread(true, false)) | |
| SKIP(return, "RSEQ not supported\n"); | |
| if (prctl(PR_RSEQ_SLICE_EXTENSION, PR_RSEQ_SLICE_EXTENSION_SET, | |
| PR_RSEQ_SLICE_EXT_ENABLE, 0, 0)) | |
| SKIP(return, "Time slice extension not supported\n"); | |
| ASSERT_EQ(sched_getaffinity(0, sizeof(affinity), &affinity), 0); | |
| /* Pin it on a single CPU. Avoid CPU 0 */ | |
| for (int i = 1; i < CPU_SETSIZE; i++) { | |
| if (!CPU_ISSET(i, &affinity)) | |
| continue; | |
| CPU_ZERO(&affinity); | |
| CPU_SET(i, &affinity); | |
| ASSERT_EQ(sched_setaffinity(0, sizeof(affinity), &affinity), 0); | |
| break; | |
| } | |
| self->noise_params.noise_nsecs = variant->noise_nsecs; | |
| self->noise_params.sleep_nsecs = variant->sleep_nsecs; | |
| self->noise_params.run = 1; | |
| ASSERT_EQ(pthread_create(&self->noise_thread, NULL, noise_thread, &self->noise_params), 0); | |
| } | |
| FIXTURE_TEARDOWN(slice_ext) | |
| { | |
| self->noise_params.run = 0; | |
| pthread_join(self->noise_thread, NULL); | |
| } | |
| TEST_F(slice_ext, slice_test) | |
| { | |
| unsigned long success = 0, yielded = 0, scheduled = 0, raced = 0; | |
| unsigned long total = 0, aborted = 0; | |
| struct rseq_abi *rs = rseq_get_abi(); | |
| struct timespec ts_start, ts_now; | |
| ASSERT_NE(rs, NULL); | |
| clock_gettime(CLOCK_MONOTONIC, &ts_start); | |
| do { | |
| struct timespec ts_cs; | |
| bool req = false; | |
| clock_gettime(CLOCK_MONOTONIC, &ts_cs); | |
| total++; | |
| RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 1); | |
| do { | |
| clock_gettime(CLOCK_MONOTONIC, &ts_now); | |
| } while (!elapsed(&ts_cs, &ts_now, variant->slice_nsecs)); | |
| /* | |
| * request can be cleared unconditionally, but for making | |
| * the stats work this is actually checking it first | |
| */ | |
| if (RSEQ_READ_ONCE(rs->slice_ctrl.request)) { | |
| RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 0); | |
| /* Race between check and clear! */ | |
| req = true; | |
| success++; | |
| } | |
| if (RSEQ_READ_ONCE(rs->slice_ctrl.granted)) { | |
| /* The above raced against a late grant */ | |
| if (req) | |
| success--; | |
| if (variant->no_yield) { | |
| syscall(__NR_getpid); | |
| aborted++; | |
| } else { | |
| yielded++; | |
| if (!syscall(__NR_rseq_slice_yield)) | |
| raced++; | |
| } | |
| } else { | |
| if (!req) | |
| scheduled++; | |
| } | |
| clock_gettime(CLOCK_MONOTONIC, &ts_now); | |
| } while (!elapsed(&ts_start, &ts_now, variant->total_nsecs)); | |
| printf("# Total %12ld\n", total); | |
| printf("# Success %12ld\n", success); | |
| printf("# Yielded %12ld\n", yielded); | |
| printf("# Aborted %12ld\n", aborted); | |
| printf("# Scheduled %12ld\n", scheduled); | |
| printf("# Raced %12ld\n", raced); | |
| } | |
| TEST_HARNESS_MAIN |