969 lines · 26.2 KB · C
RawDownload
/* * Module-based torture test facility for locking * * 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, you can access it online at * http://www.gnu.org/licenses/gpl-2.0.html. * * Copyright (C) IBM Corporation, 2014 * * Authors: Paul E. McKenney <paulmck@us.ibm.com> *          Davidlohr Bueso <dave@stgolabs.net> *	Based on kernel/rcu/torture.c. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/kthread.h>#include <linux/sched/rt.h>#include <linux/spinlock.h>#include <linux/rwlock.h>#include <linux/mutex.h>#include <linux/rwsem.h>#include <linux/smp.h>#include <linux/interrupt.h>#include <linux/sched.h>#include <linux/atomic.h>#include <linux/moduleparam.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/percpu-rwsem.h>#include <linux/torture.h> MODULE_LICENSE("GPL");MODULE_AUTHOR("Paul E. McKenney <paulmck@us.ibm.com>"); torture_param(int, nwriters_stress, -1,	     "Number of write-locking stress-test threads");torture_param(int, nreaders_stress, -1,	     "Number of read-locking stress-test threads");torture_param(int, onoff_holdoff, 0, "Time after boot before CPU hotplugs (s)");torture_param(int, onoff_interval, 0,	     "Time between CPU hotplugs (s), 0=disable");torture_param(int, shuffle_interval, 3,	     "Number of jiffies between shuffles, 0=disable");torture_param(int, shutdown_secs, 0, "Shutdown time (j), <= zero to disable.");torture_param(int, stat_interval, 60,	     "Number of seconds between stats printk()s");torture_param(int, stutter, 5, "Number of jiffies to run/halt test, 0=disable");torture_param(bool, verbose, true,	     "Enable verbose debugging printk()s"); static char *torture_type = "spin_lock";module_param(torture_type, charp, 0444);MODULE_PARM_DESC(torture_type,		 "Type of lock to torture (spin_lock, spin_lock_irq, mutex_lock, ...)"); static struct task_struct *stats_task;static struct task_struct **writer_tasks;static struct task_struct **reader_tasks; static bool lock_is_write_held;static bool lock_is_read_held; struct lock_stress_stats {	long n_lock_fail;	long n_lock_acquired;}; #if defined(MODULE)#define LOCKTORTURE_RUNNABLE_INIT 1#else#define LOCKTORTURE_RUNNABLE_INIT 0#endifint torture_runnable = LOCKTORTURE_RUNNABLE_INIT;module_param(torture_runnable, int, 0444);MODULE_PARM_DESC(torture_runnable, "Start locktorture at module init"); /* Forward reference. */static void lock_torture_cleanup(void); /* * Operations vector for selecting different types of tests. */struct lock_torture_ops {	void (*init)(void);	int (*writelock)(void);	void (*write_delay)(struct torture_random_state *trsp);	void (*task_boost)(struct torture_random_state *trsp);	void (*writeunlock)(void);	int (*readlock)(void);	void (*read_delay)(struct torture_random_state *trsp);	void (*readunlock)(void); 	unsigned long flags; /* for irq spinlocks */	const char *name;}; struct lock_torture_cxt {	int nrealwriters_stress;	int nrealreaders_stress;	bool debug_lock;	atomic_t n_lock_torture_errors;	struct lock_torture_ops *cur_ops;	struct lock_stress_stats *lwsa; /* writer statistics */	struct lock_stress_stats *lrsa; /* reader statistics */};static struct lock_torture_cxt cxt = { 0, 0, false,				       ATOMIC_INIT(0),				       NULL, NULL};/* * Definitions for lock torture testing. */ static int torture_lock_busted_write_lock(void){	return 0;  /* BUGGY, do not use in real life!!! */} static void torture_lock_busted_write_delay(struct torture_random_state *trsp){	const unsigned long longdelay_ms = 100; 	/* We want a long delay occasionally to force massive contention.  */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);#ifdef CONFIG_PREEMPT	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		preempt_schedule();  /* Allow test to be preempted. */#endif} static void torture_lock_busted_write_unlock(void){	  /* BUGGY, do not use in real life!!! */} static void torture_boost_dummy(struct torture_random_state *trsp){	/* Only rtmutexes care about priority */} static struct lock_torture_ops lock_busted_ops = {	.writelock	= torture_lock_busted_write_lock,	.write_delay	= torture_lock_busted_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_lock_busted_write_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "lock_busted"}; static DEFINE_SPINLOCK(torture_spinlock); static int torture_spin_lock_write_lock(void) __acquires(torture_spinlock){	spin_lock(&torture_spinlock);	return 0;} static void torture_spin_lock_write_delay(struct torture_random_state *trsp){	const unsigned long shortdelay_us = 2;	const unsigned long longdelay_ms = 100; 	/* We want a short delay mostly to emulate likely code, and	 * we want a long delay occasionally to force massive contention.	 */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2 * shortdelay_us)))		udelay(shortdelay_us);#ifdef CONFIG_PREEMPT	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		preempt_schedule();  /* Allow test to be preempted. */#endif} static void torture_spin_lock_write_unlock(void) __releases(torture_spinlock){	spin_unlock(&torture_spinlock);} static struct lock_torture_ops spin_lock_ops = {	.writelock	= torture_spin_lock_write_lock,	.write_delay	= torture_spin_lock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_spin_lock_write_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "spin_lock"}; static int torture_spin_lock_write_lock_irq(void)__acquires(torture_spinlock){	unsigned long flags; 	spin_lock_irqsave(&torture_spinlock, flags);	cxt.cur_ops->flags = flags;	return 0;} static void torture_lock_spin_write_unlock_irq(void)__releases(torture_spinlock){	spin_unlock_irqrestore(&torture_spinlock, cxt.cur_ops->flags);} static struct lock_torture_ops spin_lock_irq_ops = {	.writelock	= torture_spin_lock_write_lock_irq,	.write_delay	= torture_spin_lock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_lock_spin_write_unlock_irq,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "spin_lock_irq"}; static DEFINE_RWLOCK(torture_rwlock); static int torture_rwlock_write_lock(void) __acquires(torture_rwlock){	write_lock(&torture_rwlock);	return 0;} static void torture_rwlock_write_delay(struct torture_random_state *trsp){	const unsigned long shortdelay_us = 2;	const unsigned long longdelay_ms = 100; 	/* We want a short delay mostly to emulate likely code, and	 * we want a long delay occasionally to force massive contention.	 */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	else		udelay(shortdelay_us);} static void torture_rwlock_write_unlock(void) __releases(torture_rwlock){	write_unlock(&torture_rwlock);} static int torture_rwlock_read_lock(void) __acquires(torture_rwlock){	read_lock(&torture_rwlock);	return 0;} static void torture_rwlock_read_delay(struct torture_random_state *trsp){	const unsigned long shortdelay_us = 10;	const unsigned long longdelay_ms = 100; 	/* We want a short delay mostly to emulate likely code, and	 * we want a long delay occasionally to force massive contention.	 */	if (!(torture_random(trsp) %	      (cxt.nrealreaders_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	else		udelay(shortdelay_us);} static void torture_rwlock_read_unlock(void) __releases(torture_rwlock){	read_unlock(&torture_rwlock);} static struct lock_torture_ops rw_lock_ops = {	.writelock	= torture_rwlock_write_lock,	.write_delay	= torture_rwlock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_rwlock_write_unlock,	.readlock       = torture_rwlock_read_lock,	.read_delay     = torture_rwlock_read_delay,	.readunlock     = torture_rwlock_read_unlock,	.name		= "rw_lock"}; static int torture_rwlock_write_lock_irq(void) __acquires(torture_rwlock){	unsigned long flags; 	write_lock_irqsave(&torture_rwlock, flags);	cxt.cur_ops->flags = flags;	return 0;} static void torture_rwlock_write_unlock_irq(void)__releases(torture_rwlock){	write_unlock_irqrestore(&torture_rwlock, cxt.cur_ops->flags);} static int torture_rwlock_read_lock_irq(void) __acquires(torture_rwlock){	unsigned long flags; 	read_lock_irqsave(&torture_rwlock, flags);	cxt.cur_ops->flags = flags;	return 0;} static void torture_rwlock_read_unlock_irq(void)__releases(torture_rwlock){	read_unlock_irqrestore(&torture_rwlock, cxt.cur_ops->flags);} static struct lock_torture_ops rw_lock_irq_ops = {	.writelock	= torture_rwlock_write_lock_irq,	.write_delay	= torture_rwlock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_rwlock_write_unlock_irq,	.readlock       = torture_rwlock_read_lock_irq,	.read_delay     = torture_rwlock_read_delay,	.readunlock     = torture_rwlock_read_unlock_irq,	.name		= "rw_lock_irq"}; static DEFINE_MUTEX(torture_mutex); static int torture_mutex_lock(void) __acquires(torture_mutex){	mutex_lock(&torture_mutex);	return 0;} static void torture_mutex_delay(struct torture_random_state *trsp){	const unsigned long longdelay_ms = 100; 	/* We want a long delay occasionally to force massive contention.  */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms * 5);	else		mdelay(longdelay_ms / 5);#ifdef CONFIG_PREEMPT	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		preempt_schedule();  /* Allow test to be preempted. */#endif} static void torture_mutex_unlock(void) __releases(torture_mutex){	mutex_unlock(&torture_mutex);} static struct lock_torture_ops mutex_lock_ops = {	.writelock	= torture_mutex_lock,	.write_delay	= torture_mutex_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_mutex_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "mutex_lock"}; #ifdef CONFIG_RT_MUTEXESstatic DEFINE_RT_MUTEX(torture_rtmutex); static int torture_rtmutex_lock(void) __acquires(torture_rtmutex){	rt_mutex_lock(&torture_rtmutex);	return 0;} static void torture_rtmutex_boost(struct torture_random_state *trsp){	int policy;	struct sched_param param;	const unsigned int factor = 50000; /* yes, quite arbitrary */ 	if (!rt_task(current)) {		/*		 * (1) Boost priority once every ~50k 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 (!(torture_random(trsp) %		      (cxt.nrealwriters_stress * factor))) {			policy = SCHED_FIFO;			param.sched_priority = MAX_RT_PRIO - 1;		} else /* common case, do nothing */			return;	} else {		/*		 * The task will remain boosted for another ~500k 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))) {			policy = SCHED_NORMAL;			param.sched_priority = 0;		} else /* common case, do nothing */			return;	} 	sched_setscheduler_nocheck(current, policy, &param);} static void torture_rtmutex_delay(struct torture_random_state *trsp){	const unsigned long shortdelay_us = 2;	const unsigned long longdelay_ms = 100; 	/*	 * We want a short delay mostly to emulate likely code, and	 * we want a long delay occasionally to force massive contention.	 */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2 * shortdelay_us)))		udelay(shortdelay_us);#ifdef CONFIG_PREEMPT	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		preempt_schedule();  /* Allow test to be preempted. */#endif} static void torture_rtmutex_unlock(void) __releases(torture_rtmutex){	rt_mutex_unlock(&torture_rtmutex);} static struct lock_torture_ops rtmutex_lock_ops = {	.writelock	= torture_rtmutex_lock,	.write_delay	= torture_rtmutex_delay,	.task_boost     = torture_rtmutex_boost,	.writeunlock	= torture_rtmutex_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "rtmutex_lock"};#endif static DECLARE_RWSEM(torture_rwsem);static int torture_rwsem_down_write(void) __acquires(torture_rwsem){	down_write(&torture_rwsem);	return 0;} static void torture_rwsem_write_delay(struct torture_random_state *trsp){	const unsigned long longdelay_ms = 100; 	/* We want a long delay occasionally to force massive contention.  */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms * 10);	else		mdelay(longdelay_ms / 10);#ifdef CONFIG_PREEMPT	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		preempt_schedule();  /* Allow test to be preempted. */#endif} static void torture_rwsem_up_write(void) __releases(torture_rwsem){	up_write(&torture_rwsem);} static int torture_rwsem_down_read(void) __acquires(torture_rwsem){	down_read(&torture_rwsem);	return 0;} static void torture_rwsem_read_delay(struct torture_random_state *trsp){	const unsigned long longdelay_ms = 100;

Lines 1–500 of 969.