内核模块需要使用特定的 gcc 选项进行编译才能正常工作。此外,它们还需要使用某些已定义的符号进行编译。这是因为内核头文件需要根据我们编译的是内核模块还是可执行文件而表现不同。您可以使用 gcc 的-D选项,或者使用#define预处理器命令。在本节中,我们将介绍编译内核模块所需的操作。
-c: 内核模块不是独立的的可执行文件,而是一个对象文件,它将在运行时使用 insmod 链接到内核中。因此,模块应该使用-c标志进行编译。
-O2: 内核广泛使用内联函数,因此模块必须在启用优化标志的情况下进行编译。如果没有优化,一些汇编器宏调用将被编译器误认为是函数调用。这将导致模块加载失败,因为 insmod 将在内核中找不到这些函数。
-W -Wall: 一个编程错误可能会导致您的系统崩溃。您应该始终启用编译器警告,这适用于您的所有编译工作,而不仅仅是模块编译。
-isystem /lib/modules/`uname -r`/build/include: 您必须使用您正在编译的内核的内核头文件。使用默认的/usr/include/linux将无法工作。
-D__KERNEL__: 定义此符号告诉头文件,代码将在内核模式下运行,而不是作为用户进程运行。
-DMODULE: 此符号告诉头文件为内核模块提供适当的定义。
我们使用 gcc 的-isystem选项,而不是-I,因为它告诉 gcc 抑制一些“未使用变量”的警告,这些警告在您包含-W -Wall会在您包含module.h时引起。通过使用-isystem在 gcc-3.0 下,内核头文件会被特殊处理,并且警告会被抑制。如果您改为使用-I(甚至-isystem在 gcc 2.9x 下),“未使用变量”警告将被打印。如果打印了,请忽略它们。
所以,让我们看看一个简单的 Makefile,用于编译名为hello-1.c:
示例 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 将其插入内核(忽略您看到的关于 tainted kernels 的任何内容;我们稍后会介绍)。很酷,是吧?所有加载到内核中的模块都列在/proc/modules。继续并 cat 该文件以查看您的模块是否真的是内核的一部分。恭喜,您现在是 Linux 内核代码的作者了!当新鲜感消退时,使用 rmmod hello-1 从内核中删除您的模块。查看一下/var/log/messages,看看它是否已记录到您的系统日志文件中。
这是给读者的另一个练习。看看 init_module() 中返回语句上方的注释init_module()?将返回值更改为非零值,重新编译并再次加载模块。会发生什么?