(Linux内核) 1-顶层Makefile

资料来源:正点原子嵌入式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.导出变量

下面详细描述其余不一样的地方。

make xxx_defconfig 过程

第一次编译 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

接下来详细分析这两句脚本。

 

Makefile.build 脚本分析

scripts_basic

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 这两个软件。

 

%config 目标对应的命令


对应命令为@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过程

使用命令“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)

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
 

$(init-y)、 $(core-y) 、 $(net-y)

顶层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

$(libs-y)

顶层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


$(core-y)

顶层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 文件编译生成过程)

make zImage 过程

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 的转换。
 

 

你可能感兴趣的:(嵌入式)