`
isiqi
  • 浏览: 16000045 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

Linux 3.0内核Makefile分析

 
阅读更多

[ 摘要 ] 由于 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 文件的目标对象,将它们压缩和封装成引导代码,并复制到合适的位置。这包括各种安装命令。在LinuxMakefile 无法为所有的体系结构提供标准化的方法,因此常需要具体硬件体系结构下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) 定制命令

当正常执行带编译命令时命令的简短信息会被显示(要想显示详细的命令,请在命令行中加入V1 )。要让定制命令具有这种功能需要设置两个变量:
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-ylibs-ydrivers-ynet-y 中的目录将被"make clean" 命令清除。

4 小结

随着 Linux 的飞速发展,越来越多的开发人员将关注的焦点集中到 Linux 的研究和开发上。如果想对 Linux 内核进行研究和开发,就必须首先熟悉 Linux 内核 Makefile 的组织和编译过程。目前 Linux 最新的稳定内核版本为 2.6.17 ,但是当今绝大部分对于 Linux Makefile 的介绍都是基于 2.4 内核的 ,可以说关于 2.6 内核 Makefile 相关的文章凤毛麟角,我特意抽时间完成了这篇分析文章,让读者迅速熟悉 Linux 最新 Makefile 体系,从而加深对内核的理解,同时也希望能对 Linux 在公司的推广起到一定的推动作用。

5 参考资料

mbtrend linux频道:http://www.mbtrend.com

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics