• 沒有找到結果。

Kbuild Makefile

在文檔中 目 录 (頁 178-190)

第 7 章 配置编译内核

7.2 配置编译内核源码

7.2.3 Kbuild Makefile

Linux 内核源代码是通过 Makefile 组织编译的。

1.Makefile 的组织结构

Makefiles 包含 5 个部分,见表 7.7 所示。

7.7 Makefiles 的 5 个部分

Makefile 顶层目录下的 Makefile

.config 内核配置文件

arch/$(ARCH)/Makefile 对应体系结构的 Makefile

续表

tyw藏书

Makefile 顶层目录下的 Makefile scripts/Makefile.* 所有 kbuild Makefiles 的通用规则等定义

kbuild Makefiles 内核编译各级目录下的 Makefile,大约有 500 多个

顶层目录的 Makefile 读取.config 文件,根据.config 文件中的配置选项编译内核。这 个.config 文件是内核配置过程生成的。

顶层目录的 makefile 负责编译 vmlinux(常驻内存的内核映像)和 module(任何模块文 件)。它递归地遍历内核源码树中所有子目录,编译所有的目标文件。

编译访问的子目录列表依赖于内核配置。顶层目录的 Makefile 原原本本的包含了一个 arch Makefile(后面将使用这个英文名称),就是 arch/$(ARCH)/Makefile。这个 arch Makefile 给顶层目录提供了体系结构相关的信息。

每个子目录都有一个 Kbuild Makefile(内核编译过程调用),这些 Makefile 执行从上层 传递下来的命令。这些 Makefile 使用.config 文件中的信息,构建各种文件列表,由 Kbuild 编译静态链接的或者模块化的目标程序。

scripts/Makefile.*几个文件包含了 Kbuild Makfile 所有的定义和 规则等,用于编译 内核。

内核源码的大多数 Makefile 是 Kbuild Makefile,使用 Kbuild 组织结构。下面介绍一下 Kbuild Makefile 的语法。

Kbuild 大体上按照下列步骤执行编译过程。

(1)内核配置,生成.config 文件。

(2)保存内核版本信息到 include/linux/version.h。

(3)创建链接符号 include/asm,链接 include/asm-$(ARCH)源目录。

(4)升级所有依赖的前提文件,在 arch/$(ARCH)/Makefile 中指定附加依赖条件。

(5)递归地遍历各级子目录并且编译所有的目标。

init-*、core*、drivers-*、net-*、libs-*的目录变量值在 arch/$(ARCH)/Makefile 文件中有 些扩展。

(6)链接所有的目标文件,生成顶层目录的 vmlinux。链接的第一个目标文件在 head-y 列表中,是在 arch/$(ARCH)/Makefile 中定义的。

(7)最后,体系结构相关的部分作必须的后期处理,编译生成最终的引导映像。这可以 包括编译引导记录;准备 initrd 映像等类似工作。

2.Makefile 语言

内核 Makefile 是配合 GNU make 使用的。除了 GNU make 的文档中的特点,内核的 Makefile 还有一些 GNU 扩展的功能。

GNU make 支持基本的链接表处理功能。内核 Makefile 使用新颖的编译列表格式,编译 过程几乎可以不用 if 语句。

GNU make 有多种变量赋值操作符:“=”、“:=”、“?=”、“+=”。

第 1 种是“=”操作符,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义 在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后

tyw藏书

面定义的值。

可以把变量的真实值推到后面来定义。但是这种形式也有不好的地方,那就是递归定义,

这会让 make 陷入无限的变量展开过程中去,当然,make 是有能力检测这样的定义,并会报 错的。还有就是如果在变量中使用函数,那么,这种方式会让 make 运行时非常慢,更糟糕 的是,会使得 wildcard 和 shell 两个函数发生不可预知的错误。因为不会知道这两个函数会被 调用多少次。

第 2 种是“:=”操作符,这种方法,前面的变量不能使用后面的变量,只能使用前面已 定义好了的变量。如果是这样:

y := $(x) bar x := foo

那么,y 的值是“bar”,而不是“foo bar”。

第 3 种是“?=”操作符,先看示例:

FOO ?= bar

其含义是:如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”;如果 FOO 先前被 定义过,那么这条语将什么也不做。

第 4 种是“+=”操作符,将右边的变量值附加给左边的变量。例如:

FOO = string1 FOO += string2

这时,FOO 的变量值为“string1 string2”。

3.Kbuild 变量

顶层 Makefile 输出下列变量。

(1)VERSION, PATCHLEVEL, SUBLEVEL, EXTRAVERSION 定义了当前内核版本。

$(VERSION)、$(PATCHLEVEL)和$(SUBLEVEL)定义了基本的 3 个版本号,例如:2、6 和 14,都是数字,对应内核版本号。

$(EXTRAVERSION)为预先或者附加的补丁定义了更细的子版本号。通常是非数字的字 符串或者空的,例如:-mm1。

(2)KERNELRELEASE 定义了内核发布的版本,一般是单个的字符串,例如:2.6.14。

常用来作为版本显示。

(3)ARCH 定义了目标板体系结构,例如:i386、arm 或者 sparc。一些 kbuild Makefile 测试$(ARCH)来确定要编译哪一个文件。顶层 Makefile 缺省地把$(ARCH)设置成主机系统的 体系结构。对于交叉编译,需要修改定义或者在命令行重载这个值。例如:

make ARCH=arm

(4)INSTALL_PATH 为 arch Makefile 定义了安装驻留内存的内核映像和 System.map 文 件。使用这个体系结构安装目标板。

tyw藏书

(5)INSTALL_MOD_PATH 和 MODLIB。

$(INSTALL_MOD_PATH)在安装模块的时候作为$(MODLIB)的前缀。这个变量没有在 Makefile 中定义,但是可以通过命令行传递。

$(MODLIB)指定模块安装的路径。顶层 Makefile 的$(MODLIB)缺省定义如下。

$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE) 4.Kbuild Makefile 的定义

(1)目标定义

目标定义 kbuild Makefile 的核心。它们定义了要编译的文件、特殊的编译选项和要递归 地遍历的子目录。

最简单的 kbuild makefile 包含一行,例如:

obj-y += foo.o

这是告诉 kbuild,当前目录中要编译一个目标文件 foo.o,foo.o 应该从 foo.c 或者 foo.S 编译过来。

如果要把 foo.o 编译为模块,就使用变量 obj-m。因此,经常用下列方式:

obj-$(CONFIG_FOO) += foo.o

$(CONFIG_FOO)可以配置为 y(静态链接)或者 m(动态模块)。如果 CONFIG_FOO 即 不是 y,也不是 m,那么这个文件就不被编译或者链接。

(2)静态链接目标文件- obj-y

kbuild Makefile 指定了 vmlinux 的目标文件,就在$(obj-y)列表中。这些列表依赖于内核 的配置。

Kbuild 编译所有的$(obj-y)文件,再用$(LD)命令把目标文件链接成一个 built-in.o 文件。

然后 built-in.o 将被链接到顶层目录的 vmlinux 中去。

$(obj-y)中的文件顺序很重要。因为列表中允许重复,第一个实例链接到 built-in 中以后,

后面的实例将被忽略。另外,某些函数(module_init() / __initcall)会在启动过程中按照排列 顺序调用。如果改变链接顺序,也可能改变设备的初始化顺序。例如:改变 SCSI 控制器的 探测顺序,就会导致磁盘重复编号。

举例说明 obj-y。

#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)可加载模块目标文件- obj-m

$(obj-m)用来指定要编译成可加载模块的目标文件。

一个模块可以由一个或者几个源文件编译生成。对于单个源文件的情况,kbuild Makefile 可以简单地把文件添加到$(obj-m)中即可。

tyw藏书

例如:

#drivers/isdn/i4l/Makefile

obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

注意 这里的$(CONFIG_ISDN_PPP_BSDCOMP)配置为“m”。

如果内核模块由几个源文件编译生成,要指定要编译成一个模块。Kbuild 需要知道这个 模块包含哪些目标文件,那么必须设置一个$(<module_name>-objs)变量。

例如:

#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)列表中的目标文件,然 后执行“$(LD) –r”命令,把这些目标文件链接成 isdn.o。

Kbuild 可以通过-objs 和-y 后缀识别组成复合目标的目标文件。这允许 Makefile 通过 CONFIG_符号的值来确定一个目标文件是不是一个复合目标文件。

例如:

#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 就是复合目标 ext2.o 的一部分。

注意 当 把 这 些 目 标 文 件 编 译 链 接 到 内 核 中 去 的 时 候 , 上 面 的 语 法 仍 然 有 效 。 如 果 配 置 CONFIG_EXT2_FS=y,kbuild 会单独编译 ext2.o 文件,再链接到 built-in.o 文件中。

(4)库目标文件 lib-y

用 obj-列出的目标文件可以用于模块或者指定目录的 built-in.o 文件,也可以列出要包含 到一个库 lib.a 中的目标文件。用 lib-y 列出的目标文件可以组合到目录下的一个库中。在 obj-y 中列出并且在 lib-y 中列出的目标文件不会包含到这个库中,因为它们是内核可以访问的。为 了一致性,在 lib-m 中列出的目标文件会包含到 lib.a 中。

注意 同一个 kbuild makefile 可以把文件列到 built-in 表中,同时还是一个库的列表的一部分。因此,

同一个目录可以包含一个 built-in.o 和一个 lib.a 文件。

例如:

#arch/i386/lib/Makefile

lib-y := checksum.o delay.o

tyw藏书

这会基于 checksum.o 和 delay.o 创建一个 lib.a 文件。为了让 kbuild 知道有一个 lib.a 要编 译,这个目录应该添加到 libs-y 列表中。

lib-y 一般仅限于 lib/和 arch/*/lib/目录。

(5)遍历子目录

一个 Makefile 只负责在自己的目录下编译目标文件。各子目录下的文件应该由各自的 Makefile 来管理。编译系统会自动在子目录中递归地调用 make。

这项工作也要用到 obj-y 和 obj-m。比如 ext2 在一个单独的目录中,在 fs 目录下的 Makefile 使用下面的配置方法。

#fs/Makefile

obj-$(CONFIG_EXT2_FS) += ext2/

如果 CONFIG_EXT2_FS 设成“y”或者“m”,对应的 obj-变量就会设置,并且 Kbuild 就会下到 ext2 目录中编译。Kbuild 只使用这个信息决定是否需要访问这个目录,编译的工作 是子目录中的 Makefile 负责。

对于 CONFIG_选项既不是“y”也不是“m”的目录,使用 CONFIG_变量可以让 Kbuild 忽略掉。这是一个很好的办法。

(6)编译标志

编译标志包括:EXTRA_CFLAGS、EXTRA_AFLAGS、EXTRA_LDFLAGS、EXTRA_ARFL- AGS。

所有的 EXTRA_变量只适用于当前的 Kbuild makefile。EXTRA_变量适用 Kbuild makefile 中执行的所有的命令。

$(EXTRA_CFLAGS)通过$(CC)指定编译 C 文件的选项。

例如:

# drivers/sound/emu10k1/Makefile EXTRA_CFLAGS += -I$(obj)

ifdef DEBUG

EXTRA_CFLAGS += -DEMU10K1_DEBUG endif

因为顶层目录 Makefile 的变量$(CFLAGS)用于整个源码树的编译,所以这种变量定义是 必须的。

在编译汇编语言源码的时候,$(EXTRA_AFLAGS)是与每个目录的选项类似的字符串。

例如:

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

$(EXTRA_LDFLAGS)和$(EXTRA_ARFLAGS)分别是与每个目录$(LD)和$(AR)的选项 类似的字符串。

例如:

tyw藏书

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

CFLAGS_$@, AFLAGS_$@

CFLAGS_$@和 AFLAGS_$@仅适用于当前 kbuild makefile 的命令。

$(CFLAGS_$@)为$(CC)指定每个文件的选项。$@代表指定的文件名。

例如:

# 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_$@)对于汇编语言编译有类似的特点。

例如:

# arch/arm/kernel/Makefile

AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional

(7)依赖跟踪

Kbuild 按照下列步骤跟踪依赖关系。

• 所有依赖的前提文件(*.c 和*.h 文件)

• 在依赖的前提文件中用到的 CONFIG_选项

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

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

(8)特殊的规则

当 Kbuild 结构不提供必须的支持的时候,要使用特殊规则。一个典型的例子是在编译过 程中生成头文件。另外一个例子是体系结构相关的 Makefile 需要特殊的规则准备映像等。

特殊的规则可以按照普通的规则来写。Kbuild 不在 Makefile 所在的目录执行,因此所有 特殊的规则应该为依赖的文件和目标文件提供相对路径。

定义特殊规则的时候,常用到两个变量:$(src)和$(obj)。

$(src)是指向 Makefile 所在的目录的相对路径。当引用位于源码树的文件的时候,总是使 用$(src)。

$(obj)是指向目标文件保存的相对路径。当引用生成文件的时候,总是使用$(obj)。

例如:

#drivers/scsi/Makefile

$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl

$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl

在文檔中 目 录 (頁 178-190)