Restrict max number of concurrent hotplug processes (Part 2) The max number of concurrent hotplug / kmod events might need to be changed at runtime. This also allows for disabling the delivery of hotplug / kmod events altogether for debugging reasons and later enabling them again, with a complete replay of all queued events. The runtime variable is implemented as a new sysctl variable, which resets the global variable 'khelper_max' to the new value. Care has to be taken if the variable is decreased and some processes are already queued / blocked: As it's not possible to ensure that a call to down() will not be blocked we take the other route of simply not calling up() on exit from an already running process if the variable has been changed during runtime of the process. The patch alse needed to change request_module() to dynamically allocate the process arguments, as now theses arguments might be needed by a delayed execution of the kmod thread. Signed-off-by: Hannes Reinecke diff -pur linux-2.6.8-rc2-mm2/include/linux/sysctl.h linux-2.6.8-rc2-mm2.hotplug/include/linux/sysctl.h --- linux-2.6.8-rc2-mm2/include/linux/sysctl.h 2004-08-03 14:58:14.000000000 +0200 +++ linux-2.6.8-rc2-mm2.hotplug/include/linux/sysctl.h 2004-08-05 16:33:02.000000000 +0200 @@ -136,6 +136,7 @@ enum KERN_UNKNOWN_NMI_PANIC=66, /* int: unknown nmi panic flag */ KERN_INTERACTIVE=67, /* interactive tasks can have cpu bursts */ KERN_COMPUTE=68, /* adjust timeslices for a compute server */ + KERN_KHELPER_MAX=69, /* max # of concurrent khelper processes */ }; @@ -784,6 +785,8 @@ extern int proc_doulongvec_minmax(ctl_ta void __user *, size_t *); extern int proc_doulongvec_ms_jiffies_minmax(ctl_table *table, int, struct file *, void __user *, size_t *); +extern int proc_dointvec_khelper(ctl_table *, int, struct file *, + void __user *, size_t *); extern int do_sysctl (int __user *name, int nlen, void __user *oldval, size_t __user *oldlenp, diff -pur linux-2.6.8-rc2-mm2/kernel/kmod.c linux-2.6.8-rc2-mm2.hotplug/kernel/kmod.c --- linux-2.6.8-rc2-mm2/kernel/kmod.c 2004-08-05 17:27:53.000000000 +0200 +++ linux-2.6.8-rc2-mm2.hotplug/kernel/kmod.c 2004-08-05 17:14:26.000000000 +0200 @@ -47,6 +47,7 @@ extern int max_threads; static struct workqueue_struct *khelper_wq; int khelper_max = 50; +static int khelper_diff; static struct semaphore khelper_sem; #ifdef CONFIG_KMOD @@ -75,11 +76,11 @@ int request_module(const char *fmt, ...) va_list args; char module_name[MODULE_NAME_LEN]; int ret; - char *argv[] = { modprobe_path, "-q", "--", module_name, NULL }; - static char *envp[] = { "HOME=/", - "TERM=linux", - "PATH=/sbin:/usr/sbin:/bin:/usr/bin", - NULL }; + char *argv_buffer = NULL; + char *envp_buffer = NULL; + char *scratch = NULL; + char **argv = NULL; + char **envp = NULL; va_start(args, fmt); ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args); @@ -87,6 +88,51 @@ int request_module(const char *fmt, ...) if (ret >= MODULE_NAME_LEN) return -ENAMETOOLONG; + /* + * Use kmalloc for argv and envp as they might be delayed. + */ + argv_buffer = kmalloc(512,GFP_KERNEL); + if (!argv_buffer) + return -ENOMEM; + memset(argv_buffer,0x0,512); + + envp_buffer = kmalloc(128,GFP_KERNEL); + if (!envp_buffer) { + kfree(argv_buffer); + return -ENOMEM; + } + memset(envp_buffer,0x0, 128); + + scratch = argv_buffer; + argv = (char **)scratch; + scratch += sizeof(char *) * 5; + argv[0] = scratch; + strcpy(scratch, modprobe_path); + scratch += strlen(scratch) + 1; + argv[1] = scratch; + strcpy(scratch, "-q"); + scratch += 3; + argv[2] = scratch; + strcpy(scratch, "--"); + scratch += 3; + argv[3] = scratch; + strcpy(scratch, module_name); + argv[4] = NULL; + + scratch = envp_buffer; + envp = (char **)scratch; + scratch += sizeof(char *) * 4; + envp[0] = scratch; + strcpy(scratch, "HOME=/"); + scratch += strlen("HOME=/") + 1; + envp[1] = scratch; + strcpy(scratch, "TERM=linux"); + scratch += strlen("TERM=linux") + 1; + envp[2] = scratch; + strcpy(scratch,"PATH=/sbin:/usr/sbin:/bin:/usr/bin"); + + envp[3] = NULL; + /* If modprobe needs a service that is in a module, we get a recursive * loop. Limit the number of running kmod threads to max_threads/2 or * MAX_KMOD_CONCURRENT, whichever is the smaller. A cleaner method @@ -102,7 +148,7 @@ int request_module(const char *fmt, ...) * Resource checking is now implemented in * call_usermodehelper --hare */ - ret = call_usermodehelper(modprobe_path, argv, envp, 1); + ret = call_usermodehelper(argv[0], argv, envp, 1); return ret; } EXPORT_SYMBOL(request_module); @@ -203,7 +249,7 @@ static int wait_for_helper(void *data) printk(KERN_INFO "khelper: delay event %s (current %d, max %d)\n", ev_descr, atomic_read(&khelper_sem.count), khelper_max); #endif - if (wait == 0) { + if (khelper_max < 5 || wait == 0) { /* Queueing is for async events only */ wait = -1; sub_info->retval = 0; @@ -285,7 +331,14 @@ static int wait_for_helper(void *data) kfree(stored_info.envp); } - up(&khelper_sem); + if (khelper_diff > 0) { +#ifdef DEBUG_KHELPER + printk(KERN_INFO "khelper: decrease semaphore\n"); +#endif + khelper_diff--; + } else { + up(&khelper_sem); + } return 0; } @@ -363,6 +416,48 @@ void __init usermodehelper_init(void) sema_init(&khelper_sem, khelper_max); } +/* + * Worker to adapt the number of khelper processes. + * down() might block, so we need a separate thread ... + */ +int khelper_modify_number(int diff) +{ + if (khelper_max > 0) { + printk(KERN_INFO "khelper: max %d concurrent processes\n", + khelper_max); + } else { + printk(KERN_INFO "khelper: delaying events\n"); + } + + if (diff > 0) { + while (diff > 0) { + up(&khelper_sem); + diff--; + } + } else { + /* + * Note that we cannot call down() directly + * as this would block. + * So we first test whether we would block + * and instruct the running processes to not + * call up() instead. + */ + while (diff < 0) { + if (down_trylock(&khelper_sem)) { + khelper_diff++; + } else { +#ifdef DEBUG_KHELPER + printk(KERN_INFO "khelper: decrease semaphore\n"); +#endif + } + diff++; + } + } + + return 0; +} +EXPORT_SYMBOL(khelper_modify_number); + /* * Sanity check for khelper_max is done in usermodehelper_init, * as at this point of time the system is not fully initialised. diff -pur linux-2.6.8-rc2-mm2/kernel/sysctl.c linux-2.6.8-rc2-mm2.hotplug/kernel/sysctl.c --- linux-2.6.8-rc2-mm2/kernel/sysctl.c 2004-08-03 14:58:15.000000000 +0200 +++ linux-2.6.8-rc2-mm2.hotplug/kernel/sysctl.c 2004-08-05 16:51:43.000000000 +0200 @@ -64,6 +64,7 @@ extern int sysctl_lower_zone_protection; extern int min_free_kbytes; extern int printk_ratelimit_jiffies; extern int printk_ratelimit_burst; +extern int khelper_max; #if defined(CONFIG_X86_LOCAL_APIC) && defined(__i386__) int unknown_nmi_panic; @@ -414,6 +415,14 @@ static ctl_table kern_table[] = { .proc_handler = &proc_dostring, .strategy = &sysctl_string, }, + { + .ctl_name = KERN_KHELPER_MAX, + .procname = "khelper_max", + .data = &khelper_max, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = &proc_dointvec_khelper, + }, #endif #ifdef CONFIG_CHR_DEV_SG { @@ -1952,6 +1961,27 @@ int proc_dointvec_userhz_jiffies(ctl_tab do_proc_dointvec_userhz_jiffies_conv,NULL); } +extern int khelper_modify_number(int diff); + +int proc_dointvec_khelper(ctl_table *table, int write, struct file *filp, + void __user *buffer, size_t *lenp) +{ + int maxval = max_threads / 2; + struct do_proc_dointvec_minmax_conv_param param = { + .min = (int *) &zero, + .max = (int *) &maxval, + }; + int oldval = khelper_max, retval; + + retval = do_proc_dointvec(table, write, filp, buffer, lenp, + do_proc_dointvec_minmax_conv, ¶m); + + if (khelper_max != oldval) + khelper_modify_number(khelper_max - oldval); + return retval; +} + + #else /* CONFIG_PROC_FS */ int proc_dostring(ctl_table *table, int write, struct file *filp,