mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 08:43:31 +00:00

Simplify the concurrency delimiter we use for kmod with the semaphore. I had used the kmod strategy to try to implement a similar concurrency delimiter for the kernel_read*() calls from the finit_module() path so to reduce vmalloc() memory pressure. That effort didn't provide yet conclusive results, but one thing that became clear is we can use the suggested alternative solution with semaphores which Linus hinted at instead of using the atomic / wait strategy. I've stress tested this with kmod test 0008: time /data/linux-next/tools/testing/selftests/kmod/kmod.sh -t 0008 And I get only a *slight* delay. That delay however is small, a few seconds for a full test loop run that runs 150 times, for about ~30-40 seconds. The small delay is worth the simplfication IMHO. Reviewed-by: Davidlohr Bueso <dave@stgolabs.net> Reviewed-by: Miroslav Benes <mbenes@suse.cz> Reviewed-by: David Hildenbrand <david@redhat.com> Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
165 lines
4.4 KiB
C
165 lines
4.4 KiB
C
/*
|
|
* kmod - the kernel module loader
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/task.h>
|
|
#include <linux/binfmts.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/cred.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fdtable.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/security.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/resource.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/rwsem.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/async.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <trace/events/module.h>
|
|
|
|
/*
|
|
* Assuming:
|
|
*
|
|
* threads = div64_u64((u64) totalram_pages * (u64) PAGE_SIZE,
|
|
* (u64) THREAD_SIZE * 8UL);
|
|
*
|
|
* If you need less than 50 threads would mean we're dealing with systems
|
|
* smaller than 3200 pages. This assumes you are capable of having ~13M memory,
|
|
* and this would only be an upper limit, after which the OOM killer would take
|
|
* effect. Systems like these are very unlikely if modules are enabled.
|
|
*/
|
|
#define MAX_KMOD_CONCURRENT 50
|
|
static DEFINE_SEMAPHORE(kmod_concurrent_max, MAX_KMOD_CONCURRENT);
|
|
|
|
/*
|
|
* This is a restriction on having *all* MAX_KMOD_CONCURRENT threads
|
|
* running at the same time without returning. When this happens we
|
|
* believe you've somehow ended up with a recursive module dependency
|
|
* creating a loop.
|
|
*
|
|
* We have no option but to fail.
|
|
*
|
|
* Userspace should proactively try to detect and prevent these.
|
|
*/
|
|
#define MAX_KMOD_ALL_BUSY_TIMEOUT 5
|
|
|
|
/*
|
|
modprobe_path is set via /proc/sys.
|
|
*/
|
|
char modprobe_path[KMOD_PATH_LEN] = CONFIG_MODPROBE_PATH;
|
|
|
|
static void free_modprobe_argv(struct subprocess_info *info)
|
|
{
|
|
kfree(info->argv[3]); /* check call_modprobe() */
|
|
kfree(info->argv);
|
|
}
|
|
|
|
static int call_modprobe(char *module_name, int wait)
|
|
{
|
|
struct subprocess_info *info;
|
|
static char *envp[] = {
|
|
"HOME=/",
|
|
"TERM=linux",
|
|
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
|
|
NULL
|
|
};
|
|
|
|
char **argv = kmalloc(sizeof(char *[5]), GFP_KERNEL);
|
|
if (!argv)
|
|
goto out;
|
|
|
|
module_name = kstrdup(module_name, GFP_KERNEL);
|
|
if (!module_name)
|
|
goto free_argv;
|
|
|
|
argv[0] = modprobe_path;
|
|
argv[1] = "-q";
|
|
argv[2] = "--";
|
|
argv[3] = module_name; /* check free_modprobe_argv() */
|
|
argv[4] = NULL;
|
|
|
|
info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
|
|
NULL, free_modprobe_argv, NULL);
|
|
if (!info)
|
|
goto free_module_name;
|
|
|
|
return call_usermodehelper_exec(info, wait | UMH_KILLABLE);
|
|
|
|
free_module_name:
|
|
kfree(module_name);
|
|
free_argv:
|
|
kfree(argv);
|
|
out:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* __request_module - try to load a kernel module
|
|
* @wait: wait (or not) for the operation to complete
|
|
* @fmt: printf style format string for the name of the module
|
|
* @...: arguments as specified in the format string
|
|
*
|
|
* Load a module using the user mode module loader. The function returns
|
|
* zero on success or a negative errno code or positive exit code from
|
|
* "modprobe" on failure. Note that a successful module load does not mean
|
|
* the module did not then unload and exit on an error of its own. Callers
|
|
* must check that the service they requested is now available not blindly
|
|
* invoke it.
|
|
*
|
|
* If module auto-loading support is disabled then this function
|
|
* simply returns -ENOENT.
|
|
*/
|
|
int __request_module(bool wait, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char module_name[MODULE_NAME_LEN];
|
|
int ret;
|
|
|
|
/*
|
|
* We don't allow synchronous module loading from async. Module
|
|
* init may invoke async_synchronize_full() which will end up
|
|
* waiting for this task which already is waiting for the module
|
|
* loading to complete, leading to a deadlock.
|
|
*/
|
|
WARN_ON_ONCE(wait && current_is_async());
|
|
|
|
if (!modprobe_path[0])
|
|
return -ENOENT;
|
|
|
|
va_start(args, fmt);
|
|
ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);
|
|
va_end(args);
|
|
if (ret >= MODULE_NAME_LEN)
|
|
return -ENAMETOOLONG;
|
|
|
|
ret = security_kernel_module_request(module_name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = down_timeout(&kmod_concurrent_max, MAX_KMOD_ALL_BUSY_TIMEOUT * HZ);
|
|
if (ret) {
|
|
pr_warn_ratelimited("request_module: modprobe %s cannot be processed, kmod busy with %d threads for more than %d seconds now",
|
|
module_name, MAX_KMOD_CONCURRENT, MAX_KMOD_ALL_BUSY_TIMEOUT);
|
|
return ret;
|
|
}
|
|
|
|
trace_module_request(module_name, wait, _RET_IP_);
|
|
|
|
ret = call_modprobe(module_name, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
|
|
|
|
up(&kmod_concurrent_max);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(__request_module);
|