内核模块在用gcc编译时需要使用特定的参数。另外,一些宏同样需要定义。 这是因为在编译成可执行文件和内核模块时,内核头文件起的作用是不同的。 你可以用gcc的 -D 选项定义宏,或使用 #define 预处理命令。我们会在这章详细讨论这方面的内容来成功的编译我们的内核模块。
-c: 一个内核模块不是一个独立的可执行文件,而是一个内核在运行时用insmod动态连结的目标文件。所以,内核应该在编译时使用 -c 选项。
-O2: 内核使用了大量的内联函数,所以模块也应该在编译时大开优化选项。否则,一些汇编的宏调用将会被编译器错误的理解为系统调用。这将导致 加载模块失败,因为内核中并不存在这样的函数接口。
-W -Wall: 一个编程上的错误将会使你的系统崩溃。你应该始终打开编译警告选项,不仅是在编译模块时。
-isystem /lib/modules/`uname -r`/build/include: 你必须使用加载你的模块的内核的头文件,而不是默认的内核头文件 /usr/include/linux。
-D__KERNEL__: 定义这个宏通知编译器将要编译的代码在内核态运行,而不是用户态。
-DMODULE: 该选项高速头文件去使用正确的内核模块的定义。
我们使用gcc的 -isystem 选项而不是 -I 选项因为要使gcc省略"unused variable"警告当同时打开 -W -Wall 选项和包含时 module.h头文件时。 在gcc-3.0使用选项 -isystem 会让编译器特殊对待内核头文件,所以会省略"unused variable"警告。如果你使用 -I 选项(或在 gcc 2.9x使用 -isystem 选项时), 将会输出"unused variable" 警告。只管忽略就行了。
再来看一个简单的用来编译模块 hello-1.c的Makefile:
Example 2-2. 用来编译一个基本的模块的Makefile
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列出。去那看看你的模块是否真的是内核的一部分了。 祝贺你!你现在已经是内核模块的作者了。当你的新鲜劲过去后,使用命令 rmmod hello-1卸载模块。再看一下 /var/log/messages 文件的内容是否有相关的日志内容。
这儿是另一个练习。看到了在声明 init_module()上的注释吗? 改变返回值非零,重新编译再加载,发生了什么?