another (!) new kmod.c

Mikael Pettersson (Mikael.Pettersson@sophia.inria.fr)
Thu, 16 Apr 1998 10:15:17 +0200 (MET DST)


Another new kmod? Do we really need that..
Well, this one (sales pitch mode on):
* Is reentrant.
* Is implemented using a very small extension of the stock 2.1.9x
kmod: instead of having the kmod thread itself spawn modprobe
and wait for it to finish (which is why it isn't reentrant),
kmod spawns a "loader" thread, which in turns carries out all
the necessary steps. In the mean time, kmod may pick up new work.
* Doesn't bring any new problems, like having to fiddle with
work-queues or having to close() file descriptors.
* As of yesterday, it uses the CLONE_FS trick to allow it to work
properly even if you boot via initrd. (As does the stock 2.1.96)
* Has been running without a single hitch on my UP PC since Sunday.
(Testers on SMP machines are most welcome.)
* Is quite elegant, IMHO of course :-)

Patch against vanilla 2.1.96 follows below.
(If you've patched that bogus character on line 116,
the diff will probably fail.)

Cheers,

/Mikael

--- linux-2.1.96/kernel/kmod.c.orig Wed Apr 15 16:37:32 1998
+++ linux-2.1.96/kernel/kmod.c Thu Apr 16 09:55:15 1998
@@ -3,27 +3,54 @@
Kirk Petersen
*/

+/*
+ * Rewritten by Mikael Pettersson to make kmod able to handle
+ * multiple pending requests.
+ *
+ * The basic idea is to have the main kmod thread create a separate
+ * thread (the "loader") to carry out each individual request to load
+ * a module. This separation allows kmod to handle several pending
+ * requests simultaneously without having to maintain work-queues.
+ * It also prevents the deadlock that would occur should modprobe
+ * itself trigger request_module().
+ *
+ * Like the stock kmod as of kernel 2.1.96, we ensure that the kmod
+ * thread uses the same "cwd" as init. This allows kmod to function
+ * even if the kernel boots via initrd.
+ *
+ * (It's easy to remove the autoclean "feature", should that be desirable.)
+ */
+
#define __KERNEL_SYSCALLS__

#include <linux/sched.h>
#include <linux/types.h>
#include <linux/unistd.h>
+#include <linux/init.h>

/*
kmod_unload_delay and modprobe_path are set via /proc/sys.
*/
int kmod_unload_delay = 60;
char modprobe_path[256] = "/sbin/modprobe";
-static char module_name[64] = "";
-static char * argv[] = { modprobe_path, "-s", "-k", module_name, NULL };
static char * envp[] = { "HOME=/", "TERM=linux", "PATH=/usr/bin:/bin", NULL };

/*
- kmod_queue synchronizes the kmod thread and the rest of the system
- kmod_unload_timer is what we use to unload modules
- after kmod_unload_delay seconds
-*/
-static struct wait_queue * kmod_queue = NULL;
+ * kmod_do_work is used to wake up the kmod thread.
+ * kmod_request is used to pass a request from request_module() to
+ * the kmod thread, and kmod_request_empty is used for synchronization.
+ * kmod_unload_timer and kmod_do_unload is what we use to unload
+ * modules after kmod_unload_delay seconds.
+ */
+struct kmod_request {
+ char module_name[64];
+ int status; /* 0 or negative error code */
+ struct wait_queue *done; /* request_module waits here */
+};
+static struct semaphore kmod_do_work = MUTEX_LOCKED; /* INIT(0) */
+static struct semaphore kmod_request_empty = MUTEX; /* INIT(1) */
+static struct kmod_request *kmod_request = NULL;
+static int kmod_do_unload = 0;
static struct timer_list kmod_unload_timer;

/*
@@ -33,65 +60,114 @@
*/
static int kmod_exec_modprobe(void * data)
{
+ struct kmod_request *req = data;
+ char *argv[5];
+
sigemptyset(&current->blocked);
- execve(modprobe_path, argv, envp);
- printk(KERN_ERR "kmod: failed to load module %s\n", module_name);
+ argv[0] = modprobe_path;
+ argv[1] = "-s";
+ argv[2] = "-k";
+ argv[3] = req->module_name;
+ argv[4] = NULL;
+ req->status = execve(modprobe_path, argv, envp);
+ printk(KERN_ERR "kmod: failed to exec %s for module %s, errno %d\n",
+ modprobe_path, req->module_name, -req->status);
return 0;
}

/*
- kmod_thread is the thread that does most of the work. kmod_unload and
- request_module tell it to wake up and do work.
-*/
-static int kmod_thread(void * data)
+ * Each request to load a module causes an instance of
+ * kmod_loader to run in a separate thread.
+ */
+static int kmod_loader(void *data)
{
+ struct kmod_request *req = data;
int pid;

- /*
- Initialize basic thread information
- */
current->session = 1;
current->pgrp = 1;
- sprintf(current->comm, "kmod");
+ sprintf(current->comm, "kmod_loader");
sigfillset(&current->blocked);

- /*
- This is the main kmod_thread loop. It first sleeps, then
- handles requests from request_module or kmod_unload.
- */
+ pid = kernel_thread(kmod_exec_modprobe, req, SIGCHLD);
+ if( pid > 0 ) {
+ waitpid(pid, NULL, 0);
+ } else {
+ req->status = pid;
+ printk(KERN_ERR "kmod_loader: fork failed, errno %d\n", -pid);
+ }
+ wake_up(&req->done);
+ return 0;
+}

- while (1) {
- interruptible_sleep_on(&kmod_queue);
+/*
+ * kmod_thread is the thread that picks up work requests.
+ * If awakened by kmod_unload, it calls delete_module(NULL).
+ * If awakened by request_module, it spawns off an instance
+ * of kmod_loader to carry out that request.
+ */
+static int kmod_thread(void * data)
+{
+ struct k_sigaction sigact;
+ struct kmod_request *req;
+ int status;

+ current->session = 1;
+ current->pgrp = 1;
+ sprintf(current->comm, "kmod");
+ sigfillset(&current->blocked);
+
+ /* receive SIGCHLD when child (kmod_loader) processes exit */
+ sigdelset(&current->blocked, SIGCHLD);
+ sigemptyset(&sigact.sa.sa_mask);
+ sigact.sa.sa_flags = 0;
+ sigact.sa.sa_restorer = NULL;
+ sigact.sa.sa_handler = SIG_IGN;
+ do_sigaction(SIGCHLD, &sigact, NULL);
+
+ for(;;) {
+ status = down_interruptible(&kmod_do_work);
/*
- If request_module woke us up, we should try to
- load module_name. If not, kmod_unload woke us up,
- do call delete_module.
- (if somehow both want us to do something, ignore the
- delete_module request)
- */
- if (module_name[0] == '\0') {
+ * We can be awakened for three different reasons:
+ * the exit of a child (kmod_loader) process,
+ * the expiration of kmod_unload_timer,
+ * or a call to request_module().
+ * The latter two may occur simultaneously.
+ */
+ if( status < 0 ) { /* -EINTR from kernel/sched.c:__do_down() */
+ flush_signals(current);
+ while( waitpid(-1, NULL, WNOHANG) > 0 )
+ ;
+ continue;
+ }
+ req = kmod_request;
+ if( req != NULL ) { /* request_module() woke us up */
+ kmod_request = NULL;
+ kmod_request = NULL;
+ up(&kmod_request_empty);
+ }
+ if( kmod_do_unload ) { /* kmod_unload() woke us up */
+ kmod_do_unload = 0;
delete_module(NULL);
- } else {
- pid = kernel_thread(kmod_exec_modprobe, NULL, SIGCHLD);
- if (pid > 0) {
- waitpid(pid, NULL, 0);
- module_name[0] = '\0';
- wake_up(&kmod_queue);
- } else {
- printk(KERN_ERR "kmod: fork failed, errno %d\n", -pid);
- }
+ }
+ if( req == NULL )
+ continue;
+ status = kernel_thread(kmod_loader, req, SIGCHLD);
+ if( status < 0 ) {
+ req->status = status;
+ printk(KERN_ERR "kmod: fork failed, errno %d\n", -status);
+ wake_up(&req->done);
}
}
-
- return 0; /* Never reached. */
+ /*NOTREACHED*/
+ return 0;
}

/*
kmod_unload is the function that the kernel calls when
the kmod_unload_timer expires
*/
-void kmod_unload(unsigned long x)
+static void kmod_unload(unsigned long x)
{
/*
wake up the kmod thread, which does the work
@@ -99,21 +175,23 @@
we are in the bottom half of the kernel (right?))
once it is awake, reset the timer
*/
- wake_up(&kmod_queue);
+ kmod_do_unload = 1;
+ up(&kmod_do_work);
kmod_unload_timer.expires = jiffies + (kmod_unload_delay * HZ);
add_timer(&kmod_unload_timer);
}

-int kmod_init(void)
+__initfunc(int kmod_init(void))
{
- printk("Starting kmod\n");
+ printk(KERN_INFO "Starting kmod\n");

/*
* CLONE_FS means that our "cwd" will follow that of init.
* CLONE_FILES just saves some space (we don't need any
- * new file descriptors). Ditto for CLONE_SIGHAND.
+ * new file descriptors). We will need our own signal
+ * handling state, though.
*/
- kernel_thread(kmod_thread, NULL, CLONE_FILES | CLONE_FS | CLONE_SIGHAND);
+ kernel_thread(kmod_thread, NULL, CLONE_FILES | CLONE_FS);

kmod_unload_timer.next = NULL;
kmod_unload_timer.prev = NULL;
@@ -131,24 +209,21 @@
*/
int request_module(const char * name)
{
- /* first, copy the name of the module into module_name */
- /* then wake_up() the kmod daemon */
- /* wait for the kmod daemon to finish (it will wake us up) */
-
/*
- kmod_thread is sleeping, so start by copying the name of
- the module into module_name. Once that is done, wake up
- kmod_thread.
- */
- strncpy(module_name, name, sizeof(module_name));
- module_name[sizeof(module_name)-1] = '\0';
- wake_up(&kmod_queue);
+ * Will our kernel stack be resident even if the process
+ * gets swapped out? If not, change this to use kmalloc()
+ */
+ struct kmod_request req;

- /*
- Now that we have told kmod_thread what to do, we want to
- go to sleep and let it do its work. It will wake us up,
- at which point we will be done (the module will be loaded).
- */
- interruptible_sleep_on(&kmod_queue);
- return 0;
+ strncpy(req.module_name, name, sizeof req.module_name);
+ req.module_name[(sizeof req.module_name)-1] = '\0';
+ req.status = 0;
+ req.done = NULL;
+
+ down(&kmod_request_empty);
+ kmod_request = &req;
+ up(&kmod_do_work);
+
+ interruptible_sleep_on(&req.done);
+ return req.status;
}

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu