Commit f166b111 authored by Peter Oskolkov's avatar Peter Oskolkov Committed by Peter Zijlstra
Browse files

rseq/selftests: Test MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ



Based on Google-internal RSEQ work done by Paul Turner and Andrew
Hunter.

This patch adds a selftest for MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ.
The test quite often fails without the previous patch in this
patchset, but consistently passes with it.

Signed-off-by: default avatarPeter Oskolkov <posk@google.com>
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: default avatarMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Link: https://lkml.kernel.org/r/20200923233618.2572849-3-posk@google.com
parent ea366dd7
Loading
Loading
Loading
Loading
+222 −1
Original line number Diff line number Diff line
// SPDX-License-Identifier: LGPL-2.1
#define _GNU_SOURCE
#include <assert.h>
#include <linux/membarrier.h>
#include <pthread.h>
#include <sched.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -1131,6 +1133,220 @@ static int set_signal_handler(void)
	return ret;
}

struct test_membarrier_thread_args {
	int stop;
	intptr_t percpu_list_ptr;
};

/* Worker threads modify data in their "active" percpu lists. */
void *test_membarrier_worker_thread(void *arg)
{
	struct test_membarrier_thread_args *args =
		(struct test_membarrier_thread_args *)arg;
	const int iters = opt_reps;
	int i;

	if (rseq_register_current_thread()) {
		fprintf(stderr, "Error: rseq_register_current_thread(...) failed(%d): %s\n",
			errno, strerror(errno));
		abort();
	}

	/* Wait for initialization. */
	while (!atomic_load(&args->percpu_list_ptr)) {}

	for (i = 0; i < iters; ++i) {
		int ret;

		do {
			int cpu = rseq_cpu_start();

			ret = rseq_offset_deref_addv(&args->percpu_list_ptr,
				sizeof(struct percpu_list_entry) * cpu, 1, cpu);
		} while (rseq_unlikely(ret));
	}

	if (rseq_unregister_current_thread()) {
		fprintf(stderr, "Error: rseq_unregister_current_thread(...) failed(%d): %s\n",
			errno, strerror(errno));
		abort();
	}
	return NULL;
}

void test_membarrier_init_percpu_list(struct percpu_list *list)
{
	int i;

	memset(list, 0, sizeof(*list));
	for (i = 0; i < CPU_SETSIZE; i++) {
		struct percpu_list_node *node;

		node = malloc(sizeof(*node));
		assert(node);
		node->data = 0;
		node->next = NULL;
		list->c[i].head = node;
	}
}

void test_membarrier_free_percpu_list(struct percpu_list *list)
{
	int i;

	for (i = 0; i < CPU_SETSIZE; i++)
		free(list->c[i].head);
}

static int sys_membarrier(int cmd, int flags, int cpu_id)
{
	return syscall(__NR_membarrier, cmd, flags, cpu_id);
}

/*
 * The manager thread swaps per-cpu lists that worker threads see,
 * and validates that there are no unexpected modifications.
 */
void *test_membarrier_manager_thread(void *arg)
{
	struct test_membarrier_thread_args *args =
		(struct test_membarrier_thread_args *)arg;
	struct percpu_list list_a, list_b;
	intptr_t expect_a = 0, expect_b = 0;
	int cpu_a = 0, cpu_b = 0;

	if (rseq_register_current_thread()) {
		fprintf(stderr, "Error: rseq_register_current_thread(...) failed(%d): %s\n",
			errno, strerror(errno));
		abort();
	}

	/* Init lists. */
	test_membarrier_init_percpu_list(&list_a);
	test_membarrier_init_percpu_list(&list_b);

	atomic_store(&args->percpu_list_ptr, (intptr_t)&list_a);

	while (!atomic_load(&args->stop)) {
		/* list_a is "active". */
		cpu_a = rand() % CPU_SETSIZE;
		/*
		 * As list_b is "inactive", we should never see changes
		 * to list_b.
		 */
		if (expect_b != atomic_load(&list_b.c[cpu_b].head->data)) {
			fprintf(stderr, "Membarrier test failed\n");
			abort();
		}

		/* Make list_b "active". */
		atomic_store(&args->percpu_list_ptr, (intptr_t)&list_b);
		if (sys_membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ,
					MEMBARRIER_CMD_FLAG_CPU, cpu_a) &&
				errno != ENXIO /* missing CPU */) {
			perror("sys_membarrier");
			abort();
		}
		/*
		 * Cpu A should now only modify list_b, so the values
		 * in list_a should be stable.
		 */
		expect_a = atomic_load(&list_a.c[cpu_a].head->data);

		cpu_b = rand() % CPU_SETSIZE;
		/*
		 * As list_a is "inactive", we should never see changes
		 * to list_a.
		 */
		if (expect_a != atomic_load(&list_a.c[cpu_a].head->data)) {
			fprintf(stderr, "Membarrier test failed\n");
			abort();
		}

		/* Make list_a "active". */
		atomic_store(&args->percpu_list_ptr, (intptr_t)&list_a);
		if (sys_membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ,
					MEMBARRIER_CMD_FLAG_CPU, cpu_b) &&
				errno != ENXIO /* missing CPU*/) {
			perror("sys_membarrier");
			abort();
		}
		/* Remember a value from list_b. */
		expect_b = atomic_load(&list_b.c[cpu_b].head->data);
	}

	test_membarrier_free_percpu_list(&list_a);
	test_membarrier_free_percpu_list(&list_b);

	if (rseq_unregister_current_thread()) {
		fprintf(stderr, "Error: rseq_unregister_current_thread(...) failed(%d): %s\n",
			errno, strerror(errno));
		abort();
	}
	return NULL;
}

/* Test MEMBARRIER_CMD_PRIVATE_RESTART_RSEQ_ON_CPU membarrier command. */
#ifdef RSEQ_ARCH_HAS_OFFSET_DEREF_ADDV
void test_membarrier(void)
{
	const int num_threads = opt_threads;
	struct test_membarrier_thread_args thread_args;
	pthread_t worker_threads[num_threads];
	pthread_t manager_thread;
	int i, ret;

	if (sys_membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ, 0, 0)) {
		perror("sys_membarrier");
		abort();
	}

	thread_args.stop = 0;
	thread_args.percpu_list_ptr = 0;
	ret = pthread_create(&manager_thread, NULL,
			test_membarrier_manager_thread, &thread_args);
	if (ret) {
		errno = ret;
		perror("pthread_create");
		abort();
	}

	for (i = 0; i < num_threads; i++) {
		ret = pthread_create(&worker_threads[i], NULL,
				test_membarrier_worker_thread, &thread_args);
		if (ret) {
			errno = ret;
			perror("pthread_create");
			abort();
		}
	}


	for (i = 0; i < num_threads; i++) {
		ret = pthread_join(worker_threads[i], NULL);
		if (ret) {
			errno = ret;
			perror("pthread_join");
			abort();
		}
	}

	atomic_store(&thread_args.stop, 1);
	ret = pthread_join(manager_thread, NULL);
	if (ret) {
		errno = ret;
		perror("pthread_join");
		abort();
	}
}
#else /* RSEQ_ARCH_HAS_OFFSET_DEREF_ADDV */
void test_membarrier(void)
{
	fprintf(stderr, "rseq_offset_deref_addv is not implemented on this architecture. "
			"Skipping membarrier test.\n");
}
#endif

static void show_usage(int argc, char **argv)
{
	printf("Usage : %s <OPTIONS>\n",
@@ -1153,7 +1369,7 @@ static void show_usage(int argc, char **argv)
	printf("	[-r N] Number of repetitions per thread (default 5000)\n");
	printf("	[-d] Disable rseq system call (no initialization)\n");
	printf("	[-D M] Disable rseq for each M threads\n");
	printf("	[-T test] Choose test: (s)pinlock, (l)ist, (b)uffer, (m)emcpy, (i)ncrement\n");
	printf("	[-T test] Choose test: (s)pinlock, (l)ist, (b)uffer, (m)emcpy, (i)ncrement, membarrie(r)\n");
	printf("	[-M] Push into buffer and memcpy buffer with memory barriers.\n");
	printf("	[-v] Verbose output.\n");
	printf("	[-h] Show this help.\n");
@@ -1268,6 +1484,7 @@ int main(int argc, char **argv)
			case 'i':
			case 'b':
			case 'm':
			case 'r':
				break;
			default:
				show_usage(argc, argv);
@@ -1320,6 +1537,10 @@ int main(int argc, char **argv)
		printf_verbose("counter increment\n");
		test_percpu_inc();
		break;
	case 'r':
		printf_verbose("membarrier\n");
		test_membarrier();
		break;
	}
	if (!opt_disable_rseq && rseq_unregister_current_thread())
		abort();
+2 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ TEST_LIST=(
	"-T m"
	"-T m -M"
	"-T i"
	"-T r"
)

TEST_NAME=(
@@ -25,6 +26,7 @@ TEST_NAME=(
	"memcpy"
	"memcpy with barrier"
	"increment"
	"membarrier"
)
IFS="$OLDIFS"