Index: linux-2.6/lib/Kconfig.debug =================================================================== --- linux-2.6.orig/lib/Kconfig.debug 2007-04-14 14:34:41.000000000 +0200 +++ linux-2.6/lib/Kconfig.debug 2007-04-14 14:37:29.000000000 +0200 @@ -409,6 +409,14 @@ config LPC_TEST boot. Say M to be able to start them by inserting the module. Say N if you are unsure. +config CPC_TEST + tristate "Tests for concurrent pagecache" + depends on DEBUG_KERNEL + default n + help + This option provides a kernel module that runs some background + threads that exercise concurrent pagecache races more than usual. + config RCU_TORTURE_TEST tristate "torture tests for RCU" depends on DEBUG_KERNEL Index: linux-2.6/mm/Makefile =================================================================== --- linux-2.6.orig/mm/Makefile 2007-04-14 14:34:41.000000000 +0200 +++ linux-2.6/mm/Makefile 2007-04-14 14:37:45.000000000 +0200 @@ -30,3 +30,4 @@ obj-$(CONFIG_FS_XIP) += filemap_xip.o obj-$(CONFIG_MIGRATION) += migrate.o obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_LPC_TEST) += lpctest.o +obj-$(CONFIG_CPC_TEST) += cpctest.o Index: linux-2.6/mm/cpctest.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6/mm/cpctest.c 2007-04-14 15:56:30.000000000 +0200 @@ -0,0 +1,312 @@ +/* + * concurrent pagecache test thread + * + * 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. + * + * Based on kernel/rcutorture.c, which is + * Copyright (C) IBM Corporation, 2005, 2006 + * + * Copyright (C) Peter Zijlstra, Red Hat Inc., 2007 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Zijlstra "); + +static int threads = 2; +static int test = 1; +static int loop = 4; +static int nice = 0; +static int size = 256; + +module_param(threads, int, 0444); +MODULE_PARM_DESC(nreaders, "Number of threads"); +module_param(test, int, 0444); +MODULE_PARM_DESC(test, "run test"); +module_param(loop, int, 0444); +MODULE_PARM_DESC(loop, "number of inner loops"); +module_param(nice, int, 0444); +MODULE_PARM_DESC(loop, "nice level to run at"); +module_param(size, int, 0444); +MODULE_PARM_DESC(size, "number of items to user"); + +static struct task_struct **tasks; + +static RADIX_TREE(tree, GFP_ATOMIC); + +enum tags { + TAG_A, + TAG_B, +}; + +struct linear_args { + unsigned long start, end; + unsigned long intv; + enum tags tag; +}; + +#define INDEX_TO_PTR(index) ((void *)(((unsigned long)index << 2) + 2)) + +static void *linear_inserter(void *arg) +{ + struct linear_args *args = arg; + unsigned long i; + DEFINE_RADIX_TREE_CONTEXT(ctx, &tree); + + for (i = args->start; i < args->end; i += args->intv) { + int ret; + struct item *item = INDEX_TO_PTR(i); + + local_irq_disable(); + radix_tree_lock(&ctx); + ret = radix_tree_insert(ctx.tree, i, item); + radix_tree_unlock(&ctx); + local_irq_enable(); + + cond_resched(); + } + + return arg; +} + +static void *linear_remover(void *arg) +{ + struct linear_args *args = arg; + unsigned long i; + DEFINE_RADIX_TREE_CONTEXT(ctx, &tree); + + for (i = args->start; i < args->end; i += args->intv) { + struct item *item; + + local_irq_disable(); + radix_tree_lock(&ctx); + item = radix_tree_delete(ctx.tree, i); + radix_tree_unlock(&ctx); + local_irq_enable(); + + if (item) + BUG_ON(item != INDEX_TO_PTR(i)); + + cond_resched(); + } + + return arg; +} + +static void *linear_tagger(void *arg) +{ + struct linear_args *args = arg; + unsigned long i; + DEFINE_RADIX_TREE_CONTEXT(ctx, &tree); + + for (i = args->start; i < args->end; i += args->intv) { + struct item *item; + + local_irq_disable(); + radix_tree_lock(&ctx); + item = radix_tree_tag_set(ctx.tree, i, args->tag); + radix_tree_unlock(&ctx); + local_irq_enable(); + + if (item) + BUG_ON(item != INDEX_TO_PTR(i)); + + cond_resched(); + } + + return arg; +} + +static void *linear_untagger(void *arg) +{ + struct linear_args *args = arg; + unsigned long i; + DEFINE_RADIX_TREE_CONTEXT(ctx, &tree); + + for (i = args->start; i < args->end; i += args->intv) { + struct item *item; + + local_irq_disable(); + radix_tree_lock(&ctx); + item = radix_tree_tag_clear(ctx.tree, i, args->tag); + radix_tree_unlock(&ctx); + local_irq_enable(); + + if (item) + BUG_ON(item != INDEX_TO_PTR(i)); + + cond_resched(); + } + + return arg; +} + +# define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) + + +unsigned long get_intv(struct timeval *tv) +{ + struct timeval old = *tv, result; + do_gettimeofday(tv); + + timersub(tv, &old, &result); + return result.tv_sec*1000 + result.tv_usec/1000; +} + +static void do_loop(struct linear_args *lin_args) +{ + struct timeval tv; + struct task_struct *p = current; + int i, j; + + set_user_nice(current, nice); + current->flags |= PF_NOFREEZE; + + printk("start: %lx end: %lx intv: %ld\n", + lin_args->start, + lin_args->end, + lin_args->intv); + + do_gettimeofday(&tv); + i = 0; + do { + linear_inserter(lin_args); + printk("[%p] insert %d done in %lu ms\n", p, i, get_intv(&tv)); + for (j = 0; j < loop; j++) { + linear_tagger(lin_args); + printk("[%p] tag %d done in %lu ms\n", p, j, get_intv(&tv)); + linear_untagger(lin_args); + printk("[%p] untag %d done in %lu ms\n", p, j, get_intv(&tv)); + } + linear_remover(lin_args); + printk("[%p] remove %d done in %lu ms\n", p, i, get_intv(&tv)); + + i++; + } while (!kthread_should_stop()); +} + +static int interleaved_loop(void *arg) +{ + unsigned long nr = (unsigned long)arg; + struct linear_args lin_args = { + .start = nr, + .end = (unsigned long)size << 16, + .intv = threads, + .tag = TAG_A, + }; + + do_loop(&lin_args); + + return 0; +} + +static int seq_loop(void *arg) +{ + unsigned long nr = (unsigned long)arg; + unsigned long len = ((unsigned long)size << 16) / threads; + struct linear_args lin_args = { + .start = nr * len, + .end = (nr+1) * len, + .intv = 1, + .tag = TAG_A, + }; + + do_loop(&lin_args); + + return 0; +} + +static void cpc_test_cleanup(void) +{ + int i; + + if (tasks != NULL) { + for (i = 0; i < threads; i++) { + if (tasks[i] != NULL) { + kthread_stop(tasks[i]); + tasks[i] = NULL; + } + } + kfree(tasks); + tasks = NULL; + } +} + +typedef int (*test_fn)(void *); + +static int cpc_test_init(void) +{ + unsigned long i; + int err = 0; + test_fn tests[] = { + seq_loop, + interleaved_loop, + }; + + if (test > ARRAY_SIZE(tests)) + test = 0; + + tasks = kzalloc(threads * sizeof(tasks[0]), + GFP_KERNEL); + if (tasks == NULL) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < threads; i++) { + tasks[i] = kthread_run(tests[test], (void *)i, "cpc_tester%d", i); + if (IS_ERR(tasks[i])) { + err = PTR_ERR(tasks[i]); + tasks[i] = NULL; + goto out; + } + } + +out: + if (err) + cpc_test_cleanup(); + return err; +} + +module_init(cpc_test_init); +module_exit(cpc_test_cleanup);