Linux Kernel Makefiles

看Linux内核源码一般都从Makefiles看起,Linux Kernel的Makefile有一套完整的构建体系。如果你想给整个代码tree添加文件(不管基于什么目的),了解Makefile的机制往往事半功倍。源码documentation/kbuild目录下一篇文档makefiles.txt将给我们很大的帮助。下面是对这篇文档的翻译。开始我没有在网上找到类似翻译文章,翻译到最后才看到有类似的内容,真是心塞哇大哭。翻译中PS的内容是我自己加入的,自己的理解和标注,希望能给各位读者有些许帮助。

(注:我翻译的是2.6.22.6版本内核中的文档)


Linux Kernel Makefiles



这篇文当描述Linux内核Makefiles文件。


=== 目录


=== 1 概述
=== 2 谁做什么
=== 3 kbuild文件
--- 3.1 目标定义
  --- 3.2 Built-in类型目标 - obj-y
  --- 3.3 可加载的模块目标 - obj-m
  --- 3.4 导出符号的objects
  --- 3.5 库文件目标 - lib-y
  --- 3.6 下层目录访问
  --- 3.7 编译选项
  --- 3.8 命令行依赖
  --- 3.9 依赖跟踪
  --- 3.10 特殊规则
  --- 3.11 检测$(CC)支持选项的函数
 
=== 4 Host端程序支持
  --- 4.1 简单的Host程序
  --- 4.2 复合的Host程序
  --- 4.3 定义共享库
  --- 4.4 host程序使用C++
  --- 4.5 控制host程序编译选项
  --- 4.6 host程序实际构建时机
  --- 4.7 使用hostprogs-$(CONFIG_FOO)


=== 5 Kbuild 清理结构


=== 6 架构相关Makefiles
  --- 6.1 设置变量调整构建架构相关部分
  --- 6.2 增加archprepare的依赖:
  --- 6.3 列出要访问的下层目录
  --- 6.4 架构相关启动镜像
  --- 6.5 构建非kbuild结构的目标
  --- 6.6 构建启动镜像时有用的命令
  --- 6.7 自定义kbuild命令
  --- 6.8 处理链接脚本


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

=== 1 概述


Makefiles文件由5部分组成:


Makefile 顶层目录Makefile。
.config 内核配置文件。
arch/$(ARCH)/Makefile arch目录Makefile。
scripts/Makefile.* 为所有kbuild Makefile文件的一般规则。
kbuild Makefiles 大约500个。
PS:kbuild是一个Makefile框架。

顶层Makefile文件读取.config文件,.config文件来自于kernel配置过程。


顶层Makefile文件的职责是构筑两个产物:vmlinux(内核镜像)和modules(模块文件)。
它通过递归进入kernel代码目录完成构筑目的。
哪一个子目录被访问由kernel配置决定。顶层目录以文本形式只包含了arch目录下的Makefile
文件——arch/$(ARCH)/Makefile。arch目录下Makefile为顶层目录Makefile提供了架构相关
信息。


每一个子目录都有一个kbuild Makefile,kbuild Makefile承载着从上面传下来的命令。Kbuild
Makefile使用.config文件提供的信息去构建一系列文件列表,这些文件列表备用做构筑built-in
或者modular目标。
PS:built-in目标会被编译进内核,每个子目录下的文件最终都会被编译成一个built-in.o
    文件。modular目标是编译成模块形式,使用时才加载入内核。
    
scripts/Makefile.*包含所有的宏定义和规则等。被用作基于kbuild makefiles构筑内核。




=== 2 谁做什么


有四类人使用kernel的Makefiles文件。


“Users”指构建内核的人。这些人键入命令,像“make menuconfig”或者“make”。他们通常不
读也不编辑人么kernel的Makefiles文件(或者任何其它代码文件)。


“Normal developers”指致力于设备驱动、文件系统以及网络协议方面的人。他们需要维护
自己工作的子系统下的kbuild Makefiles文件。为了工作效率,他们需要了解kernel Makefiles
的整体知识,以及为kbuild提供的接口的详细知识。


“Arch developers”致力于一个整体的架构,像sparc或ia64。这些人需要知道arch目录Makefile
和kbuild Makefiles


“Kbuild developers”是致力于kernel build 系统的人。这些人需要知道kernel Makefiles
各个方面的知识。


这篇文档面向“normal developers”和“arch developers”。




=== 3 kbuild文件


大部分kernel中的Makefiles文件都是kbuild Makefile,使用者kbuild的基础结构。这一章
介绍kbuild Makefiles中使用的语法。
kbuild文件的首选名字是“Makefile”,然而“Kbuild”也可以被使用,如果“Makefile”和“Kbuild”
同时存在,将使用“Kbuild”。


3.1章“目标定义”是快速介绍,以后的章节将通过例子提供更详细的说明。


--- 3.1 目标定义


目标定义是kbuild Makefile的核心部分。这些行定义哪些文件被构建,任何的特殊编
译选项,以及哪些子目录将被递归进入编译。

大多简单的kbuild makefile包含这么一行:

Example: 
bj-y += foo.o

这告诉kbuild在那个目录有一个object文件——foo.o。foo.o由foo.c或者foo.S文件构建。

如果foo.o被构建成一个模块,变量obj-m将被使用。因此下面的模式将被使用:

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

$(CONFIG_FOO)会被替换为y(为了编译入内核)或者m(为了编译成模块)。如果CONFIG_FOO
既不是y也不是m,这个文件将不会被编译、链接。

--- 3.2 Built-in类型目标 - obj-y


kbuild Makefile在$(obj-y)列表中指定vmlinux构成所使用的object文件。这个列表是
根据kernel配置构成的。
PS:vmlinux是内核镜像文件。

Kbuild编译所有的$(obj-y)文件。然后调用"$(LD) -r"将这些文件融合成一个built-in.o
文件。built-in.o稍后被上层Makefile链接 进vmlinux中。

链接的顺序值得关注,因为某些函数(module_init() / __initcall)将在boot过程中
按照它们出现的顺序调用。例如:改变链接顺序,可能改变你SCSI控制器的检测顺序,
进而改变你硬盘的编号。

Example: 
#drivers/isdn/i4l/Makefile
# Makefile for the kernel ISDN subsystem and device drivers.
# Each configuration option enables a list of files.
obj-$(CONFIG_ISDN)             += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

--- 3.3 可加载的模块目标 - obj-m


$(obj-m)指定了被编译成内核模块的object文件。

一个模块可以由一个或者多个源文件构成。在一个源文件的情况下,kbuild 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)中列出的objects文件,
然后执行"$(LD) -r",来将列表中的这些文件组合成isdn.o。

Kbuild通过后缀-objs和后缀-y来识别一个object是否被用来组成复合object。这允许
Makefiles使用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",那么xattr.o就是复合object
ext2.o的一部分。
PS:个人觉得-y和-obj是一个意思,没有验证过。

注意:当然,你在将objects编译进内核是,上面的语法也是适用的。所以,CONFIG_EXT2_FS
=y时,kbuild将单独构造出ext2.o,然后像你期望的那样,将它编译入内核。

--- 3.4 导出符号的objects


在makefiles中没有任何东西被需要用来导出符号。

--- 3.5 库文件目标 - lib-y


obj-*中列出的objects被用作模块或者在特定目录下组合成built-in.o。同样它也有责
任列出被编译成库的objects。
同一目录下所有被obj-y列出的ojects将被编译成一个单独的库。
同时被列在obj-y和lib-y中的objects不会被包含在library中,因为这些objects随时
可以访问。
PS:在obj-y中的objects被编译入内核,所以能够随时访问。
类似的,列在lib-m中的objects将被包含在lib.a中。
PS:lib.a是编译成库的名字,所有的目录下都是这个名字。我觉得这里有问题,可能
是写错了。觉得表达的意思是同时列在obj-m和lib-y中的objects会被包含在库中。
大家可以参照原文理解下。

注意:同一个kbuild makefile可能同时列出用于built-in和library的文件。因此,同
一个目录会同时出现built-in.o和lib.a文件

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

这将基于cheksum.o和delay.o创造出一个lib.a。
想让kbuild识别一个lib.a将被构建,要在libs-y中列出对应的目录。
参见“6.3 列出从上往下访问的目录”。

通常是在lib/和arch/*/lib中使用lib-y。

--- 3.6 下层目录访问


一个Makefile只负责构筑自己目录下的objects。子目录下的文件由子目录下的Makefiles
管理。如果子目录被你告知给build系统,build系统会递归地在子目录中自动调用make。

为了做到这一点,obj-y和obj-m被使用。
ext2文件在一个独立的目录下,fs/目录下的Makefile告诉kbuild在下层目录使用一下
配置。

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

如果CONFIG_EXT2_FS被设置成“y”(built-in)或者“m”(modular),相应的obj-变量将被
设置,kbuild将进入ext2目录。
Kbuild指使用这个信息去确定需要进入相应目录,是在相应子目录中确定编译出模块还
是编译进内核。

一个好的做法是使用CONFIG_变量来配置目录是否被进入。相应的CONFIG_变量既不是“y”
也不是“m”时,kbuild将跳过这个目录。

--- 3.7 编译选项

EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS

所有的EXTRA_变量只作用于它们被定义的kbuild makefile中。这EXTRA_变量应用于所
有kbuild makefile中执行的命令中。

$(EXTRA_CFLAGS)指定C文件编译选项,编译器$(CC)。

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

这个变量是必要的,因为顶层目录有自己的变量$(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_$@只用在当前kbuild 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.8 命令行依赖
PS:Linux2.6.22.6内核中对应文件没有3.8这一节。

--- 3.9 依赖跟踪

Kbuild跟踪依赖通过下面规则:
1) 所有先决文件(*.c和*.h) 
2) 所有先决文件使用的CONFIG_选项
3) 编译目标使用的命令行 

因此,如果你改变了$(CC)的选项,所有受影响的文件将重新编译。


--- 3.10 特殊规则


当kbuild基础结构没有提供所需支持时使用特殊规则。一个典型的例子是在build过程
中头文件的创建。另一个例子是架构相关的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

这是遵循make普通语法的特殊规则。
目的文件基于两个先决文件。对于目的文件,使用$(obj)作为前缀,对于先决文件使用
$(src)作为前缀(因为它们不是被创建的文件)。

--- 3.11 检测$(CC)支持选项的函数


Kernel可能被各个不同版本的$(CC)所构建,每个版本可能支持独自的功能和选项。Kbuild
提供检测$(CC)选项是否合法的函数。$(CC)通常指gcc编译器,也可以是其它的。
PS:其实就是make里自定义函数,在给选项赋值时调用这些自动定义函数,通过函数返
回值赋值。

as-option
as-option检测$(CC)编译汇编文件(*.S)时提供的选项是否支持。如果第一个选项不
支持,第二个可选选项将会被指定。
PS:汇编文件后缀*.S和*.s有区别。小写的s文件,在后期阶段不在进行预处理操作,
所以我们不能在这里面写预处理的语句在里面。大写的S文件,还会进行预处理、
汇编等操作,所以我们可以在这里面加入预处理的命令。

Example: 
#arch/sh/Makefile
cflags-y += $(call as-option,-Wa$(comma)-isa=$(isa-y),)

在上面的例子中,如果选项-Wa$(comma)-isa=$(isa-y)被$(CC)支持,会指定给cflags-y。
第二个参数是可选的,如果第一个参数不被支持,将提供第二个参数。
PS:这里第二个参数是空的,所以如果第一个参数不支持,cflags-y +=空。

ld-option
ld-option检测$(CC)链接object文件时给出的选项是否支持。如果第一个选项不支持,
第二个可选选项将会被指定。

Example: 
#arch/i386/kernel/Makefile
vsyscall-flags += $(call ld-option, -Wl$(comma)--hash-style=sysv)

在上面的例子中,如果选项-Wl$(comma)--hash-style=sysv被$(CC)支持,会指定给
vsyscall-flags。第二个参数是可选的,如果第一个参数不被支持,将提供第二个参数。

as-instr
as-instr检测在汇编中,C转义是否被支持。支持输出option1,不持之输出option2.
PS:没有例子,我猜测的。

cc-option
cc-option检测给出的选项$(CC)是否支持,不支持则指定第二个可选参数为$(CC)选项。

Example: 
#arch/i386/Makefile
cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)

在上面的例子中,如果选项-march=pentium-mmx被$(CC)支持,会指定给cflags-y,否
则将-march=i586指定给cflags-y。cc-option第二个参数是可选的,如果被省略且第一
个参数不被支持,ccflags-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及以上版本改变了用于指定函数、loops对齐的选项类型。当$(cc-option-align)
用于一个对齐选项时,将被指定正确的前缀:
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> ,major和minor都为两位
数字。举个例子,gcc 3.41将返回0341.
当明确某一版本$(CC)在某方面有缺陷时,cc-version非常有用。例如-mregparm=3在某
些gcc版本中有缺陷,尽管这个选项被gcc支持。

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

在上面的例子中,-mregparm=3选项只被用于版本号大于3.0的gcc编译器中。

cc-ifversion
cc-ifversion用于检测$(CC)版本,如果关于版本号的表达式正确,则返回最后一个参
数。

Example: 
#fs/reiserfs/Makefile
EXTRA_CFLAGS := $(call cc-ifversion, -lt, 0402, -O1)

在这个例子中,如果$(CC)版本号小于4.2,EXTRA_CFLAGS将被指定为-01.
cc-ifversion将继承所有shell操作符:
-eq, -ne, -lt, -le, -gt, 和 -ge
在这个例子中,第三个参数是一个文本,也可是可展开的变量和宏。

=== 4 Host端程序支持


Kbuild支持在编译过程中构建host端可执行文件。
PS:Host端指make执行的平台。
使用一个host端可执行文件需要两个步骤。


第一步是要告诉kbuild这个host程序存在。通过使用hostprogs-y变量做到这一点。


第二步是增加这个可执行程序明确的依赖。可以通过两种方式做到这一点。一是通过增加规
则依赖,另一个是使用$(always)变量。
下面描述这两种可行方法。


--- 4.1 简单的Host程序


某些情况下需要在build执行的机器上编译和运行一个程序。
下面的一行告诉kbuild程序bin2hex将在host上构建。

Example: 
hostprogs-y := bin2hex

上面的例子中,kbuild假设bin2hex只通过一个c文件构建,这个c文件名为bin2hex.c,
位于Makefile所在的目录。

--- 4.2 复合的Host程序


Host程序可以通过多个objects文件构建。为host程序定义复合objects文件的语法和为
kernel objects文件使用的语法是相似的。
$(<executable>-objs)列出了链接成最终可执行程序用到的所有objects文件。

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

拓展名为.o的objects由相应的.c文件编译而来。上面的例子中,checklist.c编译成
checklist.o,lxdialog.c编译成lxdialog.o。
最终,这两个.o文件被链接成一个可执行文件lxdialog。
注意:<executable>-y这种语法不被host程序允许。

--- 4.3 定义共享库


拓展名为.so的objects文件被认作共享库,作为独立对象被编译。
Kbuild提供了共享库的支持,但是使用受到限制。
在下面的例子中,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构成。
expro.o和type.o作为独立的代码被构建,然后链接成共享库libkconfig.so。C++不被
支持共享库。

--- 4.4 host程序使用C++


kbuild支持host程序使用c++语言。这被引入是为了支持kconfig,一般不建议使用。

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

在上面的例子中,可执行文件由C++文件qconf.cc构成,qconf.cc由$(qconf-cxxobjs)
定义。

如果qconf有.c和.cc文件共同构成,就需要额外一行去标明这一点。

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


--- 4.5 控制host程序编译选项


在编译host程序时,可以设置明确的编译选项。程序总是通过$(HOSTCC)编译,并由
$(HOSTCFLAGS)定义编译选项。
通过变量HOST_EXTRACFLAGS设置编译选项将影响本Makefile中所有的host程序编译。

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 host程序实际构建时机


只有当host程序被用作先决条件时,kbuild才会构建它。
有两种方式来实现:

(1) 在特殊规则中作为先决条件明确列出。

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

在$(obj)/gen-devlist更新前,$(obj)/devlist.h目标是不会被构建的。注意在特殊规
则中的host程序必须使用$(obj)前缀。

(2) 使用$(always)
当没有合适的特殊规则,进入makefile,$(always)被使用,host程序将被构建。

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

尽管没有任何相关规则,这也告诉kbuild去构建lxdialog。


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


在Kbuild文件中有一种典型的形式像下面这样:

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

Kbuild知道“y”表示编译进内核,“m”表示编译成模块。所以如果配置符号被替换成“m”,
kbuild仍会编译这个文件。换句话说,Kbuild认为hostprogs-m和hostprogs-y是一样的。
当没有CONFIG标志时,只建议使用hostprogs-y。

=== 5 Kbuild 清理结构


“make clean”删除在obj目录下大部分创建的文件。包括被创建的host程序。
Kbuild知道列在$(hostprogs-y)、$(hostprogs-m)、$(always)、$(extra-y)和$(targets)
的目标。它们在“make clean”过程中被删除。“make clean”执行后,kernel代码树下所有匹
配"*.[oas]"、"*.ko"以及一些kbuild创建的附加文件都会被删除。


在kbuild makefiles,通过$(clean-files)指明附加文件。


Example: #drivers/pci/Makefile
clean-files := devlist.h classlist.h

当执行“make clean”时,"devlist.h classlist.h"这两个文件将被删除。Kbuild认为这些
文件跟Makefile在同样的相对目录,除非明确指定绝对路径(以“/”开头的路径)。


去删除一个目录结构使用下面的方法:


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

这将删除这个目录,包括子目录。Kbuild认为这些目录跟Makefile在同样的相对目录,除非
明确指定绝对路径(以“/”开头的路径)。


通常kbuild根据"obj-* := dir/"决定是否进入子目录,但是需要明确的是,在arch makefiles
中kbuild基础结构不满足这一点。


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

上面的命令告诉kbuild当“make clean”执行时,进入下层目录compressed/中。


在构建最终启动镜像的Makefiles中,有一个名叫archclean的可选目标用来支持清理基础结
构:


Example: 
#arch/i386/Makefile
archclean: 
$(Q)$(MAKE) $(clean)=arch/i386/boot

当“make clean”执行时,make将进入arch/i386/boot目录下,像通常一样执行clean。位于
arch/i386/boot/目录的Makefile可以通过subdir-的方式进入更深层次的目录。


注意1:arch/$(ARCH)/Makefile不适用“subdir-”,因为这个文件被包含在顶层目录中,kbuild
基础结构在这不起作用。


注意2:在执行“make clean”时,所有在core-y、libs-y、drivers-y和net-y中列出的目录
都会被访问到。


=== 6 架构相关Makefiles


在进入下层独立目录前,顶层Makefile构建环境,做先前准备。顶层makefile包含通用的部
分,而arch/$(ARCH)/Makefile包含构筑为架构服务的kbuild需要的东西。
为了实现这一点,arch/$(ARCH)/Makefile设置了很多变量,定义了一些目标。


当kbuild执行时,大体上按照下面步骤:
1) 根据内核配置生成.config文件
2) 在include/linux/version.h保存kernel版本号
3) 创建include/asm到include/asm-$(ARCH)的符号链接
4) 更新目标需要的其它先决条件:
   - 额外的先决条件在arch/$(ARCH)/Makefile指明
5) 递归进入init-* core* drivers-* net-* libs-*列出的目录,然后构建所有目标
6) 所有的object文件被链接,最终文件vmlinux被放置在obj树的根目录下。
   第一个被链接的objects在head-y中列出,head-y在arch/$(ARCH)/Makefile中被指明
7) 最后,架构相关的部分做最后请求的处理,构建最终的启动镜像。
   - 这包括建立引导记录
   - 制备initrd镜像等
   
   
--- 6.1 设置变量调整构建架构相关部分


LDFLAGS $(LD)通常选项

用于所有调用的链接器的标志。
经常指定仿真器。
PS:指LDFLAGS经常被指为选项-m,我不清楚这个参数用来做什么。

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

注意:进一步定制这个flag可以使用EXTRA_LDFLAGS和LDFLAGS_$@。参见章节7。

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

链接.ko文件时,LDFLAGS_MODULE被用来指定$(LD)选项。
默认是“-r”,for relocatable output.
PS:“-r”选项,没有看明白手册中的含义。前面提到过,似乎可以将多个.o文件合并成
一个.o。

LDFLAGS_vmlinux 链接vmlinux时$(LD)选项
当链接成最终vmlinux镜像时,LDFLAGS_vmlinux被用来指定额外的标志给链接器。
LDFLAGS_vmlinux使用LDFLAGS_$@来实现。
PS:$@表示规则中的目标文件。

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

OBJCOPYFLAGS objcopy标志

当$(call if_changed,objcopy)被用来转换一个.o文件时,OBJCOPYFLAGS中定义的标志
将会被使用。
$(call if_changed,objcopy)经常用来创建vmlinux的原始二进制文件。
PS:以ARM架构为例(其它架构不清楚是不是这样),编译器最终链接出的文件时ELF格
式,这种格式文件无法直接烧写到开发板上运行,必须通过objcopy工具转换成二进制
文件后再烧写到开发板上使用。

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

#arch/s390/boot/Makefile
$(obj)/image: vmlinux FORCE 
$(call if_changed,objcopy)
PS:FORCE用在这里表示每次构建image,不管其是否存在,都重新构建。省略的部分中
会有以FORCE为目标的规则,FORCE每次更新,image比FORCE旧,所以每次都会重新
构建。

在这个例子中,文件$(obj)/image是vmlinux的二进制版本。$(call if_changed,xxx)
的用法在后面会描述。

AFLAGS $(AS)汇编标志

默认值在顶层Makefile中定义
每种架构下按需求自己修改或追加。

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

CFLAGS $(CC)编译器flags

默认值在顶层Makefile中定义
每种架构下按需求自己修改或追加。

通常,这个变量基于配置有变化。

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

很多arch Makefiles动态探测出编译器可支持的选项:

#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_MPENTIUMII)拓展成“y”时,编译选项被选择。

CFLAGS_KERNEL 编译进内核时$(CC)选项

$(CFLAGS_KERNEL)包含额外的编译器选项去编译常驻内核的代码。

CFLAGS_KERNEL 编译成模块时$(CC)选项

$(CFLAGS_MODULE)包含额外的编译器选项去编译可加载到内核的模块代码。


--- 6.2 增加archprepare的依赖:


规则archprepare:这个规则用于在基尼如下层子目录前,列出需要被构建的先决条件。
这通常被包含汇编常量的头文件使用。

Example: 
#arch/arm/Makefile
archprepare: maketools

这个例子中,在进入下层子目录前,maketools将被处理。
章节XXX-TODO中有描述kbuild怎么支持创建offset头文件。

--- 6.3 列出要访问的下层目录


一个arch Makefile和顶层目录一起合作定义变量指明怎么构建vmlinux文件。注意对于
模块构建arch没有特定的合作部分;模块的构建机制指架构部分独立的。


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

$(head-y)列出首先被链接进vmlinux的objects。
$(libs-y)列出lib.a库位于的目录。
其余的列出built-in.o文件位于的目录栏。

$(init-y)位于$(head-y)之后。
其余的按照下面的顺序:
$(core-y), $(libs-y), $(drivers-y)和$(net-y).

顶层目录为所有一般目录定义了值,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 架构相关启动镜像


一个arch Makefile指明的目标有构建vmlinux文件,并压缩它,封装引导代码,将结果
复制到某个地方。这包含了各种安装命令。这些目标并不是标准的。

通常放置这些处理在arch/$(ARCH)/boot/目录下。

Kbuild不提供任何智能的方式去构建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)' 
endif

当执行make时没有带参数,第一个目标将被创建。在顶层目录第一个目标是all:。
一个架构将总是默认构建一个镜像。“make help”列出的目标中,默认目标用*标注。
增加一个新的依赖给all:去指定一个不是vmlinux的默认目标。

Example: 
#arch/i386/Makefile
all: bzImage

当执行“make”没有带参数,bzImage将被创建。

--- 6.5 构建非kbuild结构的目标


extra-y

extra-y指定一个在本目录下创建的额外的目标,这目标跟obj-*定义的目标不同。

在extra-y中列出所有目标是需要的,因为以下两个目的:
1) 使kbuild可以在命令行中检测文件是否有变化
  - 当$(call if_changed,xxx)被使用的时候
2) kbuild知道在"make clean"期间都删除了哪些文件

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

在这个例子中,extra-y被用来列出会被创建但不会被链接进built-in.o的object文件。



--- 6.6 构建启动镜像时有用的命令


在构建启动镜像时,Kbuild提供了一些有用的宏。

if_changed

下面是if_changed的一般用法。

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

当这个规则被执行时,将会检测是否有文件需要更新,自上次调用是否有命令行改变了。
如果可执行文件的任何选项发生变化,将强制重构。
使用if_changed的目标必须在$(targets)中列出,否则命令行检测失败,目标总是被重
构。
指定$(targets)不需要$(obj)/前缀。
if_changed可以和自定义命令一起使用,在6.7节“自定义kbuild命令”中会介绍。

注意:不要忘了写FORCE依赖。
另一个常出现的问题是空白的使用;举个例子,下面是错误的用法(注意逗号后面的空
格):
target: source(s) FORCE 
#WRONG!# $(call if_changed, ld/objcopy/gzip)

ld
链接目标。通常LDFLAGS_$@用来指定链接器选项。

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)指明所有的潜在目标,kbuild知道后将会:
1) 检测命令行是否有变化
2) 在执行“make clean”时,删除目标

": %: %.o"部分是依赖部分,这种写法使我们不用直接列出setup.o和bootsect.o文件。
PS:这是静态模式规则的语法
TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
COMMANDS
...
注意:通常会犯这样的错误,忘了指定"target :=",这样会导致目标文件不知道什么
 原因就被重建。




--- 6.7 自定义kbuild命令


当kbuild执行时,给定参数KBUILD_VERBOSE=0,自定义命令的缩写会被正常打印。
实现这些,kbuild需要设置两个变量:
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预处理得到。
kbuild知道.lds文件,包含一个规则将*lds.S转换成*lds文件。

Example: 
#arch/i386/kernel/Makefile
always := vmlinux.lds

#Makefile
export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)

指定$(always)是用来告诉kbuild构建目标vmlinux.lds。
指定$(CPPFLAGS_vmlinux.lds)是用来告诉kbuild构建目标vmlinux.lds时使用指定的选
项。

当构建*.lds目标时,kbuild使用这些变量:
CPPFLAGS : 在顶层Makefile中设置 
EXTRA_CPPFLAGS : 可以在kbuild makefile中设置
CPPFLAGS_$(@F)  : 某Target的标志。
 注意在这里使用全名。
 
*lds文件的kbuild结构在架构相关的文件中。


=== 7 Kbuild变量


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


VERSION, PATCHLEVEL, SUBLEVEL, EXTRAVERSION

这些变量定义了目前kernel版本。一些arch Makefiles经常直接使用这些变量;它们应
该用$(KERNELRELEASE)替代。

$(VERSION), $(PATCHLEVEL)和$(SUBLEVEL)定义版本号基本的三个部分,入"2", "4", 
和"0"。这三个值总是数字。

$(EXTRAVERSION)根据预补丁和额外补丁定义更细致的版本。通常用非数字的字符串表
示,如"-pre4",经常情况下是空的。

KERNELRELEASE

$(KERNELRELEASE)是一个像"2.4.0-pre4"这样的独立字符串,适合用来编制安装目录名,
或者显示版本字符串。一些arch Makefiles目录就是为了这个目的使用它。

ARCH

这个变量定义了目标架构,像"i386", "arm",或"sparc"。一些kbuild Makefiles检测
$(ARCH)这个变量去决定编译哪些恩见。

默认地,顶层目录设置$(ARCH)这个变量同host系统架构移植。交叉编译时,用户需要
在命令行上覆盖$(ARCH)这个值:

make ARCH=m68k ...


INSTALL_PATH

这个变量为arch Makefiles定义了一个安装kernel镜像和System.map文件的目录。
在架构相关部分安装目标时使用。

INSTALL_MOD_PATH, MODLIB

$(INSTALL_MOD_PATH)定义了$(MODLIB)的前缀。这个变量没有定义在Makefile
中,如果需要用户可以自己赋值使用。

$(MODLIB)定义了模块安装目录。
顶层Makfile定义$(MODLIB)为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。
如果需要,用户可以在命令行上修改这个值。

INSTALL_MOD_STRIP

PS:原文如下,我不理解用法。
If this variable is specified, will cause modules to be stripped 
after they are installed.  If INSTALL_MOD_STRIP is '1', then the
default option --strip-debug will be used.  Otherwise, 
INSTALL_MOD_STRIP will used as the option(s) to the strip command.



=== 8 Makefile语言


kernel Makefiles被设计运行在GNU Make下。Makefiles只是用了GNU Make文档中的功能,
但也做了很多GNU拓展。
GNU Make提供基本列表处理功能。Kernel Makefiles使用了一种新的列表方式,由一些“if”
状态构成。


GNU Make有两张赋值操作符,“:=”和“=”。“:=”会立即展开右边的变量,并把实际的字符串
传给左边。“=”像定义一个公式;右边保持未展开状态,直到左边值被使用。


有些情况下“=”是合适的。尽管通常“:=”是正确的选择。



=== 9 Credits
=== 10 TODO
略……
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值