嵌入式 Linux开发Kernel移植(三)——Kernel工程Makefile分析

来源:互联网 发布:mysql 当前时间毫秒数 编辑:程序博客网 时间:2024/06/05 00:55

嵌入式 Linux开发Kernel移植(三)——Kernel工程Makefile分析

    本文选择三星发布的基于SMDKV210开发板的linux 2.6.35.7版本kernel

一、Kernel Makefle体系简介

1Kernel Makefile体系组成

    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-yobj-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 文件中提取信息,生成内核编译所需的文件列表。

2Makefile的目标框架

    根据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)中。

3Makefile编译过程

  A生成控制C程序的头文件控制编译连接的文件

    控制C程序的头文件

  include/linux/version.h include/linux/utsrelease.hinclude/linux/autoconf.h

        控制编译连接的文件

  arch/arm/kernel/vmlinux.ldsinclude/config/auto.conf等文件。

  BC程序源码和汇编语言源码生成目标文件(*.o

  C将目标文件连接成*.built-in.o*/lib.a等文件

  D将顶层目录的子目录中的*.built-in.o以及部分重要的*.o文件连接生成vmlinux

  E根据arch/arm/Makefile的规则生成zImage

4Kernel编译构成元素

AMakefile的目标

     总目标实际上是在arch/arm/Makefile中定义了,比方说zImageuImage,顶层Makefile紧接着定义了这些终极目标直接的依赖目标vmlinux

     各级子目标是在scripts/Makefile.build中的__build中定义的,例如传递参数obj=drivers后的目标是drivers/built-in.o

 目标的依赖又成为了新的目标,例如drivers/net/built-in.odrivers/net/dm9000.o

BMakefile的依赖

    总目标的依赖

  vmlinux-lds  vmlinux-init vmlinux-main vmlinux.o kallsyms.o

各级子目标的依赖

    各级子目标的依赖是由子目录中的Makefile(实际是scripts/Makefile.build的包含文件)和scripts/Makefile.lib共同完成确定的。

   子目录中的Makefile负责选材,而scripts/Makefile.lib负责加工。

CMakefile的规则

总目标vmlinux的连接规则就是在顶层Makefile中定义的,至于zImageuImage则是在arch/arm/Makefile中定义的。

子目标的编译连接规则主要是在scripts/Makefile.buildscripts/Kbuild.include中定义的,其中scripts/Kbuild.include定义了许多诸如if_changed的函数。

5Kernel Makefile体系的特点

A两个Makefile

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

     通过“make -f scripts/Makefile.build obj=”的方法完成了顶层Makefilescripts/Makefile.build的调用生成*/built-in.o,以及scripts /Makefile.build的自调用生成更低一级子目录的*/built-in.o

B编译的目录始终是顶层目录

  “make -C”命令会先切换工作目录,然后执行该目录中的Makefileu-boot就是采用这种方法。而linux则是利用“make -f”的方法,所以编译的目录始终是顶层目录。

C通用规则

  Linux内核Makefile的通用子Makefilescripts/Makefile.build,而通用的其他规则则是scripts中的其他文件。

二、menuconfig情景分析

    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_basicoutputmakefileFORCE依赖目标后,下面的两个命令才会执行以完成我们指定的目标“menuconfig”。

Ascripts_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),从中获得需要编译的目标。

Boutputmakefile

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,并可以在指定的目录下开始编译。

CFORCE

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文件。

三、zImage情景分析

    使用make zImage进行编译时,zImage作为总目标。

1、总目标的依赖

总目标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

2、子目标的生成

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)=$@

    preparescripts为伪目标,用于生成include/config/kernel.releaseinclude/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.olib.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)实际上是调用链接程序来进行链接。

3zImage的生成

zImagevmlinux处理得到,处理过程如下:

    Avmlinux文件中的调试信息、符号表去除,生成一个Image的镜像文件

    BImage镜像用gzip压缩工具进行压缩,得到piggy.gz的文件

    Cpiggy.S文件中直接将piggy.gz文件包含在其中,编译piggy.S得到piggy.gz.o文件

    Dpiggy.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

4uImage的生成

$(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 $< $@

uImagezImagemkimage工具加工生成

5kernel编译过程总结

Kernel编译过程的图解如下:

wKioL1d1zDug6Z5lAAN4dtoA7JY119.png


参考博文:

Linux 内核 Makefile 体系简单分析(chinaunix mz_linux



本文出自 “生命不息,奋斗不止” 博客,转载请与作者联系!

0 0
原创粉丝点击