Linux makefile分析

阅读数:10 评论数:0

跳转到新版页面

分类

Linux

正文

    Linux内核源码中含有很多个Makefile文件,这些Makefile文件又要包含其他一些文件(比如配置信息、通用规则 等)。这些文件构成了Linux的Makefile体系。

名称 描述
顶层Makefile 它是所有Makefile文件的核心,从总体上控制着内核的编译、连接
.config 配置文件,在配置内核时生成。所有Makefile(包括顶层目录及各级子目录)都是根据.config来决定使用哪些文件
arc/${ARCH}/Makefile 对应体系结构的Makefile,它用来决定哪些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映像
scripts/Makefile.* Makefile共用的通用规则、脚本等
kbuild Makefiles 各级子目录下的Makefile,它们相对简单,被上一层Makefile调用来编译当前目录下的文件

    内核文档Documentation/kbuild/makefiles.txt对内核中Makefile的作用、用法讲解得非常透彻。

1、决定编译哪些文件

    Linux内核的编译过程从顶层Makefile开始,然后递归地进入各级子目录调用它们的Makefile,分为3个步骤:

(1)顶层Makefile决定内核根目录哪些子目录将被编译进内核。

(2)arch/${ARCH}/Makefile决定arch/${ARCH}目录下哪些文件、哪些目录被编译进内核。

(3)各级子目录的Makefile决定所在目录下哪些文件将被 编进内核,哪些文件将被编成模块(即驱动程序 ),进入哪些子目录继续调用它们的Makefile。

    先来看步骤(1),在顶层Makefile中可以看到如下内容:

init-y		:= init/
drivers-y	:= drivers/ sound/ firmware/
net-y		:= net/
libs-y		:= lib/
core-y		:= usr/
virt-y		:= virt/
...
core-y		+= kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/

    除了include目录,还有一个arch目录没有出现在内核中,它在arc/${ARCH}/Makefile中被包含进内核,在顶层Makefile中直接包含了这个Makefile,如下所示:

SRCARCH 	:= $(ARCH)
...
include arch/$(SRCARCH)/Makefile

    对于ARCH变量,可以在执行make命令时传入,比如make ARCH=arm ...。另外,对于非x86平台,还需要指定交叉编译工具,这也可以在执行make命令进传入,比如make CROSS_CoMPILE=arm-linux-...。为了方便,常在顶层Makefile中进行如下修改

#修改前
ARCH    ?=$(SUBARCH)
CROSS_COMPILE ?=$(CONFIG_CROSS_COMPILE:"%"=%)
#修改后
ARCH  ?= arm
CROSS_COMPILE ?=arm-linux-

对于步骤(2)的arch/$(ARCH)/Makefile,以ARM体系为例,在arch/arm/Makefile中可以看到以下内容:

head-y		:= arch/arm/kernel/head$(MMUEXT).o
...
core-$(CONFIG_FPE_NWFPE)	+= arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE)	+= $(FASTFPE_OBJ)
core-$(CONFIG_VFP)		+= arch/arm/vfp/
core-$(CONFIG_XEN)		+= arch/arm/xen/
core-$(CONFIG_KVM_ARM_HOST) 	+= arch/arm/kvm/
core-$(CONFIG_VDSO)		+= arch/arm/vdso/
...
core-y				+= arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y				+= arch/arm/probes/
core-y				+= arch/arm/net/
core-y				+= arch/arm/crypto/
core-y				+= arch/arm/firmware/
core-y				+= $(machdirs) $(platdirs)
...
libs-y				:= arch/arm/lib/ $(libs-y)

    MMUEXT在arch/arm/Makefile前面定义,对于没有MMU的处理器,MMUEXT的值为-nommu,使用文件 head-nommu.S,对于有MMU的处理器,MMUEXT的值为空,使用文件head.S。

    core-y、libs-y的内容,这些都是体系结构相关的目录。

    编译内核时,将依次进入init-y、core-y,drivers-y和net-y所列出的目录中执行它们的Makefile,每个子目录会生成一个built-in.o(libs-y所列目录下,有可能生成lib.a文件),最后,head-y所表示的文件将和这些build-in.o、lib.a一起被连接成内核映像文件vmlinux。

    最后,看一下步骤(3)是怎么进行的。

    在配置内核时,生成配置文件.config,内核顶层Makefile使用如下语句间接包含.config文件,以后根据.config中定义的各个变量决定编译哪些文件。之所以说是间接包含,是因为包含的include/config/auto.conf文件,而它只是将.config文件中的注释去掉,并根据顶层的Makefile中定义的变量增加了一些变量而已。

# Read in config
-include include/config/auto.conf

    在include/config/auto.conf文件中,变量的值主要有两类:“y”、“m”。各级子目录的Makefiel使用这些变量来决定哪些文件被编译进内核,哪些文件被编译成模块,要进入哪些下级子目录继续编译。

(1)obj-y用来定义哪些文件被编进内核

    obj-y中定义的.o文件由当前目录下的.c或.S文件编译生成,它们连同下级子目录的built-in.o文件一起被组合成(使用$(LD) -r)当前目录下的built-in.o文件。这个built-in.o文件将被它的上一层Makefile使用。

    obj-y中各个.o文件的顺序是有意义的,因为内核中使用module_init()或__initcall定义的函数将按照它们的连接顺序被调用。

例:

obj-$(CONFIG_ISDN) += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

   当上面的CONFIG_ISDN、CONFIG_ISDN_PPP_BSDCOMP在.config中被定义为y时isdn.c或isdn.S、isdn_bsdcomp.c或isdn_bsdcomp.S被编译成isdn.o、isdn_bsdcomp.o。这两个.o文件被组合进built-in.o文件中,最后连接进入内核。假如isdn.o isdn_bsdcomp.o中分别用module_init(A) module_init(B)定义了函数A、B,则内核启动时A先被调用、然后是B。

(2)obj-m用来定义哪些文件被编译成可加载的模块

    obj-m中定义的.o文件由当前目录下的.c或.S文件编译生成,它们不会被编进build-in.o中,而是被编成可加载模块。

    一个模块可以由一个或几个.o文件组成。对于只有一个源文件的模块,在obj-m中直接增加它的.o文件即可。对于有多个源文件的模块,除在obj-m中增加一个.o文件外,还要定义一个<module_name>-objs变量来告诉Makefil这个.o文件由哪些文件组成。

例1:

#当CONFIG_ISDN_PPP_BSDCOMP在.config文件中被定义为m时,isdn_bsdcomp.c或isdn_bsdcomp.S
#将被编译成isdn_bsdcomp.o文件
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

例2

#当下面的CONFIG_ISDN在.config文件中被定义为m时,将会生成一个isdn.o文件,
#它由isdn-objs中定义的isdn_net_lib.o isdn_v110.o isdn_common.o等
#3个文件组合
obj-$(CONFIG_ISDN) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

(3)lib-y用来定义哪些文件被编译成库文件

    lib-y中定义的.o文件由当前目录下的.c或.S文件编译生成,它们被打包成当前目录下的一个库文件:lib.a。

    同时出现在obj-y、lib-y中的.o文件,不会被包含进lib.a中。

    要把这个lib.a编进内核中,需要在顶层Makefile中libs-y变量中列出当前目录。要编进库文件的内核代码一般都在这两个目录下:lib/ 、arch/$(ARCH)/lib

(4)obj-y、obj-m还可以用来指定要进入的下一层子目录

    Linux中一个Makefile文件只负责生成当前目录下的目标文件,子目录下的目标文件由子目录的Makefile生成。Linux的编译系统会自动进入这些子目录调用它们的Makefile,只是在这之前指定这些子目录。

    这要用到obj-y、obj-m,只要在其中增加这些子目录名即可。

例子:

#fs/Makefile中有如下一行
obj-$(CONFIG_JFFS2_FS)		+= jffs2/

  2、怎么编译这些文件

    即编译选项、连接选项是什么。这些选项分为3类:全局的,适用于整个内核代码;局部的,仅适用于某个Makefile中的所有文件;个体的,仅适用于某个文件。

    全局选项在顶层Makefile和arch/$(ARCH)/Makefile中定义,这些选项的名称为CFLAG、AFLAG、LDFLAG、ARFLAG,它们分别是编译C文件的选项、编译汇编文件的选项、连接文件的选项、制作库文件的选项。

    需要使用局部选项时,它们在各个子目录中定义,名称为:EXTRA_CFLAGS、EXTRA_AFLAGS、EXTRA_LDFLAGS、EXTRA_ARFLAGS,它们的用途与前述选项相同,只是作用范围比较小,它们针对当前Makefile中的所有文件。

    另外,如果想针对某个文件定义它的编译选项,可以使用CFLAG_$@, AFLAGS_$@,前者用于编译某个C文件,后者用编译某个汇编文件。$@表示某个文件名,比如

CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF

    需要注意的是,这3类选项是一起使用的。

3、怎么连接这些文件,它们的顺序如何

      前面分析有哪些文件要编译进内核,顶层的Makefile和arch/$(ARCH)/Makefile定义几类目录(或文件):head-y, init-y, drivers-y, net-y, libs-y, core-y。除head-y外,其余的都是目录名。这些目录的名面直接加上了built-in.a或lib.a,表示要链接进内核的文件

# 以init-y为例,其实就是变成了init-y/built-in.a
init-y		:= $(patsubst %/, %/built-in.a, $(init-y))
core-y		:= $(patsubst %/, %/built-in.a, $(core-y))
drivers-y	:= $(patsubst %/, %/built-in.a, $(drivers-y))
net-y		:= $(patsubst %/, %/built-in.a, $(net-y))
libs-y1		:= $(patsubst %/, %/lib.a, $(libs-y))
libs-y2		:= $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))
virt-y		:= $(patsubst %/, %/built-in.a, $(virt-y))

顶层Makefile再往下看

export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

对于ARM体系,连接脚本就是arch/$(ARM)/kernel/vmlinux.lds,它由arch/arm/kernel/vmlinux.lds.S文件生成,规则在scripts/Makefile.build中

$(obj)/%.lds: $(src)/%.lds.S FORCE
	$(call if_changed_dep,cpp_lds_S)