本文选择三星发布的基于SMDKV210开发板的linux 2.6.35.7版本kernel。
Kernel Makefile体系包含Kconfig和Kbuild两个系统。
Kconfig系统
Kconfig 对应的是内核配置阶段,make xxconfig就是在使用Kconfig系统。Kconfig由三部分组成:
scripts/kconfig/* | Kconfig文件解析程序 |
kconfig | 各个内核源代码目录中的kconfig文件 |
arch/$(ARCH)/configs/*_defconfig | 各个平台的缺省配置文件 |
当Kconfig系统生成.config后,Kbuild 会依据.config编译指定的目标。
Kbuild系统
Kbuild 是内核Makefile体系重点,对应内核编译阶段,由5个部分组成:
顶层Makefile | 根据不同的平台,对各类target分类并调用相应的规则Makefile生成目标 |
.config | 内核配置文件 |
arch/$(ARCH)/Makefile | 具体平台相关的Makefile |
scripts/Makefile.* | 通用规则文件,面向所有的Kbuild Makefiles,所起的作用可以从后缀名中得知。 |
各子目录下的Makefile 文件 | 由其上层目录的Makefile调用,执行其上层传递下来的命令 |
scripts目录下的编译规则文件和其目录下的C程序在整个编译过程起着重要的作用。
文件名 | 作用 |
Kbuild.include | 共用的定义文件,被许多独立的Makefile.*规则文件和顶层Makefile包含 |
Makefile.build | 提供编译built-in.o, lib.a等的规则 |
Makefile.lib | 负责归类分析obj-y、obj-m和其中的目录subdir-ym所使用的规则 |
Makefile.host | 本机编译工具(hostprog-y)的编译规则 |
Makefile.clean | 内核源码目录清理规则 |
Makefile.headerinst | 内核头文件安装时使用的规则 |
Makefile.modinst | 内核模块安装规则 |
Makefile.modpost | 模块编译的第二阶段,由.o和.mod生成.ko时使用的规则 |
顶层Makefile主要是负责完成vmlinux(内核文件)与*.ko(内核模块文件) 的编译。顶层 Makefile读取.config 文件,并根据.config 文件确定访问哪些子目录,并通过递归向下访问子目录的形式完成。顶层Makefile同时根据.config 文件原封不动的包含一个具体架构的Makefile,其名字类似于 arch/$(ARCH)/Makefile。该架构Makefile 向顶层Makefile 提供其架构的特别信息。
每一个子目录都有一个Makefile 文件,用来执行从其上层目录传递下来的命令。子目录的 Makefile 也从.config 文件中提取信息,生成内核编译所需的文件列表。
根据Makefile的执行规则,在分析Makefile时,首先必须确定一个目标 ,然后才能确定所有的依赖关系 ,最后根据更新情况决定是否执行相应的命令。所以要看懂内核Makefile的目标框架,我们首先要了解她里面所定义的目标。而内核Makefile所定义的目标基本上可以通过 make help打印出来(因为help本身就是顶层Makefile的一个目标,里面是打印帮助信息的“echo”命令)
目标 | 常用目标举例 | 作用 | |
配置 | %config | config | 启动Kconfig,以不同界面来配置内核。 |
menuconfig | |||
xconfig | |||
编译 | all | 编译vmlinux内核映像和内核模块 | |
vmlinux | 编译vmlinux内核映像 | ||
modules | 编译内核模块 | ||
安装 | headers_install | 安装内核头文件/模块 | |
modules_install | |||
源码浏览 | tags | 生成代码浏览工具所需要的文件 | |
TAGS | |||
cscope | |||
静态分析 | checkstack | 检查并分析内核代码 | |
namespacecheck | |||
headers_check | |||
内核打包 | %pkg | 以不同的安装格式编译内核 | |
文档转换 | %doc | 把kernel文档转成不同格式 | |
构架相关(arm) | zImage | 生成压缩的内核映像 | |
uImage | 生成压缩的u-boot可引导的内核映像 | ||
install | 安装内核映像 |
构架相关目标在顶层Makefile上并未出现,而是被包含在平台相关的Makefile(arch/$(ARCH)/Makefile)中。
A、生成控制C程序的头文件、控制编译连接的文件
控制C程序的头文件
include/linux/version.h include/linux/utsrelease.h、include/linux/autoconf.h
控制编译连接的文件
arch/arm/kernel/vmlinux.lds、include/config/auto.conf等文件。
B、由C程序源码和汇编语言源码生成目标文件(*.o)
C、将目标文件连接成*.built-in.o、*/lib.a等文件
D、将顶层目录的子目录中的*.built-in.o以及部分重要的*.o文件连接生成vmlinux
E、根据arch/arm/Makefile的规则生成zImage等
A、Makefile的目标
总目标实际上是在arch/arm/Makefile中定义了,比方说zImage、uImage,顶层Makefile紧接着定义了这些终极目标直接的依赖目标vmlinux。
各级子目标是在scripts/Makefile.build中的__build中定义的,例如传递参数obj=drivers后的目标是drivers/built-in.o。
目标的依赖又成为了新的目标,例如drivers/net/built-in.o、drivers/net/dm9000.o。
B、Makefile的依赖
总目标的依赖
vmlinux-lds vmlinux-init vmlinux-main vmlinux.o kallsyms.o
各级子目标的依赖
各级子目标的依赖是由子目录中的Makefile(实际是scripts/Makefile.build的包含文件)和scripts/Makefile.lib共同完成确定的。
子目录中的Makefile负责选材,而scripts/Makefile.lib负责加工。
C、Makefile的规则
总目标vmlinux的连接规则就是在顶层Makefile中定义的,至于zImage、uImage则是在arch/arm/Makefile中定义的。
子目标的编译连接规则主要是在scripts/Makefile.build、scripts/Kbuild.include中定义的,其中scripts/Kbuild.include定义了许多诸如if_changed的函数。
A、两个Makefile
顶层Makefile文件负责将各个目录生成的*.built-in.o、lib.a等文件连接到一起生成vmlinux。而scripts/Makefile.build 包含子目录中的Makefile文件以及scripts/中的众多脚本来生成这些*.built-in.o、lib.a、*.o等文件。
通过“make -f scripts/Makefile.build obj=”的方法完成了顶层Makefile到scripts/Makefile.build的调用生成*/built-in.o,以及scripts /Makefile.build的自调用生成更低一级子目录的*/built-in.o。
B、编译的目录始终是顶层目录
“make -C”命令会先切换工作目录,然后执行该目录中的Makefile,u-boot就是采用这种方法。而linux则是利用“make -f”的方法,所以编译的目录始终是顶层目录。
C、通用规则
Linux内核Makefile的通用子Makefile是scripts/Makefile.build,而通用的其他规则则是scripts中的其他文件。
kernel源码的根目录下执行make menuconfig命令时,根据规则,make程序读取顶层Makefile 文件及其包含的Makefile文件,内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。由于目标“menuconfig”匹配了“%config”,make程序最终会调用规则:
%config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig $@
依赖目标是scripts_basic 和outputmakefile,以及FORCE。在完成了scripts_basic、outputmakefile、FORCE依赖目标后,下面的两个命令才会执行以完成我们指定的目标“menuconfig”。
A、scripts_basic
make程序会调用规则:
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
Q:选择静态编译与否(是否打印编译信息,位于顶层Makefile)
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =else
quiet=quiet_
Q = @
endif
MAKE: 系统环境变量,值为make
build: 值为“-f scripts/Makefile.build obj=”实际上就是调用子Makefile-- scripts/Makefile.build,然后传递参数目标文件夹。
scripts_basic:
make -f scripts/Makefile.build obj= scripts/basic
make 解析执行scripts/Makefile.build文件,且参数obj= scripts/basic。而在解析执行scripts/Makefile.build文件的时候,scripts/Makefile.build又会通过解析传入参数来包含对应文件夹下的Makefile文件(scripts/basic/Makefile),从中获得需要编译的目标。
B、outputmakefile
make程序会调用规则:
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif
outputmakefile是当KBUILD_SRC不为空(指定O=dir,编译输出目录和源代码目录分开)时,在输出目录建立Makefile时才执行命令的,在源码根目录下执行make menuconfig命令时,outputmakefile目标是空的,什么都不做。如果我们指定了O=dir时,就会执行源码目录下的scripts/mkmakefile,用于在指定的目录下产生一个Makefile,并可以在指定的目录下开始编译。
C、FORCE
PHONY += FORCE
FORCE:
FORCE 是一个没有命令或者依赖目标,不可能生成相应文件的伪目标。当make执行此规则时,总会认为FORCE不存在,必须完成这个目标,所以她是一个强制目标。也就是说:规则一旦被执行,make 就认为它的目标已经被执行并更新过了。当她作为一个规则的依赖时,由于依赖总被认为被更新过的,因此作为依赖所在的规则中定义的命令总会被执行。所以可以这么说:只要执行依赖包含FORCE的目标,其目标下的命令必被执行。
config %config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig $@
展开后得到的命令如下:
config %config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config//创建文件夹
make -f scripts/Makefile.build obj=scripts/kconfig menuconfig
make 解析执行scripts/Makefile.build文件,且参数obj=scripts/kconfig menuconfig。Makefile.build会包含对应文件夹下的Makefile文件(scripts/kconfig /Makefile),并完成scripts/kconfig /Makefile下的目标:
menuconfig: $(obj)/mconf
$< $(Kconfig)
mconf-objs := mconf.o zconf.tab.o $(lxdialog)
ifeq ($(MAKECMDGOALS),menuconfig)
hostprogs-y += mconf
Endif
实际上就是编译menuconfig对应的mconf程序,用mconf 解析arch/$(SRCARCH)/Kconfig文件
当make xxxconfig时,kernel根目录的顶层Makefile会临时编译出scripts/kconfig 中的解析工具程序conf/mconf/qconf,然后用相应的工具程序xconf
对arch/$(SRCARCH)/Kconfig文件进行解析。arch/$(SRCARCH)/Kconfig文件通过source标记调用各个目录下的Kconfig文件构建出一个Kconfig树,使得工具程序构建出整个内核的配置界面。在配置结束后,工具程序就会生成我们常见的.config文件。
使用make zImage进行编译时,zImage作为总目标。
总目标zImage目标在arch/$(ARCH)/Makefile文件中定义。
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
展开后得:
make -f scripts/Makefile.build obj=/arch/arm/boot MACHINE= arch/arm/mach-s5pv210 /arch/arm/boot/uImage
调用scripts/Makefile.build,传递参数MACHINE= arch/arm/mach-s5pv210 /arch/arm/boot/zImage
依赖目标vmlinux在顶层Makefile中定义:
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
$(call if_changed_rule,vmlinux__)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@
$(Q)rm -f .old_version
vmlinux__命令:
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) FORCE ,$^)
vmlinux依赖于 $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE,最终使用vmlinux__ 将$(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) 指定目标文件链接成vmlinux。
vmlinux-lds:
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds//顶层Makefile
vmlinux-init:
vmlinux-init := $(head-y) $(init-y)//顶层Makefile
//顶层Makefile
init-y:= init/
init-y:= $(patsubst %/, %/built-in.o, $(init-y))
//arch/arm/Makefile
head-y:= arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
vmlinux-main:
//顶层Makefile:
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y) //kernel/Makefile
//arch/arm/Makefile:
core-y+= $(machdirs) $(platdirs)
//顶层Makefile:
core-y+= arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y+= kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := usr/
最终:
core-y := usr/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 arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o
//arch/arm/Makefile;
libs-y:= arch/arm/lib/ $(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)
libs-y最终为arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
//顶层Makefile
drivers-y := drivers/ sound/ firmware/
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
drivers-y值最终等于rivers/built-in.o sound/built-in.o firmware/built-in.o
//顶层Makefile net-y
net-y := net/
net-y := $(patsubst %/, %/built-in.o, $(net-y))
net-y最终等于net/built-in.o
vmlinux.o:
modpost-init := $(filter-out init/built-in.o, $(vmlinux-init))
vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE
$(call if_changed_rule,vmlinux-modpost)
Kallsyms.o:
kallsyms.o := .tmp_kallsyms$(last_kallsyms).o
vmlinux的三大依赖$(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) 进一步依赖于$(vmlinux-dirs)。
$(sort $(vmlinux-init) $(vmlinux-main)) $(vmlinux-lds): $(vmlinux-dirs)
vmlinux-dirs:= $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m)))
vmlinux-dirs实际上是vmlinux依赖的各个子目标所在的文件夹。vmlinux-dirs内容为init usr arch/arm/kernel arch/arm/mm kernel mm fs ipc security crypto block drivers sound firmware net lib arch/arm/lib等文件夹。
各个子目标的生成:
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
prepare、scripts为伪目标,用于生成include/config/kernel.release、include/linux/version.h include/linux/utsrelease.h、include/config/auto.conf等。$(Q)$(MAKE) $(build)=$@语句表示调用通用子Makefile文件--scripts/Makefile.build。$@会依次被$(vmlinux-dirs) 中的文件夹代替,从而依次执行$(Q)$(MAKE) $(build)=$@命令编译该文件夹以生成*.built-in.o、lib.a等文件。
将文件夹drivers作为参数传递时展开如下:
make -f scripts/Makefile.build obj=drivers
Makefile.build文件的目标:
PHONY := __build
__build:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
KBUILD_BUILTIN := 1
builtin-target值为drivers/built-in.o
lib-target的值为drivers/lib.a
subdir-ym就是在__subdir-y和__subdir-m前边添加obj的前缀,所以最终等于drivers/xxx
scripts/Makefile.build的总目标__build中的一个目标是 builtin-target,而它的值为drivers/built-in.o
$(builtin-target): $(obj-y) FORCE
$(call if_changed,link_o_target)
cmd_link_o_target = $(if $(strip $(obj-y)),\
$(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
$(cmd_secanalysis),\
rm -f $@; $(AR) rcs $@)
drivers/built-in.o依赖于obj-y(子目标),然后通过调用一个if_changed函数,将子目标连接起来,生成drivers/built-in.o。子目标依赖于更深一级的子目标,生成方法与drivers/built-in.o类似。
if_changed函数的核心功能就是判断是否需要更新目标,如果需要就执行表达式$(cmd_$(1))展开后的值来完成重建目标。$(call if_changed,link_o_target)实际上是调用链接程序来进行链接。
zImage由vmlinux处理得到,处理过程如下:
A、将vmlinux文件中的调试信息、符号表去除,生成一个Image的镜像文件
B、将Image镜像用gzip压缩工具进行压缩,得到piggy.gz的文件
C、piggy.S文件中直接将piggy.gz文件包含在其中,编译piggy.S得到piggy.gz.o文件
D、将piggy.gz.o head.o misc.o三个文件链接成文压缩的内核镜像vmlinux
E、去除掉镜像vmlinux中的符号、注释、调试信息的内容,得到zImage镜像
arch/arm/boot/Makefile文件中:obj=arch/arm/boot
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
@echo ‘ Kernel: $@ is ready‘
将vmlinux文件中的调试信息、符号表去除,生成一个Image的镜像文件
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
得到压缩后的vmlinux
$(obj)/zImage:$(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo ‘ Kernel: $@ is ready‘
objcopy处理后便生成的最终的zImage
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o /
$(addprefix $(obj)/, $(OBJS)) FORCE
$(call if_changed,ld)
@:
obj=arch/arm/boot/compressed
根据链接脚本arch/arm/boot/compressed/vmlinux.lds链接生成了arch/arm/boot/compressed/vmlinux文件。
$(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
$(call if_changed,$(suffix_y))
展开后:
$(obj)/piggy.gz: $(obj)/../Image FORCE
$(call if_changed,gzip)
把由vmlinux生成的Image进行压缩生成piggy.gz
$(obj)/piggy.$(suffix_y).o: $(obj)/piggy.$(suffix_y) FORCE
生成piggy.gz.o
$(obj)/uImage:$(obj)/zImage FORCE
$(call if_changed,uimage)
@echo ‘ Image $@ is ready‘
quiet_cmd_uimage = UIMAGE $@
cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A arm -O linux -T kernel \
-C none -a $(LOADADDR) -e $(STARTADDR) \
-n ‘Linux-$(KERNELRELEASE)‘ -d $< $@
uImage由zImage经mkimage工具加工生成
Kernel编译过程的图解如下:
参考博文:
Linux 内核 Makefile 体系简单分析(chinaunix mz_linux)
本文出自 “生命不息,奋斗不止” 博客,转载请与作者联系!
嵌入式 Linux开发Kernel移植(三)——Kernel工程Makefile分析
原文地址:http://9291927.blog.51cto.com/9281927/1794808