[LWN Logo]

Date:	Thu, 16 Apr 1998 10:15:17 +0200 (MET DST)
From:	Mikael Pettersson <Mikael.Pettersson@sophia.inria.fr>
To:	torvalds@transmeta.com, linux-kernel@vger.rutgers.edu
Subject: another (!) new kmod.c

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