2.2. 编译内核模块

内核模块需要使用特定的 gcc 选项进行编译才能正常工作。此外,它们还需要使用某些已定义的符号进行编译。这是因为内核头文件需要根据我们编译的是内核模块还是可执行文件而表现不同。您可以使用 gcc 的-D选项,或者使用#define预处理器命令。在本节中,我们将介绍编译内核模块所需的操作。

我们使用 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()?将返回值更改为非零值,重新编译并再次加载模块。会发生什么?