ARM Linux 移植(一)makefile 分析

一、linux 内核makefile 组成

Linux内核Makefile文件组成
名称 描述
顶层 Makefile 它是所有Makefile文件的核心,从总体上控制着内核的编译、连接
arch/$(ARCH)/Makefile 对应体系结构的Makefile,它用来决定哪些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映像
scripts/Makefile.* Makefile公用的通用规则、脚本等
子目录kbuild Makefiles 各级子目录的Makefile相对简单,被上一层Makefile.build调用来编译当前目录的文件。
顶层.config

配置文件,配置内核时生成。所有的Makefile文件(包括顶层目录和各级子目录)都是根据.config来决定使用哪些文件的

 

 

总结:

  Linux内核Makefile体系核心的Makefile文件就两个:顶层Makefile、scripts/Makefile.build。

  子目录中的Makefile、kbuild不是Makefile文件(完整的Makefile文件),只能算作是Makefile的包含文件。

  顶层Makefile文件负责将各个目录生成的*.built-in.o、lib.a等文件连接到一起。而scripts/Makefile.build 包含子目录中的Makefile文件来生成这些*.built-in.o、lib.a、*.o等文件。

二、分析顶层makefile 以周立功的imx287移植好的 linux-2.65 为例

1. 内核版本号
打开顶层 Makefile,开头的几行记录了内核源码的版本号,通常如下所示:
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 35
EXTRAVERSION =3
说明代码版本为 2.6.35.3,编译得到的内核在目标板运行后,输入 uname -a 命令可以得
到印证:
# uname -a
Linux boy 2.6.35.3-571-gcca29a0-gd431b3d-dirty #22 PREEMPT Tue Oct 27 20:12:33 CST 2015 armv5tejl
GNU/Linux
2. 编译控制
(1)体系结构
Linux 是一个支持众多体系结构的操作系统,在编译过程中需指定体系结构,以与实际
平台对应。在顶层 Makefile 中,通过变量 ARCH 来指定:
ARCH ?= $(SUBARCH)
如果没有在编译命令行中指定 ARCH 参数,系统将会进行本地编译,通过获取本机信
息来自动指定:
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/ )
如果进行 ARM 嵌入式 Linux 开发,则必须指定 ARCH 为 arm(注意大小写,须与 arch/
目录下的 arm 一致),如:
$make ARCH=arm
当然,也可以修改 Makefile,将修改为 ARCH ?= $(SUBARCH)修改为 ARCH = arm,在
命令行直接 make 即可。
(2)编译器
如果不是进行本地编译,则须指定交叉编译器,通过 CROSS_COMPILE 来指定。Makefile
中与交叉编译器的指定如下:
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
……
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) –E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
CONFIG_CROSS_COMPILE 是一个配置选项,可在内核配置时候指定。如果在配置内
核时候没有指定 CONFIG_CROSS_COMPILE,也没有在编译参数指定 CROSS_COMPILE,
则会采用本地编译器进行编译。
进行 ARM 嵌入式 Linux 开发,必须指定交叉编译器,可以在内核配置通过 CONFIG
_CROSS_COMPILE 指定交叉编译器,也可以通过 CROSS_COMPILE 指定。假定使用的交
叉编译器是 arm-linux-gnueabihf-gcc,则指定 CROSS_COMPILE 为 arm-linux-gnueabihf-:
$ make ARCH=arm CROSS_COMPILE= arm-linux-gnueabihf-
或者在 Makefile 中,直接指定 CROSS_COMPILE 的值:
CROSS_COMPILE = arm-linux-gnueabihf-
注意: CROSS_COMPILE 指定的交叉编译器必须事先安装并正确设置系统环境变量; 如果
没有设置环境变量, 则需使用绝对地址, 例如:
CROSS_COMPILE =/home/ctools/linux-devkit/bin/arm-linux-gnueabihf-
如果同时指定了 ARCH 和 CROSS_COMPILE, 则在编译的时候, 只需简单的 make 就
可以了。
(3)编译总目标uImage

 内核配置完成后,在顶层目录中执行“#make uImage”便开始编译内核。但是,uImage却不是在顶层Makefile中定义,而是在arch/$(ARCH)/Makefile中定义。

ARM Linux 移植(一)makefile 分析_第1张图片

 

其中srctree为源码绝对路径;而SRCARCH := $(ARCH),即该变量等于架构名称,我们以arm为例进行说明

可见uImage依赖于vmlinux,要先生成vmlinux,然后执行下边这条指令完成编译。

然后执行

$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

<1> Q的定义:选择静态编译与否(是否打印编译信息)

复制代码

顶层Makefile Line 292:
ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

复制代码

<2> MAKE: 系统环境变量,值为make

<3> build: 值为“-f scripts/Makefile.build obj=”实际上就是调用子Makefile--scripts/Makefile.build,然后传递参数目标文件夹。

Kbuild.include Line 141:
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
Kbuild.include被顶层Makefile所包含
顶层Makefile Line 312:
# We need some generic definitions (do not try to remake the file).
$(srctree)/scripts/Kbuild.include: ;
include $(srctree)/scripts/Kbuild.include
2、vmlinux的生成

复制代码

顶层Makefile Line 845: 
# 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

复制代码

  vmlinux的依赖是“$(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE”,要想生成vmlinux必须要先把这些原材料准备好。

<1> vmlinux-lds

  在编译之前要把连接脚本先生成,最后的连接阶段会用的着。

顶层Makefile Line 699: 
vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds

vmlinux.lds 文件是由arch/arm/vmlinux-armv.lds.in 生成

LDSCRIPT     = arch/arm/vmlinux-armv.lds.in

arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT) \

2、scripts/Makefile.build的总目标

复制代码

scripts/Makefile.build Line 7: 
PHONY := __build
__build:

scripts/Makefile.build Line 93:
 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
     $(subdir-ym) $(always)
    @:

复制代码

 <1> KBUILD_BUILTIN

  在终端以#Make uImage执行Make时,KBUILD_BUILTIN := 1。

复制代码

顶层Makefile Line 241:
KBUILD_BUILTIN := 1

#    If we have only "make modules", don't compile built-in objects.
#    When we're building modules with modversions, we need to consider
#    the built-in objects during the descend as well, in order to
#    make sure the checksums are up to date before we record them.

ifeq ($(MAKECMDGOALS),modules)
  KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

顶层Makefile Line 264: 
export KBUILD_MODULES KBUILD_BUILTIN

复制代码

 <2> builtin-target

scripts/Makefile.build Line 85: 
ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif

  builtin-target值为drivers/built-in.o

 <3> lib-target

scripts/Makefile.build Line 81: 
ifneq ($(strip $(lib-y) $(lib-m) $(lib-n) $(lib-)),)
lib-target := $(obj)/lib.a
endif

  lib-target的值为drivers/lib.a

<4> $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) 

  初步认识时定义编译的模块

 <5> subdir-ym

subdir-ym的定义

复制代码

scripts/Makefile.lib Line 38: 
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y    += $(__subdir-y)
__subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m)))
subdir-m    += $(__subdir-m)
scripts/Makefile.lib Line 47: 
subdir-ym := $(sort $(subdir-y) $(subdir-m)) 
scripts/Makefile.lib Line 89: 
subdir-ym := $(addprefix $(obj)/,$(subdir-ym))

复制代码

 __subdir-y和__subdir-m首先将obj-y、obj-m中的非文件夹滤除,然后通过patsubst函数将最后的“/”去除。注意到drivers/Makefile中的obj-y、obj-m通通都是文件夹。我们以obj-y = net/为例进行以下内容的说明,所以__subdir-y=net。

  subdir-ym就是在__subdir-y和__subdir-m前边添加obj的前缀,所以最终等于drivers/net。

3、drivers/built-in.o的生成

   scripts/Makefile.build的总目标__build中的一个目标是 builtin-target,而它的值为drivers/built-in.o,现在就来看看它是怎样生成的。

scripts/Makefile.build Line 288: 
$(builtin-target): $(obj-y) FORCE
    $(call if_changed,link_o_target)

<1> drivers/built-in.o的依赖

  drivers/built-in.o依赖于obj-y(子目标),然后通过调用一个if_changed函数,将这些子目标连接起来,生成drivers/built-in.o。通过命令打印查看连接命令如下:

复制代码

arm-linux-ld -EL    -r -o drivers/built-in.o drivers/gpio/built-in.o drivers/video/built-in.o 
    drivers/char/built-in.o drivers/gpu/built-in.o drivers/serial/built-in.o drivers/base/built-in.o 
    drivers/block/built-in.o drivers/misc/built-in.o drivers/mfd/built-in.o drivers/macintosh/built-in.o
    drivers/scsi/built-in.o drivers/net/built-in.o drivers/ieee1394/built-in.o drivers/cdrom/built-in.o
    drivers/auxdisplay/built-in.o drivers/mtd/built-in.o drivers/usb/built-in.o drivers/usb/gadget/built-in.o
    drivers/input/built-in.o drivers/rtc/built-in.o drivers/i2c/built-in.o drivers/media/built-in.o
    drivers/watchdog/built-in.o drivers/lguest/built-in.o drivers/idle/built-in.o drivers/mmc/built-in.o 
    drivers/firmware/built-in.o drivers/crypto/built-in.o drivers/hid/built-in.o drivers/platform/built-in.o     

复制代码

  看下边的代码,原来drivers/Makefile中的obj-y是一群目录,通过第一行代码后就给这些目录后边加上了built-in.o,再经过第二行代码,给它们冠以前缀。我们以obj-y=net/为例说明,经过第一行代码obj-y=net/built-in.o,经过第二行代码obj-y=drivers/net/built-in.o

scripts/Makefile.lib Line 42:
obj-y        := $(patsubst %/, %/built-in.o, $(obj-y))

scripts/Makefile.lib  Line 78: 
obj-y        := $(addprefix $(obj)/,$(obj-y))

<2> if_changed函数

  通过if_changed函数,完成了把这些子目录中的目标连接生成dirvers/built-in.o。 

Kbuild.include Line 192: 
if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
    @set -e;                                                             \
    $(echo-cmd) $(cmd_$(1));                                             \
    echo 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)

  既然是函数,就会传递参数。相对于C语言来说形参就是(1),而传递实参的方法就是

(call if_changed,link_o_target),其中call是调用函数命令,if_changed是调用函数名,后边的link_o_target就是实参。

①$(if $(strip $(any-prereq) $(arg-check))
any-prereq检查是否有依赖比目标新,或者依赖还没有创建;arg-check检查编译目标的命令相对上次是否发生变化。如果两者中只要有一个发生改变,if_changed的值就等于
 @set -e;                                                             \
    $(echo-cmd) $(cmd_$(1));                                             \
    echo 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd

   否则,if_changed的值为空,也就是说$(call if_changed,link_o_target)将什么也不执行,因为没必要执行。

②set -e 是说下边的命令如果出错了就直接返回,不再继续执行

③echo-cmd

Kbuild.include Line 156: 
echo-cmd = $(if $($(quiet)cmd_$(1)),\
    echo '  $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)

  echo-cmd就是打印出调用的命令。if (

(quiet)cmd_(1))是判断命令(quiet)cmd_(1)或者cmd_

(1)是否定义,也就是quiet_cmd_link_o_target或者cmd_link_o_target是否定义。如果有定义,echo-cmd就会将这个命令打印出来,也就是打印。

④$(cmd_$(1))

  对于 $(call if_changed,link_o_target),该命令就是cmd_link_o_target。

scripst/Makefile.build Line 283: 
cmd_link_o_target = $(if $(strip $(obj-y)),\
              $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
              $(cmd_secanalysis),\
              rm -f $@; $(AR) rcs $@)
$(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) ,这个就是连接命令。

总结:
  if_changed函数的核心功能就是判断是否需要更新目标,如果需要就执行表达式$(cmd_$(1))展开后的值来完成重建目标。

4、drivers/net/built-in.o
   drivers/built-in.o的依赖如何生成呢,比如说drivers/net/built-in.o,还记得subdir-ym吗?subdir-ym记录了当前目录里边要参与编译连接的子文件夹。在 “2 scripts/Makefile.build阶段  2、scripts/Makefile.build的总目标 <5> $(subdir-ym)”中,我们假定subdir-ym=drivers/net。

Makefile.build Line 356: 
PHONY += $(subdir-ym)
$(subdir-ym):
    $(Q)$(MAKE) $(build)=$@
  $(Q)$(MAKE) $(build)=$@就是再一次调用scripts/Makefile.build,并传递参数drivers/net,这和生成drivers/built-in.o没什么差别。 

3 scripts/Makefile.build的再一次调用阶段

  再次转战到scripts/Makefile.build,obj=drivers/net被带到该文件。

1、Makefile.build的包含文件

  其包含文件与第一次调用阶段没什么大的区别,最为重要的区别就是包含子目录Makefile的改变,这次包含的文件是/workspace/linux-2.6.30.4/drivers/net/Makefile。 

2、scripts/Makefile.build的总目标

  总目标中的builtin-target值为drivers/net/built-in.o

3、drivers/net/built-in.o的生成

scripts/Makefile.build Line 288: 
$(builtin-target): $(obj-y) FORCE
    $(call if_changed,link_o_target)

  由于子目录的Makefile变成了/workspace/linux-2.6.30.4/drivers/net/Makefile,所以obj-y对应的发生了变化。通过执行编译查看这个连接过程。

arm-linux-ld -EL    -r -o drivers/net/built-in.o drivers/net/mii.o drivers/net/Space.o 
drivers/net/loopback.o drivers/net/dm9000.o drivers/net/arm/built-in.o drivers/net/e1000/built-in.o

<1> drivers/net/built-in.o的依赖

  从drivers/net/Makefile中摘出两个典型的obj-y组成部分如下所示:

drivers/net/Makefile Line 5: 
obj-$(CONFIG_E1000) += e1000/

drivers/net/Makefile Line 230: 
obj-$(CONFIG_DM9000) += dm9000.o

  drivers/net/built-in.o的依赖包含了文件夹,并且包含了直接由C文件生成的目标文件.o。

scripts/Makefile.lib Line 42:
obj-y        := $(patsubst %/, %/built-in.o, $(obj-y))

scripts/Makefile.lib  Line 78: 
obj-y        := $(addprefix $(obj)/,$(obj-y))

  第一行代码让obj-y如果是文件夹,就添加built-in.o,如果是普通的目标文件*.o,什么也不操作;第二行代码冠以目录obj。所以obj-y最终等于drivers/net/e1000/built-in.o、drivers/net/dm9000.o。

  我们越来越接近最终的目标了--分析到最底层的C文件生成目标文件。

<2> if_changed函数

  drivers/net/built-in.o也是一个由众多的文件组成的库文件,所以也是通过调用if_changed函数完成连接。

4、drivers/net/dm9000.o

复制代码

scripts/Makefile.build Line 226:
# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c FORCE
    $(call cmd,force_checksrc)
    $(call if_changed_rule,cc_o_c)

scripts/Makefile.build Line 263:
$(obj)/%.o: $(src)/%.S FORCE
    $(call if_changed_dep,as_o_S)

复制代码

$(obj)/%.o: $(src)/%.c FORCE是编译C程序,$(obj)/%.o: $(src)/%.S FORCE是编译汇编程序

<1> drivers/net/dm9000.o的依赖

  drivers/net/dm9000.o的依赖是对应的$(src)/%.c,也就是drivers/net/dm9000.c。

<2>cmd函数

scripts/Kbuild Line 160: 
cmd = @$(echo-cmd) $(cmd_$(1))

  此函数还未做深入分析。

<2>if_changed_rule函数

复制代码

 
scripts/Kbuild Line 205: 
# Usage: $(call if_changed_rule,foo)
# Will check if $(cmd_foo) or any of the prerequisites changed,
# and if so will execute $(rule_foo).
if_changed_rule = $(if $(strip $(any-prereq) $(arg-check) ),                 \
    @set -e;                                                             \
    $(rule_$(1)))

复制代码

   $(call if_changed_rule,cc_o_c),从而会调用函数if_changed_rule,接着会执行命令$(rule_$(1))),也就是rule_cc_o_c。

复制代码

scripts/Makefile.build Line 215:  
define rule_cc_o_c
    $(call echo-cmd,checksrc) $(cmd_checksrc)              \
    $(call echo-cmd,cc_o_c) $(cmd_cc_o_c);                  \
    $(cmd_modversions)                          \
    $(cmd_record_mcount)                          \
    scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
                                                  $(dot-target).tmp;  \
    rm -f $(depfile);                          \
    mv -f $(dot-target).tmp $(dot-target).cmd
endef

复制代码

  其中$(cmd_cc_o_c)命令的定义是

scripts/Makefile.build Line 179: 
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

5、drivers/net/e1000/built-in.o的生成

  subdir-ym肯定包含有文件夹drivers/net/e1000,于是继续再一次调用scripts/Makefile.build来完成drivers/net/e1000/built-in.o的生成。 

Makefile.build Line 356: 
PHONY += $(subdir-ym)
$(subdir-ym):
    $(Q)$(MAKE) $(build)=$@

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(ARM Linux 移植(一)makefile 分析)