8a5956ad63
32-bit PPC cannot do atomic operations on long long. Inside the loops, we are already using local counters that are summed at the end of the run---with some exceptions (rcu_stress_count for rcutorture, n_nodes for test-rcu-list): fix them to use the same technique. For test-rcu-list, remove the mostly unused member "val" from the list. Then, use a mutex to protect the global counts. Performance does not matter there because every thread will only enter the critical section once. Remaining uses of atomic instructions are for ints or pointers. Reported-by: Andreas Faerber <afaerber@suse.de> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
313 lines
7.8 KiB
C
313 lines
7.8 KiB
C
/*
|
|
* rcuq_test.c
|
|
*
|
|
* usage: rcuq_test <readers> <duration>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* Copyright (c) 2013 Mike D. Day, IBM Corporation.
|
|
*/
|
|
|
|
#include <glib.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "qemu/atomic.h"
|
|
#include "qemu/rcu.h"
|
|
#include "qemu/compiler.h"
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/thread.h"
|
|
#include "qemu/rcu_queue.h"
|
|
|
|
/*
|
|
* Test variables.
|
|
*/
|
|
|
|
static QemuMutex counts_mutex;
|
|
static long long n_reads = 0LL;
|
|
static long long n_updates = 0LL;
|
|
static long long n_reclaims = 0LL;
|
|
static long long n_nodes_removed = 0LL;
|
|
static long long n_nodes = 0LL;
|
|
static int g_test_in_charge = 0;
|
|
|
|
static int nthreadsrunning;
|
|
|
|
#define GOFLAG_INIT 0
|
|
#define GOFLAG_RUN 1
|
|
#define GOFLAG_STOP 2
|
|
|
|
static volatile int goflag = GOFLAG_INIT;
|
|
|
|
#define RCU_READ_RUN 1000
|
|
#define RCU_UPDATE_RUN 10
|
|
#define NR_THREADS 100
|
|
#define RCU_Q_LEN 100
|
|
|
|
static QemuThread threads[NR_THREADS];
|
|
static struct rcu_reader_data *data[NR_THREADS];
|
|
static int n_threads;
|
|
|
|
static int select_random_el(int max)
|
|
{
|
|
return (rand() % max);
|
|
}
|
|
|
|
|
|
static void create_thread(void *(*func)(void *))
|
|
{
|
|
if (n_threads >= NR_THREADS) {
|
|
fprintf(stderr, "Thread limit of %d exceeded!\n", NR_THREADS);
|
|
exit(-1);
|
|
}
|
|
qemu_thread_create(&threads[n_threads], "test", func, &data[n_threads],
|
|
QEMU_THREAD_JOINABLE);
|
|
n_threads++;
|
|
}
|
|
|
|
static void wait_all_threads(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n_threads; i++) {
|
|
qemu_thread_join(&threads[i]);
|
|
}
|
|
n_threads = 0;
|
|
}
|
|
|
|
|
|
struct list_element {
|
|
QLIST_ENTRY(list_element) entry;
|
|
struct rcu_head rcu;
|
|
};
|
|
|
|
static void reclaim_list_el(struct rcu_head *prcu)
|
|
{
|
|
struct list_element *el = container_of(prcu, struct list_element, rcu);
|
|
g_free(el);
|
|
/* Accessed only from call_rcu thread. */
|
|
n_reclaims++;
|
|
}
|
|
|
|
static QLIST_HEAD(q_list_head, list_element) Q_list_head;
|
|
|
|
static void *rcu_q_reader(void *arg)
|
|
{
|
|
long long n_reads_local = 0;
|
|
struct list_element *el;
|
|
|
|
*(struct rcu_reader_data **)arg = &rcu_reader;
|
|
atomic_inc(&nthreadsrunning);
|
|
while (goflag == GOFLAG_INIT) {
|
|
g_usleep(1000);
|
|
}
|
|
|
|
while (goflag == GOFLAG_RUN) {
|
|
rcu_read_lock();
|
|
QLIST_FOREACH_RCU(el, &Q_list_head, entry) {
|
|
n_reads_local++;
|
|
if (goflag == GOFLAG_STOP) {
|
|
break;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
g_usleep(100);
|
|
}
|
|
qemu_mutex_lock(&counts_mutex);
|
|
n_reads += n_reads_local;
|
|
qemu_mutex_unlock(&counts_mutex);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void *rcu_q_updater(void *arg)
|
|
{
|
|
int j, target_el;
|
|
long long n_nodes_local = 0;
|
|
long long n_updates_local = 0;
|
|
long long n_removed_local = 0;
|
|
struct list_element *el, *prev_el;
|
|
|
|
*(struct rcu_reader_data **)arg = &rcu_reader;
|
|
atomic_inc(&nthreadsrunning);
|
|
while (goflag == GOFLAG_INIT) {
|
|
g_usleep(1000);
|
|
}
|
|
|
|
while (goflag == GOFLAG_RUN) {
|
|
target_el = select_random_el(RCU_Q_LEN);
|
|
j = 0;
|
|
/* FOREACH_RCU could work here but let's use both macros */
|
|
QLIST_FOREACH_SAFE_RCU(prev_el, &Q_list_head, entry, el) {
|
|
j++;
|
|
if (target_el == j) {
|
|
QLIST_REMOVE_RCU(prev_el, entry);
|
|
/* may be more than one updater in the future */
|
|
call_rcu1(&prev_el->rcu, reclaim_list_el);
|
|
n_removed_local++;
|
|
break;
|
|
}
|
|
}
|
|
if (goflag == GOFLAG_STOP) {
|
|
break;
|
|
}
|
|
target_el = select_random_el(RCU_Q_LEN);
|
|
j = 0;
|
|
QLIST_FOREACH_RCU(el, &Q_list_head, entry) {
|
|
j++;
|
|
if (target_el == j) {
|
|
prev_el = g_new(struct list_element, 1);
|
|
n_nodes += n_nodes_local;
|
|
QLIST_INSERT_BEFORE_RCU(el, prev_el, entry);
|
|
break;
|
|
}
|
|
}
|
|
|
|
n_updates_local += 2;
|
|
synchronize_rcu();
|
|
}
|
|
synchronize_rcu();
|
|
qemu_mutex_lock(&counts_mutex);
|
|
n_nodes += n_nodes_local;
|
|
n_updates += n_updates_local;
|
|
n_nodes_removed += n_removed_local;
|
|
qemu_mutex_unlock(&counts_mutex);
|
|
return NULL;
|
|
}
|
|
|
|
static void rcu_qtest_init(void)
|
|
{
|
|
struct list_element *new_el;
|
|
int i;
|
|
nthreadsrunning = 0;
|
|
srand(time(0));
|
|
for (i = 0; i < RCU_Q_LEN; i++) {
|
|
new_el = g_new(struct list_element, 1);
|
|
QLIST_INSERT_HEAD_RCU(&Q_list_head, new_el, entry);
|
|
}
|
|
qemu_mutex_lock(&counts_mutex);
|
|
n_nodes += RCU_Q_LEN;
|
|
qemu_mutex_unlock(&counts_mutex);
|
|
}
|
|
|
|
static void rcu_qtest_run(int duration, int nreaders)
|
|
{
|
|
int nthreads = nreaders + 1;
|
|
while (atomic_read(&nthreadsrunning) < nthreads) {
|
|
g_usleep(1000);
|
|
}
|
|
|
|
goflag = GOFLAG_RUN;
|
|
sleep(duration);
|
|
goflag = GOFLAG_STOP;
|
|
wait_all_threads();
|
|
}
|
|
|
|
|
|
static void rcu_qtest(const char *test, int duration, int nreaders)
|
|
{
|
|
int i;
|
|
long long n_removed_local = 0;
|
|
|
|
struct list_element *el, *prev_el;
|
|
|
|
rcu_qtest_init();
|
|
for (i = 0; i < nreaders; i++) {
|
|
create_thread(rcu_q_reader);
|
|
}
|
|
create_thread(rcu_q_updater);
|
|
rcu_qtest_run(duration, nreaders);
|
|
|
|
QLIST_FOREACH_SAFE_RCU(prev_el, &Q_list_head, entry, el) {
|
|
QLIST_REMOVE_RCU(prev_el, entry);
|
|
call_rcu1(&prev_el->rcu, reclaim_list_el);
|
|
n_removed_local++;
|
|
}
|
|
qemu_mutex_lock(&counts_mutex);
|
|
n_nodes_removed += n_removed_local;
|
|
qemu_mutex_unlock(&counts_mutex);
|
|
synchronize_rcu();
|
|
while (n_nodes_removed > n_reclaims) {
|
|
g_usleep(100);
|
|
synchronize_rcu();
|
|
}
|
|
if (g_test_in_charge) {
|
|
g_assert_cmpint(n_nodes_removed, ==, n_reclaims);
|
|
} else {
|
|
printf("%s: %d readers; 1 updater; nodes read: " \
|
|
"%lld, nodes removed: %lld; nodes reclaimed: %lld\n",
|
|
test, nthreadsrunning - 1, n_reads, n_nodes_removed, n_reclaims);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
static void usage(int argc, char *argv[])
|
|
{
|
|
fprintf(stderr, "Usage: %s duration nreaders\n", argv[0]);
|
|
exit(-1);
|
|
}
|
|
|
|
static int gtest_seconds;
|
|
|
|
static void gtest_rcuq_one(void)
|
|
{
|
|
rcu_qtest("rcuqtest", gtest_seconds / 4, 1);
|
|
}
|
|
|
|
static void gtest_rcuq_few(void)
|
|
{
|
|
rcu_qtest("rcuqtest", gtest_seconds / 4, 5);
|
|
}
|
|
|
|
static void gtest_rcuq_many(void)
|
|
{
|
|
rcu_qtest("rcuqtest", gtest_seconds / 2, 20);
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int duration = 0, readers = 0;
|
|
|
|
qemu_mutex_init(&counts_mutex);
|
|
if (argc >= 2) {
|
|
if (argv[1][0] == '-') {
|
|
g_test_init(&argc, &argv, NULL);
|
|
if (g_test_quick()) {
|
|
gtest_seconds = 4;
|
|
} else {
|
|
gtest_seconds = 20;
|
|
}
|
|
g_test_add_func("/rcu/qlist/single-threaded", gtest_rcuq_one);
|
|
g_test_add_func("/rcu/qlist/short-few", gtest_rcuq_few);
|
|
g_test_add_func("/rcu/qlist/long-many", gtest_rcuq_many);
|
|
g_test_in_charge = 1;
|
|
return g_test_run();
|
|
}
|
|
duration = strtoul(argv[1], NULL, 0);
|
|
}
|
|
if (argc >= 3) {
|
|
readers = strtoul(argv[2], NULL, 0);
|
|
}
|
|
if (duration && readers) {
|
|
rcu_qtest(argv[0], duration, readers);
|
|
return 0;
|
|
}
|
|
|
|
usage(argc, argv);
|
|
return -1;
|
|
}
|