2.1. Hello, World (part 1): The Simplest Module
When the first caveman programmer chiseled the first program on the walls of the first cave computer, it was a program to paint the string `Hello, world' in Antelope pictures. Roman programming textbooks began with the `Salut, Mundi' program. I don't know what happens to people who break with this tradition, but I think it's safer not to find out. We'll start with a series of hello world programs that demonstrate the different aspects of the basics of writing a kernel module.Here's the simplest module possible. Don't compile it yet; we'll cover module compilation in the next section.
/* hello-1.c - The simplest kernel module.
*/
#include
|
Typically, init_module() either registers a handler for something with the kernel, or it replaces one of the kernel functions with its own code (usually code to do something and then call the original function). The cleanup_module() function is supposed to undo whatever init_module()did, so the module can be unloaded safely.
Lastly, every kernel module needs to include linux/module.h. We needed to include linux/kernel.h only for the macro expansion for the printk()log level, KERN_ALERT, which you'll learn about in Section 2.1.1.
2.1.1. Introducing printk()
Despite what you might think, printk() was not meant to communicate information to the user, even though we used it for exactly this purpose in hello-1! It happens to be a logging mechanism for the kernel, and is used to log information or give warnings. Therefore, each printk()statement comes with a priority, which is the <1> and KERN_ALERT you see. There are 8 priorities and the kernel has macros for them, so you don't have to use cryptic numbers, and you can view them (and their meanings) in linux/kernel.h. If you don't specify a priority level, the default priority, DEFAULT_MESSAGE_LOGLEVEL, will be used.Take time to read through the priority macros. The header file also describes what each priority means. In practise, don't use number, like<4>. Always use the macro, like KERN_WARNING.
If the priority is less than int console_loglevel, the message is printed on your current terminal. If both syslogd and klogd are running, then the message will also get appended to /var/log/messages, whether it got printed to the console or not. We use a high priority, like KERN_ALERT, to make sure the printk() messages get printed to your console rather than just logged to your logfile. When you write real modules, you'll want to use priorities that are meaningful for the situation at hand.
2.2. Compiling Kernel Modules
Kernel modules need to be compiled with certain gcc options to make them work. In addition, they also need to be compiled with certain symbols defined. This is because the kernel header files need to behave differently, depending on whether we're compiling a kernel module or an executable. You can define symbols using gcc's -D option, or with the #define preprocessor command. We'll cover what you need to do in order to compile kernel modules in this section.- -c: A kernel module is not an independant executable, but an object file which will be linked into the kernel during runtime using insmod. As a result, modules should be compiled with the -c flag.
- -O2: The kernel makes extensive use of inline functions, so modules must be compiled with the optimization flag turned on. Without optimization, some of the assembler macros calls will be mistaken by the compiler for function calls. This will cause loading the module to fail, since insmod won't find those functions in the kernel.
- -W -Wall: A programming mistake can take take your system down. You should always turn on compiler warnings, and this applies to all your compiling endeavors, not just module compilation.
- -isystem /lib/modules/`uname -r`/build/include: You must use the kernel headers of the kernel you're compiling against. Using the default/usr/include/linux won't work.
- -D__KERNEL__: Defining this symbol tells the header files that the code will be run in kernel mode, not as a user process.
- -DMODULE: This symbol tells the header files to give the appropriate definitions for a kernel module.
So, let's look at a simple Makefile for compiling a module named hello-1.c:
TARGET := hello-1
WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes
INCLUDE := -isystem /lib/modules/`uname -r`/build/include
CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE}
CC := gcc-3.0
${TARGET}.o: ${TARGET}.c
.PHONY: clean
clean:
rm -rf {TARGET}.o
|
Here's another exercise to the reader. See that comment above the return statement in init_module()? Change the return value to something non-zero, recompile and load the module again. What happens?
2.3. Hello World (part 2)
As of Linux 2.4, you can rename the init and cleanup functions of your modules; they no longer have to be called init_module() andcleanup_module() respectively. This is done with the module_init() and module_exit() macros. These macros are defined in linux/init.h. The only caveat is that your init and cleanup functions must be defined before calling the macros, otherwise you'll get compilation errors. Here's an example of this technique:/* hello-2.c - Demonstrating the module_init() and module_exit() macros. This is the
* preferred over using init_module() and cleanup_module().
*/
#include
|
WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes
INCLUDE := -isystem /lib/modules/`uname -r`/build/include
CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE}
CC := gcc-3.0
OBJS := ${patsubst %.c, %.o, ${wildcard *.c}}
all: ${OBJS}
.PHONY: clean
clean:
rm -rf *.o
|
2.4. Hello World (part 3): The __init and __exit Macros
This demonstrates a feature of kernel 2.2 and later. Notice the change in the definitions of the init and cleanup functions. The __init macro causes the init function to be discarded and its memory freed once the init function finishes for built-in drivers, but not loadable modules. If you think about when the init function is invoked, this makes perfect sense.There is also an __initdata which works similarly to __init but for init variables rather than functions.
The __exit macro causes the omission of the function when the module is built into the kernel, and like __exit, has no effect for loadable modules. Again, if you consider when the cleanup function runs, this makes complete sense; built-in drivers don't need a cleanup function, while loadable modules do.
These macros are defined in linux/init.h and serve to free up kernel memory. When you boot your kernel and see something like Freeing unused kernel memory: 236k freed, this is precisely what the kernel is freeing.
/* hello-3.c - Illustrating the __init, __initdata and __exit macros.
*/
#include
|
__initfunction(int init_module(void))
{
printk(KERN_ALERT "Hi there.\n");
return 0;
}
|
2.5. Hello World (part 4): Licensing and Module Documentation
If you're running kernel 2.4 or later, you might have noticed something like this when you loaded the previous example modules:# insmod hello-3.o
Warning: loading hello-3.o will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Hello, world 3
Module hello-3 loaded, with warnings
|
Similarly, MODULE_DESCRIPTION() is used to describe what the module does, MODULE_AUTHOR() declares the module's author, andMODULE_SUPPORTED_DEVICE() declares what types of devices the module supports.
These macros are all defined in linux/module.h and aren't used by the kernel itself. They're simply for documentation and can be viewed by a tool like objdump. As an exercise to the reader, try grepping through linux/drivers to see how module authors use these macros to document their modules.
/* hello-4.c - Demonstrates module documentation.
*/
#include
|
2.6. Passing Command Line Arguments to a Module
Modules can take command line arguments, but not with the argc/argv you might be used to.To allow arguments to be passed to your module, declare the variables that will take the values of the command line arguments as global and then use the MODULE_PARM() macro, (defined in linux/module.h) to set the mechanism up. At runtime, insmod will fill the variables with any command line arguments that are given. The variable declarations and macros should be placed at the beginning of the module for clarity. The example code should clear up my admittedly lousy explanation.
The MODULE_PARM() macro takes 2 arguments: the name of the variable and its type. The supported variable types are "b": single byte, "h": short int, "i": integer, "l": long int and "s": string. Strings should be declared as "char *" and insmod will allocate memory for them. You should always try to give the variables an initial default value. This is kernel code, and you should program defensively. For example:
int myint = 3;
char *mystr;
MODULE_PARM (myint, "i");
MODULE_PARM (mystr, "s");
|
int myshortArray[4];
MODULE_PARM (myintArray, "2-4i");
|
/* hello-5.c - Demonstrates command line argument passing to a module.
*/
#include
|
2.7. Modules Spanning Multiple Files
Sometimes it makes sense to divide a kernel module between several source files. In this case, you need to:- In all the source files but one, add the line #define __NO_VERSION__. This is important because module.h normally includes the definition of kernel_version, a global variable with the kernel version the module is compiled for. If you need version.h, you need to include it yourself, because module.h won't do it for you with __NO_VERSION__.
- Compile all the source files as usual.
- Combine all the object files into a single one. Under x86, use ld -m elf_i386 -r -o
<1st src file.o> <2nd src file.o> .
/* start.c - Illustration of multi filed modules
*/
#include
|
/* stop.c - Illustration of multi filed modules
*/
#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS)
#include
|
CC=gcc
MODCFLAGS := -O -Wall -DMODULE -D__KERNEL__
hello.o: hello2_start.o hello2_stop.o
ld -m elf_i386 -r -o hello2.o hello2_start.o hello2_stop.o
start.o: hello2_start.c
${CC} ${MODCFLAGS} -c hello2_start.c
stop.o: hello2_stop.c
${CC} ${MODCFLAGS} -c hello2_stop.
|
James Thornton
|
No comments:
Post a Comment