为了清晰的编译Linux内核,内核编译系统使用Kbuild规则对编译的过程以及依赖进行规约。在内核模块的编译中,为了保持与内核源码的兼容以及传递编译链接选项给GCC,也使用Kbuild规则。
内核模块的源代码可以在内核源码树中,也可以在内核源码树外,当使用Kbuild时,两种情况的编译方式也大致相似。一般的内核模块在开发时,都是放在源码树外的。
本文主要是针对源码树外部的内核模块的编译。为了屏蔽内核模块编译的复杂性,开发人员需要编写额外的Makefile,最终让编译内核模块就像编译普通的应用程序一样,敲入”make”就行了。本文后面就给了一个实例。
编译外部模块在编译外部模块之前,需要首先准备好当前内核的配置以及内核头文件,同时,当前内核的modules enable选项应该开启(编译内核时指定)。
命令行选项使用如下命令编译外部模块:
make –C <kernerl_src_dir>M=<ext_module_path>
其中-C表明make要调用<kernel_src_dir>下的Makefile,该Makefile就是内核的Makefile,M为该Makefile的参数,指定外部模块源码的路径。当Makefile接收到M参数时,就默认编译外部模块。
例如,当前目录下存放一个外部模块的源码,其编译命令如下:
make –C /lib/modules/`uname -r`/buildM=`pwd`
其中uname –r获取当前运行内核的版本,pwd为当前源码路径,将其展开之后为:
make –C /lib/modules/ 2.6.42.9/buildM=/home/user/hello
其中/lib/modules/ 2.6.42.9/build是指向内核源码目录的符号链接。
编译完成之后,要安装驱动时,调用如下命令:
make –C /lib/modules/`uname -r`/buildM=`pwd` modules_install
编译目标modules
编译外部模块,默认目标就是modules
modules_install
安装编译成功了的外部模块,默认的安装目录为/lib/modules/<kernel_release>/extra/,前缀可以同过INSTALL_MOD_PATH指定。
clean
清除选项
help
列出可用的外部目标
Kbuild文件在执行了make –C /lib/modules/`uname-r`/build M=`pwd`之后,内核源码树中的Makefile会再次跳转到`pwd`目录下,加载Kbuild或Makefile(如果没有Kbuild文件,则加载Makefile,因此,Kbuild文件中的内容也可以放到Makefile中)。
如果模块源码目录中的Kbuild或Makefile中没有定义编译目标时,编译过程最终是什么都没生成的。以下一行就是定义生成目标:
obj-m: = <module_name1>.o <module_name2>.o…
上面的obj-m变量是指外部模块,其后面的一组<module_name?>.o最终生成<module_name?>.ko模块。同样,还有一个变量obj-y,它包含要静态编译进入内核的模块。本文不考虑它。
在默认情况下,内核源码编译系统会将<module_name1>.c编译成<module_name1>.o,并最终链接生成<module_name1>.ko。如果<module_name1>.ko需要多个源文件时,Kbuild或Makefile中要添加如下行:
<module_name1>-y: = src1.o src2.o ….
Makefile与Kbuild合并为了屏蔽编译内核模块的复杂性,让使用人员简单的调用make/makeinstall即可完成内核模块的编译,模块源码目录下通常添加了一个wrapper Makefile,供向的Makefile包含了Kbuild部分,内容如下:
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
default::
$(MAKE) -C /lib/modules/`uname -r`/buildM=`pwd` modules
endif
Kbuild与Makefile分离当内核模块源码目录下同时包含了Kbuild与Makefile时,编译系统只加载Kbuild文件。两个文件内容分别如下:
Makefile内容如下:
default::
$(MAKE)-C /lib/modules/`uname -r`/build M=`pwd`
这里的Makefile只是对内核Makefile调用进行了封装。
Kbuild内容如下:
EXTRA_CFLAGS := -I.-include ./xxx.h
obj-m := module1.o module2.o
module1-objs := src1.o
module2-objs := src2.o
头文件在内核源码树中,头文件的存放规则如下:
1.如果该头文件定义的是模块内部的接口,则头文件放在模块所在的目录下
2.如果头文件中内容在内核其他子系统中使用,则放在include/linux
模块版本模块版本选项是通过内核编译选项CONFIG_MODVERSIONS定义的,它是一个简单的ABI兼容性检查机制。对于模块的每个导出符号,都有一个对应的CRC校验值。当模块加载或使用时,内核会用自己的CRC值与模块的CRC值进行对比,如果不同,则拒绝加载模块。
在内核源码树根目录中,其中的Module.symvers文件就包含了内核所有的导出符号以及所有编译后模块的导出符号。
symbol from kernel(vmlinux+all modules)在编译内核时,根目录下会生成Module.symvers文件,它包含了内核以及编译后的模块导出的所有符号。对于每一个符号,相应的CRC校验值也被保存,Module.symvers每一行数据格式如下:
<CRC> <Symbol> <module>
0x2d036834 scsi_remove_host drivers/scsi/scsi_mod
当内核编译选项CONFIG_MODVERSIONS关闭时,所有的CRC值都为0x00000000。
Module.symvers文件主要有以下用途:
1.列出vmlinux和所有模块的导出函数
2.列出所有符号的CRC校验值
symbol and extern modules当编译外部模块时,在MODPOST阶段时,会访问内核源码树中的Module.symvers检测当前模块的外部符号是否已经被定义,同时,如果外部模块源码根目录下包含了Module.symvers文件,该文件也会被检测。
对于当前模块的每个外部符号,编译系统都会从当前目录下的Module.symvers以及内核源码树下的Module.symvers中查找,检测是否有该符号。
在MODPOST阶段,会在当前目录下生成一个新的Module.symvers,它包含kernel中未定义的所有符号。
当外部模块需要从另一个外部模块中导入符号时,有三种方法可以解决。
1.使用top-level Kbuild文件
例如,两个模块foo.ko,bar.ko,其中foo.ko依赖bar.ko中的导出符号,可以使用一个顶层公用的Kbuild文件对两个模块同时编译,假设目录如下:
./foo/ <= contains foo.ko
./bar/ <= contains bar.ko
顶层的Kbuild文件内容如下:
obj-y := foo/ bar/
执行make -C $KDIR M=$PWD,在编译过程中,两个模块的导出符号是共享的。
2.使用额外的Module.symvers文件
首先生成外部模块bar.ko,生成之后,bar.ko目录下生成一个Module.symvers文件,它包含了kernel中的Module.symvers未定义的所有符号(当然包含bar.ko的导出符号)。
在编译foo.ko时,为了能访问bar.ko的导出符号,可以将bar.ko生成的Module.symvers文件复制到foo编译目录。编译时,会读取foo目录下的Module.symvers文件,同时,生成一个所有未在kernel中定义的符号文件Module.symvers。
3. 调用make时传入参数KBUILD_EXTRA_SYMBOLS
杂项
模块编译有时候需要通过检查内核编译选项CONFIG_option决定哪些功能被编译进模块,在Kbuild中,可以直接使用这些选项。
例如:
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o dir.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
通常情况下,$(CONFIG_EXT2_FS)的值为m,y或未定义。为m时,说明目标模块编译成内核模块,若未y,则目标编译进mlinux,若未定义,则目标不编译。