// 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
// 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