The Linux Kernel Module Programming Guide简译(二)
sighofwraith 发表于 2005-6-20 17:37:34
2.1. Hello, World (part 1): The Simplest Module
当世界上第一个穴居人程序员在第一台石头计算机的墙上刻下第一个程序的时候,这个程序绘出一个字符串“Hello world”。在罗马人的编程手册里,一切从“Salut,Mundi”程序开始。我不知道如果有人不用这个开头会有什么结果,不过我也没什么理由不这么做。所以,让我们从Hello world开始了解linux内核开发。
下面是个尽可能简单的例子。先别编译,下一节我们会讲到怎么编译内核模块。
Example 2-1. hello-1.c
/* hello-1.c - The simplest kernel module.
*/
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */
int init_module(void)
{
printk("<1>Hello world 1.\n");
// A non 0 return means init_module failed; module can't be loaded.
return 0;
}
void cleanup_module(void)
{
printk(KERN_ALERT "Goodbye world 1.\n");
}
内核模块至少要有两个函数:一个叫做init_module()的“start”初始化函数供insmod的时候调用,一个叫clean_module()的“end”清除函数供rmmod的时候调用。实际上,2.3.13之后的内核这两个函数不再必须使用这两个名字了。你给它们起什么名字都可以,2.3节我会将具体怎么做。给这两个函数自己起名字其实是个挺好的主意,不过还是有很多人使用init_module和clean_module函数。L
很经典的,init_module函数会向内核注册一些什么东西,或者用自己的代码替换内核的一些什么功能(通常它们做点什么动作之后还是会调用原来的函数)。而clean_module函数则会把init_module干的事情做个了结,以便安全的卸载模块。
最后,每个内核模块都要包含linux/module.h。仅仅为了KERL_ALERT(2.1.1节会讲到它)还需要在这个例子中包括linux/kernel.h。
2.1.1. Introducing printk()
不管你怎么认为,printk函数设计出来其实并不是为了和用户交流,虽然我们这个例子里面确实是这么用的。它实际上是内核的一种日志机制,用来记录下日志信息或者给出警告提示。所以每个printk都会有个优先级,比方你看的那个<1>或者KERN_ALERT。如果你没指定优先级的话,内核会选择默认的优先级。内核一共有8个优先级,它们都有宏定义,所以你大可不必每次都直接使用数字,去linux/kernel.h里找找就好了,默认的优先级是DEFAULT_MESSAGE_LOGLEVEL。
花点时间看看这几个优先级的宏吧,头文件里还描述了它们的意思。实际上你不要使用数字,还是用这些宏比较好。如果优先级数字比int console_loglevel变量小的话,消息就会打印到终端上。如果syslogd和klogd在运行的话,则不管是不是向控制台输出,消息都会被追加进/var/log/messages。这个例子里我们选了个高优先级KERN_ALERT以保证消息输出到console而不是写进日志文件了事,等你真的写一个内核模块,可以根据情况自己挑个合适的优先级来用。
2.2. Compiling Kernel Modules
为了确保内核模块能够正常工作,编译的时候需要使用一些特别的gcc选项。而且基于我们是编译成可执行的文件还是内核模块,还需要定义一些符号,这些符号定义用gcc的-D可以,在源代码中使用#define预处理命令也可以。下面我们来讲怎么编译内核模块。
-c: 内核模块不同于独立的可执行文件,它是个使用insmod运行时link进内核的目标文件(object file)。所以,模块应该用-c标志来编译。
-O2: 内核广泛的使用内联函数,所以编译模块也必须打开这个选项。没有这个优化现象的话,有些汇编宏调用会被编译器当成是函数调用。这会导致模块无法载入,因为insmod在内核当中找不到那些函数。
-W -Wall: 一个程序错误会导致你的整个系统down掉。所以你应该打开编译器的警告开关。其实这一点在编写任何程序的时候都应如此,而不仅仅是在模块编译上。
-isystem /lib/modules/`uname -r`/build/include: 这是为了让/usr/include/linux头文件目录失效,而使用对应内核版本的头文件。
-D__KERNEL__: 定义这个符号是为了告诉头文件,这个代码是要编译成内核模式。而不是一个用户进程。
-DMODULE: 这个符号告诉头文件使用内核模块该用的定义。
我们使用gcc的-isystem来代替-I是因为它会让gcc放弃一些-W –Wall情况下装入module.h文件带来的“unused variable”警告。通过在gcc-3.0下使用-isystem,内核头文件会强制指定它们把警告放弃掉。如果你使用的是-I(或者你用的虽然是-isystem,gcc却是3.0以下),你就会看到一大堆“unused variable”警告信息。看到的话忽略就可以了。
好了,现在让我们来看看编译hello-1.c的Makefile吧:
Example 2-2. Makefile for a basic kernel module
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
作为给读者的一个练习,请编译hello-1.c并用insmod ./hello-1.o将它插入到内核里(先忽略掉你看到的警告什么的,很快就会讲到)。很利落是吧,呵呵~~所有被载入的模块都在
/proc/modules中列了出来。去那个文件里看看你的模块是否真的已经成为了内核的一部分。恭喜你,你也算是个内核代码的作者了J。等你这股新鲜劲头过去了,就用rmmod hello-1把这个模块移除出去。对了,你还可以去/var/log/messages文件看看你的日志信息。J
这还有个读者的练习。看见init_module的注释了么?把返回值改成非零值再编译加载一下看看会发生什么~~
2.3. Hello World (part 2)
在linux2.4上,init和cleanup函數不一定非要叫做init_module和cleanup_module了,你可以自己給它們起個名字。這是通過module_init和module_exit這兩個宏來實現的。這兩個宏是在linux/init.h中定義的。唯一的要求是,你的初始化函數和清除函數要寫在宏調用的前面。否則編譯是不會通過的J:
Example 2-3. hello-2.c
/* hello-2.c - Demonstrating the module_init() and module_exit() macros. This is the
* preferred over using init_module() and cleanup_module().
*/
#include <linux/module.h> // Needed by all modules
#include <linux/kernel.h> // Needed for KERN_ALERT
#include <linux/init.h> // Needed for the macros
static int hello_2_init(void)
{
printk(KERN_ALERT "Hello, world 2\n");
return 0;
}
static void hello_2_exit(void)
{
printk(KERN_ALERT "Goodbye, world 2\n");
}
module_init(hello_2_init);
module_exit(hello_2_exit);
現在我們有兩個自己定義的内核模塊了。這裡高級了,我們的Makefile不妨也高級點。下面是高級了一些得Makefile,它是針對代碼尺寸和穩定性優化的。(如果看不懂的話,建議先去看一下makefile的info pages或者GNU Make Manual)。
Example 2-4. Makefile for both our modules
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
這裡給讀者一個練習,如果我們再同一個目錄下還有另外一個模塊,hello-3.c,Makefile應該怎麽修改?
2.4. Hello World (part 3): The __init and __exit Macros
這一節對2.2或者2.2以後的内核版本才有用処。注意一下init和cleanup這兩個函數定義的變化。__init宏使内建模塊中的init函數在執行完成后釋放掉,不過可裝載的模塊不受影響。如果你關心init函數什麽時候調用,這一點是很有用的。
還有個__initdata,和__init的作用基本上一樣,不過它是針對變量而不是函數的。
__exit宏會使那些内建到内核的模塊省略掉cleanup函數,不過和__init一樣,對loadable模塊沒影響。再説一遍,如果你關心cleanup運行的時機,這是重要的。Built-in的驅動不需要cleanup,反正它們也不能退出,不過loadable式的模塊顯然是需要一個的。
這些宏都定義在linux/init.h中,它們會釋放内核的内存。你啓動内核的時候會看到一些諸如Freeing unused kernel memory:236k freed,之類的信息,這多半就是它們干的。
Example 2-5. hello-3.c
/* hello-3.c - Illustrating the __init, __initdata and __exit macros.
*/
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */
#include <linux/init.h> /* Needed for the macros */
static int hello3_data __initdata = 3;
static int __init hello_3_init(void)
{
printk(KERN_ALERT "Hello, world %d\n", hello3_data);
return 0;
}
static void __exit hello_3_exit(void)
{
printk(KERN_ALERT "Goodbye, world 3\n");
}
module_init(hello_3_init);
module_exit(hello_3_exit);
順便說一句,你會發現在2.2以後的内核驅動裏有“__initfunction”指令:
__initfunction(int init_module(void))
{
printk(KERN_ALERT "Hi there.\n");
return 0;
}
這個宏的目的和__init一樣,但是這個現在很少用了,我只是提一下而已,因爲你偶爾可能會看到它。在2.4.18裏,有38個__initfunction(),在2.4.20裏有37個。不過甭怎麽說,反正你就別在自己的代碼裏用它了