内核模块需要使用特定的 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 -Wallcauses when you includemodule.h时会发生。 通过在 gcc-3.0 下使用-isystem,内核头文件会被特殊处理,并且警告会被抑制。 如果您改为使用-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 将其插入到内核中(忽略您看到的关于污染内核的任何内容;我们稍后会介绍)。 很棒,对吧? 所有加载到内核中的模块都列在/proc/modules中。 继续 cat 该文件以查看您的模块是否真的是内核的一部分。 恭喜您,您现在是 Linux 内核代码的作者了! 当新鲜感消失后,使用 rmmod hello-1 从内核中删除您的模块。 查看一下/var/log/messages,看看它是否已记录到您的系统日志文件中。
这是给读者的另一个练习。 看看init_module()中 return 语句上方的注释? 将返回值更改为非零值,重新编译并再次加载模块。 会发生什么?