编译linux外部驱动模块时的基础知识

linux内核模块编译引言

为了清晰的编译Linux内核,内核编译系统使用Kbuild规则对编译的过程以及依赖进行规约。在内核模块的编译中,为了保持与内核源码的兼容以及传递编译链接选项给GCC,也使用Kbuild规则。

内核模块的源代码可以在内核源码树中,也可以在内核源码树外,当使用Kbuild时,两种情况的编译方式也大致相似。一般的内核模块在开发时,都是放在源码树外的。

本文主要是针对源码树外部的内核模块的编译。为了屏蔽内核模块编译的复杂性,开发人员需要编写额外的Makefile,最终让编译内核模块就像编译普通的应用程序一样,敲入”make”就行了。本文后面就给了一个实例。

编译外部模块

在编译外部模块之前,需要首先准备好当前内核的配置以及内核头文件,同时,当前内核的modules enable选项应该开启(编译内核时指定)。

命令行选项

使用如下命令编译外部模块:

make –C M=

其中-C表明make要调用下的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//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: = .o .o… 

上面的obj-m变量是指外部模块,其后面的一组.o最终生成.ko模块。同样,还有一个变量obj-y,它包含要静态编译进入内核的模块。本文不考虑它。

在默认情况下,内核源码编译系统会将.c编译成.o,并最终链接生成.ko。如果.ko需要多个源文件时,Kbuild或Makefile中要添加如下行:

-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每一行数据格式如下:

                                     

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,若未定义,则目标不编译。

你可能感兴趣的:(linux驱动)