221 lines · 4.9 KB · C
RawDownload
// 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