mirror of
				https://git.proxmox.com/git/mirror_spl-debian
				synced 2025-10-31 07:01:32 +00:00 
			
		
		
		
	 8842263bd0
			
		
	
	
		8842263bd0
		
	
	
	
	
		
			
			As of Linux 3.4 the UMH_WAIT_* constants were renumbered. In particular, the meaning of "1" changed from UMH_WAIT_PROC (wait for process to complete), to UMH_WAIT_EXEC (wait for the exec, but not the process). A number of call sites used the number 1 instead of the constant name, so the behavior was not as expected on kernels with this change. Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
		
			
				
	
	
		
			286 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*****************************************************************************\
 | |
|  *  Copyright (C) 2011 Lawrence Livermore National Security, LLC.
 | |
|  *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 | |
|  *  Written by Brian Behlendorf <behlendorf1@llnl.gov>.
 | |
|  *  UCRL-CODE-235197
 | |
|  *
 | |
|  *  This file is part of the SPL, Solaris Porting Layer.
 | |
|  *  For details, see <http://github.com/behlendorf/spl/>.
 | |
|  *
 | |
|  *  The SPL 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.
 | |
|  *
 | |
|  *  The SPL 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 the SPL.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  *****************************************************************************
 | |
|  *  Solaris Porting LAyer Tests (SPLAT) Kernel Compatibility Tests.
 | |
| \*****************************************************************************/
 | |
| 
 | |
| #include <sys/kmem.h>
 | |
| #include "splat-internal.h"
 | |
| 
 | |
| #define SPLAT_LINUX_NAME		"linux"
 | |
| #define SPLAT_LINUX_DESC		"Kernel Compatibility Tests"
 | |
| 
 | |
| #define SPLAT_LINUX_TEST1_ID		0x1001
 | |
| #define SPLAT_LINUX_TEST1_NAME		"shrink_dcache"
 | |
| #define SPLAT_LINUX_TEST1_DESC		"Shrink dcache test"
 | |
| 
 | |
| #define SPLAT_LINUX_TEST2_ID		0x1002
 | |
| #define SPLAT_LINUX_TEST2_NAME		"shrink_icache"
 | |
| #define SPLAT_LINUX_TEST2_DESC		"Shrink icache test"
 | |
| 
 | |
| #define SPLAT_LINUX_TEST3_ID		0x1003
 | |
| #define SPLAT_LINUX_TEST3_NAME		"shrinker"
 | |
| #define SPLAT_LINUX_TEST3_DESC		"Shrinker test"
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Attempt to shrink the dcache memory.  This is simply a functional
 | |
|  * to ensure we can correctly call the shrinker.  We don't check that
 | |
|  * the cache actually decreased because we have no control over what
 | |
|  * else may be running on the system.  This avoid false positives.
 | |
|  */
 | |
| static int
 | |
| splat_linux_test1(struct file *file, void *arg)
 | |
| {
 | |
| 	int remain_before;
 | |
| 	int remain_after;
 | |
| 
 | |
| 	remain_before = shrink_dcache_memory(0, GFP_KERNEL);
 | |
| 	remain_after = shrink_dcache_memory(KMC_REAP_CHUNK, GFP_KERNEL);
 | |
| 
 | |
| 	splat_vprint(file, SPLAT_LINUX_TEST1_NAME,
 | |
| 	    "Shrink dcache memory, remain %d -> %d\n",
 | |
| 	    remain_before, remain_after);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Attempt to shrink the icache memory.  This is simply a functional
 | |
|  * to ensure we can correctly call the shrinker.  We don't check that
 | |
|  * the cache actually decreased because we have no control over what
 | |
|  * else may be running on the system.  This avoid false positives.
 | |
|  */
 | |
| static int
 | |
| splat_linux_test2(struct file *file, void *arg)
 | |
| {
 | |
| 	int remain_before;
 | |
| 	int remain_after;
 | |
| 
 | |
| 	remain_before = shrink_icache_memory(0, GFP_KERNEL);
 | |
| 	remain_after = shrink_icache_memory(KMC_REAP_CHUNK, GFP_KERNEL);
 | |
| 
 | |
| 	splat_vprint(file, SPLAT_LINUX_TEST2_NAME,
 | |
| 	    "Shrink icache memory, remain %d -> %d\n",
 | |
| 	    remain_before, remain_after);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Wait queue used to eliminate race between dropping of slab
 | |
|  * and execution of the shrinker callback
 | |
|  */
 | |
| DECLARE_WAIT_QUEUE_HEAD(shrinker_wait);
 | |
| 
 | |
| SPL_SHRINKER_CALLBACK_FWD_DECLARE(splat_linux_shrinker_fn);
 | |
| SPL_SHRINKER_DECLARE(splat_linux_shrinker, splat_linux_shrinker_fn, 1);
 | |
| static unsigned long splat_linux_shrinker_size = 0;
 | |
| static struct file *splat_linux_shrinker_file = NULL;
 | |
| 
 | |
| static int
 | |
| __splat_linux_shrinker_fn(struct shrinker *shrink, struct shrink_control *sc)
 | |
| {
 | |
| 	static int failsafe = 0;
 | |
| 	static unsigned long last_splat_linux_shrinker_size = 0;
 | |
| 
 | |
| 	/*
 | |
| 	 * shrinker_size can only decrease or stay the same between callbacks
 | |
| 	 * in the same run, so Reset failsafe whenever shrinker increases
 | |
| 	 * as this indicates a new run.
 | |
| 	 */
 | |
| 	if (last_splat_linux_shrinker_size < splat_linux_shrinker_size)
 | |
| 		failsafe = 0;
 | |
| 
 | |
| 	last_splat_linux_shrinker_size = splat_linux_shrinker_size;
 | |
| 
 | |
| 	if (sc->nr_to_scan) {
 | |
| 		splat_linux_shrinker_size = splat_linux_shrinker_size -
 | |
| 		    MIN(sc->nr_to_scan, splat_linux_shrinker_size);
 | |
| 
 | |
| 		splat_vprint(splat_linux_shrinker_file, SPLAT_LINUX_TEST3_NAME,
 | |
| 		    "Reclaimed %lu objects, size now %lu\n",
 | |
| 		    sc->nr_to_scan, splat_linux_shrinker_size);
 | |
| 	} else {
 | |
| 		splat_vprint(splat_linux_shrinker_file, SPLAT_LINUX_TEST3_NAME,
 | |
| 		    "Cache size is %lu\n", splat_linux_shrinker_size);
 | |
| 	}
 | |
| 
 | |
| 	/* Far more calls than expected abort drop_slab as a failsafe */
 | |
| 	if (failsafe > 100) {
 | |
| 		splat_vprint(splat_linux_shrinker_file, SPLAT_LINUX_TEST3_NAME,
 | |
| 		    "Far more calls than expected (%d), size now %lu\n",
 | |
| 		   failsafe, splat_linux_shrinker_size);
 | |
| 		return -1;
 | |
| 	} else {
 | |
| 		/*
 | |
| 		 * We only increment failsafe if it doesn't trigger.  This
 | |
| 		 * makes any failsafe failure persistent until the next test.
 | |
| 		 */
 | |
| 		failsafe++;
 | |
| 	}
 | |
| 
 | |
| 	/* Shrinker has run, so signal back to test. */
 | |
| 	wake_up(&shrinker_wait);
 | |
| 
 | |
| 	return (int)splat_linux_shrinker_size;
 | |
| }
 | |
| 
 | |
| SPL_SHRINKER_CALLBACK_WRAPPER(splat_linux_shrinker_fn);
 | |
| 
 | |
| #define DROP_SLAB_CMD \
 | |
| 	"exec 0</dev/null " \
 | |
| 	"     1>/proc/sys/vm/drop_caches " \
 | |
| 	"     2>/dev/null; " \
 | |
| 	"echo 2"
 | |
| 
 | |
| static int
 | |
| splat_linux_drop_slab(struct file *file)
 | |
| {
 | |
| 	char *argv[] = { "/bin/sh",
 | |
| 	                 "-c",
 | |
| 	                 DROP_SLAB_CMD,
 | |
| 	                 NULL };
 | |
| 	char *envp[] = { "HOME=/",
 | |
| 	                 "TERM=linux",
 | |
| 	                 "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
 | |
| 	                 NULL };
 | |
| 	int rc;
 | |
| 
 | |
| 	rc = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
 | |
| 	if (rc)
 | |
| 		splat_vprint(file, SPLAT_LINUX_TEST3_NAME,
 | |
| 	            "Failed user helper '%s %s %s', rc = %d\n",
 | |
| 		    argv[0], argv[1], argv[2], rc);
 | |
| 
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Verify correct shrinker functionality by registering a shrinker
 | |
|  * with the required compatibility macros.  We then use a simulated
 | |
|  * cache and force the systems caches to be dropped.  The shrinker
 | |
|  * should be repeatedly called until it reports that the cache is
 | |
|  * empty.  It is then cleanly unregistered and correct behavior is
 | |
|  * verified.  There are now four slightly different supported shrinker
 | |
|  * API and this test ensures the compatibility code is correct.
 | |
|  */
 | |
| static int
 | |
| splat_linux_test3(struct file *file, void *arg)
 | |
| {
 | |
| 	int rc = -EINVAL;
 | |
| 
 | |
| 	/*
 | |
| 	 * Globals used by the shrinker, it is not safe to run this
 | |
| 	 * test concurrently this is a safe assumption for SPLAT tests.
 | |
| 	 * Regardless we do some minimal checking a bail if concurrent
 | |
| 	 * use is detected.
 | |
| 	 */
 | |
| 	if (splat_linux_shrinker_size || splat_linux_shrinker_file) {
 | |
| 		splat_vprint(file, SPLAT_LINUX_TEST3_NAME,
 | |
| 		    "Failed due to concurrent shrinker test, rc = %d\n", rc);
 | |
| 		return (rc);
 | |
| 	}
 | |
| 
 | |
| 	splat_linux_shrinker_size = 1024;
 | |
| 	splat_linux_shrinker_file = file;
 | |
| 
 | |
| 	spl_register_shrinker(&splat_linux_shrinker);
 | |
| 	rc = splat_linux_drop_slab(file);
 | |
| 	if (rc)
 | |
| 		goto out;
 | |
| 
 | |
| 	/*
 | |
| 	 * By the time we get here, it is possible that the shrinker has not
 | |
| 	 * yet run. splat_linux_drop_slab sends a signal for it to run, but
 | |
| 	 * there is no guarantee of when it will actually run. We wait for it
 | |
| 	 * to run here, terminating when either the shrinker size is now 0 or
 | |
| 	 * we timeout after 1 second, which should be an eternity (error).
 | |
| 	 */
 | |
| 	rc = wait_event_timeout(shrinker_wait, !splat_linux_shrinker_size, HZ);
 | |
| 	if (!rc) {
 | |
| 		splat_vprint(file, SPLAT_LINUX_TEST3_NAME,
 | |
| 		    "Failed cache shrinking timed out, size now %lu",
 | |
| 		    splat_linux_shrinker_size);
 | |
| 		rc = -ETIMEDOUT;
 | |
| 	} else {
 | |
| 		rc = 0;
 | |
| 	}
 | |
| 
 | |
| 	if (!rc && splat_linux_shrinker_size != 0) {
 | |
| 		splat_vprint(file, SPLAT_LINUX_TEST3_NAME,
 | |
| 		    "Failed cache was not shrunk to 0, size now %lu",
 | |
| 		    splat_linux_shrinker_size);
 | |
| 		rc = -EDOM;
 | |
| 	}
 | |
| out:
 | |
| 	spl_unregister_shrinker(&splat_linux_shrinker);
 | |
| 
 | |
| 	splat_linux_shrinker_size = 0;
 | |
| 	splat_linux_shrinker_file = NULL;
 | |
| 
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| splat_subsystem_t *
 | |
| splat_linux_init(void)
 | |
| {
 | |
| 	splat_subsystem_t *sub;
 | |
| 
 | |
| 	sub = kmalloc(sizeof(*sub), GFP_KERNEL);
 | |
| 	if (sub == NULL)
 | |
| 		return NULL;
 | |
| 
 | |
| 	memset(sub, 0, sizeof(*sub));
 | |
| 	strncpy(sub->desc.name, SPLAT_LINUX_NAME, SPLAT_NAME_SIZE);
 | |
| 	strncpy(sub->desc.desc, SPLAT_LINUX_DESC, SPLAT_DESC_SIZE);
 | |
| 	INIT_LIST_HEAD(&sub->subsystem_list);
 | |
| 	INIT_LIST_HEAD(&sub->test_list);
 | |
| 	spin_lock_init(&sub->test_lock);
 | |
| 	sub->desc.id = SPLAT_SUBSYSTEM_LINUX;
 | |
| 
 | |
| 	SPLAT_TEST_INIT(sub, SPLAT_LINUX_TEST1_NAME, SPLAT_LINUX_TEST1_DESC,
 | |
| 			SPLAT_LINUX_TEST1_ID, splat_linux_test1);
 | |
| 	SPLAT_TEST_INIT(sub, SPLAT_LINUX_TEST2_NAME, SPLAT_LINUX_TEST2_DESC,
 | |
| 			SPLAT_LINUX_TEST2_ID, splat_linux_test2);
 | |
| 	SPLAT_TEST_INIT(sub, SPLAT_LINUX_TEST3_NAME, SPLAT_LINUX_TEST3_DESC,
 | |
| 			SPLAT_LINUX_TEST3_ID, splat_linux_test3);
 | |
| 
 | |
| 	return sub;
 | |
| }
 | |
| 
 | |
| void
 | |
| splat_linux_fini(splat_subsystem_t *sub)
 | |
| {
 | |
| 	ASSERT(sub);
 | |
| 	SPLAT_TEST_FINI(sub, SPLAT_LINUX_TEST3_ID);
 | |
| 	SPLAT_TEST_FINI(sub, SPLAT_LINUX_TEST2_ID);
 | |
| 	SPLAT_TEST_FINI(sub, SPLAT_LINUX_TEST1_ID);
 | |
| 
 | |
| 	kfree(sub);
 | |
| }
 | |
| 
 | |
| int
 | |
| splat_linux_id(void) {
 | |
| 	return SPLAT_SUBSYSTEM_LINUX;
 | |
| }
 |