Библиотека сайта rus-linux.net
Linux Device Drivers, 2nd EditionBy Alessandro Rubini & Jonathan Corbet2nd Edition June 2001 0-59600-008-1, Order Number: 0081 586 pages, $39.95 |
Chapter 11
kmod and Advanced ModularizationContents:
Loading Modules on Demand
Intermodule Communication
Version Control in Modules
Backward Compatibility
Quick ReferenceIn this second part of the book, we discuss more advanced topics than we've seen up to now. Once again, we start with modularization.
Loading Modules on Demand
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.
Requesting Modules in the Kernel
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);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.
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 User-Space Side
modprobe can do a great many things. In the simplest case, it just calls insmodwith 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]
path[misc]=directory
This directive tells modprobe that miscellaneous modules can be found in the miscsubdirectory under the given directory. Other paths worth setting include
boot
, 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
alias alias_name real_name
options [-k] module opts
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 given module 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.
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 es1370Module Loading and Security
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.
Module Loading Example
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.
keep path[misc]=~rubini/driverBook/src/misc-modules#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) { }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 1A 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 1Running User-Mode Helper Programs
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);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.
Intermodule Communication
Very late in the pre-2.4.0 development series, the kernel developers added a new interface providing limited communication between modules. This intermodule scheme allows modules to register strings pointing to data of interest, which can be retrieved by other modules. We'll look briefly at this interface, using a variation of our master and slavemodules.
static char *string = "inter says 'Hello World'"; void ime_function(const char *who) { printk(KERN_INFO "inter: ime_function called by %s\n", who); } int ime_init(void) { inter_module_register("ime_string", THIS_MODULE, string); inter_module_register("ime_function", THIS_MODULE, ime_function); return 0; } void ime_cleanup(void) { inter_module_unregister("ime_string"); inter_module_unregister("ime_function"); }This code uses inter_module_register, which has this prototype:
void inter_module_register(const char *string, struct module *module, const void *data);void inter_module_unregister(const char *string);Two functions are exported that can access data shared via inter_module_register:
const void *inter_module_get(const char *string);
const void *inter_module_get_request(const char *string, const char *module);
This function is like inter_module_get with the added feature that, if the given
string
is not found, it will call request_module with the givenmodule
name and then will try again.
Both functions also increment the usage count for the module that registered the data. Thus, a pointer obtained with inter_module_get or inter_module_get_request will remain valid until it is explicitly released. At least, the module that created that pointer will not be unloaded during that time; it is still possible for the module itself to do something that will invalidate the pointer.
void inter_module_put(const char *string);will release the pointer, which should not be used after this call.
static const char *ime_string = NULL; static void master_test_inter(); void master_test_inter() { void (*ime_func)(); ime_string = inter_module_get_request("ime_string", "inter"); if (ime_string) printk(KERN_INFO "master: got ime_string '%s'\n", ime_string); else printk(KERN_INFO "master: inter_module_get failed"); ime_func = inter_module_get("ime_function"); if (ime_func) { (*ime_func)("master"); inter_module_put("ime_function"); } } void master_cleanup_module(void) { if (ime_string) inter_module_put("ime_string"); }There are a few other worthwhile details to keep in mind when using the intermodule functions. First, they are available even in kernels that have been configured without support for loadable modules, so there is no need for a bunch of
#ifdef
lines to test for that case. The namespace implemented by the intermodule communication functions is global, so names should be chosen with care or conflicts will result. Finally, intermodule data is stored in a simple linked list; performance will suffer if large numbers of lookups are made or many strings are stored. This facility is intended for light use, not as a general dictionary subsystem.Version Control in Modules
One of the main problems with modules is their version dependency, which was introduced in Chapter 2, "Building and Running Modules". The need to recompile the module against the headers of each kernel version being used can become a real pain when you run several custom modules, and recompiling is not even possible if you run a commercial module distributed in binary form.
The issue of version dependencies is thus handled by mangling the name of each symbol exported by the kernel to include the checksum of all the information related to that symbol. This information is obtained by parsing the header files and extracting the information from them. This facility is optional and can be enabled at compilation time. Modular kernels shipped by Linux distributors usually have versioning support enabled.
For example, the symbol
printk
is exported to modules as something likeprintk_R12345678
when version support is enabled, where12345678
is the hexadecimal representation of the checksum of the software interface used by the function. When a module is loaded into the kernel, insmod (or modprobe) can accomplish its task only if the checksum added to each symbol in the kernel matches the one added to the same symbol in the module.But let's see what happens in both the kernel and the module when version support is enabled:
The public symbol table is built using the versioned names, and this is what appears in /proc/ksyms.
Using Version Support in Modules
Driver writers must add some explicit support if their modules are to work with versioning. Version control can be inserted in one of two places: in the makefile or in the source itself. Since the documentation of the modutils package describes how to do it in the makefile, we'll show you how to do it in the C source. The master module used to demonstrate how kmod works is able to support versioned symbols. The capability is automatically enabled if the kernel used to compile the module exploits version support.
The main facility used to mangle symbol names is the header
<linux/modversions.h>
, which includes preprocessor definitions for all the public kernel symbols. This file is generated as part of the kernel compilation (actually, "make depend'') process; if your kernel has never been built, or is built without version support, there will be little of interest inside.<linux/modversions.h>
must be included before any other header file, so place it first if you put it directly in your driver source. The usual technique, however, is to tell gcc to prepend the file with a compilation command like:gcc -DMODVERSIONS -include /usr/src/linux/include/linux/modversions.h...To enable versioning in the module if it has been enabled in the kernel, we must make sure that
CONFIG_MODVERSIONS
has been defined in<linux/config.h>
. That header controls what features are enabled (compiled) in the current kernel. EachCONFIG_
macro defined states that the corresponding option is active.[46]The initial part of master.c, therefore, consists of the following lines:
#include <linux/config.h> /* retrieve the CONFIG_* macros */ #if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS) # define MODVERSIONS /* force it on */ #endif #ifdef MODVERSIONS # include <linux/modversions.h> #endif00000034 T cleanup_module 00000000 t gcc2_compiled. 00000000 T init_module 00000034 T master_cleanup_module 00000000 T master_init_module U printk_Rsmp_1b7d4074 U request_module_Rsmp_27e4dc04 morgana% fgrep 'printk' /proc/ksyms c011b8b0 printk_Rsmp_1b7d4074Exporting Versioned Symbols
The one thing not covered by the previous discussion is what happens when a module exports symbols to be used by other modules. If we rely on version information to achieve module portability, we'd like to be able to add a CRC code to our own symbols. This subject is slightly trickier than just linking to the kernel, because we need to export the mangled symbol name to other modules; we need a way to build the checksums.
ifdef CONFIG_MODVERSIONS export.o import.o: export.ver endif export.ver: export.c $(CC) -I$(INCLUDEDIR) $(CFLAGS) -E -D__GENKSYMS__ $^ | \ $(GENKSYMS) -k 2.4.0 > $@ifdef CONFIG_SMP GENKSYMS += -p smp_ endif#include <linux/config.h> /* retrieve the CONFIG_* macros */ #if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS) # define MODVERSIONS #endif /* * Include the versioned definitions for both kernel symbols and our * symbol, *unless* we are generating checksums (__GENKSYMS__ * defined) */ #if defined(MODVERSIONS) && !defined(__GENKSYMS__) # include <linux/modversions.h> # include "export.ver" /* redefine "export_function" to include CRC */ #endifThe simple import module calls export_function by passing the numbers 2 and 2 as arguments; the expected result is therefore 4. The following example shows that import actually links to the versioned symbol of export and calls the function. The versioned symbol appears in /proc/ksyms.
morgana.root#insmod ./export.o
morgana.root#grep export /proc/ksyms
c883605c export_function_Rsmp_888cb211 [export] morgana.root#insmod ./import.o
import: my mate tells that 2+2 = 4 morgana.root#cat /proc/modules
import 312 0 (unused) export 620 0 [import]Backward Compatibility
The request_module function, however, remained unchanged, as did all aspects of the modules themselves. It was, however, necessary to include
<linux/kerneld.h>
instead of<linux/kmod.h>
.Quick Reference
This chapter introduced the following kernel symbols.
/etc/modules.conf
This is the configuration file for modprobeand depmod. It is used to configure demand loading and is described in the manpages for the two programs.
#include <linux/kmod.h>
int request_module(const char *name);
void inter_module_register(const char *string, struct module *module, const void *data);
void inter_module_unregister(const char *);
inter_module_register makes data available to other modules via the inter-module communication system. When the data is no longer to be shared, inter_module_unregister will end that availability.
const void *inter_module_get(const char *string);
const void *inter_module_get_request(const char *string, const char *module);
void inter_module_put(const char *string);
The first two functions look up a string in the intermodule communication system; inter_module_get_requestalso attempts to load the given
module
if the string is not found. Both increment the usage count of the module that exported the string; inter_module_put should be called to decrement it when the data pointer is no longer needed.
#include <linux/config.h>
CONFIG_MODVERSIONS
This macro is defined only if the current kernel has been compiled to support versioned symbols.
#ifdef MODVERSIONS
#include <linux/modversions.h>
This header, which exists only if
CONFIG_MODVERSIONS
is valid, contains the versioned names for all the symbols exported by the kernel.
__GENKSYMS__
int call_usermodehelper(char *path, char *argv[], char *envp[]);
This function runs a user-mode program in the keventd process context.
Back to: Linux Device Drivers, 2nd Edition
oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy
╘ 2001, O'Reilly & Associates, Inc.