2.2.2 寻找第一个目标
在make menucofig之后,即编译内核的第二步,是执行make命令(2.6之前的内核是执行make bzImage)。我们知道,make命令没有任何参数的时候,默认去执行当前目录下的Makefile文件。我们编译内核的时候是在刚刚从kernel.org下载来的linux-2.6.34.1源码包,所以执行的就是linux-2.6.34.1/Makefile文件,而这个文件的突破口,是找到它的第一个目标。下面,我们就来详细分析这个文件。
首先是几个关于版本信息的宏。
1 VERSION = 2
2 PATCHLEVEL = 6
3 SUBLEVEL = 34
4 EXTRAVERSION = .1
5 NAME = Sheep on Meth
随后17到23行设置环境参数,主要是把LC_COLLATE和LC_NUMERIC两个环境变量的值设置成C,表示C语言。
继续走,44到49行:
44ifeq ("$(origin V)", "command line")
45 KBUILD_VERBOSE = $(V)
46endif
47ifndef KBUILD_VERBOSE
48 KBUILD_VERBOSE = 0
49endif
我们本着“看内核学知识”的原则,介绍介绍origin函数。函数origin并不操作变量的值,只是告诉你你的这个变量是哪里来的,其语法是: $(origin <variable>;)。注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“$”字符。origin 函数会以其返回值来告诉你这个变量的“出生情况”,origin函数的返回值有:“undefined”从来没有定义过、“default”是一个默认的定义、“environment”是一个环境变量、“file”这个变量被定义在Makefile中、“command line”这个变量是来自命令行的、“override”是被override指示符重新定义的、“automatic”是一个命令运行中的自动化变量,应用变量的语法是:$(变量名)。
这些信息对于我们编写 Makefile 是非常有用的,例如,在这里我们make命令后跟了一个参数“V”,而我们的环境中也有一个环境变量“V”,此时,我们想判断一下,如果变量来源于参数,那么我们就把之重定义了,如果来源于非命令行或环境变量,那么我们就不重新定义它。
KBUILD_VERBOSE的值根据在命令行中是否定义了变量V,当没有定义时,默认为V=0,输出为short version;可以用make V=1 来输出全部的命令。
好了,我们跳过若干行变量定义,看到105行,我们的故事开始了:
105 # That's our default target when none is given on the command line
106 PHONY := _all
107 _all:
看到105行的注释,如果make后什么参数都没有,就到这里。在继续往下走之前,我们先来看个前提条件,就是97行对KBUILD_SRC有个判断,即KBUILD_SRC为空才会来到这里。我们看到注释,KBUILD_SRC当然是空的,所以我们敲击make之后,首先来到的是第107行。
当然,你可以做个试验,比如在107行后面添加一行,然后写一个@echo “hello, world!”命令,看看make以后会不会打印出来。试验的结果很令人诧异,什么也没有,看来我们的GNU make基本功还不扎实,还需要继续刻苦学习。看到142行:
142 ifeq ($(KBUILD_EXTMOD),)
143 _all: all
144 else
145 _all: modules
146 endif
这里怎么又出现了一个_all目标呢?这里引出一个非常重要的GNU make知识,那就是如果出现了相同的Target,那么后面的Target将会把前面的Target覆盖,并且执行后一个Target的命令和依赖。所以,当你make的时候,虽然会首先来到107行的_all目标,但紧接着,make会发现后面143或145还有一个_all,那么后面这一个_all就会覆盖它,所以我们看到142行。说起这个KBUILD_EXTMOD变量,就要回到我们的71行:
71 ifdef SUBDIRS
72 KBUILD_EXTMOD ?= $(SUBDIRS)
73 endif
75 ifeq ("$(origin M)", "command line")
76 KBUILD_EXTMOD := $(M)
77 endif
前面讲了操作符“:=”与操作符“+=”的功能相同,只是后面的操作符“:=”用来定义变量(KBUILD_EXTMOD)的变量M只能是前面定义好的,也就是命令行中有M=dir,那么这个M就等于dir对应的值(SUBDIRS)。如果操作符“?=”前面的变量KBUILD_EXTMOD没有定义过,那么就将SUBDIRS赋给KBUILD_EXTMOD;如果定义过,则语句KBUILD_EXTMOD ?= $(SUBDIRS)什么也不做。
KBUILD_EXTMOD叫做Kbuild扩展模式,如果我们make M=dir,那么会将变量KBUILD_EXTMOD的值设置为dir,随后在142行,会跳到145行去执行依赖于modules的代码,就相当于执行make modules。这个make方法也是用来提高我们的驱动开发效率的,如果你只想编译某个具体的驱动,只要指定对应的子目录,就可以只编译这个驱动而不去碰其他的内核代码了。
继续往下走之前,还要强调一个知识点。all这个目标被定义在顶层Makefile的527行:
527 all: vmlinux
大家看到在我们的535行,include了一个$(srctree)/arch/$(SRCARCH)/Makefile文件,也就是前面在KBuild体系中提到的那个具体CPU架构的Makefile。针对我的系统,这个文件自然而然就是linux-2.6.34.1/arch/x86/Makefile,我们打开这个文件,可以看到它的150行也有一个all目标。也就是说,x86体系的Makefile把顶层Makefile的all目标给覆盖了:
150 all: bzImage
151
152 # KBUILD_IMAGE specify target image being built
153 KBUILD_IMAGE := $(boot)/bzImage
154
155 bzImage: vmlinux
有意思的是,不管是顶层的Makefile,还是具体体系的Makefile,他们的all目标都依赖于vmlinux(当然,arch/x86/Makefile是间接依赖)。所以我们还是来关注位于顶层Makefile的vmlinux目标。vmlinux目标是顶层Makefile中最重要的Target,其目的主要是用来初始化编译期间KBuild使用到的内核目标。查找vmlinux还真费劲,在后面849行:
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
注意,这个vmlinux后面依赖了FORCE,说明是伪目标,跟最后生成的vmlinux风马牛不相及。vmlinux又依赖了五个东西,个个都不是省油的灯,非常重要,我们一个一个来看,其中前3个目标定义来自来自699、700、702行(这也是为什么vmlinux要定义在800多行的原因):
699 vmlinux-init := $(head-y) $(init-y)
700 vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
701 vmlinux-all := $(vmlinux-init) $(vmlinux-main)
702 vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds
703 export KBUILD_VMLINUX_OBJS := $(vmlinux-all)
vmlinux.o目标来自869行:
869 vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE
870 $(call if_changed_rule,vmlinux-modpost)
kallsyms.o 变量来自776行:
776 kallsyms.o := .tmp_kallsyms$(last_kallsyms).o
vmlinx是我们的重中之重,要分析它,必须把它的依赖详细展开来看。不过这个工作先放一放,我们这里的目的是寻找第一个目标,所以这部分的展开先略过。vmlinux的第一个依赖是$(vmlinux-lds),其值被赋成了arch/x86/kernel/vmlinux.lds,这个文件是很重要的,后面会详细讲解。而$(vmlinux-lds)跟其他vmlinux的依赖一起,又依赖于$(vmlinux-dirs) ,我们看到874行:
874 $(sort $(vmlinux-init) $(vmlinux-main)) $(vmlinux-lds): $(vmlinux-dirs) ;
这条语句是顶层Makefile中最为关键的一行。vmlinux-dirs变量是什么?就是形成vmlinux的所有待编译目录的集合,定义于655行:
655 vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) /
656 $(core-y) $(core-m) $(drivers-y) $(drivers-m) /
657 $(net-y) $(net-m) $(libs-y) $(libs-m)))
filter函数很简单,过滤掉$(init-y) $(init-m) $(core-y) $(core-m) $(drivers-y) $(drivers-m) $(net-y) $(net-m) $(libs-y) $(libs-m)这么一长串字符串中的%/,那么vmlinux-dirs最后的值就是类似init usr kernel mm fs ipc security crypto block drivers sound firmware net lib……这么一长串的字符串。当然,这串字符串具体是什么,以及后面执行啥命令,这是整个KBuild体系的工作核心,后面还会详细分析,现在只是说明一下这里有这么一个过程,一个思路。
这里顺便提一下,659行vmlinux-alldirs变量的赋值过程类似,只不过最顶层是通过sort函数对最后的字符串进行一个排序,还要加上一些init-n或init-形式的目录。vmlinux-alldirs变量基本上用不到。
看到883行,$(vmlinux-dirs)又有两个依赖:
883 $(vmlinux-dirs): prepare scripts
884 $(Q)$(MAKE) $(build)=$@
885 ifdef CONFIG_MODULES
886 $(Q)$(MAKE) $(modbuiltin)=$@
887 endif
他们分别是prepare和scripts,先来看第一个依赖目标,来自990行:
990 prepare: prepare0
不错,它又依赖于prepare0,看到985行:
985 prepare0: archprepare FORCE
又依赖于archprepare,来自983行:
983 archprepare: prepare1 scripts_basic
它又依赖于prepare1和scripts_basic,看来寻找第一个目标的任务还得继续啊,来看prepare1,来自979行:
979 prepare1: prepare2 include/linux/version.h include/generated/utsrelease.h /
980 include/config/auto.conf
继续走,又依赖prepare2,来自977:
977 prepare2: prepare3 outputmakefile
继续走,又依赖prepare3,来自966:
prepare3: include/config/kernel.release
原本到966行,我以为prepare3的依赖就是一个文件,只是不存在的文件,都准备把prepare3作为第一个目标了,结果眼睛一瞥,949行:
949 include/config/kernel.release: include/config/auto.conf FORCE
还好我留了一个心眼,果然,include/config/auto.conf也是一个Target:
507 include/config/auto.conf:
经过艰苦的努力,终于把顶层Makefile的第一个目标找到了。当然本着学习的态度,我们还要稍微深入地讨论一下,那就是整个顶层Makefile还有三个重要的变量:mixed-targets、config-targets和dot-config,定义在416行:
416 config-targets := 0
417 mixed-targets := 0
418 dot-config = 1
随后看到426行:
426 ifeq ($(KBUILD_EXTMOD),)
427 ifneq ($(filter config %config,$(MAKECMDGOALS)),)
428 config-targets := 1
429 ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
430 mixed-targets := 1
431 endif
432 endif
433 endif
$(MAKECMDGOALS)是环境变量,就是make后面跟的参数字符串。注意,这里一个很重要的知识点,make在执行时设置一个特殊变量“MAKECMDGOALS”,记录了命令行参数指定的终极目标列表,没有通过参数指定终极目标时此变量为空。注意:此变量仅用在特殊的场合(比如判断),在Makeifle中不要对它进行重新定义!但在编译内核这种特殊环境中,如果在make menuconfig之前,make后没有任何参数, $(MAKECMDGOALS)是有值的:silentoldconfig。而在配置完成后,$(MAKECMDGOALS)是为空。至于这是为什么,我也不知道,我知道结论是通过做实验来得到的:
make menuconfig前:
mixed-targets = 0
config-targets = 1
dot-config = 1
$(MAKECMDGOALS) = silentoldconfig
make menuconfig后:
mixed-targets = 0
config-targets = 0
dot-config = 1
$(MAKECMDGOALS) =
如果make后面跟的参数是config或形式为%config,那么config-targets就为1,并且还有其他的参数,那么mixed-targets也为1。但更为重要的是dot-config变量,看到420行:
420 ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
421 ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
422 dot-config := 0
423 endif
424 endif
所以这里,如果make不跟任何参数,那么config-targets 和dot-config都是等于1的。在411行说明了no-dot-config-targets是由这些字符串组成:clean mrproper distclean cscope TAGS tags help %docs check% include/linux/version.h headers_% kernelrelease kernelversion,那么420行说明如果make后面的参数是以上的形式,那么421行再把这些过滤掉,如果为空,则dot-config就为0。大家注意,no-dot-config-targets所涉及的参数都跟我们的.config无关,而dot-config变量的值就是这个意思。
综上所述,如果在make menuconfig之前make,那么$(MAKECMDGOALS)是silentoldconfig,因此config-targets 的值是1,那么顶层的Makefile的从463到1433行的代码将会被略过。那么第一个目标是385行的scripts_basic。具体的细节请去看上一节编译配置的内容。
如果在make menuconfig之后make那么$(MAKECMDGOALS)的值是空的,那么编译配置时会生成文件include/config/auto.conf,大概46k,其内容如下:
CONFIG_USB_SISUSBVGA=m
CONFIG_PCMCIA_FMVJ18X=m
CONFIG_BLK_CPQ_DA=m
CONFIG_BLK_DEV_FD=m
CONFIG_ACPI_AC=m
CONFIG_ACPI_SYSFS_POWER=y
CONFIG_SECURITY_NETWORK=y
CONFIG_OSF_PARTITION=y
CONFIG_USB_LEGOTOWER=m
……
所以949行其实有个FORCE,因此虽然include/config/auto.conf生成了,但还是要执行include/config/kernel.release目标的命令,那么这个目标就是我们寻址的第一个目标。