解读Makefile的内容介绍

解读Makefile的内容介绍

Makefile解读
http://www.chinaunix.net 作者:雪中独行发表于:2003-02-09 12:27:09

原文出自:http://www.linuxforum.net
作者:jkl

==========================================
Makefile初探
==========================================
Linux的内核配置文件有两个,一个是隐含的.config文件,嵌入到主Makefile中;另一个是include/linux/autoconf.h,嵌入到各个c源文件中,它们由makeconfig、makemenuconfig、makexconfig这些过程创建。几乎所有的源文件都会通过linux/config.h而嵌入autoconf.h,如果按照通常方法建立文件依赖关系(.depend),只要更新过autoconf.h,就会造成所有源代码的重新编绎。

为了优化make过程,减少不必要的重新编绎,Linux开发了专用的mkdep工具,用它来取代gcc来生成.depend文件。mkdep在处理源文件时,忽略linux/config.h这样的头文件,识别源文件宏指令中具有"CONFIG_"特征的行。例如,如果有"#ifdefCONFIG_SMP"这样的行,它就会在.depend文件中输出$(wildcard/usr/src/linux/include/config/smp.h)。

include/config/下的文件是另一个工具split-include从autoconf.h中生成,它利用autoconf.h中的CONFIG_标记,生成与mkdep相对应的文件。例如,如果autoconf.h中有"#undefCONFIG_SMP"这一行,它就生成include/config/smp.h文件,内容为"#undefCONFIG_SMP"。这些文件名只在.depend文件中出现,内核源文件是不会嵌入它们的。每配置一次内核,运行split-include一次。split-include会检查旧的子文件的内容,确定是不是要更新它们。这样,不管autoconf.h修改日期如何,只要其配置不变,make就不会重新编绎内核。

如果系统的编绎选项发生了变化,Linux也能进行增量编绎。为了做到这一点,make每编绎一个源文件时生成一个flags文件。例如编绎sched.c时,会在相同的目录下生成隐含的.sched.o.flags文件。它是Makefile的一个片断,当make进入某个子目录编绎时,会搜索其中的flags文件,将它们嵌入到Makefile中。这些flags代码测试当前的编绎选项与原来的是不是相同,如果相同,就将自已对应的目标文件加入FILES_FLAGS_UP_TO_DATE列表,然后,系统从编绎对象表中删除它们,得到FILES_FLAGS_CHANGED列表,最后,将它们设为目标进行更新。

下一步准备逐步深入的剖析Makefile代码。

==========================================
Makefile解读之二:sub-make
==========================================
Linux各级内核源代码的子目录下都有Makefile,大多数Makefile要嵌入主目录下的Rule.make,Rule.make将识别各个Makefile中所定义的一些变量。变量obj-y表示需要编绎到内核中的目标文件名集合,定义O_TARGET表示将obj-y连接为一个O_TARGET名称的目标文件,定义L_TARGET表示将obj-y合并为一个L_TARGET名称的库文件。同样obj-m表示需要编绎成模块的目标文件名集合。如果还需进行子目录make,则需要定义subdir-y和subdir-m。在Makefile中,用"obj-$(CONFIG_BINFMT_ELF)+=binfmt_elf.o"和"subdir-$(CONFIG_EXT2_FS)+=ext2"这种形式自动为obj-y、obj-m、subdir-y、subdir-m添加文件名。有时,情况没有这么单纯,还需要使用条件语句个别对待。Makefile中还有其它一些变量,如mod-subdirs定义了subdir-m以外的所有模块子目录。

Rules.make是如何使make进入子目录的呢?先来看subdir-y是如何处理的,在Rules.make中,先对subdir-y中的每一个文件名加上前缀"_subdir_"再进行排序生成subdir-list集合,再以它作为目标集,对其中每一个目标产生一个子make,同时将目标名的前缀去掉得到子目录名,作为子make的起始目录参数。subdir-m与subdir-y类似,但情况稍微复杂一些。由于subdir-y中可能有模块定义,因此利用mod-subdirs变量将subdir-y中模块目录提取出来,再与subdir-m合成一个大的MOD_SUB_DIRS集合。subdir-m的目标所用的前缀是"_modsubdir_"。

一点说明,子目录中的Makefile与Rules.make都没有嵌入.config文件,它是通过主Makefile向下传递MAKEFILES变量完成的。MAKEFILES是make自已识别的一个变量,在执行新的Makefile之前,make会首先加载MAKEFILES所指的文件。在主Makefile中它即指向.config。


==========================================
Makefile解读之三:模块的版本化处理
==========================================
模块的版本化是内核与模块接口之间进行严格类型匹配的一种方法。当内核配置了CONFIG_MODVERSIONS之后,makedep操作会在include/linux/modules/目录下为各级Makefile中export-objs变量所对应的源文件生成扩展名为.ver的文件。

例如对于kernel/ksyms.c,make用以下命令生成对应的ksyms.ver:

gcc-E-D__KERNEL__-D__GENKSYMS__ksyms.c|/sbin/genksyms-k2.4.1>ksyms.ver

-D__GENKSYMS__的作用是使ksyms.c中的EXPORT_SYMBOL宏不进行扩展。genksyms命令识别EXPORT_SYMBOL()中的函数名和对应的原型,再根据其原型计算出该函数的版本号。

例如ksyms.c中有一行:
EXPORT_SYMBOL(kmalloc);
kmalloc原型是:
void*kmalloc(size_t,int);
genksyms程序对应的输出为:
#define__ver_kmalloc93d4cfe6
#definekmalloc_set_ver(kmalloc)
在内核符号表和模块中,kmalloc将变成kmalloc_R93d4cfe6。

在生成完所有的.ver文件后,make将重建include/linux/modversions.h文件,它包含一系列#include指令行嵌入各个.ver文件。在编绎内核本身export-objs中的文件时,make会增加一个"-DEXPORT_SYMTAB"编绎标志,它使源文件嵌入modversions.h文件,将EXPORT_SYMBOL宏展开中的函数名字符串进行版本名扩展;同时,它也定义_set_ver()宏为一空操作,使代码中的函数名不受其影响。
在编绎模块时,make会增加"-include=linux/modversion.h-DMODVERSIONS"编绎标志,使模块中代码的函数名得到相应版本扩展。

由于生成.ver文件比较费时,make还为每个.ver创建了一个后缀为.stamp时戳文件。在makedep时,如果其.stamp文件比源文件旧才重新生成.ver文件,否则只是更新.stamp文件时戳。另外,在生成.ver和modversions.h文件时,make都会比较新文件和旧文件的内容,保持它们修改时间为最旧。



==========================================
Makefile解读之四:Rules.make的注释
==========================================
[code:1:974578564b]
#
#ThisfilecontainsruleswhicharesharedbetweenmultipleMakefiles.
#

#
#Falsetargets.
#
#
.PHONY:dummy

#
#Specialvariableswhichshouldnotbeexported
#
#取消这些变量通过环境向make子进程传递。
unexportEXTRA_AFLAGS #as的开关
unexportEXTRA_CFLAGS #cc的开关
unexportEXTRA_LDFLAGS #ld的开关
unexportEXTRA_ARFLAGS #ar的开关
unexportSUBDIRS #
unexportSUB_DIRS #编绎内核需进入的子目录,等于subdir-y
unexportALL_SUB_DIRS #所有的子目录
unexportMOD_SUB_DIRS #编绎模块需进入的子目录
unexportO_TARGET #ld合并的输出对象
unexportALL_MOBJS #所有的模块名

unexportobj-y #编绎成内核的文件集
unexportobj-m #编绎成模块的文件集
unexportobj-n #
unexportobj- #
unexportexport-objs #需进行版本处理的文件集
unexportsubdir-y #编绎内核所需进入的子目录
unexportsubdir-m #编绎模块所需进入的子目录
unexportsubdir-n
unexportsubdir-

#
#Getthingsstarted.
#
first_rule:sub_dirs
$(MAKE)all_targets

#在内核编绎子目录中过滤出可以作为模块的子目录。
both-m:=$(filter$(mod-subdirs),$(subdir-y))
SUB_DIRS :=$(subdir-y)
#求出总模块子目录
MOD_SUB_DIRS :=$(sort$(subdir-m)$(both-m))
#求出总子目录
ALL_SUB_DIRS :=$(sort$(subdir-y)$(subdir-m)$(subdir-n)$(subdir-))


#
#Commonrules
#
#将c文件编绎成汇编文件的规则,$@为目标对象。
%.s:%.c
$(CC)$(CFLAGS)$(EXTRA_CFLAGS)$(CFLAGS_$@)-S$<-o$@
#将c文件生成预处理文件的规则。
%.i:%.c
$(CPP)$(CFLAGS)$(EXTRA_CFLAGS)$(CFLAGS_$@)$<>$@
#将c文件编绎成目标文件的规则,$<为第一个所依赖的对象;
#
在目标文件的目录下生成flags文件,strip删除多余的空格,subst将逗号替换成冒号

%.o:%.c
$(CC)$(CFLAGS)$(EXTRA_CFLAGS)$(CFLAGS_$@)-c-o$@$<
@(/
echo'ifeq($(strip$(subst$(comma),:,$(CFLAGS)$(EXTRA_CFLAGS)
$(CFLAGS_$@))),$$(strip$$(subst$$(comma),:,$$(CFLAGS)$$(EXTRA_CFLAGS)
$$(CFLAGS_$@))))';/
echo'FILES_FLAGS_UP_TO_DATE+=$@';/
echo'endif'/
)>$(dir$@)/.$(notdir$@).flags
#汇编文件生成目标文件的规则。
%.o:%.s
$(AS)$(AFLAGS)$(EXTRA_CFLAGS)-o$@$<

#Oldmakefilesdefinetheirownrulesforcompiling.Sfiles,
#butthesestandardrulesareavailableforanyMakefilethat
#wantstousethem.Ourplanistoincrementallyconvertall
#theMakefilestothesestandardrules.--rmk,mec

ifdefUSE_STANDARD_AS_RULE
#汇编文件生成预处理文件的标准规则。
%.s:%.S
$(CPP)$(AFLAGS)$(EXTRA_AFLAGS)$(AFLAGS_$@)$<>$@
#汇编文件生成目标文件的标准规则。
%.o:%.S
$(CC)$(AFLAGS)$(EXTRA_AFLAGS)$(AFLAGS_$@)-c-o$@$<

endif
#c文件生成调试列表文件的规则,$*扩展为目标的主文件名。
%.lst:%.c
$(CC)$(CFLAGS)$(EXTRA_CFLAGS)$(CFLAGS_$@)-g-c-o$*.o$<
$(TOPDIR)/scripts/makelst$*$(TOPDIR)$(OBJDUMP)
#
#
#
all_targets:$(O_TARGET)$(L_TARGET)

#
#Ruletocompileasetof.ofilesintoone.ofile
#
ifdefO_TARGET
$(O_TARGET):$(obj-y)
rm-f$@
#$^扩展为全部依赖对象,如果obj-y为空就生成一个同名空的库文件。
ifneq"$(strip$(obj-y))"""
$(LD)$(EXTRA_LDFLAGS)-r-o$@$(filter$(obj-y),$^)
else
$(AR)rcs$@
endif
#生成flags文件的shell语句。
@(/
echo'ifeq($(strip$(subst$(comma),:,$(EXTRA_LDFLAGS)
$(obj-y))),$$(strip$$(subst$$(comma),:,$$(EXTRA_LDFLAGS)$$(obj-y))))';
/
echo'FILES_FLAGS_UP_TO_DATE+=$@';/
echo'endif'/
)>$(dir$@)/.$(notdir$@).flags
endif#O_TARGET

#
#Ruletocompileasetof.ofilesintoone.afile
#
#将obj-y组合成库L_TARGET的方法。
ifdefL_TARGET
$(L_TARGET):$(obj-y)
rm-f$@
$(AR)$(EXTRA_ARFLAGS)rcs$@$(obj-y)
@(/
echo'ifeq($(strip$(subst$(comma),:,$(EXTRA_ARFLAGS)
$(obj-y))),$$(strip$$(subst$$(comma),:,$$(EXTRA_ARFLAGS)$$(obj-y))))';
/
echo'FILES_FLAGS_UP_TO_DATE+=$@';/
echo'endif'/
)>$(dir$@)/.$(notdir$@).flags
endif


#
#Thismakedependenciesquickly
#
#wildcard为查找目录中的文件名的宏。
fastdep:dummy
$(TOPDIR)/scripts/mkdep$(wildcard*.[chS]local.h.master)>.depend
ifdefALL_SUB_DIRS
#
将ALL_SUB_DIRS中的目录名加上前缀_sfdep_作为目标运行子make,并将ALL_SUB_DIRS
通过
#变量_FASTDEP_ALL_SUB_DIRS传递给子make。
$(MAKE)$(patsubst%,_sfdep_%,$(ALL_SUB_DIRS))
_FASTDEP_ALL_SUB_DIRS="$(ALL_SUB_DIRS)"
endif

ifdef_FASTDEP_ALL_SUB_DIRS
#
与上一段相对应,定义子目录目标,并将目标名还原为目录名,进入该子目录make。
$(patsubst%,_sfdep_%,$(_FASTDEP_ALL_SUB_DIRS)):
$(MAKE)-C$(patsubst_sfdep_%,%,$@)fastdep
endif


#
#Aruletomakesubdirectories
#
#下面2段完成内核编绎子目录中的make。
subdir-list=$(sort$(patsubst%,_subdir_%,$(SUB_DIRS)))
sub_dirs:dummy$(subdir-list)

ifdefSUB_DIRS
$(subdir-list):dummy
$(MAKE)-C$(patsubst_subdir_%,%,$@)
endif

#
#Aruletomakemodules
#
#求出有效的模块文件表。
ALL_MOBJS=$(filter-out$(obj-y),$(obj-m))
ifneq"$(strip$(ALL_MOBJS))"""
#取主目录TOPDIR到当前目录的路径。
PDWN=$(shell$(CONFIG_SHELL)$(TOPDIR)/scripts/pathdown.sh)
endif

unexportMOD_DIRS
MOD_DIRS:=$(MOD_SUB_DIRS)$(MOD_IN_SUB_DIRS)
#编绎模块时,进入模块子目录的方法。
ifneq"$(strip$(MOD_DIRS))"""
.PHONY:$(patsubst%,_modsubdir_%,$(MOD_DIRS))
$(patsubst%,_modsubdir_%,$(MOD_DIRS)):dummy
$(MAKE)-C$(patsubst_modsubdir_%,%,$@)modules
#安装模块时,进入模块子目录的方法。
.PHONY:$(patsubst%,_modinst_%,$(MOD_DIRS))
$(patsubst%,_modinst_%,$(MOD_DIRS)):dummy
$(MAKE)-C$(patsubst_modinst_%,%,$@)modules_install
endif

#makemodules的入口。
.PHONY:modules
modules:$(ALL_MOBJS)dummy/
$(patsubst%,_modsubdir_%,$(MOD_DIRS))

.PHONY:_modinst__
#拷贝模块的过程。
_modinst__:dummy
ifneq"$(strip$(ALL_MOBJS))"""
mkdir-p$(MODLIB)/kernel/$(PDWN)
cp$(ALL_MOBJS)$(MODLIB)/kernel/$(PDWN)
endif

#makemodules_install的入口,进入子目录安装。
.PHONY:modules_install
modules_install:_modinst__/
$(patsubst%,_modinst_%,$(MOD_DIRS))

#
#Aruletodonothing
#
dummy:

#
#Thisisusefulfortesting
#
script:
$(SCRIPT)

#
#Thissetsversionsuffixesonexportedsymbols
#Separatetheobjectinto"normal"objectsand"exporting"objects
#Exportingobjectsare:allobjectsthatdefinesymboltables
#
ifdefCONFIG_MODULES
#list-multi列出那些由多个文件复合而成的模块;
#从编绎文件表和模块文件表中过滤出复合模块名。
multi-used :=$(filter$(list-multi),$(obj-y)$(obj-m))
#取复合模块的构成表。
multi-objs :=$(foreachm,$(multi-used),$($(basename$(m))-objs))
#求出需进行编译的总模块表。
active-objs :=$(sort$(multi-objs)$(obj-y)$(obj-m))

ifdefCONFIG_MODVERSIONS
ifneq"$(strip$(export-objs))"""
#如果有需要进行版本化的文件。
MODINCL=$(TOPDIR)/include/linux/modules

#The-woption(enablewarnings)forgenksymswillreturnherein2.1
#Sowherehasitgone?
#
#AddedtheSMPseparatortostopmoduleaccidentsbetweenuniprocessor
#andSMPIntelboxes-AC-frombitsbyMichaelChastain
#

ifdefCONFIG_SMP
genksyms_smp_prefix:=-psmp_
else
genksyms_smp_prefix:=
endif
#从源文件计算版本文件的规则。
$(MODINCL)/%.ver:%.c
@if[!-r$(MODINCL)/$*.stamp-o$(MODINCL)/$*.stamp-ot$<];then/
echo'$(CC)$(CFLAGS)-E-D__GENKSYMS__$<';/
echo'|$(GENKSYMS)$(genksyms_smp_prefix)-k
$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)>$@.tmp';/
$(CC)$(CFLAGS)-E-D__GENKSYMS__$</
|$(GENKSYMS)$(genksyms_smp_prefix)-k
$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)>$@.tmp;/
if[-r$@]&&cmp-s$@$@.tmp;thenecho$@isunchanged;rm-f
$@.tmp;/
elseechomv$@.tmp$@;mv-f$@.tmp$@;fi;/
fi;touch$(MODINCL)/$*.stamp
#
将版本处理源文件的扩展名改为.ver,并加上完整的路径名,它们依赖于autoconf.h?br>?br>$(addprefix$(MODINCL)/,$(export-objs:.o=.ver)):
$(TOPDIR)/include/linux/autoconf.h

#updates.verfilesbutnotmodversions.h
#通过fastdep,逐个生成export-objs对应的版本文件。
fastdep:$(addprefix$(MODINCL)/,$(export-objs:.o=.ver))

#updates.verfilesandmodversions.hlikebefore(isthisneeded?)
#makedep过程的入口
dep:fastdepupdate-modverfile

endif#export-objs

#updatemodversions.h,butonlyifitwouldchange
#刷新版本文件的过程。
update-modverfile:
@(echo"#ifndef_LINUX_MODVERSIONS_H";/
echo"#define_LINUX_MODVERSIONS_H";/
echo"#include<linux/modsetver.h>";/
cd$(TOPDIR)/include/linux/modules;/
forfin*.ver;do/
if[-f$$f];thenecho"#include<linux/modules/$${f}>";fi;/
done;/
echo"#endif";/
)>$(TOPDIR)/include/linux/modversions.h.tmp
@if[-r$(TOPDIR)/include/linux/modversions.h]&&cmp-s
$(TOPDIR)/include/linux/modversions.h
$(TOPDIR)/include/linux/modversions.h.tmp;then/
echo$(TOPDIR)/include/linux/modversions.hwasnotupdated;/
rm-f$(TOPDIR)/include/linux/modversions.h.tmp;/
else/
echo$(TOPDIR)/include/linux/modversions.hwasupdated;/
mv-f$(TOPDIR)/include/linux/modversions.h.tmp
$(TOPDIR)/include/linux/modversions.h;/
fi
$(active-objs):$(TOPDIR)/include/linux/modversions.h

else
#如果没有配置版本化,modversions.h的内容。
$(TOPDIR)/include/linux/modversions.h:
@echo"#include<linux/modsetver.h>">$@

endif#CONFIG_MODVERSIONS

ifneq"$(strip$(export-objs))"""
#版本化目标文件的编绎方法。
$(export-objs):$(export-objs:.o=.c)$(TOPDIR)/include/linux/modversions.h
$(CC)$(CFLAGS)$(EXTRA_CFLAGS)$(CFLAGS_$@)-DEXPORT_SYMTAB-c$(@:.o=.c)
@(/
echo'ifeq($(strip$(subst$(comma),:,$(CFLAGS)$(EXTRA_CFLAGS)
$(CFLAGS_$@)-DEXPORT_SYMTAB)),$$(strip$$(subst$$(comma),:,$$(CFLAGS)
$$(EXTRA_CFLAGS)$$(CFLAGS_$@)-DEXPORT_SYMTAB)))';/
echo'FILES_FLAGS_UP_TO_DATE+=$@';/
echo'endif'/
)>$(dir$@)/.$(notdir$@).flags
endif

endif#CONFIG_MODULES


#
#includedependencyfilesiftheyexist
#
#嵌入源文件之间的依赖关系。
ifneq($(wildcard.depend),)
include.depend
endif
#嵌入头文件之间的依赖关系。
ifneq($(wildcard$(TOPDIR)/.hdepend),)
include$(TOPDIR)/.hdepend
endif

#
#Findfileswhoseflagshavechangedandforcerecompilation.
#Forsafety,thisworksintheconversedirection:
#everyfileisforced,exceptthosewhoseflagsarepositively
up-to-date.
#
#已经更新过的文件列表。
FILES_FLAGS_UP_TO_DATE:=

#Foruseinexpungingcommasfromflags,whichmungourchecking.
comma=,
#将当前目录下所有flags文件嵌入。
FILES_FLAGS_EXIST:=$(wildcard.*.flags)
ifneq($(FILES_FLAGS_EXIST),)
include$(FILES_FLAGS_EXIST)
endif
#将无需更新的文件从总的对象中删除。
FILES_FLAGS_CHANGED:=$(strip/
$(filter-out$(FILES_FLAGS_UP_TO_DATE),/
$(O_TARGET)$(L_TARGET)$(active-objs)/
))

#Akludge:.Sfilesdon'tgetflagdependencies(yet),
#becausethatwillinvolvechangingalotofMakefiles.Also
#suppressobjectfilesexplicitlylistedin$(IGNORE_FLAGS_OBJS).
#Thisallowshandlingofassemblyfilesthatgettranslatedinto
#multipleobjectfiles(seearch/ia64/lib/idiv.S,forexample).
#
#将由汇编文件生成的目件文件从FILES_FLAGS_CHANGED删除。
FILES_FLAGS_CHANGED:=$(strip/
$(filter-out$(patsubst%.S,%.o,$(wildcard*.S)
$(IGNORE_FLAGS_OBJS)),/
$(FILES_FLAGS_CHANGED)))
#将FILES_FLAGS_CHANGED设为目标。
ifneq($(FILES_FLAGS_CHANGED),)
$(FILES_FLAGS_CHANGED):dummy
endif