跳至主要内容

【转】Linux内核Makefile文件

本文是/Documentation/kbuild/makefiles.txt的中文译稿

Linux内核Makefiles

本篇文章描述了Linux内核Makefiles。


=== 目录
     === 1 概述
     === 2 角色分工
     === 3 内核编译文件
        --- 3.1 目标定义
        --- 3.2 内嵌对象 - obj-y
        --- 3.3 可加载模块 - obj-m
        --- 3.4 导出符号
        --- 3.5 库文件 - lib-y
        --- 3.6 目录递归
        --- 3.7 编译标记
        --- 3.8 命令依赖
        --- 3.9 依赖关系
        --- 3.10 特殊规则
        --- 3.11 $(CC)支持功能

     === 4 辅助程序
        --- 4.1 简单辅助程序
        --- 4.2 组合辅助程序
        --- 4.3 定义共享库
        --- 4.4 C++语言使用方法
        --- 4.5 辅助程序编译控制选项
        --- 4.6 何时建立辅助程序
        --- 4.7 使用hostprogs-$(CONFIG_FOO)

     === 5 编译清除机制

     === 6 体系Makefile文件
        --- 6.1 变量设置
        --- 6.2 增加预设置项
        --- 6.3 目录表
        --- 6.4 引导映像
        --- 6.5 编译非内核目标
        --- 6.6 编译引导映像命令
        --- 6.7 定制编译命令
        --- 6.8 预处理连接脚本

     === 7 Kbuild变量
     === 8 Makefile语言
     === 9 Credits
     === 10 TODO


=== 1 概述

Makefile包括五部分:

     Makefile                 顶层Makefile文件
     .config                  内核配置文件
     arch/$(ARCH)/Makefile    机器体系Makefile文件
     scripts/Makefile.*       所有内核Makefiles共用规则
     kbuild Makefiles         其它Makefile文件

通过内核配置操作产生.config文件,顶层Makefile文件读取该文件的配置。

顶层Makefile文件负责产生两个主要的程序:vmlinux (内核image)和模块。顶层Makefile文件根据内核配置,通过递归编译内核代码树子目录建立这两个文件。顶层Makefile文件文本一个名为 arch/$(ARCH)/Makefile的机器体系Makefile文件。机器体系Makefile文件为顶层Makefile文件提供与机器相关的信息。

每一个子目录有一个Makefile文件,子目录Makefile文件根据上级目录Makefile文件命令启动编译。这些Makefile使用. config文件配置数据构建各种文件列表,并使用这些文件列表编译内嵌或模块目标文件。

scripts/Makefile.*包含了所有的定义和规则,与Makefile文件一起编译出内核程序。


=== 2 角色分工

人们与内核Makefile存在四种不同的关系:

*用户* 用户使用"make menuconfig"或"make"命令编译内核。他们通常不读或编辑内核Makefile文件或其他源文件。

*普通开发者* 普通开发者维护设备驱动程序、文件系统和网络协议代码,他们维护相关子系统的Makefile文件,因此他们需要内核Makefile文件整体性的一般知识和关于kbuild公共接口的详细知识。

*体系开发者* 体系开发者关注一个整体的体系架构,比如sparc或者ia64。体系开发者既需要掌握关于体系的Makefile文件,也要熟悉内核Makefile文件。

*内核开发者* 内核开发者关注内核编译系统本身。他们需要清楚内核Makefile文件的所有方面。

本文档的读者对象是普通开发者和系统开发者。


=== 3 内核编译文件

内核中大多数Makefile文件是使用kbuild基础架构的Makefile文件。本章介绍kbuild的Makefile中的语法。

3.1节"目标定义"是一个快速导引,后面各章有详细介绍和实例。

--- 3.1 目标定义

     目标定义是Makefile文件的主要部分(核心)。这些目标定义行定义了如何编译文件,特殊的兼容选项和递归子目录。
    
     最简单的Makefile文件只包含一行:

     Example:
             obj-y += foo.o

     这行告诉kbuild在该目录下名为foo.o的目标文件(object),foo.o通过编译foo.c或者foo.S而得到。

     如果foo.o编译成一个模块,则使用obj-m变量,因此常见写法如下:

     Example:
             obj-$(CONFIG_FOO) += foo.o

     $(CONFIG_FOO)可以代表y(built-in对象)或m(module对象)。如果CONFIG_FOO不是y或m,那么这个文件不会被编译和链接。

--- 3.2 内嵌对象 - obj-y

     Makefile文件将为编译vmlinux的目标文件放在$(obj-y)列表中,这些列表依赖于内核配置。

     Kbuild编译所有的$(obj-y)文件,然后调用"$(LD) -r"合并这些文件到一个built-in.o文件中。built-in.o经过父Makefile文件链接到vmlinux。$(obj-y)中的文件 顺序很重要。列表中文件允许重复,文件第一次出现将被链接到built-in.o,后续出现该文件将被忽略。

     链接顺序之所以重要是因为一些函数在内核引导时将按照他们出现的顺序被调用,如函数(module_init() / __initcall)。所以要牢记改变链接顺序意味着也要改变SCSI控制器的检测顺序和重数磁盘。

     Example:
             #drivers/isdn/i4l/Makefile
             # 内核ISDN子系统和设备驱动程序Makefile
             # 每个配置项是一个文件列表
             obj-$(CONFIG_ISDN)         += isdn.o
             obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

--- 3.3 可加载模块 - obj-m

     $(obj-m)表示对象文件(object files)编译成可加载的内核模块。

     一个模块可以通过一个源文件或几个源文件编译而成。Makefile只需简单地它们加到$(obj-m)。

     Example:
             #drivers/isdn/i4l/Makefile
             obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

     注意:在这个例子中$(CONFIG_ISDN_PPP_BSDCOMP)含义是'm'。

     如果内核模块通过几个源文件编译而成,使用以上同样的方法。

     Kbuild需要知道通过哪些文件编译模块,因此需要设置一个$(<module_name>-objs)变量。

     Example:
             #drivers/isdn/i4l/Makefile
             obj-$(CONFIG_ISDN) += isdn.o
             isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

     在这个例子中,模块名isdn.o. Kbuild首先编译$(isdn-objs)中的object文件,然后运行"$(LD) -r"将列表中文件生成isdn.o.

     Kbuild使用后缀-objs、-y识别对象文件。这种方法允许Makefile使用CONFIG_符号值确定一个object文件是否是另外一个object的组成部分。

     Example:
             #fs/ext2/Makefile
             obj-$(CONFIG_EXT2_FS)     += ext2.o
             ext2-y := balloc.o bitmap.o
             ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o

     在这个例子中,如果$(CONFIG_EXT2_FS_XATTR)表示'y',则ext2.o只有xattr.o组成部分。

     注意: 当然,当你将对象文件编译到内核时,以上语法同样有效。因此,如果CONFIG_EXT2_FS=y,Kbuild将先编译ext2.o文件,然后链接到built-in.o。

--- 3.4 导出符号目标

      在Makefile文件中没有特别导出符号的标记。

--- 3.5 库文件 - lib-y

      obj-*中的object文件用于模块或built-in.o编译。object文件也可能编译到库文件中--lib.a。所有罗列在lib-y中的object文件都将编译到该目录下的一个单一的库文件中。包含在0bj-y中的object文件如果也列举在lib-y中将不会包含到库文件中,因为他们不能被访问。但lib-m中的object文件将被编译进lib.a库文件。

      注意在相同的Makefile中可以列举文件到buit-in内核中也可以作为库文件的一个组成部分。因此在同一个目录下既可以有built-in.o也可以有lib.a文件。

      Example:
              #arch/i386/lib/Makefile
              lib-y   := checksum.o delay.o

      这样将基于checksum.o、delay.o创建一个lib.a文件。对于内核编译来说,lib.a文件被包含在libs-y中。将"6.3 目录表"。
      lib-y通常被限制使用在lib/和arch/*/lib目录中。

--- 3.6 目录递归

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

      如果ext2是一个子目录,fs目录下的Makefile将使用以下赋值语句是编译系统编译ext2子目录。

      Example:
              #fs/Makefile
              obj-$(CONFIG_EXT2_FS) += ext2/

      如果CONFIG_EXT2_FS设置成'y(built-in)或'm'(modular),则对应的obj-变量也要设置,内核编译系统将进入ext2目录编译文件。内核编译系统只使用这些信息来决定是否需要编译这个目录,子目录中Makefile文件规定那些文件编译为模块那些是内核内嵌对象。

      当指定目录名时使用CONFIG_变量是一种良好的做法。如果CONFIG_选项不为'y'或'm',内核编译系统就会跳过这个目录。

--- 3.7 编译标记

EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS

      所有的EXTRA_变量只能使用在定义该变量后的Makefile文件中。EXTRA_变量被Makefile文件所有的执行命令语句所使用。

      $(EXTRA_CFLAGS)是使用$(CC)编译C文件的选项。

      Example:
              # drivers/sound/emu10k1/Makefile
              EXTRA_CFLAGS += -I$(obj)
              ifdef DEBUG
                  EXTRA_CFLAGS += -DEMU10K1_DEBUG
              endif

     定义这个变量是必须的,因为顶层Makefile定义了$(CFLAGS)变量并使用该变量编译整个代码树。

     $(EXTRA_AFLAGS)是每个目录编译汇编语言源文件的选项。

     Example:
             #arch/x86_64/kernel/Makefile
             EXTRA_AFLAGS := -traditional

     $(EXTRA_LDFLAGS)和$(EXTRA_ARFLAGS)用于每个目录的$(LD)和$(AR)选项。

     Example:
             #arch/m68k/fpsp040/Makefile
             EXTRA_LDFLAGS := -x

CFLAGS_$@, AFLAGS_$@

     CFLAGS_$@和AFLAGS_$@只使用到当前Makefile文件的命令中。

     $(CFLAGS_$@)定义了使用$(CC)的每个文件的选项。$@部分代表该文件。

     Example:
             # drivers/scsi/Makefile
             CFLAGS_aha152x.o =   -DAHA152X_STAT -DAUTOCONF
             CFLAGS_gdth.o   = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \
                                 -DGDTH_STATISTICS
             CFLAGS_seagate.o =   -DARBITRATE -DPARITY -DSEAGATE_USE_ASM

     这三行定义了aha152x.o、gdth.o和seagate.o文件的编译选项。

     $(AFLAGS_$@)使用在汇编语言代码文件中,具有同上相同的含义。

     Example:
             # arch/arm/kernel/Makefile
             AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional
             AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional

--- 3.9 依赖关系

     内核编译记录如下依赖关系:
      1) 所有的前提文件(both *.c and *.h)
      2) CONFIG_ 选项影响到的所有文件
      3) 编译目标文件使用的命令行

     因此,假如改变$(CC)的一个选项,所有相关的文件都要重新编译。

--- 3.10 特殊规则

     特殊规则使用在内核编译需要规则定义而没有相应定义的时候。典型的例子如编译时头文件的产生规则。其他例子有体系Makefile编译引导映像的特殊规则。

     特殊规则写法同普通的Make规则。Kbuild(应该是编译程序)在Makefile所在的目录不能被执行,因此所有的特殊规则需要提供前提文件和目标文件的相对路径。

     定义特殊规则时将使用到两个变量:

$(src):
     $(src)是对于Makefile文件目录的相对路径,当使用代码树中的文件时使用该变量$(src)。

$(obj):
     $(obj)是目标文件目录的相对路径。生成文件使用$(obj)变量。

     Example:
             #drivers/scsi/Makefile
             $(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
                     $(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl

     这就是使用普通语法的特殊编译规则。目标文件依赖于两个前提文件。目标文件的前缀是$(obj), 前提文件的前缀是$(src)(因为它们不是生成文件)。

--- 3.11 $(CC)支持功能

     内核可能会用不同版本的$(CC)进行编译,每个版本有不同的性能和选项,内核编译系统提供基本的支持用于验证$(CC)选项。$(CC)通常是gcc编译器,但其它编译器也是可以。

cc-option
     cc-option 用于检测$(CC)是否支持给定的选项,如果不支持就使用第二个可选项。
     Example:
             #arch/i386/Makefile
             cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)

     在上面例子中如果$(CC)支持-march=pentium-mmx则cflags-y等于该值,否则等于-march-i586。如果没有第二个可选项且第一项不支持则cflags-y没有被赋值。

cc-option-yn
     cc-option-yn用于检测gcc是否支持给定的选项,支持返回'y'否则'n'。

     Example:
             #arch/ppc/Makefile
             biarch := $(call cc-option-yn, -m32)
             aflags-$(biarch) += -a32
             cflags-$(biarch) += -m32

     在上面例子中如果$(CC)支持-m32选项则$(biarch)设置为y。当$(biarch)等于y时,变量$(aflags-y)和$(cflags-y)将分别等于-a32和-m32。

cc-option-align
     gcc版本>= 3.0用于定义functions、loops等边界对齐选项。
     gcc < 3.00
             cc-option-align = -malign
     gcc >= 3.00
             cc-option-align = -falign

     Example:
        CFLAGS += $(cc-option-align)-functions=4

     在上面例子中对于gcc >= 3.00来说-falign-functions=4,gcc < 3.00版本使用-malign-functions=4。

cc-version
     cc-version返回$(CC)编译器数字版本号。
     版本格式是<major><minor>,均为两位数字。例如gcc 3.41将返回0341。当一个特定$(CC)版本在某个方面有缺陷时cc-version是很有用的。例如-mregparm=3在一些gcc版本会失败尽管gcc接受这个选项。

     Example:
             #arch/i386/Makefile
             GCC_VERSION := $(call cc-version)
             cflags-y += $(shell \
             if [ $(GCC_VERSION) -ge 0300 ] ; then echo "-mregparm=3"; fi ;)

     在上面例子中-mregparm=3只使用在版本大于等于3.0的gcc中。


=== 4 辅助程序

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

--- 4.1 简单辅助程序

     在一些情况下需要在主机上编译和运行主机程序。下面这行告诉kbuild在主机上建立bin2hex程序。

     Example:
             hostprogs-y := bin2hex

     Kbuild假定使用Makefile相同目录下的单一C代码文件bin2hex.c编译bin2hex。

--- 4.2 组合辅助程序

     主机程序也可以由多个object文件组成。定义组合辅助程序的语法同内核对象的定义方法。$(<executeable>-objs)包含了所有的用于链接最终可执行程序的对象。

     Example:
             #scripts/lxdialog/Makefile
             hostprogs-y   := lxdialog
             lxdialog-objs := checklist.o lxdialog.o

     扩展名.o文件都编译自对应的.c文件。在上面的例子中checklist.c编译成checklist.o,lxdialog.c编译为lxdialog.o。最后两个.o文件链接成可执行文件lxdialog。

     注意:语法<executable>-y不能用于定义主机程序。

--- 4.3 定义共享库

     扩展名为.so的对象是共享库文件,并且是位置无关的object文件。内核编译系统提供共享库使用支持,但使用方法有限制。在下面例子中libkconfig.so库文件被链接到可执行文件conf中。

     Example:
             #scripts/kconfig/Makefile
             hostprogs-y   := conf
             conf-objs     := conf.o libkconfig.so
             libkconfig-objs := expr.o type.o

     共享库文件需要对应的-objs定义, 在上面例子中库libkconfig由两个对象组成:expr.o和type.o。expr.o和type.o将被编译为位置无关代码并被链接如libkconfig.so。共享库不支持C++语言。

--- 4.4 C++语言使用方法

     内核编译系统提供了对C++主机程序的支持以用于内核配置,但不主张其它方面使用这种方法。

     Example:
             #scripts/kconfig/Makefile
             hostprogs-y   := qconf
             qconf-cxxobjs := qconf.o

     在上面例子中可执行文件由C++文件qconf.cc组成 - 通过$(qconf-cxxobjs)标识。

     如果qconf由.c和.cc文件混合组成,附加行表示这种情况。

     Example:
             #scripts/kconfig/Makefile
             hostprogs-y   := qconf
             qconf-cxxobjs := qconf.o
             qconf-objs   := check.o

--- 4.5 辅助程序编译控制选项

     当编译主机程序时仍然可以使用$(HOSTCFLAGS)设置编译选项传递给$(HOSTCC)。这些选项将影响所有使用变量HOST_EXTRACFLAG的Makefile创建的主机程序。

     Example:
             #scripts/lxdialog/Makefile
             HOST_EXTRACFLAGS += -I/usr/include/ncurses

     为单个文件设置选项使用下面方式:

     Example:
             #arch/ppc64/boot/Makefile
             HOSTCFLAGS_piggyback.o := -DKERNELBASE=$(KERNELBASE)

     也可以使用附加链接选项:

     Example:
             #scripts/kconfig/Makefile
             HOSTLOADLIBES_qconf := -L$(QTDIR)/lib

     当链接qconf时将使用外部选项"-L$(QTDIR)/lib"。

--- 4.6 何时建立辅助程序

     只有当需要时内核编译系统才会编译主机程序。有两种方式:

     (1) 在特殊规则中作为隐式的前提需求

     Example:
             #drivers/pci/Makefile
             hostprogs-y := gen-devlist
             $(obj)/devlist.h: $(src)/pci.ids $(obj)/gen-devlist
              ( cd $(obj); ./gen-devlist ) < $<

     编译目标文件$(obj)/devlist.h需要先建立$(obj)/gen-devlist。注意在特殊规则中使用主机程序必须加前缀$(obj)。

     (2) 使用$(always)

     当没有合适的特殊规则可以使用,并且在进入Makefile文件时就要建立主机程序,可以使用变量$(always)。

     Example:
             #scripts/lxdialog/Makefile
             hostprogs-y   := lxdialog
             always     := $(hostprogs-y)

     这样就告诉内核编译系统即使没有任何规则使用lxdialog也要编译它。

--- 4.7 使用hostprogs-$(CONFIG_FOO)

     在Kbuild文件中典型模式如下:

     Example:
             #scripts/Makefile
             hostprogs-$(CONFIG_KALLSYMS) += kallsyms

     对Kbuild来说'y'用于内嵌对象'm'用于模块。因此如果config符号是'm',编译系统也将创建该程序。换句话说内核编译系统等同看待hostprogs-m和hostprogs-y。但如果不涉及到CONFIG符号仅建议使用hostprogs-y。


=== 5 编译清除机制

"make clean"命令删除在编译内核生成的大部分文件,例如主机程序,列举在 $(hostprogs-y)、$(hostprogs-m)、$(always)、$(extra-y)和$(targets)中目标文件都将被删除。 代码目录数中的"*.[oas]"、"*.ko"文件和一些由编译系统产生的附加文件也将被删除。

附加文件可以使用$(clean-files)进行定义。
     Example:
             #drivers/pci/Makefile
             clean-files := devlist.h classlist.h

当执行"make clean"命令时, "devlist.h classlist.h"两个文件将被删除。内核编译系统默认这些文件与Makefile具有相同的相对路径,否则需要设置以'/'开头的绝对路径。

删除整个目录使用以下方式:

     Example:
             #scripts/package/Makefile
             clean-dirs := $(objtree)/debian/

这样就将删除包括子目录在内的整个debian目录。如果不使用以'/'开头的绝对路径内核编译系统见默认使用相对路径。

通常内核编译系统根据"obj-* := dir/"进入子目录,但是在体系Makefile中需要显式使用如下方式:

     Example:
             #arch/i386/boot/Makefile
             subdir- := compressed/

上面赋值语句指示编译系统执行"make clean"命令时进入compressed/目录。

在编译最终的引导映像文件的Makefile中有一个可选的目标对象名称是archclean。
      Example:
              #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"命令清除。

=== 6 体系Makefile文件

在开始进入各个目录编译之前,顶层Makefile文件设置编译环境和做些准备工作。顶层Makefile文件包含通用部分,arch/$(ARCH) /Makefile包含该体系架构所需的设置。因此arch/$(ARCH)/Makefile会设置一些变量和少量的目标。

当编译时将按照以下大概步骤执行:
1) 配置内核 => 产生 .config文件
2) 保存内核版本到include/linux/version.h文件中
3) 符号链接include/asm to include/asm-$(ARCH)
4) 更新所有目标对象的其它前提文件
- 附加前提文件定义在arch/$(ARCH)/Makefile文件中
5) 递归进入init-* core* drivers-* net-* libs-*中的所有子目录和编译所有的目标对象
- 上面变量值都引用到arch/$(ARCH)/Makefile文件。
6) 链接所有的object文件生成vmlinux文件,vmlinux文件放在代码树根目录下。
最开始链接的几个object文件列举在arch/$(ARCH)/Makefile文件的head-y变量中。
7) 最后体系Makefile文件定义编译后期处理规则和建立最终的引导映像bootimage。
- 包括创建引导记录
- 准备initrd映像和相关处理

--- 6.1 变量设置

LDFLAGS      $(LD)一般选项

     选项使用于链接器的所有调用中。通常定义emulation就可以了。

     Example:
             #arch/s390/Makefile
             LDFLAGS   := -m elf_s390

     注意: EXTRA_LDFLAGS和LDFLAGS_$@可以进一步订制使用选项,请参考第7章。

LDFLAGS_MODULE       $(LD)链接模块的选项

     LDFLAGS_MODULE通常设置$(LD)链接模块的.ko选项。默认为"-r"即可重定位输出文件。

LDFLAGS_vmlinux   $(LD)链接vmlinux选项

     LDFLAGS_vmlinux定义链接最终vmlinux时链接器的选项。LDFLAGS_vmlinux支持使用LDFLAGS_$@。

     Example:
             #arch/i386/Makefile
             LDFLAGS_vmlinux := -e stext

OBJCOPYFLAGS      objcopy选项

     当使用$(call if_changed,objcopy)转化a .o文件时,OBJCOPYFLAGS中的选项将被使用。$(call if_changed,objcopy)经常被用作为vmlinux产生原始的二进制文件。

     Example:
             #arch/s390/Makefile
             OBJCOPYFLAGS := -O binary

             #arch/s390/boot/Makefile
             $(obj)/image: vmlinux FORCE
                      $(call if_changed,objcopy)

     在上面例子中$(obj)/image是vmlinux的二进制版本文件。$(call if_changed,xxx)的使用方法见后。

AFLAGS   $(AS)汇编选项

     默认值见顶层Makefile文件。针对每个体系需要另外添加和修改它。

     Example:
             #arch/sparc64/Makefile
             AFLAGS += -m64 -mcpu=ultrasparc

CFLAGS      $(CC)编译器选项

     默认值见顶层Makefile文件。针对每个体系需要另外添加和修改它。

     通常CFLAGS变量值取决于内核配置。

     Example:
             #arch/i386/Makefile
             cflags-$(CONFIG_M386) += -march=i386
             CFLAGS += $(cflags-y)

     许多体系Makefiles文件动态启动市场目标机器上的C编译器检测支持的选项:

           #arch/i386/Makefile
           ...
           cflags-$(CONFIG_MPENTIUMII)   += $(call cc-option,\
                 -march=pentium2,-march=i686) ...
           # Disable unit-at-a-time mode ...
           CFLAGS += $(call cc-option,-fno-unit-at-a-time)
           ...

     第一个例子当config选项是'y'时将被选中。

CFLAGS_KERNEL      $(CC)编译built-in对象的选项

     $(CFLAGS_KERNEL)包含外部C编译器选项编译本地内核代码。

CFLAGS_MODULE      $(CC)编译模块选项

     $(CFLAGS_MODULE)包含外部C编译器选项编译可加载内核代码。

--- 6.2 增加预设置项

     prepare: 这个规则用于列举开始进入子目录编译前需要的前提文件。通常是些包含汇编常量的头文件。
     prepare: 这个规则用于列举开始进入子目录编译前需要的前提文件。通常是些包含汇编常量的头文件。

     Example:
             #arch/s390/Makefile
             prepare: include/asm-$(ARCH)/offsets.h

     在这个例子中include/asm-$(ARCH)/offsets.h将在进入子目录前编译。详见XXX-TODO文件描述了kbuild如何产生offset头文件。

--- 6.3 目录表

     体系Makefile文件和顶层Makefile文件共同定义了如何建立vmlinux文件的变量。注意没有体系相关的模块对象定义部分:所有的模块对象都是体系无关的。

head-y, init-y, core-y, libs-y, drivers-y, net-y

     $(head-y) 列举首先链接到vmlinux的对象文件。
     $(libs-y) 列举了能够找到lib.a文件的目录。
     其余的变量列举了能够找到内嵌对象文件的目录。

     $(init-y) 列举的对象位于$(head-y)对象之后。
     然后是如下位置秩序:
     $(core-y), $(libs-y), $(drivers-y) 和 $(net-y)。

     顶层Makefile定义了所有同用目录,arch/$(ARCH)/Makefile文件只需增加体系相关的目录。

     Example:
             #arch/sparc64/Makefile
             core-y += arch/sparc64/kernel/
             libs-y += arch/sparc64/prom/ arch/sparc64/lib/
             drivers-$(CONFIG_OPROFILE) += arch/sparc64/oprofile/

--- 6.4 引导映像

     体系Makefile文件定义了编译vmlinux文件的目标对象,将它们压缩和封装成引导代码,并复制到合适的位置。这包括各种安装命令。如何定义实际的目标对象无法为所有的体系结构提供标准化的方法。

     附加处理过程常位于arch/$(ARCH)/下的boot/目录。

     内核编译系统无法在boot/目录下提供一种便捷的方法创建目标系统文件。因此arch/$(ARCH)/Makefile要调用make命令在 boot/目录下建立目标系统文件。建议使用的方法是在arch/$(ARCH)/Makefile中设置调用,并且使用完整路径引用arch/$ (ARCH)/boot/Makefile。

     Example:
             #arch/i386/Makefile
             boot := arch/i386/boot
             bzImage: vmlinux
                     $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@

     建议使用"$(Q)$(MAKE) $(build)=<dir>"方式在子目录中调用make命令。
     没有定义体系目标系统文件的规则,但执行"make help"命令要列出所有目标系统文件,因此必须定义$(archhelp)变量。

     Example:
             #arch/i386/Makefile
             define archhelp
               echo '* bzImage     - Image (arch/$(ARCH)/boot/bzImage)'
             endef

     当执行不带参数的make命令时,将首先编译第一个目标对象。在顶层Makefile中第一个目标对象是all:。
     一个体系结构需要定义一个默认的可引导映像。
     "make help"命令的默认目标是以*开头的对象。
     增加新的前提文件给all目标可以设置不同于vmlinux的默认目标对象。

     Example:
           #arch/i386/Makefile
           all: bzImage

     当执行不带参数的"make"命令时,bzImage文件将被编译。

--- 6.5 编译非内核目标

extra-y

     extra-y定义了在当前目录下创建没有在obj-*定义的附加的目标文件。

     在extra-y中列举目标是处于两个目的:
      1) 是内核编译系统在命令行中检查变动情况
        - 当使用$(call if_changed,xxx)时
      2) 内核编译系统知道执行"make clean"命令时删除哪些文件

     Example:
             #arch/i386/kernel/Makefile
             extra-y := head.o init_task.o

     上面例子extra-y中的对象文件将被编译但不会练接到built-in.o中。

--- 6.6 编译引导映像命令

     Kbuild提供了一些编译引导映像有用的宏。

if_changed

     if_changed是后面命令使用的基础。

     用法:
        target: source(s)
            FORCE $(call if_changed,ld/objcopy/gzip)

     当这条规则被使用时它将检查哪些文件需要更新,或命令行被改变。后面这种情况将迫使重新编译编译选项被改变的执行文件。使用if_changed的目标对象必须列举在$(targets)中,否则命令行检查将失败,目标一直会编译。
     赋值给$(targets)的对象没有$(obj)/前缀。
     if_changed也可以和定制命令配合使用,见6.7"kbuild定制命令"。

     注意: 一个常见错误是忘记了FORCE前导词。

ld
      链接目标。常使用LDFLAGS_$@作为ld的选项。

objcopy
      复制二进制文件。常用于arch/$(ARCH)/Makefile中和使用OBJCOPYFLAGS作为选项。
     也可以用OBJCOPYFLAGS_$@设置附加选项。

gzip
      压缩目标文件。使用最大压缩算法压缩目标文件。

     Example:
             #arch/i386/boot/Makefile
             LDFLAGS_bootsect := -Ttext 0x0 -s --oformat binary
             LDFLAGS_setup   := -Ttext 0x0 -s --oformat binary -e begtext

             targets += setup setup.o bootsect bootsect.o
             $(obj)/setup $(obj)/bootsect: %: %.o FORCE
                     $(call if_changed,ld)

      在上面例子中有两个可能的目标对象,分别需要不同的链接选项。使用LDFLAGS_$@语法为每个目标对象设置不同的链接选项。
     $(targets)包含所有的目标对象,因此内核编译系统知道所有的目标对象并且将:
      1) 检查命令行的改变情况
      2) 执行make clean命令时删除目标对象

     ": %: %.o"是简写方法,减写setup.o和bootsect.o文件。

     注意: 常犯错误是忘记"target :="语句,导致没有明显的原因目标文件被重新编译。

--- 6.7 定制编译命令

     当执行带KBUILD_VERBOSE=0参数的编译命令时命令的简短信息会被显示。要让定制命令具有这种功能需要设置两个变量:
     quiet_cmd_<command> - 将被显示的内容
      cmd_<command>      - 被执行的命令

     Example:
             #
             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 KBUILD_VERBOSE=0"命令编译$(obj)/bzImage目标时将显示:

     BUILD   arch/i386/boot/bzImage

--- 6.8 预处理连接脚本

     当编译vmlinux映像时将使用arch/$(ARCH)/kernel/vmlinux.lds链接脚本。
     相同目录下的vmlinux.lds.S文件是这个脚本的预处理的变体。内核编译系统知晓.lds文件并使用规则*lds.S -> *lds。

    Example:
            #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) : 目标编译选项。注意要使用文件全名。


=== 7 Kbuild变量

顶层Makefile文件导出下面这些变量:

VERSION, PATCHLEVEL, SUBLEVEL, EXTRAVERSION

     这几个变量定义了当前内核版本号。很少体系体系Makefiles文件直接使用他们,常用$(KERNELRELEASE)代替。

     $(VERSION)、$(PATCHLEVEL)和$(SUBLEVEL)定义了三个基本部分版本号,例如"2", "4",和"0"。这三个变量一直使用数值表示。

     $(EXTRAVERSION)定义了更细的补钉号,通常是短横跟一些非数值字符串,例如"-pre4"。

KERNELRELEASE

    $(KERNELRELEASE)是一个单一字符如"2.4.0-pre4",适合用于构造安装目录和显示版本字符串。一些体系文件使用它用于以上目的。

ARCH

    这个变量定义了目标系统体系结构,例如"i386"、"arm"、"sparc". 一些内核编译文件测试$(ARCH)用于确定编译哪个文件。默认情况下顶层Makefile文件设置$(ARCH)为主机相同的系统体系。当交叉编译编译 时,用户可以使用命令行改变$(ARCH)值:

       make ARCH=m68k ...

INSTALL_PATH

     这个变量定义了体系Makefiles文件安装内核映项和System.map文件的路径。

INSTALL_MOD_PATH, MODLIB

     $(INSTALL_MOD_PATH)定义了模块安装变量$(MODLIB)的前缀。这个变量通常不在Makefile文件中定义,如果需要可以由用户添加。
     $(MODLIB)定义了模块安装目录。
     顶层Makefile定义$(MODLIB)为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。用户可以使用命令行修改这个值。


=== 8 Makefile语言

内核Makefiles设计目标用于运行GNU Make程序。Makefiles仅使用GNU Make提到的特性,但使用了较多的GNU扩展部分。

GNU Make程序支持基本的列表处理功能。内核Makefiles文件结合"if"语句使用了简单的列表建立和维护功能。

GNU Make程序有两种赋值操作符:":="和"="。 ":="执行时立即计算右值并赋值给左值。"="类似公式定义,当每次使用左值要被使用时计算右值并赋给它。

一些情况中使用"="合适,而一些情况中使用":="才是正确选择。


=== 9 Credits

Original version made by Michael Elizabeth Chastain, <mailto:mec@shout.net>
Updates by Kai Germaschewski <kai@tp1.ruhr-uni-bochum.de>
Updates by Sam Ravnborg <sam@ravnborg.org>

=== 10 TODO

- Describe how kbuild support shipped files with _shipped.
- Generating offset header files.
- Add more variables to section 7?

 

评论

此博客中的热门博文

【转】VxWorks中的地址映射

在运用嵌入式系统VxWorks和MPC860进行通信系统设计开发时,会遇到一个映射地址不能访问的问题。 缺省情况下,VxWorks系统已经进行了如下地址的映射:   memory地址、bcsr(Board Control and Status)地址、PC_BASE_ADRS(PCMCIA)地址、Internal Memory地址、rom(Flach memory)地址等,但是当你的硬件开发中要加上别的外设时,如(falsh、dsp、FPGA等),对这些外设的访问也是通过地址形式进行读写,如果你没有加相应的地址映射,那么是无法访问这些外设的。   和VxWorks缺省地址映射类似,你也可以进行相应的地址映射。   如下是地址映射原理及实现:   1、 地址映射结构 在Tornado\target\h\vmLib.h文件中 typedef struct phys_mem_desc { void *virtualAddr; void *physicalAddr; UINT len; UINT initialStateMask; /* mask parameter to vmStateSet */ UINT initialState; /* state parameter to vmStateSet */ } PHYS_MEM_DESC; virtualAddr:你要映射的虚拟地址 physicalAddr:硬件设计时定义的实际物理地址 len;要进行映射的地址长度 initialStateMask:可以初始化的地址状态: 有如下状态: #define VM_STATE_MASK_VALID 0x03 #define VM_STATE_MASK_WRITABLE 0x0c #define VM_STATE_MASK_CACHEABLE 0x30 #define VM_STATE_MASK_MEM_COHERENCY 0x40 #define VM_STATE_MASK_GUARDED 0x80 不同的CPU芯片类型还有其特殊状态 initialState:实际初始化的地址状态: 有如下状态: #define VM_STATE_VALID 0x01 #define VM_STATE_VALID_NOT 0x00 #define VM_STATE_WRITA

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如下内容: Config ARM_CS8900    tristate "CS8900 support" depends on NET_ETHERNET && A

【转】多迷人Gtkmm啊

前边已经说过用glade设计界面然后动态装载,接下来再来看看怎么改变程序的皮肤(主题)     首先从 http://art.gnome.org/themes/gtk2 下载喜欢的主题,从压缩包里提取gtk-2.0文件夹让它和我们下边代码生成的可执行文件放在同一个目录下,这里我下载的的 http://art.gnome.org/download/themes/gtk2/1317/GTK2-CillopMidnite.tar.gz     然后用glade设计界面,命名为main.glade,一会让它和我们下边代码生成的可执行程序放在同一个目录下边     然后开始写代码如下: //main.cc #include <gtkmm.h> #include <libglademm/xml.h> int main(int argc, char *argv[]) {     Gtk::Main kit(argc,argv);         Gtk::Window *pWnd;        gtk_rc_parse("E:\\theme-viewer\\themes\\gtk-2.0\\gtkrc");       Glib::RefPtr<Gnome::Glade::Xml> refXml;     try     {         refXml = Gnome::Glade::Xml::create("main.glade");     }     catch(const Gnome::Glade::XmlError& ex)     {         Gtk::MessageDialog dialog("Load glade file failed!", false,       \                                   Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);         dialog.run();               return 1;     }         refXml->get_widget("main", pWnd);     if(pW