异想天开

What's the true meaning of light, Could you tell me why

linux的makefile分析

日期:2013-09-15 11:04:13
  
最后更新日期:2015-08-22 09:33:23
目录:
1.顶层makefile简介
2.压缩内核镜像bzimage构建
2.1 bzimage由来
2.2 setup.bin构建
2.3 vmlinux.bin构建
3. 总结
摘要:
本文假定读者具有的前提:粗略的makefile知识,但不是很熟悉,编译过x86平台linux内核,内核版本为2.6.35.13-正在执行如下动作make -O=OUTPUTDIR menuconfig,make -O=OUTPUTDIR bzimage, make -O=OUTPUTDIR modules , make -O=OUTPUTDIR modules_install。希望从细节上看整个make过程。注:可以用-O指定一个输出目录,避免源代码目录空间不足,导致编译失败。
1 顶层makefile简介
怎么配置内核?
先定义config-targets := 0,在这个逻辑中:
$(srctree)/Makefile:
[code lang="cpp"]
ifeq ($(KBUILD_EXTMOD),) // KBUILD_EXTMOD变量用于选定module编译到指定目录,默认目录为代码树的module目录
ifneq ($(filter config %config,$(MAKECMDGOALS)),) // MAKECMDGOALS为执行make时命令行中的make的参数。
config-targets := 1
ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
mixed-targets := 1
endif
endif
endif
[/code]
判断此刻不是编译module,如果有config对象,则config-targets赋值为1;若还有其他对象,则为mixed-targets,mixed-targets指同时包括config对象和镜像对象(注:行号,对应的版本2.6.35.13版本的makefile,若是其他版本可以忽略,由于添加注释缘故,行号会有些出入,所以行号本身并不重要,但余念想彼时大学园中,孤诣coding,凡事必究其原因,大耗精力,若今日有此类读者,故添之,亦不必强迫纠结无行号之痒)。

顶层makefile的主要逻辑:
$(srctree)/Makefile:
[code lang="cpp"]
ifeq ($(mixed-targets),1)
//编译混合对象
444:else
445: ifeq ($(config-targets),1)
include $(srctree)/arch/$(SRCARCH)/Makefile // $(srctree)变量即为当前代码树的目录
//编译配置对象
464: else
include $(srctree)/arch/$(SRCARCH)/Makefile
// 编译vmlinux
// 编译modules
1376: endif
1377:endif
[/code]
怎么得到特定体系结构?
$(srctree)/Makefile:
[code lang="cpp"]
162: SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/
-e s/arm.*/arm/ -e s/sa110/arm/
-e s/s390x/s390/ -e s/parisc64/parisc/
-e s/ppc.*/powerpc/ -e s/mips.*/mips/
-e s/sh[234].*/sh/ )
189: ARCH ?= $(SUBARCH)
[/code]
// 若ARCH没有定义,则将SUBARCH的值赋给ARCH,若需要交叉编译其他平台的内核,则需要执行:
make ARCH=arm menuconfig,make ARCH=arm bzimage, make ARCH=arm modules,再下载到开发板上,当然这个不代表具体命令,毕竟我没有做过这方面的开发。

2  bzimage构建
2.1 bzimage由来
在顶层makefile搜索一下bziamge没有发现bzimage对象,bzimage对象与体系结构相关,故在体系结构的makefile里面构建。$(srctree)/arch/x86/makefile搜索bzimage:
$(srctree)/arch/x86/Makefile:
[code lang="cpp"]
156:bzImage: vmlinux
ifeq ($(CONFIG_X86_DECODER_SELFTEST),y)
$(Q)$(MAKE) $(build)=arch/x86/tools posttest
endif
$(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
$(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot
162: $(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@
[/code]
注释:
1. 变量boot,其值arch/x86/boot。顶层makefile定义export KBUILD_IMAGE ?= vmlinux,后在体系结构的makefile更改:
$(srctree)/arch/x86/Makefile:
154:KBUILD_IMAGE := $(boot)/bzImage

2. 变量Q-make程序默认会将执行的动作打印出来,但是若makefile里面命令前加了“@”符号,则不会打印,变量Q即控制是否需要quiet编译(不打印make执行的命令);变量Q定义:
$(srctree)/Makefile:
[code lang="cpp"]
287:ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
290:else
quiet=quiet_
Q = @
293:endif
[/code]

3. 变量build-linux内核将一些通用的规则,写成统一的脚本,就想函数一样便于调用。
$(srctree)/scripts/kbuild.include:
[code lang="cpp"]
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
150:build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
[/code]
if为makefile函数,这行逻辑用c语言会这样写:
if ( $(KBUILD_SRC) )
$(srctree)/
即若$(KBUILD_SRC)有定义,定义build := -f $(srctree)/scripts/Makefile.build obj= ,否则定义build := -f scripts/Makefile.build obj=。

4.
[code lang="cpp"]
$(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
[/code]
可以表示如下信息: make -f scripts/Makefile.build obj=arch/x86/boot arch/x86/boot/bzimage 。 -f参数指定使用的makefile为scripts/Makefile.build。这条规则是一条普遍的规则,使用比较多,在此讲一下其逻辑:
$(srctree)/scripts/Makefile.build:
[code lang="cpp"]
5:src := $(obj)
42:kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
[/code]
makefile内建函数-filter函数过滤掉文件,留下目录。makefile内建函数一般是这种调用模式:$(函数名 参数1,参数2)
[code lang="cpp"]
43:kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
[/code]
makefile内建函数-wildcard函数判断文件是否存在 makefile内建函数-if函数,条件判断。$(if 条件判断,条件成立执行语句,条件不成立执行语句)
[code lang="cpp"]
44:include $(kbuild-file)
[/code]
上面的逻辑即包括boot目录下的makefile,若有kbuild,则包括kbuild。即include arch/x86/boot/Makefile(该目录下只有Makefile)
arch/x86/boot/Makefile:
[code lang="cpp"]
77: quiet_cmd_image = BUILD $@
78: cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin
$(ROOT_DEV) > $@
81: $(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
$(call if_changed,image)
[/code]
当依赖更新时,重新调用cmd_image命令。这个逻辑如下:
[code lang="cpp"]
# Execute command if command has changed or prerequisite(s) are updated.
#
if_changed = $(if $(strip $(any-prereq) $(arg-check)),
@set -e;
$(echo-cmd) $(cmd_$(1));
echo 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
[/code]
bzimage压缩内核由arch/x86/boot/setup.bin和arch/x86/boot/vmlinux.bin,通过arch/x86/boot/tools/build组建。跟踪这两个目标即知道内核是怎么构建的。

2.2 vmlinux
$(srctree)/Makefile:
[code lang="cpp"]
# vmlinux image - including updated kernel symbols
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
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
[/code]
script/Kbuild.include:
[code lang="cpp"]
if_changed_rule = $(if $(strip $(any-prereq) $(arg-check) ),
@set -e;
$(rule_$(1)))
[/code]
其实调用rule_vmlinux__命令:
$(srctree)/Makefile:
[code lang="cpp"]
define rule_vmlinux__
:
$(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version))
$(call cmd,vmlinux__)
$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
$(Q)$(if $($(quiet)cmd_sysmap),
echo ' $($(quiet)cmd_sysmap) System.map' && )
$(cmd_sysmap) $@ System.map;
if [ $$? -ne 0 ]; then
rm -f $@;
/bin/false;
fi;
$(verify_kallsyms)
endef
[/code]
实质调用cmd命令:
cmd = @$(echo-cmd) $(cmd_$(1))
即调用cmd_vmlinux__
$(srctree)/Makefile:
[code lang="cpp"]
cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@
-T $(vmlinux-lds) $(vmlinux-init)
--start-group $(vmlinux-main) --end-group
$(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)
[/code]

这里可以看出代码树的vmlinux的组成是$(vmlinux-init),$(vmlinux-main)以及其他。
vmlinux-init := $(head-y) $(init-y)
head-y := arch/x86/kernel/head_$(BITS).o
head-y += arch/x86/kernel/head$(BITS).o
head-y += arch/x86/kernel/head.o
head-y += arch/x86/kernel/init_task.o

2.3 arch/x86/boot/setup.bin
$(srctree)/arch/x86/boot/Makefile:
[code lang="cpp"]
$(obj)/setup.bin: $(obj)/setup.elf FORCE
$(call if_changed,objcopy)
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
$(call if_changed,ld)
[/code]

$(SETUP_OBJS)这些目标即开机初始化目标,在$(srctree)/arch/x86/boot/Makefile定义。
setup-y += a20.o bioscall.o cmdline.o copy.o cpu.o cpucheck.o edd.o
setup-y += header.o main.o mca.o memory.o pm.o pmjump.o
setup-y += printf.o regs.o string.o tty.o video.o video-mode.o
setup-y += version.o
setup-$(CONFIG_X86_APM_BOOT) += apm.o
setup-y += video-vga.o

2.4 arch/x86/boot/vmlinux.bin $(srctree)/arch/x86/boot/Makefile:
[code lang="cpp"]
$(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
$(obj)/compressed/vmlinux: FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
[/code]
$(srctree)/arch/x86/boot/compressed/Makefile:
[code lang="cpp"]
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o $(obj)/piggy.o FORCE
$(call if_changed,ld)
@:
$(obj)/vmlinux.bin: vmlinux FORCE
$(call if_changed,objcopy) //objcopy顶层目录生成的vmlinux为当前目录vmlinux.binvmlinux.bin.all-y := $(obj)/vmlinux.bin
$(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE
$(call if_changed,gzip) // gzip压缩内核

suffix-$(CONFIG_KERNEL_GZIP) := gz

quiet_cmd_mkpiggy = MKPIGGY $@
cmd_mkpiggy = $(obj)/mkpiggy $< > $@ || ( rm -f $@ ; false )

targets += piggy.S
$(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy FORCE
$(call if_changed,mkpiggy)
[/code]
查看mkpiggy.c源码:
printf("input_data:n");
printf(".incbin "%s"n", argv[1]);
printf("input_data_end:n");
这几行,意思是将vmlinux.bin.gzip压缩的二进制文件包含进来。

ld和gzip命令调用,scripts/makefile.lib:
[code lang="cpp"]
quiet_cmd_ld = LD $@
cmd_ld = $(LD) $(LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F))
$(filter-out FORCE,$^) -o $@quiet_cmd_gzip = GZIP $@
cmd_gzip = (cat $(filter-out FORCE,$^) | gzip -f -9 > $@) ||
(rm -f $@ ; false)
[/code]

3 总结
编译过程中首先在源代码目录会产生一个包含32位保护模式代码的bin文件vmlinux,然后拷贝到arch/x86/boot/compressed/目录,objcopy洗净,加上解压代码,拷贝到arch/x86/boot/vmlinux.bin。然后与实模式启动代码链接而成的bin文件setup.bin通过build程序组装为bzimage。