内核映像的形成——寻找第一个目标

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

 

随后1723行设置环境参数,主要是把LC_COLLATELC_NUMERIC两个环境变量的值设置成C,表示C语言。

 

继续走,4449行:

  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,当没有定义时,默认为V0,输出为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会发现后面143145还有一个_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这个目标被定义在顶层Makefile527行:

527 all: vmlinux

 

大家看到在我们的535行,include了一个$(srctree)/arch/$(SRCARCH)/Makefile文件,也就是前面在KBuild体系中提到的那个具体CPU架构的Makefile。针对我的系统,这个文件自然而然就是linux-2.6.34.1/arch/x86/Makefile,我们打开这个文件,可以看到它的150行也有一个all目标。也就是说,x86体系的Makefile把顶层Makefileall目标给覆盖了:

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是间接依赖)。所以我们还是来关注位于顶层Makefilevmlinux目标。vmlinux目标是顶层Makefile中最重要的Target,其目的主要是用来初始化编译期间KBuild使用到的内核目标。查找vmlinux还真费劲,在后面849行:

vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE

 

注意,这个vmlinux后面依赖了FORCE,说明是伪目标,跟最后生成的vmlinux风马牛不相及。vmlinux又依赖了五个东西,个个都不是省油的灯,非常重要,我们一个一个来看,其中前3个目标定义来自来自699700702行(这也是为什么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体系的工作核心,后面还会详细分析,现在只是说明一下这里有这么一个过程,一个思路。

 

这里顺便提一下,659vmlinux-alldirs变量的赋值过程类似,只不过最顶层是通过sort函数对最后的字符串进行一个排序,还要加上一些init-ninit-形式的目录。vmlinux-alldirs变量基本上用不到。

 

看到883行,$(vmlinux-dirs)又有两个依赖:

883 $(vmlinux-dirs): prepare scripts

884        $(Q)$(MAKE) $(build)=$@

885 ifdef CONFIG_MODULES

886        $(Q)$(MAKE) $(modbuiltin)=$@

887 endif

 

他们分别是preparescripts,先来看第一个依赖目标,来自990行:

990 prepare: prepare0

 

不错,它又依赖于prepare0,看到985行:

985 prepare0: archprepare FORCE

 

又依赖于archprepare,来自983行:

983 archprepare: prepare1 scripts_basic

 

它又依赖于prepare1scripts_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-targetsconfig-targetsdot-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的从4631433行的代码将会被略过。那么第一个目标是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目标的命令,那么这个目标就是我们寻址的第一个目标。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值