Commit e513cc1c authored by Masami Hiramatsu's avatar Masami Hiramatsu Committed by Rusty Russell

module: Remove stop_machine from module unloading

Remove stop_machine from module unloading by adding new reference
counting algorithm.

This atomic refcounter works like a semaphore, it can get (be
incremented) only when the counter is not 0. When loading a module,
kmodule subsystem sets the counter MODULE_REF_BASE (= 1). And when
unloading the module, it subtracts MODULE_REF_BASE from the counter.
If no one refers the module, the refcounter becomes 0 and we can
remove the module safely. If someone referes it, we try to recover
the counter by adding MODULE_REF_BASE unless the counter becomes 0,
because the referrer can put the module right before recovering.
If the recovering is failed, we can get the 0 refcount and it
never be incremented again, it can be removed safely too.

Note that __module_get() forcibly gets the module refcounter,
users should use try_module_get() instead of that.
Signed-off-by: default avatarMasami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 2f35c41f
...@@ -42,7 +42,6 @@ ...@@ -42,7 +42,6 @@
#include <linux/vermagic.h> #include <linux/vermagic.h>
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/stop_machine.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/mutex.h> #include <linux/mutex.h>
...@@ -98,7 +97,7 @@ ...@@ -98,7 +97,7 @@
* 1) List of modules (also safely readable with preempt_disable), * 1) List of modules (also safely readable with preempt_disable),
* 2) module_use links, * 2) module_use links,
* 3) module_addr_min/module_addr_max. * 3) module_addr_min/module_addr_max.
* (delete uses stop_machine/add uses RCU list operations). */ * (delete and add uses RCU list operations). */
DEFINE_MUTEX(module_mutex); DEFINE_MUTEX(module_mutex);
EXPORT_SYMBOL_GPL(module_mutex); EXPORT_SYMBOL_GPL(module_mutex);
static LIST_HEAD(modules); static LIST_HEAD(modules);
...@@ -628,14 +627,23 @@ static char last_unloaded_module[MODULE_NAME_LEN+1]; ...@@ -628,14 +627,23 @@ static char last_unloaded_module[MODULE_NAME_LEN+1];
EXPORT_TRACEPOINT_SYMBOL(module_get); EXPORT_TRACEPOINT_SYMBOL(module_get);
/* MODULE_REF_BASE is the base reference count by kmodule loader. */
#define MODULE_REF_BASE 1
/* Init the unload section of the module. */ /* Init the unload section of the module. */
static int module_unload_init(struct module *mod) static int module_unload_init(struct module *mod)
{ {
/*
* Initialize reference counter to MODULE_REF_BASE.
* refcnt == 0 means module is going.
*/
atomic_set(&mod->refcnt, MODULE_REF_BASE);
INIT_LIST_HEAD(&mod->source_list); INIT_LIST_HEAD(&mod->source_list);
INIT_LIST_HEAD(&mod->target_list); INIT_LIST_HEAD(&mod->target_list);
/* Hold reference count during initialization. */ /* Hold reference count during initialization. */
atomic_set(&mod->refcnt, 1); atomic_inc(&mod->refcnt);
return 0; return 0;
} }
...@@ -734,39 +742,39 @@ static inline int try_force_unload(unsigned int flags) ...@@ -734,39 +742,39 @@ static inline int try_force_unload(unsigned int flags)
} }
#endif /* CONFIG_MODULE_FORCE_UNLOAD */ #endif /* CONFIG_MODULE_FORCE_UNLOAD */
struct stopref /* Try to release refcount of module, 0 means success. */
static int try_release_module_ref(struct module *mod)
{ {
struct module *mod; int ret;
int flags;
int *forced;
};
/* Whole machine is stopped with interrupts off when this runs. */ /* Try to decrement refcnt which we set at loading */
static int __try_stop_module(void *_sref) ret = atomic_sub_return(MODULE_REF_BASE, &mod->refcnt);
{ BUG_ON(ret < 0);
struct stopref *sref = _sref; if (ret)
/* Someone can put this right now, recover with checking */
ret = atomic_add_unless(&mod->refcnt, MODULE_REF_BASE, 0);
return ret;
}
static int try_stop_module(struct module *mod, int flags, int *forced)
{
/* If it's not unused, quit unless we're forcing. */ /* If it's not unused, quit unless we're forcing. */
if (module_refcount(sref->mod) != 0) { if (try_release_module_ref(mod) != 0) {
if (!(*sref->forced = try_force_unload(sref->flags))) *forced = try_force_unload(flags);
if (!(*forced))
return -EWOULDBLOCK; return -EWOULDBLOCK;
} }
/* Mark it as dying. */ /* Mark it as dying. */
sref->mod->state = MODULE_STATE_GOING; mod->state = MODULE_STATE_GOING;
return 0;
}
static int try_stop_module(struct module *mod, int flags, int *forced)
{
struct stopref sref = { mod, flags, forced };
return stop_machine(__try_stop_module, &sref, NULL); return 0;
} }
unsigned long module_refcount(struct module *mod) unsigned long module_refcount(struct module *mod)
{ {
return (unsigned long)atomic_read(&mod->refcnt); return (unsigned long)atomic_read(&mod->refcnt) - MODULE_REF_BASE;
} }
EXPORT_SYMBOL(module_refcount); EXPORT_SYMBOL(module_refcount);
...@@ -921,11 +929,11 @@ bool try_module_get(struct module *module) ...@@ -921,11 +929,11 @@ bool try_module_get(struct module *module)
if (module) { if (module) {
preempt_disable(); preempt_disable();
/* Note: here, we can fail to get a reference */
if (likely(module_is_live(module))) { if (likely(module_is_live(module) &&
atomic_inc(&module->refcnt); atomic_inc_not_zero(&module->refcnt) != 0))
trace_module_get(module, _RET_IP_); trace_module_get(module, _RET_IP_);
} else else
ret = false; ret = false;
preempt_enable(); preempt_enable();
...@@ -936,9 +944,12 @@ EXPORT_SYMBOL(try_module_get); ...@@ -936,9 +944,12 @@ EXPORT_SYMBOL(try_module_get);
void module_put(struct module *module) void module_put(struct module *module)
{ {
int ret;
if (module) { if (module) {
preempt_disable(); preempt_disable();
atomic_dec(&module->refcnt); ret = atomic_dec_if_positive(&module->refcnt);
WARN_ON(ret < 0); /* Failed to put refcount */
trace_module_put(module, _RET_IP_); trace_module_put(module, _RET_IP_);
preempt_enable(); preempt_enable();
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment