// SPDX-License-Identifier: GPL-2.0+ /* * Module-based torture test facility for locking * * Copyright (C) IBM Corporation, 2014 * * Authors: Paul E. McKenney <paulmck@linux.ibm.com> * Davidlohr Bueso <dave@stgolabs.net> * Based on kernel/rcu/torture.c.
*/
static cpumask_var_t bind_readers; // Bind the readers to the specified set of CPUs. static cpumask_var_t bind_writers; // Bind the writers to the specified set of CPUs.
// Parse a cpumask kernel parameter. If there are more users later on, // this might need to got to a more central location. staticint param_set_cpumask(constchar *val, conststruct kernel_param *kp)
{
cpumask_var_t *cm_bind = kp->arg; int ret; char *s;
if (!alloc_cpumask_var(cm_bind, GFP_KERNEL)) {
s = "Out of memory";
ret = -ENOMEM; goto out_err;
}
ret = cpulist_parse(val, *cm_bind); if (!ret) return ret;
s = "Bad CPU range";
out_err:
pr_warn("%s: %s, all CPUs set\n", kp->name, s);
cpumask_setall(*cm_bind); return ret;
}
staticint torture_lock_busted_write_lock(int tid __maybe_unused)
{ return 0; /* BUGGY, do not use in real life!!! */
}
staticvoid torture_lock_busted_write_delay(struct torture_random_state *trsp)
{ /* We want a long delay occasionally to force massive contention. */ if (long_hold && !(torture_random(trsp) % (cxt.nrealwriters_stress * 2000 * long_hold)))
mdelay(long_hold); if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))
torture_preempt_schedule(); /* Allow test to be preempted. */
}
staticvoid torture_lock_busted_write_unlock(int tid __maybe_unused)
{ /* BUGGY, do not use in real life!!! */
}
if (!rt_task(current)) { /* * Boost priority once every rt_boost_factor operations. When * the task tries to take the lock, the rtmutex it will account * for the new priority, and do any corresponding pi-dance.
*/ if (trsp && !(torture_random(trsp) %
(cxt.nrealwriters_stress * factor))) {
sched_set_fifo(current);
} else/* common case, do nothing */ return;
} else { /* * The task will remain boosted for another 10 * rt_boost_factor * operations, then restored back to its original prio, and so * forth. * * When @trsp is nil, we want to force-reset the task for * stopping the kthread.
*/ if (!trsp || !(torture_random(trsp) %
(cxt.nrealwriters_stress * factor * 2))) {
sched_set_normal(current, 0);
} else/* common case, do nothing */ return;
}
}
staticvoid torture_rt_boost(struct torture_random_state *trsp)
{ if (rt_boost != 2) return;
/* We want a short delay mostly to emulate likely code, and * we want a long delay occasionally to force massive contention.
*/ if (long_hold && !(torture_random(trsp) % (cxt.nrealwriters_stress * 2000 * long_hold))) {
j = jiffies;
mdelay(long_hold);
pr_alert("%s: delay = %lu jiffies.\n", __func__, jiffies - j);
} if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 200 * shortdelay_us)))
udelay(shortdelay_us); if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))
torture_preempt_schedule(); /* Allow test to be preempted. */
}
staticvoid torture_spin_lock_write_unlock(int tid __maybe_unused)
__releases(torture_spinlock)
{
spin_unlock(&torture_spinlock);
}
/* We want a short delay mostly to emulate likely code, and * we want a long delay occasionally to force massive contention.
*/ if (long_hold && !(torture_random(trsp) % (cxt.nrealwriters_stress * 2000 * long_hold)))
mdelay(long_hold); else
udelay(shortdelay_us);
}
staticvoid torture_rwlock_write_unlock(int tid __maybe_unused)
__releases(torture_rwlock)
{
write_unlock(&torture_rwlock);
}
staticint torture_rwlock_read_lock(int tid __maybe_unused)
__acquires(torture_rwlock)
{
read_lock(&torture_rwlock); return 0;
}
/* We want a short delay mostly to emulate likely code, and * we want a long delay occasionally to force massive contention.
*/ if (long_hold && !(torture_random(trsp) % (cxt.nrealreaders_stress * 2000 * long_hold)))
mdelay(long_hold); else
udelay(shortdelay_us);
}
staticvoid torture_rwlock_read_unlock(int tid __maybe_unused)
__releases(torture_rwlock)
{
read_unlock(&torture_rwlock);
}
for (i = 0; i < MAX_NESTED_LOCKS; i++)
__mutex_init(&torture_nested_mutexes[i], __func__,
&nested_mutex_keys[i]);
}
staticint torture_mutex_nested_lock(int tid __maybe_unused,
u32 lockset)
{ int i;
for (i = 0; i < nested_locks; i++) if (lockset & (1 << i))
mutex_lock(&torture_nested_mutexes[i]); return 0;
}
staticint torture_mutex_lock(int tid __maybe_unused)
__acquires(torture_mutex)
{
mutex_lock(&torture_mutex); return 0;
}
staticvoid torture_mutex_delay(struct torture_random_state *trsp)
{ /* We want a long delay occasionally to force massive contention. */ if (long_hold && !(torture_random(trsp) % (cxt.nrealwriters_stress * 2000 * long_hold)))
mdelay(long_hold * 5); if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))
torture_preempt_schedule(); /* Allow test to be preempted. */
}
staticvoid torture_mutex_unlock(int tid __maybe_unused)
__releases(torture_mutex)
{
mutex_unlock(&torture_mutex);
}
staticvoid torture_mutex_nested_unlock(int tid __maybe_unused,
u32 lockset)
{ int i;
for (i = nested_locks - 1; i >= 0; i--) if (lockset & (1 << i))
mutex_unlock(&torture_nested_mutexes[i]);
}
#include <linux/ww_mutex.h> /* * The torture ww_mutexes should belong to the same lock class as * torture_ww_class to avoid lockdep problem. The ww_mutex_init() * function is called for initialization to ensure that.
*/ static DEFINE_WD_CLASS(torture_ww_class); staticstruct ww_mutex torture_ww_mutex_0, torture_ww_mutex_1, torture_ww_mutex_2; staticstruct ww_acquire_ctx *ww_acquire_ctxs;
ww_acquire_ctxs = kmalloc_array(cxt.nrealwriters_stress, sizeof(*ww_acquire_ctxs),
GFP_KERNEL); if (!ww_acquire_ctxs)
VERBOSE_TOROUT_STRING("ww_acquire_ctx: Out of memory");
}
/* * We want a short delay mostly to emulate likely code, and * we want a long delay occasionally to force massive contention.
*/ if (long_hold && !(torture_random(trsp) % (cxt.nrealwriters_stress * 2000 * long_hold)))
mdelay(long_hold); if (!(torture_random(trsp) %
(cxt.nrealwriters_stress * 200 * shortdelay_us)))
udelay(shortdelay_us); if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))
torture_preempt_schedule(); /* Allow test to be preempted. */
}
staticvoid torture_rtmutex_unlock(int tid __maybe_unused)
__releases(torture_rtmutex)
{
rt_mutex_unlock(&torture_rtmutex);
}
staticvoid torture_rt_boost_rtmutex(struct torture_random_state *trsp)
{ if (!rt_boost) return;
__torture_rt_boost(trsp);
}
staticvoid torture_rtmutex_nested_unlock(int tid __maybe_unused,
u32 lockset)
{ int i;
for (i = nested_locks - 1; i >= 0; i--) if (lockset & (1 << i))
rt_mutex_unlock(&torture_nested_rtmutexes[i]);
}
staticvoid torture_rwsem_write_delay(struct torture_random_state *trsp)
{ /* We want a long delay occasionally to force massive contention. */ if (long_hold && !(torture_random(trsp) % (cxt.nrealwriters_stress * 2000 * long_hold)))
mdelay(long_hold * 10); if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))
torture_preempt_schedule(); /* Allow test to be preempted. */
}
staticvoid torture_rwsem_up_write(int tid __maybe_unused)
__releases(torture_rwsem)
{
up_write(&torture_rwsem);
}
staticint torture_rwsem_down_read(int tid __maybe_unused)
__acquires(torture_rwsem)
{
down_read(&torture_rwsem); return 0;
}
staticvoid torture_rwsem_read_delay(struct torture_random_state *trsp)
{ /* We want a long delay occasionally to force massive contention. */ if (long_hold && !(torture_random(trsp) % (cxt.nrealreaders_stress * 2000 * long_hold)))
mdelay(long_hold * 2); else
mdelay(long_hold / 2); if (!(torture_random(trsp) % (cxt.nrealreaders_stress * 20000)))
torture_preempt_schedule(); /* Allow test to be preempted. */
}
staticvoid torture_rwsem_up_read(int tid __maybe_unused)
__releases(torture_rwsem)
{
up_read(&torture_rwsem);
}
/* * Lock torture writer kthread. Repeatedly acquires and releases * the lock, checking for duplicate acquisitions.
*/ staticint lock_torture_writer(void *arg)
{ unsignedlong j; unsignedlong j1;
u32 lockset_mask; struct lock_stress_stats *lwsp = arg;
DEFINE_TORTURE_RANDOM(rand); bool skip_main_lock; int tid = lwsp - cxt.lwsa;
VERBOSE_TOROUT_STRING("lock_torture_writer task started"); if (!rt_task(current))
set_user_nice(current, MAX_NICE);
do { if ((torture_random(&rand) & 0xfffff) == 0)
schedule_timeout_uninterruptible(1);
lockset_mask = torture_random(&rand); /* * When using nested_locks, we want to occasionally * skip the main lock so we can avoid always serializing * the lock chains on that central lock. By skipping the * main lock occasionally, we can create different * contention patterns (allowing for multiple disjoint * blocked trees)
*/
skip_main_lock = (nested_locks &&
!(torture_random(&rand) % 100));
cxt.cur_ops->task_boost(&rand); if (cxt.cur_ops->nested_lock)
cxt.cur_ops->nested_lock(tid, lockset_mask);
if (!skip_main_lock) { if (acq_writer_lim > 0)
j = jiffies;
cxt.cur_ops->writelock(tid); if (WARN_ON_ONCE(lock_is_write_held))
lwsp->n_lock_fail++;
lock_is_write_held = true; if (WARN_ON_ONCE(atomic_read(&lock_is_read_held)))
lwsp->n_lock_fail++; /* rare, but... */ if (acq_writer_lim > 0) {
j1 = jiffies;
WARN_ONCE(time_after(j1, j + acq_writer_lim), "%s: Lock acquisition took %lu jiffies.\n",
__func__, j1 - j);
}
lwsp->n_lock_acquired++;
stutter_wait("lock_torture_reader");
} while (!torture_must_stop());
torture_kthread_stopping("lock_torture_reader"); return 0;
}
/* * Create an lock-torture-statistics message in the specified buffer.
*/ staticvoid __torture_print_stats(char *page, struct lock_stress_stats *statp, bool write)
{ long cur; bool fail = false; int i, n_stress; long max = 0, min = statp ? data_race(statp[0].n_lock_acquired) : 0; longlong sum = 0;
n_stress = write ? cxt.nrealwriters_stress : cxt.nrealreaders_stress; for (i = 0; i < n_stress; i++) { if (data_race(statp[i].n_lock_fail))
fail = true;
cur = data_race(statp[i].n_lock_acquired);
sum += cur; if (max < cur)
max = cur; if (min > cur)
min = cur;
}
page += sprintf(page, "%s: Total: %lld Max/Min: %ld/%ld %s Fail: %d %s\n",
write ? "Writes" : "Reads ",
sum, max, min,
!onoff_interval && max / 2 > min ? "???" : "",
fail, fail ? "!!!" : ""); if (fail)
atomic_inc(&cxt.n_lock_torture_errors);
}
/* * Print torture statistics. Caller must ensure that there is only one * call to this function at a given time!!! This is normally accomplished * by relying on the module system to only have one copy of the module * loaded, and then by giving the lock_torture_stats kthread full control * (or the init/cleanup functions when lock_torture_stats thread is not * running).
*/ staticvoid lock_torture_stats_print(void)
{ int size = cxt.nrealwriters_stress * 200 + 8192; char *buf;
if (cxt.cur_ops->readlock)
size += cxt.nrealreaders_stress * 200 + 8192;
buf = kmalloc(size, GFP_KERNEL); if (!buf) {
pr_err("lock_torture_stats_print: Out of memory, need: %d",
size); return;
}
/* * Periodically prints torture statistics, if periodic statistics printing * was specified via the stat_interval module parameter. * * No need to worry about fullstop here, since this one doesn't reference * volatile state or register callbacks.
*/ staticint lock_torture_stats(void *arg)
{
VERBOSE_TOROUT_STRING("lock_torture_stats task started"); do {
schedule_timeout_interruptible(stat_interval * HZ);
lock_torture_stats_print();
torture_shutdown_absorb("lock_torture_stats");
} while (!torture_must_stop());
torture_kthread_stopping("lock_torture_stats"); return 0;
}
// If requested, maintain call_rcu() chains to keep a grace period always // in flight. These increase the probability of getting an RCU CPU stall // warning and associated diagnostics when a locking primitive stalls.
if (!smp_load_acquire(&crcp->crc_stop)) {
(void)start_poll_synchronize_rcu(); // Start one grace period...
call_rcu(&crcp->crc_rh, call_rcu_chain_cb); // ... and later start another.
}
}
// Start the requested number of call_rcu() chains. staticint call_rcu_chain_init(void)
{ int i;
if (call_rcu_chains <= 0) return 0;
call_rcu_chain_list = kcalloc(call_rcu_chains, sizeof(*call_rcu_chain_list), GFP_KERNEL); if (!call_rcu_chain_list) return -ENOMEM; for (i = 0; i < call_rcu_chains; i++) {
call_rcu_chain_list[i].crc_stop = false;
call_rcu(&call_rcu_chain_list[i].crc_rh, call_rcu_chain_cb);
} return 0;
}
// Stop all of the call_rcu() chains. staticvoid call_rcu_chain_cleanup(void)
{ int i;
if (!call_rcu_chain_list) return; for (i = 0; i < call_rcu_chains; i++)
smp_store_release(&call_rcu_chain_list[i].crc_stop, true);
rcu_barrier();
kfree(call_rcu_chain_list);
call_rcu_chain_list = NULL;
}
staticvoid lock_torture_cleanup(void)
{ int i;
if (torture_cleanup_begin()) return;
/* * Indicates early cleanup, meaning that the test has not run, * such as when passing bogus args when loading the module. * However cxt->cur_ops.init() may have been invoked, so beside * perform the underlying torture-specific cleanups, cur_ops.exit() * will be invoked if needed.
*/ if (!cxt.lwsa && !cxt.lrsa) goto end;
if (writer_tasks) { for (i = 0; i < cxt.nrealwriters_stress; i++)
torture_stop_kthread(lock_torture_writer, writer_tasks[i]);
kfree(writer_tasks);
writer_tasks = NULL;
}
if (reader_tasks) { for (i = 0; i < cxt.nrealreaders_stress; i++)
torture_stop_kthread(lock_torture_reader,
reader_tasks[i]);
kfree(reader_tasks);
reader_tasks = NULL;
}
torture_stop_kthread(lock_torture_stats, stats_task);
lock_torture_stats_print(); /* -After- the stats thread is stopped! */
if (atomic_read(&cxt.n_lock_torture_errors))
lock_torture_print_module_parms(cxt.cur_ops, "End of test: FAILURE"); elseif (torture_onoff_failures())
lock_torture_print_module_parms(cxt.cur_ops, "End of test: LOCK_HOTPLUG"); else
lock_torture_print_module_parms(cxt.cur_ops, "End of test: SUCCESS");
if (!torture_init_begin(torture_type, verbose)) return -EBUSY;
/* Process args and tell the world that the torturer is on the job. */ for (i = 0; i < ARRAY_SIZE(torture_ops); i++) {
cxt.cur_ops = torture_ops[i]; if (strcmp(torture_type, cxt.cur_ops->name) == 0) break;
} if (i == ARRAY_SIZE(torture_ops)) {
pr_alert("lock-torture: invalid torture type: \"%s\"\n",
torture_type);
pr_alert("lock-torture types:"); for (i = 0; i < ARRAY_SIZE(torture_ops); i++)
pr_alert(" %s", torture_ops[i]->name);
pr_alert("\n");
firsterr = -EINVAL; goto unwind;
}
if (nwriters_stress == 0 &&
(!cxt.cur_ops->readlock || nreaders_stress == 0)) {
pr_alert("lock-torture: must run at least one locking thread\n");
firsterr = -EINVAL; goto unwind;
}
if (cxt.cur_ops->init) {
cxt.cur_ops->init();
cxt.init_called = true;
}
#ifdef CONFIG_DEBUG_MUTEXES if (str_has_prefix(torture_type, "mutex"))
cxt.debug_lock = true; #endif #ifdef CONFIG_DEBUG_RT_MUTEXES if (str_has_prefix(torture_type, "rtmutex"))
cxt.debug_lock = true; #endif #ifdef CONFIG_DEBUG_SPINLOCK if ((str_has_prefix(torture_type, "spin")) ||
(str_has_prefix(torture_type, "rw_lock")))
cxt.debug_lock = true; #endif
/* Initialize the statistics so that each run gets its own numbers. */ if (nwriters_stress) {
lock_is_write_held = false;
cxt.lwsa = kmalloc_array(cxt.nrealwriters_stress, sizeof(*cxt.lwsa),
GFP_KERNEL); if (cxt.lwsa == NULL) {
VERBOSE_TOROUT_STRING("cxt.lwsa: Out of memory");
firsterr = -ENOMEM; goto unwind;
}
for (i = 0; i < cxt.nrealwriters_stress; i++) {
cxt.lwsa[i].n_lock_fail = 0;
cxt.lwsa[i].n_lock_acquired = 0;
}
}
if (cxt.cur_ops->readlock) { if (nreaders_stress >= 0)
cxt.nrealreaders_stress = nreaders_stress; else { /* * By default distribute evenly the number of * readers and writers. We still run the same number * of threads as the writer-only locks default.
*/ if (nwriters_stress < 0) /* user doesn't care */
cxt.nrealwriters_stress = num_online_cpus();
cxt.nrealreaders_stress = cxt.nrealwriters_stress;
}
if (nreaders_stress) {
cxt.lrsa = kmalloc_array(cxt.nrealreaders_stress, sizeof(*cxt.lrsa),
GFP_KERNEL); if (cxt.lrsa == NULL) {
VERBOSE_TOROUT_STRING("cxt.lrsa: Out of memory");
firsterr = -ENOMEM;
kfree(cxt.lwsa);
cxt.lwsa = NULL; goto unwind;
}
for (i = 0; i < cxt.nrealreaders_stress; i++) {
cxt.lrsa[i].n_lock_fail = 0;
cxt.lrsa[i].n_lock_acquired = 0;
}
}
}
firsterr = call_rcu_chain_init(); if (torture_init_error(firsterr)) goto unwind;
lock_torture_print_module_parms(cxt.cur_ops, "Start of test");
/* Prepare torture context. */ if (onoff_interval > 0) {
firsterr = torture_onoff_init(onoff_holdoff * HZ,
onoff_interval * HZ, NULL); if (torture_init_error(firsterr)) goto unwind;
} if (shuffle_interval > 0) {
firsterr = torture_shuffle_init(shuffle_interval); if (torture_init_error(firsterr)) goto unwind;
} if (shutdown_secs > 0) {
firsterr = torture_shutdown_init(shutdown_secs,
lock_torture_cleanup); if (torture_init_error(firsterr)) goto unwind;
} if (stutter > 0) {
firsterr = torture_stutter_init(stutter, stutter); if (torture_init_error(firsterr)) goto unwind;
}
if (nwriters_stress) {
writer_tasks = kcalloc(cxt.nrealwriters_stress, sizeof(writer_tasks[0]),
GFP_KERNEL); if (writer_tasks == NULL) {
TOROUT_ERRSTRING("writer_tasks: Out of memory");
firsterr = -ENOMEM; goto unwind;
}
}
/* cap nested_locks to MAX_NESTED_LOCKS */ if (nested_locks > MAX_NESTED_LOCKS)
nested_locks = MAX_NESTED_LOCKS;
if (cxt.cur_ops->readlock) {
reader_tasks = kcalloc(cxt.nrealreaders_stress, sizeof(reader_tasks[0]),
GFP_KERNEL); if (reader_tasks == NULL) {
TOROUT_ERRSTRING("reader_tasks: Out of memory");
kfree(writer_tasks);
writer_tasks = NULL;
firsterr = -ENOMEM; goto unwind;
}
}
/* * Create the kthreads and start torturing (oh, those poor little locks). * * TODO: Note that we interleave writers with readers, giving writers a * slight advantage, by creating its kthread first. This can be modified * for very specific needs, or even let the user choose the policy, if * ever wanted.
*/ for (i = 0, j = 0; i < cxt.nrealwriters_stress ||
j < cxt.nrealreaders_stress; i++, j++) { if (i >= cxt.nrealwriters_stress) goto create_reader;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.