内核顶层Makefile相关3
http://www.groad.net/bbs/simple/?f104.html
伪目标
.PHONY是一个特殊工作目标(special target),它用来指定一个假想的工作目标,也就是说它后面的并不是一个实际文件,而且肯定要视为未更新(也就是说条件总是满足,需要处理)
PHONY 目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用PHONY 目标:避免和同名文件冲突(防止存在和PHONY执行目标的名称相同的文件,虽然在写比较小的makefile工程的时候看不出来.PHONY的好处,但是当面对一些大工程的时候,.PHONY则显得相当有用),改善性能。
如果编写一个规则,并不产生目标文件,则其命令在每次make 该目标时都执行。
例如:
clean:
rm *.o temp
因为"rm"命令并不产生"clean"文件,则每次执行"make clean"的时候,该命令都会执行。如果目录中出现了"clean"文件,则规则失效了:没有依赖文件,文件"clean"始终是最新的,命令永远不会执行;为避免这个问题,可使用".PHONY"指明该目标。如:
.PHONY : clean
这样执行"make clean"会无视"clean"文件存在与否。
已知phony 目标并非是由其它文件生成的实际文件,make 会跳过隐含规则搜索。这就是声明phony 目标会改善性能的原因,即使你并不担心实际文件存在与否。
完整的例子如下:
.PHONY : clean
clean :
rm *.o temp
PHONY 目标不应是真正目标文件的依赖。如果这样,每次make 在更新此文件时,命令都会执行。只要PHONY目标不是真正目标的依赖,规则的命令只有在指定此目标时才执行。
一般情况下,一个伪目标不作为另外一个目标文件的依赖。这是因为当一个目标文件的依赖包含伪目标时,每一次在执行这个规则时伪目标所定义的命令都会被执行(因为它是规则的依赖,重建规则目标文件时需要首先重建它的依赖)。伪目标作为另外一个目标的依赖这种情况,常见与 FORCE 的应用。
当伪目标没作为任何目标(此目标是一个可被创建或者已经存在的文件)的依赖时,一般上只能通过 make 的命令行选项明确的指定这个伪目标来执行它所定义的命令,如经常所用的 "make clean" 。但是,如果将伪目标作为第一个目标时,仍然只会执行伪目标,而底下的其它目标是不会执行的,比如:
clean: @echo "always be executed" helloworld:file1.o file2.o gcc file1.o file2.o -o helloworld file1.o:file1.c file2.h gcc -c file1.c -o file1.o file2.o:file2.c file2.h gcc -c file2.c -o file2.o PHONY += clean .PHONY: $(PHONY)
运行输出:
[beyes@SLinux Makefile]$ make
always be executed
clean 伪目标被放在第 1 行,默认 make 时,它总是被执行,执行完后整个 make 也就退出了。而底下所定义的 helloworld 目标,如果你想生成它,那就要显示的去执行:
[beyes@SLinux Makefile]$ make helloworld gcc -c file1.c -o file1.o gcc -c file2.c -o file2.o gcc file1.o file2.o -o helloworld
从这般的用法看来,helloworld 倒是像是伪目标了。
综合起来,不论是明确声明了的伪目标,还是可以生成实际文件的目标,它们都只是个标签,谁在最前就执行谁。在后面的要想被执行,得显示的去指出。
说到标签,那么同一个 Makefile 中,还可以有多个同名的标签,看起来是有多个同名的目标,在这种情况下会是怎么样的呢?一个 Makefile 文件内容为:
PHONY := __build .PHONY: $(PHONY) __build: obj-y := timesheet obj-m := hello __build: lib-y := world __build: @echo $(obj-y) @echo $(lib-y)
上面,__build 被声明为一个伪目标。这里有多处它的标签,但实际上在运行时会看到:
$ make
timesheet
world
如果将下面含有 echo 命令的 __build 都注释掉,再运行时看到:
$ make make: Nothing to be done for `__build'.
这时候提示,对于 __build 没啥可做。由此可以知道,标签在 Makefile 中的地位仅仅是标签而已,不论它是真实的目标标签,还是被当成是伪目标的标签,最终起主要作用的还是“依赖”或者“命令” 。
再比较一个特殊的情况,现在将上面的 Makefile 修改为:
PHONY := __build .PHONY: $(PHONY) __build: obj-y := timesheet obj-m := hello temp: @echo "--------------------------" __build: @echo "i am here"
我们在中间插入了另外一个伪目标 temp,但是最后得到执行的不是 temp 下的命令,而是 __build 下的命令。因为在 temp 的上面已经对 __build 做了预先的声明,它等于告诉 make,它是先来的,并已经占据座位。同理,如果 temp 被先声明在 __build 之前,那么被执行的是 temp 下面的命令。
在 Makefile 中,伪目标也可以有自己的依赖。如果希望在一个目录下生成多个可执行程序,我们也可以用一个 Makefile 来实现。在 Makefile 中,第 1 个目标是“终极目标”(像上面的例子中,伪目标放在第 1 位时,它也一样成为终极目标),约定的做法是使用一个称为 "all" 的伪目标来作为终极目标(其它名字也可以)。这时,它的依赖文件就是那些需要创建的程序。比如一个 Makefile 文件的内容如下:
all: hello world ok .PHONY:all hello : hello.o gcc hello.o -o hello hello.o: hello.c gcc -c hello.c -o hello.o world : world.o gcc world.o -o world world.o : world.c gcc -c world.c -o world.o ok : ok.o gcc ok.o -o ok ok.o : ok.c gcc -c ok.c -o ok.o clean: -rm -f *.o hello world ok
执行 make 时,因为 all 有 3 个依赖,所以这 3 个依赖所表示的可执行文件都会被生成。如果需要单独的生成某 1 个程序,那么需要使用 make 来明确指定(如 "make hello") 。如果这些可执行文件都已经生成,且它们的各个依赖都已经是最新,再次 make 时啥也不做:
[beyes@SLinux temp2]$ make make: Nothing to be done for `all'.
在这一点上,倒不像 GNU MAKE 中文译本(翻译时间为 2004 年)里说的 ”重建已存在目标 all 的所有依赖文件" 。
伪目标也可以作为另外一个伪目标的依赖,这时候 make 会将它作为另外一个伪目标的子例程来处理。比如有下面一个 Makefile 文件:
PHONY += all all: hello world ok hello : hello.o gcc hello.o -o hello hello.o: hello.c gcc -c hello.c -o hello.o world : world.o gcc world.o -o world world.o : world.c gcc -c world.c -o world.o ok : ok.o gcc ok.o -o ok ok.o : ok.c gcc -c ok.c -o ok.o PHONY += cleanall clwanobj cleanexe cleanall:cleanobj cleanexe cleanobj: rm *.o cleanexe: rm hello world ok .PHONY: $(PHONY)
在 make 后,接着执行 make cleanall ,就会看到 Makefile 分别执行了 cleanobj 和 cleanexe 这两个目标下的命令。
FORCE
在内核的 Makefile 中会在多处地方看到 FORCE ,比如:
# vmlinux image - including updated kernel symbols
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
实际上它是一个伪目标:
PHONY +=FORCE FORCE: # Declare the contents of the .PHONY variable as phony. We keep that # information in a variable so we can use it in if_changed and friends. .PHONY: $(PHONY)
从上面看到,FORCE 既没有依赖的规则,其底下也没有可执行的命令。
如果一个规则没有命令或者依赖,而且它的目标不是一个存在的文件名,在执行此规则时,目标总会被认为是最新的。也就是说,这个规则一旦被执行,make 就认为它所表示的目标已经被更新过。当将这样的目标(FORCE)作为一个规则的依赖时(如上的 vmlinux: ),由于依赖总被认为是被更新过的,所以作为依赖所在的规则定义的命令总会被执行。
比如上面的 vmlinux: 在每次 make 时,它下面的这些命令总会被执行:
ifdef CONFIG_HEADERS_CHECK $(Q)$(MAKE)-f $(srctree)/Makefile headers_check endif ifdef CONFIG_SAMPLES $(Q)$(MAKE) $(build)=samples endif ifdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentation endif $(call vmlinux-modpost) $(call if_changed_rule,vmlinux__) $(Q)rm -f .old_version
用一个直观的例子可以清楚看到这一点,比如有 1 Makefile 文件:
helloworld:file1.o file2.o gcc file1.o file2.o -o helloworld file1.o:file1.c file2.h gcc -c file1.c -o file1.o file2.o:file2.c file2.h gcc -c file2.c -o file2.o clean: rm -rf *.o helloworld PHONY +=FORCE FORCE: .PHONY: $(PHONY)
在执行 make 后,观察文件的生成时间:
[beyes@SLinux Makefile]$ ll total 32 -rw-rw-r--. 1 beyes beyes 129 Apr 16 19:00 file1.c -rw-rw-r--. 1 beyes beyes 924 Apr 16 20:20 file1.o -rw-rw-r--. 1 beyes beyes 108 Apr 16 19:01 file2.c -rw-rw-r--. 1 beyes beyes 139 Apr 16 18:49 file2.h -rw-rw-r--. 1 beyes beyes 880 Apr 16 20:20 file2.o -rwxrwxr-x. 1 beyes beyes 4786 Apr 16 20:20 helloworld -rw-rw-r--. 1 beyes beyes 246 Apr 16 20:20 Makefile
helloworld 文件的生成时间是 20:20
如果将上面的 Makefile 文件的 helloworld:file1.o file2.o 这一句后面加个 FORCE,那么再过几分钟后再 make 时,再观察一下 helloworld 的生成时间,可以看到是重新生成的了,当然在 make 执行时命令的输出也能知道该命令被再次执行:
[beyes@SLinux Makefile]$ ll total 32 -rw-rw-r--. 1 beyes beyes 129 Apr 16 19:00 file1.c -rw-rw-r--. 1 beyes beyes 924 Apr 16 20:20 file1.o -rw-rw-r--. 1 beyes beyes 108 Apr 16 19:01 file2.c -rw-rw-r--. 1 beyes beyes 139 Apr 16 18:49 file2.h -rw-rw-r--. 1 beyes beyes 880 Apr 16 20:20 file2.o -rwxrwxr-x. 1 beyes beyes 4786 Apr 16 20:26 helloworld -rw-rw-r--. 1 beyes beyes 246 Apr 16 20:20 Makefile
$(if $(KBUILD_VERBOSE:1=),@) 语法释疑
在 Makefile 的 125 行中有一句:
$(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \ KBUILD_SRC=$(CURDIR) \ KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile \ $(filter-out _all sub-make,$(MAKECMDGOALS))
其中 $(if $(KBUILD_VERBOSE:1=)),@) 的用法看起来有点蹊跷,实际上 $(VAR:x=y) 这种语法相当于 $(patsubst x,y,$(VAR)) 的缩写。这里需要注意一点,x 和 y 前面不能有 ‘%’ 匹配符,这是因为 '%' 已经被默认添加,所以它就如同如下形式:
$(patsubst %x,%y,$(VAR))
这样,$(if $(KBUILD_VERBOSE:1=)),@) 被展开为:
$(if $(patsubst %1,%,$(KBUILD_VERBOSE)),@)
所以,只要 KBUILD_VERBOSE 为非 1 的任何字符时,整个表达式的结果就是 : @ 。如果 KBUILD_VERBOSE 为 1 时,那么整个表达式结果为空。实际上,表达式结果为 @ 时,就是希望后面的命令能够静默执行。
测试代码-1:
KBUILD_VERBOSE := hello1 all: @echo "$(if $(KBUILD_VERBOSE:1=),@)"
运行输出:
[beyes@beyes Makefile]$ make
@
测试代码-2:
KBUILD_VERBOSE := 1 all: @echo "$(if $(KBUILD_VERBOSE:1=),@)"
运行输出:
$ make
#输出为空
关于 U-Boot Makefile 中的 $(@:_config=) 变量的说明
在 U-Boot 的 Makefile 里有这么一个变量:
sbc2410x_config: unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t sbc2410x NULL s3c24x0
那这个变量是什么意思呢?下面先看一个 Makefile 文件的例子:
STRING = hello world
all:
@echo $(STRING)
@echo $(STRING:world=Makefile)
运行输出:
$ make
hello world
hello Makefile
由输出的第 2 行可以看到 world 替换成 Makefile 了! 也就是说,在 STRING 变量中匹配到 world 的字符串会被 '=' 号后面的字符串所替换。
从这里,可以看到变量的另外一种用法:
$(var:string1=string2)
其意是,将变量 var 中的匹配 string1 的字符串替换为字符串 string2 。
注意:string1 要是匹配到整个变量末尾的字符串,而不是中间的某部分,否则替换失败,变量仍然保持
比如说,当我们更改上面的 @echo $(STRING:world=Makefile) 改成 @echo $(STRING:wor=Makefile),那么输出:
$ make
hello world
hello world
由上面的示例,我们已经猜到 U-BOOT 中的 $(@:_config=) 的用法:
在 $(@:_config=) 中,$@ 表示所有的目标文件。也就是说,原先生成的目标文件的文件名末尾是 "_config" 字符串,而 '=' 号后为空,表示去掉 _config 这部分。
比如有 Makefile 文件内容如下:
hello_config: hello.o gcc -o $(@:_config=) $^ hello.o: hello.c gcc -Wall -c hello.c -o hello.o
make 后输出:
$ ls
hello hello.c hello.h hello.o Makefile
从这里可见,原本要输出的目标文件 hello_config 的可执行文件被改名为 hello 。
MAKECMDGOALS 变量
make 在执行时会设置一个特殊变量 -- "MAKECMDGOALS" ,该变量记录了命令行参数指定的终极目标列表,没有通过参数指定终极目标时此变量为空。该变量仅限于用在特殊场合(比如判断),在 Makefile 中最好不要对它进行重新定义。
下面通过一个实例来说明此变量的意思,设一 Makefile 内容如下:
ifeq ("$(MAKECMDGOALS)","are you ok cmdgoals") who="you" endif are: @echo "are" you: @echo "you" ok: @echo "ok" cmdgoals: @echo "MAKECMDGOALS: $(MAKECMDGOALS)" @echo "who are $(who)"
运行输出:
[beyes@SLinux tempa]$ make are you ok cmdgoals
are
you
ok
MAKECMDGOALS: are you ok cmdgoals
who are you
我们在命令行中同时指定了 "are" , "you", "ok", 以及 "cmdgoals" 这 4 个目标。这些目标的名字都会传入 MAKECMDGOALS 中。
make 的 -f 参数以及在 Makefile 文件间传递参数
make 的 -f 参数后接文件名(可含路径),由该文件名指定的文件作为 Makefile 文件,并且这个文件名后可接参数,该参数可作为该文件中的某个变量。
某个目录下文件:
[beyes@SLinux temp]$ ll total 8 -rw-rw-r--. 1 beyes beyes 103 Apr 18 11:00 Makefile drwxrwxr-x. 2 beyes beyes 4096 Apr 18 11:01 scripts [beyes@SLinux temp]$ ll scripts/ total 8 -rw-rw-r--. 1 beyes beyes 74 Apr 18 10:49 Kbuild.include -rw-rw-r--. 1 beyes beyes 37 Apr 18 10:49 Makefile.building
其中 Makefile 文件的内容为:
include ./scripts/Kbuild.include all: @make $(build)="pass init for another Makefile\'s parameter"
上面,$(build) 变量在 scripts/Kbuild.include 文件中有定义,所以这里要在第 1 行将 Kbuild.include 包含进来。Kbuild.include 中的内容为:
build := -f ./scripts/Makefile.building obj
所以,我们在执行 make 时,实际上就是展开以下命令:
make -f ./scripts/Makefile.building obj="pass init for another Makefile\'s parameter"
上面 obj= 后面的双引号中的 's 前要加一反斜杠表示字符转义,否则会在 make 时发生语法错误。这里,我们指定 scripts/Makefile.building 作为另一个 Makefile 文件,并且要向它传递进一个 obj 参数,这个参数的内容就是双引号里的字符串。
Makefile.building 文件内容如下所示:
src := $(obj)
output:
@echo $(src)
上面,我们将 $(obj) 的内容赋给 src 这个变量,并在最后显示这个变量。
现在 make 一下,看输出内容:
[beyes@SLinux temp]$ make make[1]: Entering directory `/home/beyes/Makefile/temp' pass init for another Makefile's parameter make[1]: Leaving directory `/home/beyes/Makefile/temp'
正确输出。
总结一下 make 的过程:
在主 Makefile 文件中,因为仅有一个目标 all,所以就只要简单执行 make 命令即可,它通过 -f 参数指定了要 make 另外一个 Makefile 文件(Makefile.building),并且要向那个文件传递进一个 obj 变量参数。这时,我们的 make 流程转入到 Makefile.building 中,因为该文件中亦只有一个目标,所以默认下也就执行该目标下的命令,即打印出传递进来的参数内容。
在 Linux 内核的 Makefile 中,在研究 bzImage 的生成过程,也会看到如上所经历的过程。
另外,还可以通过 export 关键字将参数导出到当前工作的环境变量中,这样子 Makefile 也能使用这些变量。但是如果子 Makefile 中含有同名变量的话,那么就以子 Makefile 中的变量为准,这和 C 语言中的局部变量是同一个道理。
Makefile.build 的作用
在内核源码路径下,scripts/ 下有一个 Makefile.build 文件,这个文件的作用是对一个指定的目录进行编译。那么它是怎么做到的呢?答案正是通过 include 关键字。
对于 include 关键字,有一句话比较重要:make 命令执行时,它会找到所有 include 所指定的文件,并将它们的内容放置在当前的位置。
这里通过修改内核源码根目录下的 Makefile 以及 Makefile.build 来观察整个作用过程。
首先了解,scripts 目录下有一个文件为 Kbuild.include 。这个文件里面对一些通用变量做了定义,其中它包含了对主 Makefile 文件中 $(build) 变量的定义:
### # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= # Usage: # $(Q)$(MAKE) $(build)=dir build :=-f $(if$(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
而主 Makefile 文件又是通过以下语句将 Kbuild.include 包含进来:
include $(srctree)/scripts/Kbuild.include
这里需要提醒一下,Kbuild.include 里面没有定义任何的目标,所以这里用 include 包含一点问题都没有,如果是定义了目标,那可能会出现问题,而出现的问题就是下面要说的 Makefile.build 的情况。
接下来,在主 Makefile 的下面两行:
# We need some generic definitions (do not try to remake the file). $(srctree)/scripts/Kbuild.include: ; include $(srctree)/scripts/Kbuild.include
的底下添加:
stub: @echo "==================================" @make $(build)=helloworld
这里,我们自定义了自己一个 stub 目标,为的是跟踪 Makefile.build 的流程。
通过上面一节我们知道 Makefile 文件之间是如何传递参数的。如上面,我们将 helloworld 这个参数传递给 $(build) 变量中。
接着手动在内核源码顶层目录下创建一个 helloworld 的目录。然后在这个目录中创建一个很简单的 Makefile 文件,其内容为:
all: @echo "hello world"
接着,转到 scripts 目录下,修改一下 Makefile.build 文件:
1. 屏蔽掉文件顶上的 __build: 这个目标:
#__build
2. 来到:
# The filename Kbuild has precedence over Makefile kbuild-dir := $(if$(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file)
这里,我们先屏蔽掉 include $(kbuild-file) 这一句,并在底下添加一个自定义的目标:
temp: @echo "---------- $(kbuild-dir) -----------" @echo "---------- $(kbuild-file) ------------"
现在可以测试一下 make stub 这个目标了:
[beyes@SLinux linux-2.6.34.9]$ make stub ================================== ---------- /home/beyes/kernel/linux-2.6.34.9/helloworld ----------- ---------- /home/beyes/kernel/linux-2.6.34.9/helloworld/Makefile ------------
从输出可以看到:
$(kbuild-dir) 表示的是我们要编译的子目录 helloworld 。
$(kbuild-file) 表示的是要编译的子目录下的 Makefile 文件。而就是在这个文件中,它含有要编译的目标!
现在我们可以打开上面被屏蔽的 include $(kbuild-file) 这句话了,解除屏蔽后,我们再 make stub 一下看会有什么结果:
[beyes@SLinux linux-2.6.34.9]$ make stub ================================== i am here
从输出可以看到,由于 include 的 $(kbuild-file) 中另有要生成的目标,从而转去生成那里的目标,而之前的 temp 目标就不再执行编译生成,也就是说 temp 目标被新 include 进来的 all: 目标给覆盖了 (override ) 。
最后,上面之所以要先屏蔽 __build 的目的是为了更好的演示如何将另外目录下的 Makefile 文件包含进来。如果没有屏蔽,那么将执行的是底下的 __build 目标,而不是我们自定义的 temp,因为 __build 已经先得到声明。
分号 ;
在 Makefile 中,一般的语法形式如:
helloworld:file1.o file2.o FORCE gcc file1.o file2.o -o helloworld file1.o:file1.c file2.h gcc -c file1.c -o file1.o file2.o:file2.c file2.h gcc -c file2.c -o file2.o
像 gcc 这些命令往往会另起一行并且要 TAB 键开头。如果不想这么书写,还可以在依赖文件的后面接一个分号,然后再接命令,如下所示:
helloworld:file1.o file2.o FORCE gcc file1.o file2.o -o helloworld file1.o:file1.c file2.h gcc -c file1.c -o file1.o file2.o:file2.c file2.h;gcc -c file2.c -o file2.o
另外,Makefile 中的分号也能像 shell 里一样用来分隔命令,比如:
clean: rm -rf *.o helloworld; echo "This is ok"
执行 make :
$ make clean rm -rf *.o helloworld; echo "This is ok" This is ok
分号不但可以像上面一样分隔命令,它还可以起着像 C 语言中空语句的作用。内核 Makefile 中对此有一处用法:
# We need some generic definitions (do not try to remake the file). $(srctree)/scripts/Kbuild.include: ; include $(srctree)/scripts/Kbuild.include
这里,可以看到第 2 行后面紧接着一个分号。从第 1 行的注释中可以了解到:“我们需要一些通用的定义(不要试图去 remake 这个文件) 。” 换句话说,你即使 remake Kbuild.include 这个文件也不会成功。为了更详细的了解这里的细节,我们模拟这一做法:
1. 在当前 Makefile 目录下也建立一个 scripts/Kbuild.include 。
2. 给 Kbuild.include 文件里只添加一行 VERSION = 13. 先书写一个 Makefile 文件,内容如下:
srctree := $(CURDIR) include $(srctree)/scripts/Kbuild.include all: @echo $(VERSION)
运行输出:
[beyes@SLinux temp]$ make 1
很正常的,输出了变量 VERSION 的值 1 。
现在再改一下 Makefile 文件,内容如下:
srctree := $(CURDIR)$(srctree)/scripts/Kbuild.iniclude: include $(srctree)/scripts/Kbuild.include all: @echo $(VERSION)
注意上面,第 2 行中 kbuild.include: 后面没有接分号。现在 make 一下:
[beyes@SLinux temp]$ make make: Nothing to be done for `/home/beyes/Makefile/temp/scripts/Kbuild.iniclude'.
这时候,提示,对这个/home/beyes/Makefile/temp/scripts/Kbuild.iniclude 目标啥都不做,因为 Kbuild.include 后面有一个冒号,所以它是一个目标文件。自然的,底下的 all 目标不会执行。
最后,在 Kbuild.include: 后面添加一个分号,即:
srctree := $(CURDIR)$(srctree)/scripts/Kbuild.iniclude: ; include $(srctree)/scripts/Kbuild.include all: @echo $(VERSION)
再 make 一下:
[beyes@SLinux temp]$ make make: `/home/beyes/Makefile/temp/scripts/Kbuild.iniclude' is up to date.
这时候提示 Kbuild.inlcude 这个目标已经是最新了。
所以,在内核的 Makefile 中,如上面注释所说,不要试图去 remake 这个 Kbuild.include 这个文件,否则你同样会看到“该目标已经是最新“的提示 ,同时也知道,Kbuild.include 这个文件只是用来提供一些 make 时会用到的常用定义。因此,分号接在一个目标后,它相当于一个空的依赖,而空的依赖则永远是不会被更改的,且永保持最新,从而它对应的目标也永远保持最新。
CONFIG_MODVERSIONS
CONFIG_MODVERSIONS 用来决定是否开启版本控制,该选项防止将接口定义和当前内核版本不再匹配的废弃模块载入内核。
在编译 vmlinux 时,最后会利用 modpost 来解析 vmlinux.o 对象文件,并将基本内核导出的所有符号都记录到文件 Module.symvers 中去。如果没有打开 CONFIG_MODVERSIONS 选项,那么你看到的 Module.symvers 文件中第一列符号 CRC 校验值都是 0x00000000 。如果打开该选项,那么第一列就会显示出各个符号的 CRC 校验值,如:
0x2d6c0f40 put_mnt_ns vmlinux EXPORT_SYMBOL 0x6c386a8c clear_bdi_congested vmlinux EXPORT_SYMBOL ... ...
一旦设置过这个配置选项,就意味着打开了内核的 Module versioning功能。Module versioning 功能应用在我们使用模块的场合。如果 Module versioning 功能被打开的话,它会以每个导出符号的 C 原型声明作为输入,计算出对应的CRC校验值,保存在文件 Module.symvers 中。如此一来,内核在后面要加载使用模块的时候,会两相比较模块中的CRC值和保存下来的CRC值,如果发现不相等,内核就拒绝加载这个模块。
编译外部模块的 M 与 SUBDIRS 变量
在编译内核模块时,Makefile 里一般会写 “make -C /lib/modules/`uname -r`/build M=`pwd` modules” 这句话。其中 -C 选项来自 make 命令本身,它后接一个目录名,这里表示内核源码所在目录 。
M 变量 和 SUBDIRS 变量都来自内核顶层 Makefile ,定义如下:
# Use make M=dir to specify directory of external module to build # Old syntax make ... SUBDIRS=$PWD is still supported # Setting the environment variable KBUILD_EXTMOD take precedence ifdef SUBDIRS KBUILD_EXTMOD ?= $(SUBDIRS) endif ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M) endif
从注释知道,SUBDIRS 变量在老式风格中使用,现在都用 M 变量来指定要编译内核模块代码所在路径。也就是说以下两句是等效的:
make -C /lib/modules/`uname -r`/build M=`pwd` modules
和
make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` modules
如果同时在命令行里同时指定了 SUBDIRS 和 M,那么被编译的只有 M 所目录下指定的模块代码,而 SUBDIRS 的代码不会编译,这是因为 Makefile 文件从上至下解析,KBUILD_EXTMOD 变量的值最终会被 $(M) 所覆盖。