Linux Device Drivers 2nd Edition (779877), страница 76
Текст из файла (страница 76)
This subject is slightly trickier than just linking to the kernel,because we need to export the mangled symbol name to other modules; we needa way to build the checksums.The task of parsing the header files and building the checksums is performed bygenksyms, a tool released with the modutils package. This program receives theoutput of the C preprocessor on its own standard input and prints a new headerfile on standard output. The output file defines the checksummed version of eachsymbol exported by the original source file. The output of genksyms is usuallysaved 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 modulescalled export.c and import.c. export exports a simple function called export_function, which is used by the second module, import.c.
This function receives twointeger arguments and returns their sum—we are not interested in the function,but rather in the linking process.The makefile in the misc-modules directory has a rule to build an export.ver filefrom export.c, so that the checksummed symbol for export_function can be usedby the import module:ifdef CONFIG_MODVERSIONSexport.o import.o: export.verendifexport.ver: export.c$(CC) -I$(INCLUDEDIR) $(CFLAGS) -E -D_ _GENKSYMS_ _ $ˆ | \$(GENKSYMS) -k 2.4.0 > $@These lines demonstrate how to build export.ver and add it to the dependencies ofboth 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 tellgenksyms which version of the kernel you are working with.
Its purpose is todetermine the format of the output file; it need not match the kernel you are usingexactly.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 kernelis built for SMP systems. The genksyms utility does not add this prefix itself; it mustbe told explicitly to do so. The following makefile code will cause the prefix to beset appropriately:31722 June 2001 16:40http://openlib.org.uaChapter 11: kmod and Advanced Modularizationifdef CONFIG_SMPGENKSYMS += -p smp_endifThe source file, then, must declare the right preprocessor symbols for every conceivable preprocessor pass: the input to genksyms and the actual compilation, bothwith version support enabled and with it disabled. Moreover, export.c should beable to autodetect version support in the kernel, as master.c does.
The followinglines 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 */#endifThe 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 2as arguments; the expected result is therefore 4.
The following example shows thatimport actually links to the versioned symbol of export and calls the function. Theversioned symbol appears in /pr oc/ksyms.morgana.root# insmod ./export.omorgana.root# grep export /proc/ksymsc883605c export_function_Rsmp_888cb211 [export]morgana.root# insmod ./import.oimport: my mate tells that 2+2 = 4morgana.root# cat /proc/modulesimport3120 (unused)export6200 [import]Backward CompatibilityThe demand-loading capability was entirely reimplemented in the 2.1 development series.
Fortunately, very few modules need to be aware of the change in anyway. For completeness, however, we will describe the old implementation here.31822 June 2001 16:40http://openlib.org.uaQuick ReferenceIn the 2.0 days, demand loading was handled by a separate, user-space daemonprocess called ker neld. This process connected into the kernel via a special interface and received module load (and unload) requests as they were generated bykernel code. There were numerous disadvantages to this scheme, including thefact that no modules could be loaded until the system initialization process hadgotten far enough to start ker neld.The request_module function, however, remained unchanged, as did all aspects ofthe modules themselves.
It was, however, necessary to include <linux/kerneld.h> instead of <linux/kmod.h>.Symbol versioning in the 2.0 kernel did not use the smp_ prefix on SMP systems.As a result, insmod would happily load an SMP module into a uniprocessor kernel, or vice versa. The usual result of such a mismatch was extreme chaos.The ability to run user-mode helper programs and the intermodule communicationmechanism did not exist until Linux 2.4.Quick ReferenceThis chapter introduced the following kernel symbols./etc/modules.confThis is the configuration file for modpr obe and depmod.
It is used to configuredemand loading and is described in the manpages for the two programs.#include <linux/kmod.h>int request_module(const char *name);This function performs demand loading of modules.void inter_module_register(const char *string, struct module*module, const void *data);void inter_module_unregister(const char *);inter_module_r egister makes data available to other modules via the intermodule communication system.
When the data is no longer to be shared,inter_module_unr egister 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_r equest also attempts to load the given module if thestring is not found. Both increment the usage count of the module thatexported the string; inter_module_ put should be called to decrement it whenthe data pointer is no longer needed.31922 June 2001 16:40http://openlib.org.uaChapter 11: kmod and Advanced Modularization#include <linux/config.h>CONFIG_MODVERSIONSThis macro is defined only if the current kernel has been compiled to supportversioned symbols.#ifdef MODVERSIONS#include <linux/modversions.h>This header, which exists only if CONFIG_MODVERSIONS is valid, containsthe versioned names for all the symbols exported by the kernel._ _GENKSYMS_ _This macro is defined by make when preprocessing files to be read bygenksyms to build new version codes.
It is used to conditionally prevent inclusion of <linux/modversions.h> when building new checksums.int call_usermodehelper(char *path, char *argv[], char*envp[]);This function runs a user-mode program in the keventd process context.32022 June 2001 16:40http://openlib.org.uaCHAPTER TWELVELOADING BLOCKDRIVERSOur discussion thus far has been limited to char drivers. As we have already mentioned, however, char drivers are not the only type of driver used in Linux systems. Here we turn our attention to block drivers. Block drivers provide access toblock-oriented devices—those that transfer data in randomly accessible, fixed-sizeblocks.
The classic block device is a disk drive, though others exist as well.The char driver interface is relatively clean and easy to use; the block interface,unfortunately, is a little messier. Kernel developers like to complain about it. Thereare two reasons for this state of affairs. The first is simple history—the block interface has been at the core of every version of Linux since the first, and it hasproved hard to change.
The other reason is performance. A slow char driver is anundesirable thing, but a slow block driver is a drag on the entire system. As aresult, the design of the block interface has often been influenced by the need forspeed.The block driver interface has evolved significantly over time. As with the rest ofthe book, we cover the 2.4 interface in this chapter, with a discussion of thechanges at the end.
The example drivers work on all kernels between 2.0 and 2.4,however.This chapter explores the creation of block drivers with two new example drivers.The first, sbull (Simple Block Utility for Loading Localities) implements a blockdevice using system memory—a RAM-disk driver, essentially.
Later on, we’ll introduce a variant called spull as a way of showing how to deal with partition tables.As always, these example drivers gloss over many of the issues found in real blockdrivers; their purpose is to demonstrate the interface that such drivers must workwith. Real drivers will have to deal with hardware, so the material covered inChapter 8 and Chapter 9 will be useful as well.32122 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block DriversOne quick note on terminology: the word block as used in this book refers to ablock of data as determined by the kernel. The size of blocks can be different indifferent disks, though they are always a power of two.
A sector is a fixed-size unitof data as determined by the underlying hardware. Sectors are almost always 512bytes long.Registering the DriverLike char drivers, block drivers in the kernel are identified by major numbers.Block major numbers are entirely distinct from char major numbers, however. Ablock device with major number 32 can coexist with a char device using the samemajor number since the two ranges are separate.The functions for registering and unregistering block devices look similar to thosefor char devices:#include <linux/fs.h>int register_blkdev(unsigned int major, const char *name,struct block_device_operations *bdops);int unregister_blkdev(unsigned int major, const char *name);The arguments have the same general meaning as for char devices, and majornumbers can be assigned dynamically in the same way. So the sbull device registers itself in almost exactly the same way as scull did:result = register_blkdev(sbull_major, "sbull", &sbull_bdops);if (result < 0) {printk(KERN_WARNING "sbull: can’t get major %d\n",sbull_major);return result;}if (sbull_major == 0) sbull_major = result; /* dynamic */major = sbull_major; /* Use ‘major’ later on to save typing */The similarity stops here, however.