[ 摘要 ] 由于 Linux 的独特优势,使越来越多的企业和科研机构把目光转向 Linux 的开发和研究上。目前 Linux 最新的稳定内核版本为 2.6.17 ,但是当今绝大部分对于 Linux Makefile 的介绍文章都是基于 2.4 内核的 ,可以说关于 2.6 内核 Makefile 相关的文章凤毛麟角,笔者抽时间完成了这篇分析文章,让读者迅速熟悉 Linux 最新 Makefile 体系,从而加深对内核的理解,同时也希望能对 Linux 在公司的推广起到一定的推动作用,算是抛砖引玉吧!
1 Makefile 组织层次
Linux 的 Make 体系由如下几部分组成:
Ø 顶层 Makefile
顶层 Makefile 通过读取配置文件,递归编译内核代码树的相关目录,从而产生两个重要的目标文件: vmlinux 和模块。
Ø 内核相关 Makefile
位于 arch/$(ARCH) 目录下,为顶层 Makefile 提供与具体硬件体协结构相关的信息。
Ø 公共编译规则定义文件。
包括 Makefile.build 、 Makefile.clean 、 Makefile.lib 、 Makefile.host 等文件组成。这些文件位于 scripts 目录中,定义了编译需要的公共的规则和定义。
Ø 内核配置文件 .config
通过调用 make menuconfig 或者 make xconfig 命令,用户可以选择需要的配置来生成期望的目标文件。
Ø 其他 Makefile
主要为整个 Makefile 体系提供各自模块的目标文件定义,上层 Makefile 根据它所定义的目标来完成各自模块的编译。
2 Makefile 的使用
在编译内核之前,用户必须首先完成必要的配置。 Linux 内核提供了数不胜数的功能,支持众多的硬件体系结构,这就需要用户对将要生成的内核进行裁减。内核提供了多种不同的工具来简化内核的配置,最简单的一种是字符界面下命令行工具:
make config
这个工具会依次遍历内核所有的配置项,要求用户进行逐项的选择配置。这个工具会耗费用户太多时间,除非万不得以(你的编译主机不支持其他配置工具)一般不建议使用。
用户还可以使用利用 ncurse 库编制的图形界面工具,这就是大名鼎鼎的:
make menuconfig
相信以前对 2.4 内核比较熟悉的用户一定不会陌生。当然在 2.6 内核中提供了更漂亮和方便的基于 X11 的图形配置工具:
make xconfig
当用户使用这个工具对 Linux 内核进行配置时,界面下方会出现这个配置项相关的帮助信息和简单描述,当你对内核配置选项不太熟悉时,建议你使用这个工具来进行内核配置。
当用户完成配置后,配置工具会自动生成 .config 文件,它被保存在内核代码树的根目录下。用户可以很容易找到它,当然用户也可以直接对这个文件进行简单的修改。但是当你修改过配置文件之后,你必须通过下面的命令来验证和更新配置:
make oldconfig
跟 2.4 版本的不同之处在于,用户不需要显示的调用 make dep 命令来生成依赖文件,内核会自动维护代码间的依赖关系。
当一切工作完成以后,用户只需要简单键入 make ,剩下所有的工作 makefile 就会自动替你完成了。
3 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 映象和相关处理
4 Makefile 关键规则和定义描述
1) 目标定义
目标定义是 Makefile 文件的核心部分,目标定义通知 Makefile 需要生成哪些目标文件、如何根据特殊的编译选项链接目标文件,同时控制哪些子目录要递归进入进行编译。
这个例子 Makefile 文件位于 /fs/ext2 目录 :
#
# Makefile for the linux ext2-filesystem routines.
#
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 中,后来出现的同名文件将会被忽略。文件顺序直接决定了他们被调用的顺序,这一点读者需要特别注意。
读者可能会在某些 Makefile 中发现 lib-y 定义,所有包含在 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_ 选项相关的文件
Ø 编译目标文件所使用到的命令行
位于 init 目录下的 main.c 文件的依赖文件 .main.o.cmd 内容如下,读者可以结合起来理解上述文件依赖关系的三个组成部分 :
cmd_init/main.o := gcc -m32 -Wp,-MD,init/.main.o.d -nostdinc -isystem /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include -D__KERNEL__ -Iinclude -Iinclude2 -I/home/linux/linux-2.6.17.11/include -include include/linux/autoconf.h -I/home/linux/linux-2.6.17.11/init -Iinit -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Os -fomit-frame-pointer -pipe -msoft-float -mpreferred-stack-boundary=2 -march=i686 -mcpu=pentium4 -mregparm=3 -ffreestanding -I/home/linux/linux-2.6.17.11/include/asm-i386/mach-default -Iinclude/asm-i386/mach-default -D"KBUILD_STR(s)=/#s" -D"KBUILD_BASENAME=KBUILD_STR(main)" -D"KBUILD_MODNAME=KBUILD_STR(main)" -c -o init/.tmp_main.o /home/linux/linux-2.6.17.11/init/main.c
deps_init/main.o := /
/home/linux/linux-2.6.17.11/init/main.c /
$(wildcard include/config/x86/local/apic.h) /
$(wildcard include/config/acpi.h) /
# 由于篇幅的关系,此处略去一些定义
……………………………………..
include2/asm/mpspec_def.h /
/home/linux/linux-2.6.17.11/include/asm-i386/mach-default/mach_mpspec.h /
include2/asm/io_apic.h /
include2/asm/apic.h /
init/main.o: $(deps_init/main.o)
$(deps_init/main.o):
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) 变量。这一部分的内容相对于其他内核文件的编译要简单的多,感兴趣的读者可以参考scripts/Makefile.build 中的相关内容。
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" 命令清除。
随着 Linux 的飞速发展,越来越多的开发人员将关注的焦点集中到 Linux 的研究和开发上。如果想对 Linux 内核进行研究和开发,就必须首先熟悉 Linux 内核 Makefile 的组织和编译过程。目前 Linux 最新的稳定内核版本为 2.6.17 ,但是当今绝大部分对于 Linux Makefile 的介绍都是基于 2.4 内核的 ,可以说关于 2.6 内核 Makefile 相关的文章凤毛麟角,我特意抽时间完成了这篇分析文章,让读者迅速熟悉 Linux 最新 Makefile 体系,从而加深对内核的理解,同时也希望能对 Linux 在公司的推广起到一定的推动作用。