From: Rusty Russell module.c currently spawns threads directly to stop the machine, s a module can be atomically tested for removal. Unfortunately, this means that the current task is manipulated (which races with set_cpus_allowed, for example), and it can't set its priority artificially high. Using a kernel thread can solve this cleanly, and with kthread_run, it's simple. --- include/linux/sched.h | 2 kernel/module.c | 151 ++++++++++++++++++++++++++------------------------ 2 files changed, 82 insertions(+), 71 deletions(-) diff -puN include/linux/sched.h~module-removal-use-kthread include/linux/sched.h --- 25/include/linux/sched.h~module-removal-use-kthread 2004-01-29 22:33:20.000000000 -0800 +++ 25-akpm/include/linux/sched.h 2004-01-29 22:33:20.000000000 -0800 @@ -793,6 +793,8 @@ extern task_t *child_reaper; extern int do_execve(char *, char __user * __user *, char __user * __user *, struct pt_regs *); extern long do_fork(unsigned long, unsigned long, struct pt_regs *, unsigned long, int __user *, int __user *); extern struct task_struct * copy_process(unsigned long, unsigned long, struct pt_regs *, unsigned long, int __user *, int __user *); +extern asmlinkage long sys_sched_setscheduler(pid_t pid, int policy, + struct sched_param __user *parm); #ifdef CONFIG_SMP extern void wait_task_inactive(task_t * p); diff -puN kernel/module.c~module-removal-use-kthread kernel/module.c --- 25/kernel/module.c~module-removal-use-kthread 2004-01-29 22:33:20.000000000 -0800 +++ 25-akpm/kernel/module.c 2004-01-29 22:33:20.000000000 -0800 @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -457,6 +458,40 @@ static void module_unload_free(struct mo } } +#ifdef CONFIG_MODULE_FORCE_UNLOAD +static inline int try_force(unsigned int flags) +{ + int ret = (flags & O_TRUNC); + if (ret) + tainted |= TAINT_FORCED_MODULE; + return ret; +} +#else +static inline int try_force(unsigned int flags) +{ + return 0; +} +#endif /* CONFIG_MODULE_FORCE_UNLOAD */ + +static int try_stop_module_local(struct module *mod, int flags, int *forced) +{ + local_irq_disable(); + + /* If it's not unused, quit unless we are told to block. */ + if ((flags & O_NONBLOCK) && module_refcount(mod) != 0) { + if (!(*forced = try_force(flags))) { + local_irq_enable(); + return -EWOULDBLOCK; + } + } + + /* Mark it as dying. */ + mod->waiter = current; + mod->state = MODULE_STATE_GOING; + local_irq_enable(); + return 0; +} + #ifdef CONFIG_SMP /* Thread to stop each CPU in user context. */ enum stopref_state { @@ -475,13 +510,6 @@ static int stopref(void *cpu) int irqs_disabled = 0; int prepared = 0; - sprintf(current->comm, "kmodule%lu\n", (unsigned long)cpu); - - /* Highest priority we can manage, and move to right CPU. */ -#if 0 /* FIXME */ - struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 }; - setscheduler(current->pid, SCHED_FIFO, ¶m); -#endif set_cpus_allowed(current, cpumask_of_cpu((int)(long)cpu)); /* Ack: we are alive */ @@ -535,29 +563,30 @@ static void stopref_set_state(enum stopr } } -/* Stop the machine. Disables irqs. */ -static int stop_refcounts(void) +struct stopref { - unsigned int i, cpu; - cpumask_t old_allowed; - int ret = 0; + struct module *mod; + int flags; + int *forced; +}; - /* One thread per cpu. We'll do our own. */ - cpu = smp_processor_id(); +static int spawn_stopref(void *data) +{ + struct stopref *sref = data; + struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 }; + unsigned int i, cpu = smp_processor_id(); + int ret = 0; - /* FIXME: racy with set_cpus_allowed. */ - old_allowed = current->cpus_allowed; + /* One high-prio thread per cpu. We'll do one (any one). */ set_cpus_allowed(current, cpumask_of_cpu(cpu)); + sys_sched_setscheduler(current->pid, SCHED_FIFO, ¶m); atomic_set(&stopref_thread_ack, 0); stopref_num_threads = 0; stopref_state = STOPREF_WAIT; - /* No CPUs can come up or down during this. */ - lock_cpu_hotplug(); - - for (i = 0; i < NR_CPUS; i++) { - if (i == cpu || !cpu_online(i)) + for_each_online_cpu(i) { + if (i == cpu) continue; ret = kernel_thread(stopref, (void *)(long)i, CLONE_KERNEL); if (ret < 0) @@ -572,40 +601,52 @@ static int stop_refcounts(void) /* If some failed, kill them all. */ if (ret < 0) { stopref_set_state(STOPREF_EXIT, 1); - unlock_cpu_hotplug(); return ret; } /* Don't schedule us away at this point, please. */ preempt_disable(); - /* Now they are all scheduled, make them hold the CPUs, ready. */ + /* Now they are all started, make them hold the CPUs, ready. */ stopref_set_state(STOPREF_PREPARE, 0); /* Make them disable irqs. */ stopref_set_state(STOPREF_DISABLE_IRQ, 0); - local_irq_disable(); - return 0; -} + /* Atomically disable module if possible */ + ret = try_stop_module_local(sref->mod, sref->flags, sref->forced); -/* Restart the machine. Re-enables irqs. */ -static void restart_refcounts(void) -{ stopref_set_state(STOPREF_EXIT, 0); - local_irq_enable(); preempt_enable(); - unlock_cpu_hotplug(); + + /* Wait for kthread_stop */ + while (!signal_pending(current)) { + __set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + return ret; } -#else /* ...!SMP */ -static inline int stop_refcounts(void) + +static int try_stop_module(struct module *mod, int flags, int *forced) { - local_irq_disable(); - return 0; + struct task_struct *p; + struct stopref sref = { mod, flags, forced }; + int ret; + + /* No CPUs can come up or down during this. */ + lock_cpu_hotplug(); + p = kthread_run(spawn_stopref, &sref, "krmmod"); + if (IS_ERR(p)) + ret = PTR_ERR(p); + else + ret = kthread_stop(p); + unlock_cpu_hotplug(); + return ret; } -static inline void restart_refcounts(void) +#else /* ...!SMP */ +static inline int try_stop_module(struct module *mod, int flags, int *forced) { - local_irq_enable(); + return try_stop_module_local(mod, flags, forced); } #endif @@ -622,21 +663,6 @@ EXPORT_SYMBOL(module_refcount); /* This exists whether we can unload or not */ static void free_module(struct module *mod); -#ifdef CONFIG_MODULE_FORCE_UNLOAD -static inline int try_force(unsigned int flags) -{ - int ret = (flags & O_TRUNC); - if (ret) - tainted |= TAINT_FORCED_MODULE; - return ret; -} -#else -static inline int try_force(unsigned int flags) -{ - return 0; -} -#endif /* CONFIG_MODULE_FORCE_UNLOAD */ - /* Stub function for modules which don't have an exitfn */ void cleanup_module(void) { @@ -706,26 +732,9 @@ sys_delete_module(const char __user *nam goto out; } } - /* Stop the machine so refcounts can't move: irqs disabled. */ - DEBUGP("Stopping refcounts...\n"); - ret = stop_refcounts(); - if (ret != 0) - goto out; - - /* If it's not unused, quit unless we are told to block. */ - if ((flags & O_NONBLOCK) && module_refcount(mod) != 0) { - forced = try_force(flags); - if (!forced) { - ret = -EWOULDBLOCK; - restart_refcounts(); - goto out; - } - } - /* Mark it as dying. */ - mod->waiter = current; - mod->state = MODULE_STATE_GOING; - restart_refcounts(); + /* Stop the machine so refcounts can't move and disable module. */ + ret = try_stop_module(mod, flags, &forced); /* Never wait if forced. */ if (!forced && module_refcount(mod) != 0) _