815 lines · 22.1 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 * * Author: Paul E. McKenney <paulmck@us.ibm.com> *	Based on kernel/rcu/torture.c. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/kthread.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/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 (*writeunlock)(void);	int (*readlock)(void);	void (*read_delay)(struct torture_random_state *trsp);	void (*readunlock)(void);	unsigned long flags;	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 struct lock_torture_ops lock_busted_ops = {	.writelock	= torture_lock_busted_write_lock,	.write_delay	= torture_lock_busted_write_delay,	.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,	.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,	.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,	.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,	.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,	.writeunlock	= torture_mutex_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "mutex_lock"}; 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; 	/* We want a long delay occasionally to force massive contention.  */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms * 2);	else		mdelay(longdelay_ms / 2);#ifdef CONFIG_PREEMPT	if (!(torture_random(trsp) % (cxt.nrealreaders_stress * 20000)))		preempt_schedule();  /* Allow test to be preempted. */#endif} static void torture_rwsem_up_read(void) __releases(torture_rwsem){	up_read(&torture_rwsem);} static struct lock_torture_ops rwsem_lock_ops = {	.writelock	= torture_rwsem_down_write,	.write_delay	= torture_rwsem_write_delay,	.writeunlock	= torture_rwsem_up_write,	.readlock       = torture_rwsem_down_read,	.read_delay     = torture_rwsem_read_delay,	.readunlock     = torture_rwsem_up_read,	.name		= "rwsem_lock"}; /* * Lock torture writer kthread.  Repeatedly acquires and releases * the lock, checking for duplicate acquisitions. */static int lock_torture_writer(void *arg){	struct lock_stress_stats *lwsp = arg;	static DEFINE_TORTURE_RANDOM(rand); 	VERBOSE_TOROUT_STRING("lock_torture_writer task started");	set_user_nice(current, MAX_NICE); 	do {		if ((torture_random(&rand) & 0xfffff) == 0)			schedule_timeout_uninterruptible(1); 		cxt.cur_ops->writelock();		if (WARN_ON_ONCE(lock_is_write_held))			lwsp->n_lock_fail++;		lock_is_write_held = 1;		if (WARN_ON_ONCE(lock_is_read_held))			lwsp->n_lock_fail++; /* rare, but... */ 		lwsp->n_lock_acquired++;		cxt.cur_ops->write_delay(&rand);		lock_is_write_held = 0;		cxt.cur_ops->writeunlock(); 		stutter_wait("lock_torture_writer");	} while (!torture_must_stop());	torture_kthread_stopping("lock_torture_writer");	return 0;} /* * Lock torture reader kthread.  Repeatedly acquires and releases * the reader lock. */static int lock_torture_reader(void *arg){	struct lock_stress_stats *lrsp = arg;	static DEFINE_TORTURE_RANDOM(rand); 	VERBOSE_TOROUT_STRING("lock_torture_reader task started");	set_user_nice(current, MAX_NICE); 	do {		if ((torture_random(&rand) & 0xfffff) == 0)			schedule_timeout_uninterruptible(1); 		cxt.cur_ops->readlock();		lock_is_read_held = 1;		if (WARN_ON_ONCE(lock_is_write_held))			lrsp->n_lock_fail++; /* rare, but... */ 		lrsp->n_lock_acquired++;		cxt.cur_ops->read_delay(&rand);		lock_is_read_held = 0;		cxt.cur_ops->readunlock(); 		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. */static void __torture_print_stats(char *page,				  struct lock_stress_stats *statp, bool write){

Lines 1–500 of 815.