通过hello world介绍2.6内核模块编译的最基本原理

1、makefile的预备知识

本文旨在介绍编译模块的原理,不详细介绍makefile。

下面是一个简单的没有任何用途的makefile:

1 MAKE_TEST = make test 

3 all: 
4     @echo "make all
5     @echo "MAKE_TEST = $(MAKE_TEST)" 

7 test: 
8     @echo "make test
9     @echo "MAKE_TEST = $(MAKE_TEST)"

本Makefile首先定义了一个变量MAKE_TEST,然后有两个目标:all和test

下面是分别执行make和make test的运行结果:

wangjiankun@driver:~/make_study$ make 
make all 
MAKE_TEST = make test 
wangjiankun@driver:~/make_study$ make test 
make test 
MAKE_TEST = make test 
wangjiankun@driver:~/make_study$

make test的运行结果说明:虽然指定了目标test,但是目标test之前定义的变量仍然是起作用的,这是介绍这个例子的唯一目的,用来解释下面的变量KERNELRELEASE在目标modules之前定义。

2、hello world的makefile文件

ifneq ($(KERNELRELEASE),) 
2     obj-m := hello_world.o 

else 
5     KERNELDIR ?= /lib/modules/2.6.19/build 
6     PWD := $(shell pwd
default
8     $(MAKE) -C $(KERNELDIRM=$(PWDmodules 
endif

上面是hello world模块的Makefile文件,当在当前目录下执行make命令时,由于变量KERNELRELEASE没有定义,所以make走else分支;

?=操作符make手册是如下解释的:

This statement:

FOO ?= bar

is exactly equivalent to this

ifeq ($(origin FOO), undefined)

FOO = bar

endif

Note that a variable set to an empty value is still defined, so ‘?=’ will not set that variable.

也就是说,第5行只有变量KERNELDIR没有被定义时,才给它赋值,所以此时给KERNELDIR赋值为:/lib/modules/2.6.19/build;

第7行,make遇到了第一个目标:default,执行第8行的命令:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules。第8行中有如下3个介绍点:(1)、-C选项是进目录的意思;(2)、定义了变量M,对于make来说,M就是一个变量,除此之外没有任何特殊的含义,make更不会因为他是module的首字母而认为是在编译模块;(3)、第8行的编译目标是modules。这样make带着两个信息(一个是变量M及其值,另一个是目标是modules)进入由变量KERNELDIR定义的目录来编译,在此是进入目录:/lib/modules/2.6.19/build。如下所示:

wangjiankun@driver:/lib/modules/2.6.19$ pwd 
/lib/modules/2.6.19 
wangjiankun@driver:/lib/modules/2.6.19$ ls -l 
total 1313 
lrwxrwxrwx 1 root root     37 Dec 14 16:18 build -> /home/wangjiankun/kernel/linux-2.6.19 
drwxr-xr-x 9 root root   1024 Dec 14 16:18 kernel 
-rw-r--r-- 1 root root 296327 Dec 14 16:18 modules.alias 
-rw-r--r-- 1 root root     69 Dec 14 16:18 modules.ccwmap 
-rw-r--r-- 1 root root 299229 Dec 14 16:18 modules.dep 
-rw-r--r-- 1 root root    813 Dec 14 16:18 modules.ieee1394map 
-rw-r--r-- 1 root root    730 Dec 14 16:18 modules.inputmap 
-rw-r--r-- 1 root root  21916 Dec 14 16:18 modules.isapnpmap 
-rw-r--r-- 1 root root     74 Dec 14 16:18 modules.ofmap 
-rw-r--r-- 1 root root 226143 Dec 14 16:18 modules.pcimap 
-rw-r--r-- 1 root root   1135 Dec 14 16:18 modules.seriomap 
-rw-r--r-- 1 root root 135623 Dec 14 16:18 modules.symbols 
-rw-r--r-- 1 root root 342460 Dec 14 16:18 modules.usbmap 
lrwxrwxrwx 1 root root     37 Dec 14 16:18 source -> /home/wangjiankun/kernel/linux-2.6.19 
wangjiankun@driver:/lib/modules/2.6.19$

这个目录是在debian下编译内核时生成的,/lib/modules/2.6.19/build指向内核源码包。这样make就要运行内核源码包的主Makefile文件了。

3、linux 2.6内核主Makefile文件对编译模块的支持

由上面的介绍可知:make进入内核源码包带着两个信息:(1)定义了变量M(= 模块的当前目录路径)和(2)目标是modules来执行make命令。先看一下modules目标。

内核主Makefile定义了目标modules:

1202 module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD)) 
1203 PHONY += $(module-dirs) modules 
1204 $(module-dirs): crmodverdir $(objtree)/Module.symvers 
1205     $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@) 
1206 
1207 modules: $(module-dirs) 
1208     @echo '  Building modules, stage 2.'; 
1209     @echo "wangjiankun noted 1
1210     $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost

modules目标依赖于由变量module-dirs定义的目标,所以要先编译由变量module-dirs定义的目标,1202行定义了变量module-dirs( := $(addprefix _module_,$(KBUILD_EXTMOD)) ),内核主Makefile有如下定义:

63 # Use make M=dir to specify directory of external module to build 
64 # Old syntax make ... SUBDIRS=$PWD is still supported 
65 # Setting the environment variable KBUILD_EXTMOD take precedence 
66 ifdef SUBDIRS 
67   KBUILD_EXTMOD ?= $(SUBDIRS) 
68 endif 
69 ifdef M 
70   ifeq ("$(origin M)", "command line") 
71     KBUILD_EXTMOD := $(M) 
72   endif 
73 endif

由以上定义可知变量KBUILD_EXTMOD就是我们在模块的Makefile中定义的变量M的值,即模块的当前目录。1202行用到了make的一个函数addprefix,addprefix在make手册中如下解释:

$(addprefix prefix,names...) 
The argument names is regarded as a series of names, separated by whitespace
prefix is used as a unit. The value of prefix is prepended to the front of each 
individual name and the resulting larger names are concatenated with single 
spaces between them. For example, 
$(addprefix src/,foo bar) 
produces the result ‘src/foo src/bar’.

这样变量module-dirs就等于_module_模块的当前目录路径,在我的测试系统中的值为:_module_/home/wangjiankun/ldd3/ch2

1204行目标:$(module-dirs)依赖于crmodverdir和$(objtree)/Module.symvers

内核主Makefile文件定义了目标:crmodverdir如下所示,其实就是在模块的当前目录下建立一个文件夹.tmp_versions,并将其内部文件全部删除

1190 crmodverdir: 
1191     $(Q)mkdir -p $(MODVERDIR) 
1192     $(Q)rm -f $(MODVERDIR)/*

内核主Makefile文件定义了目标:$(objtree)/Module.symvers如下所示,其实就是检查内核源码根目录下是否存在文件Module.symvers,如果不存在打印出警告信息,但是模块仍然可以成功编译。

1195 $(objtree)/Module.symvers: 
1196     @test -e $(objtree)/Module.symvers || ( / 
1197     echo; / 
1198     echo "  WARNING: Symbol version dump $(objtree)/Module.symvers"; / 
1199     echo "           is missing; modules will have no dependencies and modversions."; / 
1200     echo )

1205行的$(patsubst _module_%,%,$@)就是变量module-dirs的值:_module_/home/wangjiankun/ldd3/ch2除去_module_的结果,即:/home/wangjiankun/ldd3/ch2。表达式中的$@意为:The file name of the target.

$(build)的值为:-f scripts/Makefile.build obj(没有找到在什么地方定义,待查,此处是在我的测试系统上,通过echo打印出来的),这样1205行的实际语句为:

@make -f scripts/Makefile.build obj=/home/wangjiankun/ldd3/ch2

scripts/Makefile.build文件比较复杂没有详细研究,但有一点是肯定的,make二次执行模块目录下的Makefile文件就是通过这个脚本直接或间接进行的(目前猜测,可能不是二次执行了模块的Makefile文件,而是将模块的Makefile文件包含到了内核的build系统的脚本或Makefile中使得obj-m := hello_world.o起作用而编译了模块)。通过这个脚本,make二次执行模块的Makefile文件,此时变量KERNELRELEASE已经定义,内核主Makefile有如下定义:

320 # Read KERNELRELEASE from include/config/kernel.release (if it exists) 
321 KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)

include/config/kernel.release文件内容如下:

wangjiankun@driver:~/kernel/linux-2.6.19/include/config$ pwd 
/home/wangjiankun/kernel/linux-2.6.19/include/config 
wangjiankun@driver:~/kernel/linux-2.6.19/include/config$ cat kernel.release 
2.6.19 
wangjiankun@driver:~/kernel/linux-2.6.19/include/config$

也就是说,此时KERNELRELEASE不但定义了,而且值为:内核版本号,所以make走第2行的分支并通过隐含规则将hello_world.c编译成hello_world.o文件。

make回到1208行打印出:Building modules, stage 2.,并通过1210行生成模块。

没有评论: