Linux内核的Makefile分为5个部分:
        
     Makefile                 顶层Makefile,它读取.config文件,并负责创建vmlinux(内核镜像)和modules(模块文件)。
     .config                  内核配置文件,调用make menuconfig或者make xconfig命令等配置
     arch/$(ARCH)/Makefile    内核相关,具体架构的Makefile
     scripts/Makefile.*       公共编译规则定义文件。所有kbuild Makefile的规则,它们包含了定义/规则等。
     kbuild Makefiles         每个子目录都有kbuild Makefile,它们负责生成built-in或模块化目标。(注意:kbuild Makefile是指使用kbuild结构的Makefile,内核中的大多数Makefile都是kbuild Makefile。)

顶层Makefile阅读的.config文件,而该文件是由内核配置程序生成的。

顶层Makefile负责制作:vmlinux(内核文件)与modules(模块文件)。制作的过程主要是通过递归向下访问子目录的形式完成。并根据内核配置文件确定访问哪些子目录。顶层Makefile要原封不动的包含一具体架构的Makefile,其名字类似于 arch/$(ARCH)/Makefile。该架构Makefile向顶层Makefile提供其架构的特别信息。

每一个子目录都有一个Kbuild Makefile文件,用来执行从其上层目录传递下来的命令。
Kbuild Makefile从.config文件中提取信息,生成Kbuild完成内核编译所需的文件列表。

scripts/Makefile.*包含了所有的定义、规则等信息。这些文件被用来编译基于kbuild
Makefile的内核

 

 Makefile编译流程

当用户使用Linux的Makefile编译内核版本时,Makefile的编译流程如下:

         使用命令行或者图形界面配置工具,对内核进行裁减,生成.config配置文件

         保存内核版本信息到 include/linux/version.h

         产生符号链接 include/asm,指向实际目录 include/asm-$(ARCH)

         为最终目标文件的生成进行必要的准备工作

         递归进入 /init 、/core、 /drivers、 /net、 /lib等目录和其中的子目录来编译生成所有的目标文件

         链接上述过程产生的目标文件生成vmlinux,vmlinux存放在内核代码树的根目录下

         最后根据 arch/$(ARCH)/Makefile文件定义的后期编译的处理规则建立最终的映象bootimage,包括创建引导记录、准备initrd映象和相关处理

 

Makefile关键规则和定义描述

1)        目标定义

目标定义是Makefile文件的核心部分,目标定义通知Makefile需要生成哪些目标文件、如何根据特殊的编译选项链接目标文件,同时控制哪些子目录要递归进入进行编译。

这个例子Makefile文件位于/fs/ext2目录 :

obj-$(CONFIG_EXT2_FS) += ext2.o

ext2-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o ioctl.o namei.o super.o symlink.o

ext2-$(CONFIG_EXT2_FS_XATTR)   += xattr.o xattr_user.o xattr_trusted.o

ext2-$(CONFIG_EXT2_FS_POSIX_ACL) += acl.o

ext2-$(CONFIG_EXT2_FS_SECURITY)     += xattr_security.o

ext2-$(CONFIG_EXT2_FS_XIP) += xip.o

这表示与 ext2相关的目标文件由 ext2-y定义的文件列表组成,其中ext2-$(*)是由内核配置文件.config中的配置项决定,最终Makefile会在这个目录下统一生成一个目标文件ext2.o(由obj-$(CONFIG_EXT2_FS)决定)。其中obj-y表示为生成vmlinux文件所需要的目标文件集合,具体的文件依赖于内核配置。

Makefile会编译所有的$(obj-y)中定义的文件,然后调用链接器将这些文件链接到built-in.o 文件中。最终built-in.o文件通过顶层Makefile链接到vmlinux中。$(obj-y)的文件顺序很重要。列表文件可以重复,文件第一次出现时将会链接到built-in.o中,后来出现的同名文件将会被忽略。文件顺序直接决定了他们被调用的顺序。

所有包含在lib-y定义中的目标文件都将会被编译到该目录下一个统一的库文件中。值得注意的是lib-y定义一般被限制在 lib 和arch/$(ARCH)/lib 目录中。

体系makefile文件和顶层makefile文件共同定义了如何建立vmlinux文件的规则。

     $(head-y) 列举首先链接到vmlinux的对象文件。
     $(libs-y) 列举了能够找到lib.a文件的目录。
     其余的变量列举了能够找到内嵌对象文件的目录。
     $(init-y) 列举的对象位于$(head-y)对象之后。
     然后是如下位置顺序:
     $(core-y), $(libs-y), $(drivers-y) 和 $(net-y)。
     顶层makefile定义了所有通用目录,arch/$(ARCH)/Makefile文件只需增加体系相关的目录。
     例如: #arch/i386/Makefile
           libs-y                   += arch/i386/lib/

          core-y                   += arch/i386/kernel/ \

                                           arch/i386/mm/ \

                                           arch/i386/$(mcore-y)/ \

                                           arch/i386/crypto/

drivers-$(CONFIG_MATH_EMULATION) += arch/i386/math-emu/

drivers-$(CONFIG_PCI)            += arch/i386/pci/

           …………………………………………

 

2)        目录递归

Makefile文件只负责当前目录下的目标文件,子目录中的文件由子目录中的makefile负责编译,编译系统使用obj-y 和obj-m来自动递归编译各个子目录中的文件。

对于fs/Makefile:

obj-$(CONFIG_EXT2_FS) += ext2/

如果在内核配置文件.config中,CONFIG_EXT2_FS被设置为y或者m,则内核makefile会自动进入ext2目录来进行编译。内核 Makefile只使用这些信息来决定是否需要编译这个目录,子目录中的makefile规定哪些文件编译为模块,哪些文件编译进内核。

 

3)        依赖关系

Linux Makefile通过在编译过程中生成的 .文件名.o.cmd(比如对于main.c文件,它对应的依赖文件名为.main.o.cmd)来定义相关的依赖关系。

一般文件的依赖关系由如下部分组成:

          所有的前期依赖文件(包括所有相关的*.c 和 *.h)

          所有与CONFIG_选项相关的文件

          编译目标文件所使用到的命令行

 

4)    特殊规则

特殊规则使用在内核编译需要规则定义而没有相应定义的时候。典型的例子如编译时头文件的产生规则。其他例子有体系makefile编译引导映像的特殊规则。特殊规则写法同普通的makefile规则。
     编译程序在makefile所在的目录不能被执行,因此所有的特殊规则需要提供前提文件和目标文件的相对路径。
     定义特殊规则时将使用到两个变量:
     $(src): $(src)是对于makefile文件目录的相对路径,当使用代码树中的文件时使用该变量$(src)。
     $(obj): $(obj)是目标文件目录的相对路径。生成文件使用$(obj)变量。
     例如: #drivers/scsi/Makefile
     $(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
        $(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl
     这就是使用普通语法的特殊编译规则。
     目标文件依赖于两个前提文件。目标文件的前缀是$(obj), 前提文件的前缀是$(src)(因为它们不是生成文件)。

5)    引导映象

体系makefile文件定义了编译vmlinux文件的目标对象,将它们压缩和封装成引导代码,并复制到合适的位置。这包括各种安装命令。在Linux中 Makefile无法为所有的体系结构提供标准化的方法,因此常需要具体硬件体系结构下makefile提供附加处理规则。
     附加处理过程常位于arch/$(ARCH)/下的boot/目录。
     内核编译体系无法在boot/目录下提供一种便捷的方法创建目标系统文件。因此arch/$(ARCH)/Makefile要调用make命令在boot /目录下建立目标系统文件。建议使用的方法是在arch/$(ARCH)/Makefile中设置调用,并且使用完整路径引用arch/$(ARCH) /boot/Makefile。
     例如: #arch/i386/Makefile
           boot := arch/i386/boot
           bzImage: vmlinux
              $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
     建议使用"$(Q)$(MAKE) $(build)=<dir>"方式在子目录中调用make命令。
     当执行不带参数的make命令时,将首先编译第一个目标对象。在顶层makefile中第一个目标对象是all:。
     一个体系结构需要定义一个默认的可引导映像。
     增加新的前提文件给all目标可以设置不同于vmlinux的默认目标对象。
     例如: #arch/i386/Makefile
           all: bzImage
     当执行不带参数的"make"命令时,bzImage文件将被编译。

6)   常用编译命令

if_changed
        如果必要,执行传递的命令。
        用法:
          $(builtin-target): $(obj-y) FORCE

          $(call if_changed,link_o_target)
        当这条规则被使用时它将检查哪些文件需要更新,或命令行被改变。后面这种情况将迫使重新编译编译选项被改变的执行文件。使用if_changed的目标对象必须列举在$( builtin-target)中,否则命令行检查将失败,目标一直会编译。

 

if_changed_dep

       如果必要,执行传递的命令并更新依赖文件。

       用法:

         %.o: %.S FORCE

         $(call if_changed_dep,as_o_S)

当这条规则被使用时它将检查哪些文件需要更新,或命令行被改变。同时它会重新检测依赖关系的改变并将生成新的依赖文件。这是与if_changed命令的区别。

 

7)   定制命令

 当正常执行带编译命令时命令的简短信息会被显示(要想显示详细的命令,请在命令行中加入V=1)。要让定制命令具有这种功能需要设置两个变量:
      quiet_cmd_<command> - 将被显示的内容
      cmd_<command>      - 被执行的命令
      例如: #
           quiet_cmd_image = BUILD   $@
            cmd_image = $(obj)/tools/build $(BUILDFLAGS) \
                    $(obj)/vmlinux.bin > $@
           targets += bzImage
           $(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE
                $(call if_changed,image)
                @echo 'Kernel: $@ is ready'
     执行make命令编译$(obj)/bzImage目标时将显示:
     BUILD   arch/i386/boot/bzImage

 

8)   预处理链接脚本

当编译vmlinux映像时将使用arch/$(ARCH)/kernel/vmlinux.lds链接脚本。
        相同目录下的vmlinux.lds.S文件是这个脚本预处理的变体。内核编译系统知晓.lds文件。并使用规则*lds.S -> *lds。
        例如: #arch/i386/kernel/Makefile
           always := vmlinux.lds
           #Makefile
           export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
        $(always)赋值语句告诉编译系统编译目标是vmlinux.lds。$(CPPFLAGS_vmlinux.lds)

赋值语句告诉编译系统编译vmlinux.lds目标的编译选项。
        编译*.lds时将使用到下面这些变量:
        CPPFLAGS      : 定义在顶层Makefile
        EXTRA_CPPFLAGS      : 可以设置在编译的makefile文件中
        CPPFLAGS_$(@F) : 目标编译选项。注意要使用文件全名。

 

9)        主机辅助程序的编译

内核编译系统支持在编译阶段编译主机可执行程序。为了使用主机程序需要两个步骤:第一个步骤使用hostprogs-y变量告诉内核编译系统有主机程序可用。第二步给主机程序添加潜在的依赖关系。有两种方法,在规则中增加依赖关系或使用$(always)变量。

 

10)     Clean机制

clean命令清除在编译内核生成的大部分文件,例如主机程序,列举在 $(hostprogs-y)、$(hostprogs-m)、$(always)、$(extra-y)和$(targets)中目标文件都将被删除。代码目录数中的"*.[oas]"、"*.ko"文件和一些由编译系统产生的附加文件也将被删除。
  附加文件可以使用$(clean-files)进行定义。
     例如: #drivers/pci/Makefile
           clean-files := devlist.h classlist.h
当执行"make clean"命令时, "devlist.h classlist.h"两个文件将被删除。内核编译系统默认这些文件与makefile具有相同的相对路径,否则需要设置以'/'开头的绝对路径。
  删除整个目录使用以下方式:
  例如: #scripts/package/Makefile
     clean-dirs := $(objtree)/debian/
  这样就将删除包括子目录在内的整个debian目录。如果不使用以'/'开头的绝对路径内核编译系统见默认使用相对路径。
  通常内核编译系统根据"obj-* := dir/"进入子目录,但是在体系makefile中需要显式使用如下方式:
     例如: #arch/i386/boot/Makefile
           subdir- := compressed/
  上面赋值语句指示编译系统执行"make clean"命令时进入compressed/目录。
  在编译最终的引导映像文件的makefile中有一个可选的目标对象名称是archclean。
     例如: #arch/i386/Makefile
           archclean:
              $(Q)$(MAKE) $(clean)=arch/i386/boot
  当执行"make clean"时编译器进入arch/i386/boot,arch/i386/boot中的makefile文件可以使用subdir-标识进入更下层的目录。
  注意1: arch/$(ARCH)/Makefile不能使用"subdir-",因为它被包含在顶层makefile文件中,在这个位置编译机制是不起作用的。
  注意2: 所有列举在core-y、libs-y、drivers-y和net-y中的目录将被"make clean"命令清除。