资料来源:正点原子嵌入式Linux
目录
make xxx_defconfig 过程
Makefile.build 脚本分析
scripts_basic
%config 目标对应的命令
make过程
$(head-y)
$(init-y)、 $(core-y) 、 $(net-y)
$(libs-y)
$(core-y)
链接脚本
make zImage 过程
Linux内核顶层Makefile与U-boot的十分相似。以下与Uboot一致:
1.版本号
2.MAKEFLAGS变量
3.命令输出
4.静默输出
5.设置编译结果输出目录
6.代码检查
7.模块编译
8.设置目标架构和交叉编译器
9.调用 scripts/Kbuild.include 文件
10.交叉编译工具变量设置
11.头文件路径变量
12.导出变量
下面详细描述其余不一样的地方。
第一次编译 Linux 之前都要使用“make xxx_defconfig”先配置 Linux 内核,在顶层 Makefile中有“%config”这个目标
%config: scripts_basic outputmakefile FORCE #make xxx_defconfig”的时候就会匹配到%config
$(Q)$(MAKE) $(build)=scripts/kconfig $@
铜Uboot一样,FORCE强制执行,outputmakefile为空,真正有意义的依赖只有scripts_basic。scripts_basic定义:
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
build定义在scripts/Kbuild.include中,
build := -f $(srctree)/scripts/Makefile.build obj |
srctree=. #也就是当前目录 |
前面第9点已经对其进行了调用,因此依赖scripts_basic展开后:
scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount //也可以没有@
回到%config中,命令展开后:
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
所以“ make xxx_defconfig“配置 Linux 的时候如下两行命令会执行脚本:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
接下来详细分析这两句脚本。
scripts_basic 目标对应的命令为: @make -f ./scripts/Makefile.build obj=scripts/basic
Makefile.build中有代码:
src := $(obj)
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) #(filter /%,$(src))为空,所以kbuild-dir=$(srctree)/$(src)=./scripts/basic
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) #./scripts/basic里没有Kbuild,所以kbuild-file=./scripts/basic/Makefile
include $(kbuild-file)
展开后:
kbuild-dir=$(srctree)/$(src)=./scripts/basic
kbuild-file=./scripts/basic/Makefile
include ./scripts/basic/Makefile
继续分析:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
由于scripts_basic 目标对应的命令为: @make -f ./scripts/Makefile.build obj=scripts/basic,并没有指定目标,所以默认目标__build,在顶层MakeFile中,KBUILD_BUILTIN 为 1,KBUILD_MODULES 为空,因此展开后目标__build 为:
__build:$(builtin-target) $(lib-target) $(extra-y)$(subdir-ym) $(always)
可以看出目标__build 有 5 个依赖: builtin-target、 lib-target、 extra-y、 subdir-ym 和 always。具体内容如下:
builtin-target =
lib-target =
extra-y =
subdir-ym =
always = scripts/basic/fixdep scripts/basic/bin2c
可以看出,仅有always有效,所以__build最终为:
__build: scripts/basic/fixdep scripts/basic/bin2c
@:
__build 依赖于 scripts/basic/fixdep 和 scripts/basic/bin2c,所以要先将 scripts/basic/fixdep.c 和scripts/basic/bin2c.c 这两个文件编译成 fixdep 和 bin2c。
综上所述, scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 和 scripts/basic/bin2c 这两个软件。
对应命令为@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
Makefile.build中已经得到变量
kbuild-dir=$(srctree)/$(src)=./scripts/basic
kbuild-file=./scripts/basic/Makefile
include ./scripts/basic/Makefile
include会调用./scripts/basic/Makefile。子Makefile中有如下内容:
%_defconfig: $(obj)/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
这里会直接匹配到命令@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
展开后:
%_defconfig: scripts/kconfig/conf
@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig
%_defconfig依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c生成conf这个软件。此软件就会将%_defconfig 中的配置输出到.config 文件中,最终生成 Linux kernel 根目录下的.config 文件。
综上所述,%config 目标对应的命令主要是为了根据%_defconfig生成.config 文件。
所以,make xxx_defconfig命令共执行了:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
@make -f ./scripts/Makefile.build obj=scripts/basic用于编译出 scripts/basic/fixdep 和 scripts/basic/bin2c 这两个软件
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig主要是为了根据%_defconfig生成.config 文件。
使用命令“make xxx_defconfig”配置好 Linux 内核以后就可以使用“make”或者“make all”命令进行编译。
顶层Makefile中:
PHONY := _all
_all:
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif
all: vmlinux
默认目标__all,依赖于all,all依赖于vmlinux。同时有:
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) 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
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py
endif
+$(call if_changed,link-vmlinux)
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
其中KBUILD_LDS= arch/$(SRCARCH)/kernel/vmlinux.lds,其中 SRCARCH=arm,因此 KBUILD_LDS= arch/arm/kernel/vmlinux.lds。
KBUILD_VMLINUX_INIT= $(head-y) $(init-y)。
KBUILD_VMLINUX_MAIN = $(core-y) $(libs-y) $(drivers-y) $(net-y)。
所以vmlinux的依赖为:scripts/link-vmlinux.sh、 $(head-y) 、 $(init-y)、 $(core-y) 、$(libs-y) 、 $(drivers-y) 、 $(net-y)、 arch/arm/kernel/vmlinux.lds 和 FORCE。
其中scripts/link-vmlinux.sh提供链接过程, $(head-y) 、 $(init-y)、 $(core-y) 、$(libs-y) 、 $(drivers-y) 、 $(net-y)提供链接文件,arch/arm/kernel/vmlinux.lds提供链接脚本。
make的过程其实就是生成vmlinux的过程,接下来主要看几个依赖。
head-y 定义在文件 arch/arm/Makefile 中,
head-y := arch/arm/kernel/head$(MMUEXT).o
当不使能 MMU 的话 MMUEXT=-nommu,如果使能 MMU 的话为空,因此 head-y 最终的值为
head-y = arch/arm/kernel/head.o
顶层Makefile中:
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
......
init-y := $(patsubst %/, %/built-in.o, $(init-y))
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y := $(patsubst %/, %/built-in.o, $(net-y))
最终得:
init-y = init/built-in.o
drivers-y = drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y = net/built-in.o
顶层makefile:
libs-y := lib/
......
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y := $(libs-y1) $(libs-y2)
arch/arm/Makefile中:
libs-y := arch/arm/lib/ $(libs-y)
最终:libs-y = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
顶层makefile:
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := $(patsubst %/, %/built-in.o, $(core-y))
arch/arm/Makefile:
core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ)
core-$(CONFIG_VFP) += arch/arm/vfp/
core-$(CONFIG_XEN) += arch/arm/xen/
core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/
core-$(CONFIG_VDSO) += arch/arm/vdso/
# If we have a machine-specific directory, then include it in the build.
core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y += arch/arm/probes/
core-y += arch/arm/net/
core-y += arch/arm/crypto/
core-y += arch/arm/firmware/
core-y += $(machdirs) $(platdirs)
最终转换后:
core-y = usr/built-in.o arch/arm/vfp/built-in.o \
arch/arm/vdso/built-in.o arch/arm/kernel/built-in.o \
arch/arm/mm/built-in.o arch/arm/common/built-in.o \
arch/arm/probes/built-in.o arch/arm/net/built-in.o \
arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o \
arch/arm/mach-imx/built-in.o kernel/built-in.o\
mm/built-in.o fs/built-in.o \
ipc/built-in.o security/built-in.o \
crypto/built-in.o block/built-in.o
head-y 、 init-y、 core-y 、 libs-y 、 drivers-y 和 net-y 这 6 个变量都是一些 built-in.o 或.a 等文件,都是将相应目录中的源码文件进行编译,然后在各自目录下生成 built-in.o 文件,有些生成了.a 库文件。最终将这些 built-in.o 和.a 文件进行链接即可形成 ELF 格式的可执行文件,也就是 vmlinux!但是链接是需要连接脚本的,vmlinux 的依赖 arch/arm/kernel/vmlinux.lds 就是整个 Linux 的链接脚本。
再回到vmlinux的依赖语句中:
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) 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
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py
endif
+$(call if_changed,link-vmlinux)
前面都是很多条件判断,最终执行的命令为:+$(call if_changed,link-vmlinux),$(call if_changed,link-vmlinux)是调用函数 if_changed, link-vmlinux 是函数 if_changed 的参数。最终+$(call if_changed,link-vmlinux)展开为(PDF页码897):
cmd_link-vmlinux = /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --noundefined -X --pic-veneer --build-id
该命令会调用scripts/link-vmlinux.sh,也就是vmlinux的第一个依赖。scripts/link-vmlinux.sh有如下代码:
vmlinux_link()
{
local lds="${objtree}/${KBUILD_LDS}"
if [ "${SRCARCH}" != "um" ]; then
${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \
-T ${lds} ${KBUILD_VMLINUX_INIT} \
--start-group ${KBUILD_VMLINUX_MAIN} --end-group ${1}
else
${CC} ${CFLAGS_vmlinux} -o ${2} \
-Wl,-T,${lds} ${KBUILD_VMLINUX_INIT} \
-Wl,--start-group \
${KBUILD_VMLINUX_MAIN} \
-Wl,--end-group \
-lutil ${1}
rm -f linux
fi
}
.......
info LD vmlinux
vmlinux_link "${kallsymso}" vmlinux
SRCARCH=ARM,所以if执行
${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \
-T ${lds} ${KBUILD_VMLINUX_INIT} \
--start-group ${KBUILD_VMLINUX_MAIN} --end-group ${1}
这里就是将所有的链接连接成vmlinux,lds指链接脚本 arch/arm/kernel/vmlinux.lds,文件由变量KBUILD_VMLINUX_INIT 和
KBUILD_VMLINUX_MAIN决定,也就是上文说的
KBUILD_VMLINUX_INIT= $(head-y) $(init-y)。
KBUILD_VMLINUX_MAIN = $(core-y) $(libs-y) $(drivers-y) $(net-y)。
vmlinux_link "${kallsymso}" vmlinux 调用 vmlinux_link 函数来链接出 vmlinux
综上所述,make的过程其实就是将各个子目录下的 built-in.o、 .a 等文件链接在一起,最终生成 vmlinux 这个 ELF 格式的可执行文件。链接脚本为 arch/arm/kernel/vmlinux.lds,链接过程是由shell脚本scripts/link-vmlinux.sh来完成的。
(built-in.o 文件编译生成过程)
vmlinux 是 ELF 格式的文件,但是在实际中我们不会使用 vmlinux,而是使用 zImage 或 uImage 这样的 Linux 内核镜像文件。
vmlinux 是编译出来的最原始的内核文件,是未压缩的。
Image 是 Linux 内核镜像文件,但是 Image 仅包含可执行的二进制数据。 Image 就是使用 objcopy 取消掉 vmlinux 中的一些其他信息,比如符号表什么的。
zImage 是经过 gzip 压缩后的 Image。
顶层Makefile有:
BOOT_TARGETS = zImage Image xipImage bootpImage uImage
......
$(BOOT_TARGETS): vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
变量 BOOT_TARGETS 包含 zImage, Image, xipImage 等镜像文件。 |
BOOT_TARGETS 依赖 vmlinux,因此如果使用“make zImage”编译的 Linux 内
核的话,首先肯定要先编译出 vmlinux。
命令展开为:@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage
使用 scripts/Makefile.build 文件来完成 vmliux 到 zImage 的转换。