In this second part of the book, we discuss more advanced topics than we’ve seen up to now. Once again, we start with modularization.
The introduction to modularization in Chapter 2 was only part of the story; the kernel and the modutils package support some advanced features that are more complex than we needed earlier to get a basic driver up and running. The features that we talk about in this chapter include the kmod process and version support inside modules (a facility meant to save you from recompiling your modules each time you upgrade your kernel). We also touch on how to run user-space helper programs from within kernel code.
The implementation of demand loading of modules has changed significantly over time. This chapter discusses the 2.4 implementation, as usual. The sample code works, as far as possible, on the 2.0 and 2.2 kernels as well; we cover the differences at the end of the chapter.
To make it easier for users to load and unload modules, to avoid wasting kernel memory by keeping drivers in core when they are not in use, and to allow the creation of “generic” kernels that can support a wide variety of hardware, Linux offers support for automatic loading and unloading of modules. To exploit this feature, you need to enable kmod support when you configure the kernel before you compile it; most kernels from distributors come with kmod enabled. This ability to request additional modules when they are needed is particularly useful for drivers using module stacking.
The idea behind kmod is simple, yet effective. Whenever the kernel tries to access certain types of resources and finds them unavailable, it makes a special kernel call to the kmod subsystem instead of simply returning an error. If kmod succeeds in making the resource available by loading one or more modules, the kernel continues working; otherwise, it returns the error. Virtually any resource can be requested this way: char and block drivers, filesystems, line disciplines, network protocols, and so on.
One example of a driver that benefits from demand loading is the Advanced Linux Sound Architecture (ALSA) sound driver suite, which should (someday) replace the current sound implementation (Open Sound System, or OSS) in the Linux kernel.[42] ALSA is split into many pieces. The set of core code that every system needs is loaded first. Additional pieces get loaded depending on both the installed hardware (which sound card is present) and the desired functionality (MIDI sequencer, synthesizer, mixer, OSS compatibility, etc.). Thus, a large and complicated system can be broken down into components, with only the necessary parts being actually present in the running system.
Another common use of automatic module loading is to make a “one size fits all” kernel to package with distributions. Distributors want their kernels to support as much hardware as possible. It is not possible, however, to simply configure in every conceivable driver; the resulting kernel would be too large to load (and very wasteful of system memory), and having that many drivers trying to probe for hardware would be a near-certain way to create conflicts and confusion. With automatic loading, the kernel can adapt itself to the hardware it finds on each individual system.
Any kernel-space code can request the loading of a module when needed,
by invoking a facility known as kmod.
kmod was initially implemented as a
separate, standalone kernel process that handled module loading
requests, but it has long since been simplified by not requiring the
separate process context. To use kmod, you
must include <linux/kmod.h>
in your driver
source.
To request the loading of a module, call request_module:
int request_module(const char *module_name);
The module_name
can either be the name of a
specific module file or the name of a more generic capability; we’ll
look more closely at module names in the next section. The return
value from request_module will be 0, or one of
the usual negative error codes if something goes wrong.
Note that request_module is synchronous—it will sleep until the attempt to load the module has completed. This means, of course, that request_module cannot be called from interrupt context. Note also that a successful return from request_module does not guarantee that the capability you were after is now available. The return value indicates that request_module was successful in running modprobe, but does not reflect the success status of modprobe itself. Any number of problems or configuration errors can lead request_module to return a success status when it has not loaded the module you needed.
Thus the proper usage of request_module usually requires testing for the existence of a needed capability twice:
if ( (ptr = look_for_feature()) == NULL) { /* if feature is missing, create request string */ sprintf(modname, "fmt-for-feature-%i\n", featureid); request_module(modname); /* and try lo load it */ } /* Check for existence of the feature again; error if missing */ if ( (ptr = look_for_feature()) == NULL) return -ENODEV;
The first check avoids redundant calls to request_module. If the feature is not available in the running kernel, a request string is generated and request_module is used to look for it. The final check makes sure that the required feature has become available.
The actual task of loading a module requires help from user space, for the simple reason that it is far easier to implement the required degree of configurability and flexibility in that context. When the kernel code calls request_module, a new “kernel thread” process is created, which runs a helper program in the user context. This program is called modprobe; we have seen it briefly earlier in this book.
modprobe can do a great many things. In
the simplest case, it just calls insmod
with the name of a module as passed to
request_module. Kernel code, however, will often
call request_module with a more abstract name
representing a needed capability, such as
scsi_hostadapter
;
modprobe will then find and load the
correct module. modprobe can also handle
module dependencies; if a requested module requires yet another module
to function, modprobe will load
both—assuming that depmod -a was run
after the modules have been installed.[43]
The modprobe utility is configured by the
file /etc/modules.conf
.[44] See the
modules.conf
manpage for the full list of things
that can appear in this file. Here is an overview of the most common
sorts of entries:
-
path[misc]=
directory
This directive tells modprobe that miscellaneous modules can be found in the misc subdirectory under the given
directory
. Other paths worth setting includeboot
, which points to a directory of modules that should be loaded at boot time, andtoplevel
, which gives a top-level directory under which a tree of module subdirectories may be found. You almost certainly want to include a separatekeep
directive as well.-
keep
Normally, a
path
directive will cause modprobe to discard all other paths (including the defaults) that it may have known about. By placing akeep
before anypath
directives, you can cause modprobe to add new paths to the list instead of replacing it.-
alias
alias_name
real_name
Causes modprobe to load the module
real_name
when asked to loadalias_name
. The alias name usually identifies a specific capability; it has values such asscsi_hostadapter
,eth0
, orsound
. This is the means by which generic requests (“a driver for the first Ethernet card”) get mapped into specific modules. Alias lines are usually created by the system installation process; once it has figured out what hardware a specific system has, it generates the appropriatealias
entries to get the right drivers loaded.-
options [-k]
module
opts
Provides a set of options (
opts
) for the givenmodule
when it is loaded. If the -k flag is provided, the module will not be automatically removed by a modprobe -r run.-
pre-install
,module
command
post-install
,module
command
pre-remove
,module
command
post-remove
module
command
The first two specify a
command
to be run either before or after the givenmodule
is installed; the second two run the command before or after module removal. These directives are useful for causing extra user-space processing to happen or for running a required daemon process. The command should be given as a full pathname to avoid possible problems.Note that, for the removal commands to be run, the module must be removed with modprobe. They will not be run if the module is removed with rmmod, or if the system goes down (gracefully or otherwise).
modprobe supports far more directives than we have listed here, but the others are generally only needed in complicated situations.
A typical /etc/modules.conf
looks like this:
alias scsi_hostadapter aic7xxx alias eth0 eepro100 pre-install pcmcia_core /etc/rc.d/init.d/pcmcia start options short irq=1 alias sound es1370
This file tells modprobe which drivers to load to make the SCSI system, Ethernet, and sound cards work. It also ensures that if the PCMCIA drivers are loaded, a startup script is invoked to run the card services daemon. Finally, an option is provided to be passed to the short driver.
The loading of a module into the kernel has obvious security implications, since the loaded code runs at the highest possible privilege level. For this reason, it is important to be very careful in how you work with the module-loading system.
When editing the modules.conf
file, one should
always keep in mind that anybody who can load kernel modules has
complete control over the system. Thus, for example, any directories
added to the load path should be very carefully protected, as should
the modules.conf
file itself.
Note that insmod will normally refuse to
load any modules that are not owned by the root account; this behavior
is an attempt at a defense against an attacker who obtains write
access to a module directory. You can override this check with an
option to insmod (or a
modules.conf
line), but doing so reduces the
security of your system.
One other thing to keep in mind is that the module name parameter that you pass to request_module eventually ends up on the modprobe command line. If that module name is provided by a user-space program in any way, it must be very carefully validated before being handed off to request_module. Consider, for example, a system call that configures network interfaces. In response to an invocation of ifconfig, this system call tells request_module to load the driver for the (user-specified) interface. A hostile user can then carefully choose a fictitious interface name that will cause modprobe to do something improper. This is a real vulnerability that was discovered late in the 2.4.0-test development cycle; the worst problems have been cleaned up, but the system is still vulnerable to malicious module names.
Let’s now try to use the demand-loading functions in practice. To
this end, we’ll use two modules called
master and
slave, found in the directory
misc-modules
in the source files provided on the
O’Reilly FTP site.
In order to run this test code without installing the modules in the
default module search path, you can add something like the following
lines to your /etc/modules.conf
:
keep path[misc]=~rubini/driverBook/src/misc-modules
The slave module performs no function; it just takes up space until removed. The master module, on the other hand, looks like this:
#include <linux/kmod.h> #include "sysdep.h" int master_init_module(void) { int r[2]; /* results */ r[0]=request_module("slave"); r[1]=request_module("nonexistent"); printk(KERN_INFO "master: loading results are %i, %i\n", r[0],r[1]); return 0; /* success */ } void master_cleanup_module(void) { }
At load time, master tries to load two modules: the slave module and one that doesn’t exist. The printk messages reach your system logs and possibly the console. This is what happens in a system configured for kmod support when the daemon is active and the commands are issued on the text console:
morgana.root#depmod -a
morgana.root#insmod ./master.o
master: loading results are 0, 0 morgana.root#cat /proc/modules
slave 248 0 (autoclean) master 740 0 (unused) es1370 34832 1
Both the return value from request_module and the
/proc/modules
file (described in Section 2.4 in Chapter 2) show that the
slave module has been correctly loaded. Note, however, that the
attempt to load nonexistent
also shows a successful
return value. Because modprobe was run,
request_module returns success, regardless of
what happened to modprobe.
A subsequent removal of master will produce results like the following:
morgana.root#rmmod master
morgana.root#cat /proc/modules
slave 248 0 (autoclean) es1370 34832 1
The slave module has been left behind in the kernel, where it will remain until the next module cleanup pass is done (which is often never on modern systems).
As we have seen, the request_module function runs
a program in user mode (i.e., running as a separate process, in an
unprivileged processor mode, and in user space) to help it get its job
done. In the 2.3 development series, the kernel developers made the
“run a user-mode helper” capability available to the rest of the
kernel code. Should your driver need to run a user-mode program to
support its operations, this mechanism is the way to do it. Since
it’s part of the kmod implementation, we’ll
look at it here. If you are interested in this capability, a look at
kernel/kmod.c
is recommended; it’s not much code
and illustrates nicely the use of user-mode helpers.
The interface for running helper programs is fairly simple. As of kernel 2.4.0-test9, there is a function call_usermodehelper; it is used primarily by the hot-plug subsystem (i.e., for USB devices and such) to perform module loading and configuration tasks when a new device is attached to the system. Its prototype is:
int call_usermodehelper(char *path, char **argv, char **envp);
The arguments will be familiar: they are the name of the executable to
run, arguments to pass to it (argv[0]
, by
convention, is the name of the program itself), and the values of any
environment variables. Both arrays must be terminated by
NULL
values, just like with the
execve system call.
call_usermodehelper will sleep until the program
has been started, at which point it returns the status of the
operation.
Helper programs run in this mode are actually run as children of a kernel thread called keventd. An important implication of this design is that there is no way for your code to know when the helper program has finished or what its exit status is. Running helper programs is thus a bit of an act of faith.
It is worth pointing out that truly legitimate uses of user-mode helper programs are rare. In most cases, it is better to set up a script to be run at module installation time that does all needed work as part of loading the module rather than to wire invocations of user-mode programs into kernel code. This sort of policy is best left to the user whenever possible.
[42] The ALSA drivers can be found at http://www.alsa-project.org.
[43] Most distributions run depmod -a automatically at boot time, so you don’t need to worry about that unless you installed new modules after you rebooted. See the modprobe documentation for more details.
[44] On older
systems, this file is often called
/etc/conf.modules
instead. That name still
works, but its use is deprecated.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.