One of the main problems with modules is their version dependency, which was introduced in Chapter 2. 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.
Fortunately, the kernel developers found a flexible way to deal with version problems. The idea is that a module is incompatible with a different kernel version only if the software interface offered by the kernel has changed. The software interface, then, can be represented by a function prototype and the exact definition of all the data structures involved in the function call. Finally, a CRC algorithm can be used to map all the information about the software interface to a single 32-bit number.
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 like
version support is enabled, where
12345678 is the
hexadecimal representation of the checksum of the software interface
used by the function. When a module is loaded into the kernel,
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.
There are some limitations to this scheme. A common source of
surprises has been loading a module compiled for SMP systems into a
uniprocessor kernel, or vice versa. Because numerous inline functions
(e.g., spinlock operations) and symbols are defined differently for
SMP kernels, it is important that modules and the kernel agree on
whether they are built for SMP. Version 2.4 and recent 2.2 kernels
throw an extra
smp_ string onto each symbol when
compiling for SMP to catch this particular case. There are still
potential traps, however. Modules and the kernel can differ in which
version of the compiler was used to build them, which view of memory
they take, which version of the processor they were built for, and
more. The version support scheme can catch the most common problems,
but it still pays to be careful.
But let’s see what happens in both the kernel and the module when version support is enabled:
The module must be compiled using the mangled names, which appear in the object files as undefined symbols.
The loading program (insmod) matches the undefined symbols in the module with the public symbols in the kernel, thus using the version information.
Note that the kernel and the module must both agree on whether versioning is in use. If one is built for versioned symbols and the other isn’t, insmod will refuse to load the module.
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
gcc -DMODVERSIONS -include /usr/src/linux/include/linux/modversions.h...
After the header is included, whenever the module uses a kernel symbol, the compiler sees the mangled version.
To enable versioning in the module if it has been enabled in the
kernel, we must make sure that
has been defined in
header controls what features are enabled (compiled) in the current
CONFIG_ macro defined states that the
corresponding option is active.
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> #endif
When compiling the file against a versioned kernel, the symbol table
in the object file refers to versioned symbols, which match the ones
exported by the kernel itself. The following screendump shows the
symbol names stored in
master.o. In the output of
T means “text,”
D means “data,” and
“undefined.” The “undefined” tag denotes symbols that the object
file references but doesn’t declare.
00000034 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_1b7d4074
Because the checksums added to the symbol names in
master.o are derived from the entire prototypes
of printk and
request_module, the module is compatible with a
wide range of kernel versions. If, however, the data structures
related to either function get modified,
insmod will refuse to load the module
because of its incompatibility with the kernel.
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.
The task of parsing the header files and building the checksums is
performed by genksyms, a tool released with
the modutils package. This program receives
the output of the C preprocessor on its own standard input and prints
a new header file on standard output. The output file defines the
checksummed version of each symbol exported by the original source
file. The output of genksyms is usually
saved with a
.ver suffix; it is a good idea to stay
consistent with this practice.
To show you how symbols are exported, we have created two dummy
exports a simple function called export_function,
which is used by the second module,
This function receives two integer arguments and returns their
sum—we are not interested in the function, but rather in the
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 > $@
These lines demonstrate how to build
and add it to the dependencies of both object files, but only if
MODVERSIONS is defined. A few lines added to
Makefile take care of defining
MODVERSIONS if version support is enabled in the
kernel, but they are not worth showing here. The
-k option must be used to tell
genksyms which version of the kernel you
are working with. Its purpose is to determine the format of the
output file; it need not match the kernel you are using exactly.
One thing that is worth showing, however, is the definition of the
GKSMP symbol. As mentioned above, a prefix
-p smp_) is added to every checksum if the kernel
is built for SMP systems. The genksyms
utility does not add this prefix itself; it must be told explicitly to
do so. The following makefile code will cause the prefix to be set
ifdef CONFIG_SMP GENKSYMS += -p smp_ endif
The source file, then, must declare the right preprocessor symbols for
every conceivable preprocessor pass:
the input to genksyms and the actual
compilation, both with version support enabled and with it disabled.
export.c should be able to autodetect
version support in the kernel, as
The following lines show you how to do this successfully:
#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 */ #endif
The code, though hairy, has the advantage of leaving the makefile in a clean state. Passing the correct flags from make, on the other hand, involves writing long command lines for the various cases, which we won’t do here.
The 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
grep export /proc/ksymsc883605c export_function_Rsmp_888cb211 [export] morgana.root#
insmod ./import.oimport: my mate tells that 2+2 = 4 morgana.root#
cat /proc/modulesimport 312 0 (unused) export 620 0 [import]
 CRC means “cyclic redundancy check,” a way of generating a short, unique number from an arbitrary amount of data.
CONFIG_ macros are defined in
<linux/autoconf.h>. You should, however,
<linux/config.h> instead, because the
latter is protected from double inclusion, and sources