[RFC PATCH 1/1] module: Make wait module's refcount to zero procedure as async

From: Peter Chen
Date: Wed Sep 11 2013 - 23:18:00 EST


Currently, if module's refcount is not zero during the unload,
it waits there until the user decreases that refcount. Assume
we have two modules (A & B), there are no symbol relationship
between each other. module B is module A's user, if the end
user tries to unload module A first wrongly, it will stop there
until there is another user process to unload B, and this
process can't be killed.
One use case is: the QA engineers do error test, they unload
module wrongly on purpose, after that, they find the console
is stopped there, and they can't do any thing go on.

Signed-off-by: Peter Chen <peter.chen@xxxxxxxxxxxxx>
---
include/linux/module.h | 4 +-
kernel/module.c | 61 ++++++++++++++++++++++++++---------------------
2 files changed, 36 insertions(+), 29 deletions(-)

diff --git a/include/linux/module.h b/include/linux/module.h
index 05f2447..12edf07 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -367,8 +367,8 @@ struct module
/* What modules do I depend on? */
struct list_head target_list;

- /* Who is waiting for us to be unloaded */
- struct task_struct *waiter;
+ /* The work for waiting refcount to zero */
+ struct work_struct wait_refcount_work;

/* Destruction function. */
void (*exit)(void);
diff --git a/kernel/module.c b/kernel/module.c
index dc58274..94abc7e 100644
--- a/kernel/module.c
+++ b/kernel/module.c
@@ -61,6 +61,7 @@
#include <linux/pfn.h>
#include <linux/bsearch.h>
#include <linux/fips.h>
+#include <linux/delay.h>
#include <uapi/linux/module.h>
#include "module-internal.h"

@@ -644,8 +645,6 @@ static int module_unload_init(struct module *mod)

/* Hold reference count during initialization. */
__this_cpu_write(mod->refptr->incs, 1);
- /* Backwards compatibility macros put refcount during init. */
- mod->waiter = current;

return 0;
}
@@ -813,19 +812,38 @@ EXPORT_SYMBOL(module_refcount);
/* This exists whether we can unload or not */
static void free_module(struct module *mod);

+/* Final destruction now no one is using it. */
+static void module_final_free(struct module *mod)
+{
+ if (mod->exit != NULL)
+ mod->exit();
+ blocking_notifier_call_chain(&module_notify_list,
+ MODULE_STATE_GOING, mod);
+ async_synchronize_full();
+
+ /* Store the name of the last unloaded module for diagnostic purposes */
+ strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module));
+
+ free_module(mod);
+}
+
static void wait_for_zero_refcount(struct module *mod)
{
- /* Since we might sleep for some time, release the mutex first */
- mutex_unlock(&module_mutex);
for (;;) {
pr_debug("Looking at refcount...\n");
- set_current_state(TASK_UNINTERRUPTIBLE);
if (module_refcount(mod) == 0)
break;
- schedule();
+ msleep(1000);
}
- current->state = TASK_RUNNING;
- mutex_lock(&module_mutex);
+ module_final_free(mod);
+}
+
+static void wait_module_refcount(struct work_struct *work)
+{
+ struct module *mod = container_of(work,
+ struct module, wait_refcount_work);
+
+ wait_for_zero_refcount(mod);
}

SYSCALL_DEFINE2(delete_module, const char __user *, name_user,
@@ -859,8 +877,7 @@ SYSCALL_DEFINE2(delete_module, const char __user *, name_user,

/* Doing init or already dying? */
if (mod->state != MODULE_STATE_LIVE) {
- /* FIXME: if (force), slam module count and wake up
- waiter --RR */
+ /* FIXME: if (force), slam module count */
pr_debug("%s already dying\n", mod->name);
ret = -EBUSY;
goto out;
@@ -876,30 +893,23 @@ SYSCALL_DEFINE2(delete_module, const char __user *, name_user,
}
}

- /* Set this up before setting mod->state */
- mod->waiter = current;
-
/* Stop the machine so refcounts can't move and disable module. */
ret = try_stop_module(mod, flags, &forced);
if (ret != 0)
goto out;

/* Never wait if forced. */
- if (!forced && module_refcount(mod) != 0)
- wait_for_zero_refcount(mod);
+ if (!forced && module_refcount(mod) != 0) {
+ INIT_WORK(&mod->wait_refcount_work, wait_module_refcount);
+ schedule_work(&mod->wait_refcount_work);
+ ret = -EBUSY;
+ goto out;
+ }

mutex_unlock(&module_mutex);
- /* Final destruction now no one is using it. */
- if (mod->exit != NULL)
- mod->exit();
- blocking_notifier_call_chain(&module_notify_list,
- MODULE_STATE_GOING, mod);
- async_synchronize_full();

- /* Store the name of the last unloaded module for diagnostic purposes */
- strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module));
+ module_final_free(mod);

- free_module(mod);
return 0;
out:
mutex_unlock(&module_mutex);
@@ -1005,9 +1015,6 @@ void module_put(struct module *module)
__this_cpu_inc(module->refptr->decs);

trace_module_put(module, _RET_IP_);
- /* Maybe they're waiting for us to drop reference? */
- if (unlikely(!module_is_live(module)))
- wake_up_process(module->waiter);
preempt_enable();
}
}
--
1.7.1


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/