⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 makefile.txt

📁 makefile文件的理解
💻 TXT
📖 第 1 页 / 共 3 页
字号:
内核源码中的makefiles(不含动态生成的.flags文件)主要分为以下四类:

1. 主目录下的Makefile(不妨称为main-makefile)
它主要有两个作用:生成vmlinux(内核映像)和modules(模块)。

2. 主目录下的Rules.make
Rules.make中定义通用规则供main-makefile和subdir-makefiles调用。
变量subdir-y为用于构建vmlinux(内核)的目录,变量subdir-m为用于构建modules(模块)的目录;subdir-n和subdir-中的目录将不参加构建工作。
变量obj-y为内核的目标文件列表,在各subdir-y中会对其赋相应的值。如:lib/Makefile的第13-14行。变量obj-m为各模块的目标文件列表,在各subdir-m中会对其赋相应的值。如在drivers/Makefile中,在第13行对subdir-y赋值,并在第14行将此列表复制给subdir-m。从第17行开始对subdir-y和subdir-m进行补充,这两个列表的建立完成。obj-n和obj-中所有的目标将被忽略。

3. arch/*/目录下的Makefile(不妨称为arch-makefiles)
arch-makefiles是与特定的体系结构相关的,它们分别是针对特定CPU的makefiles。

4. 除arch外的子目录下的Makefile(不妨称为subdir-makefiles)
subdir -makefiles只负责在自己的目录中生成目标文件。它们接受来自上层Make传递下来的信息,并根据这些信息来构造一个需要编译的文件列表,并交由 Rules.make处理。subdir-makefiles都比较简短,因为通用的规则都已在Rules.make定义了。一般subdir- makefiles的主要工作是对obj-*或subdir-*变量赋值。

二、内核和模块构建的过程

  下面分析内核编译的动态过程。

第一步:make ARCH=arm menuconfig
目标menuconfig的定义在main-makefile的第306行。main-makefile的第308行调用Menuconfig 工具(scripts/Menuconfig),让它根据arch/$(ARCH)/config.in文件的内容在主目录下生成.config文件供以后调用(main-makefile的第37行)。

第二步:make ARCH=arm CROSS_COMPILE=arm-linux- dep
include/linux/autoconf.h根据.config文件生成,其内容是若干#define和#undef宏。这些宏通知编译器哪些组件需要编译而哪些不需要,是编译进内核还是编译成模块。这两个文件中的变量是相互对应的。例如.config的第4行是:
CONFIG_ARM=y
这是编译进内核的情况。相应地,include/linux/autoconf.h的第5行是:
#define CONFIG_ARM 1
  编译成模块的例子见.config的第216行:
CONFIG_BINFMT_AOUT=m
  相应地,autoconf.h的第217和218行是:
#undef CONFIG_BINFMT_AOUT
#define CONFIG_BINFMT_AOUT_MODULE 1

接下来main-makefile调用split-include工具(scripts/split-include.c)(main-makefile的第314行),分割解析autoconf.h并在include/config目录下生成相应的头文件。autoconf.h的路径通过第一个参数传递给 split-include(scripts/split-include.c的第71行)根据刚才提到的autoconf.h的第5行的内容, split-include工具会在include/config/目录下生成arm.h文件,其内容就是一行:
#define CONFIG_ARM 1

include/linux/config.h 的第4行嵌入了autoconf.h,而内核源码中很多c文件又嵌入这个config.h(例如kernel/printk.c的第28行),于是间接地嵌入了autoconf.h,可见config.h起到的是一个桥梁的作用。

在这一步骤中,makefile会调用mkdep工具(scripts/mkdep.c)生成两种包含依赖关系的文件:.hdepend和.depend。
.hdepend只有一个,在主目录下,它表示相应的头文件又依赖于哪些其它的头文件。.hdepend是在main-makefile的第496行执行后生成的。如果存在.hdepend文件,Rules.make会嵌入它(Rules.make第295-297行)。
.depend 文件有很多,它表示相应的目标文件依赖哪些源文件(*.c)和头文件(*.h)。主目录下的.depend文件是在main-makefile的第497 行执行后生成的。mkdep读取*.c文件,分析出其所依赖的文件。下面以mm/page_alloc.c举例。page_alloc.c的第15-23 行为(“…”表示省略):
#include <linux/config.h>
#include <linux/mm.h>
#include <linux/module.h>
mkdep就会在mm/.depend中的第110-118行中写入:
page_alloc.o: page_alloc.c \
  /usr/src/arm-linux/linux-roy/include/linux/mm.h \
  …
  /usr/src/arm-linux/linux-roy/include/linux/module.h \
但接下来的第119行和上面的写法有些不同:
$(wildcard /usr/src/arm-linux/linux-roy/include/config/discontigmem.h) \
  这是因为page_alloc.c中的第244行的内容如下:
#ifndef CONFIG_DISCONTIGMEM
这需要判断discontigmem.h的存在性,而mm目录下的.depend文件的第119行正是起到了这样的作用(若存在则返回文件名,若不存在则返回空)
第三步:make ARCH=arm CROSS_COMPILE=arm-linux- clean
目标clean在main-makefile的第444-449行定义,它所清除文件的文件和目录列表(CLEAN_FILES和CLEAN_DIRS)在203-229行被赋值。 

第四步:make ARCH=arm CROSS_COMPILE=arm-linux- zImage
make zImage 执行后会生成一个用gzip压缩过的内核。对于arm类型的CPU,目标zImage是在arch/arm/boot/Makefile的第146行定义的。它依赖于compressed/vmlinux。而在第152行,又说明了compressed/vmlinux依赖于主目录下的vmlinux。它的生成规则在main-makefile的第281-289行:
vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o \
init/do_mounts.o linuxsubdirs
  $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o init/do_mounts.o \
    --start-group \
    $(CORE_FILES) \
    $(DRIVERS) \
    $(NETWORKS) \
    $(LIBS) \
    --end-group \
    -o vmlinux
ZLDFLAGS 是在arch/arm/boot/Makefile的第14行定义的。如果我们指定CROSS_COMPILE 为arm-linux-,则LD为arm -linux-ld(main-makefile的第29行)。LINKFLAGS在arch/arm/Makefile的第10行定义:
LINKFLAGS  :=-p -X -T arch/arm/vmlinux.lds
  它指定了链接脚本为arch/arm/vmlinux.lds。

make 从主目录Makefile开始执行,从中获得与体系结构无关的变量和依赖关系,并同时从arch-makefiles中获得体系特定的变量等信息,这些信息继承并扩展了主目录Makefile提供的变量。先是所有通过“:=”赋值的变量在这个过程中被赋值,然后是所有通过“=”赋值的变量被赋值(实际上类似于宏替换)。然后,main-makefile向下递归调用子目录中的Makefile,把部分变量传递给子目录Makefile。此时构建内核需要的子目录Makefile根据配置信息决定编译哪些源文件,从而构建出一个需要编译的文件列表。在make zImage的时候make会递归进入列表 subdir-y里的目录,根据其定义的编译规则决定这些文件的编译方式。然后,make会根据依赖关系树执行命令,编译生成一系列的目标文件,最后根据 vmlinux的生成规则(main-makefile的第281-289行),通过链接脚本把这些目标文件链接成vmlinux(例如 arch/arm/Makefile第267行)。

第五步:make ARCH=arm CROSS_COMPILE=arm-linux- modules
  make module用于生成内核模块(modules),它的规则在main-makefile的388-389行定义。在make modules的时候make会递归进入列表subdir-m里的目录。
vmlinux:
  第六步:make ARCH=arm CROSS_COMPILE=arm-linux- INSTALL_MOD_PATH=<root fs>modules_install
  make module用于安装内核模块,它的规则在main-makefile的396行定义。

三、几点说明

main-makefile的第37行是:
MAKEFILES  = $(TOPDIR)/.config
  MAKEFILES是一个环境变量,如果你的当前环境中定义了MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include 不同的是,从这个环境变中引入的Makefile 中的目标不会起作用,如果环境变量中定义的文件发现错误,make 也会忽略。子目录中的Makefile与Rules.make都没有嵌入.config文件,它们就是通过main-makefile向下传递MAKEFILES变量得到相关信息的。

  在main-makefile的第48-67行所完成的工作是这样的:首先all被定义为第一个目标,然后在55-58行判断.config文件和.depend文件的存在性,如果存在就包含进来。如果.config文件不存在,则把do-it-all设为config(第66行)。目标conifg在第310行定义。如果.config文件存在而. depend文件不存在,则把do-it-all设为depend(第62行)。目标depend在第506行定义。在执行make后,主目录下会生成配置文件.config(如果原来存在.config文件,则被更名为.config.old)。
  
makemrproper用于重新构建内核,它做的清除工作比make clean更加彻底:除了做makeclean的工作外,还要删除.config,.depend等文件,把核心源码恢复到最原始的状态。它在main-makefile的第451-455行定义,它所清除文件的文件和目录列表(MRPROPER_FILES和 MRPROPER_DIRS)在232-256行被赋值。

递归的make 必须使用MAKE变量,而不是直接用make 命令。如main-makefile的第279行:
@$(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" -C arch/$(ARCH)/boot
当使用“-C”参数来make下层Makefile时,“-w”参数就会被自动打开,即在处理makefile
之前和之后,显示工作目录,所以在make的时候会看到屏幕上反复出现Entering directory[some dir]和Leaving directory [some dir]。

  一对反引号(`)的作用相当于shell函数,里面的内容是系统shell命令(例如main-make的第496行)。

如果在编译的第一步用的是xconfig,则可以参见第302行的定义。在xconfig中,会使用wish程序调用scripts目录下的tk脚本形成图形界面供用户配置。

在编译的第二步中,为什么要通过mkdep和split-include工具,而不是通过gcc加上-MM参数来建立依赖关系呢?上文提到过,很多c源文件都会通过linux/config.h而嵌入autoconf.h,如果按照通常方法建立文件依赖关系(.depend),只要更新过 autoconf.h,就会造成所有源代码的重新编译。为了优化make过程,减少不必要的重新编译,linux开发了专用的mkdep工具,用它来生成.depend文件。配置内核时,split-include会检查旧的子文件的内容,确定是不是要更新它们。这样,不管autoconf.h修改日期如何,只要其配置不变,make就不会重新编译内核。

Rules.make把当前目录下所有的目标文件称为O_TARGET,它的生成规则在第96-102行,最终生成一系列的.o文件;把当前目录下所有的库文件称为L_TARGET,它的生成规则在第114-116行,最终生成一系列的.a文件。注意第62-65行、第104-107行、118-121行、279-282行和304-317行实现了一种增量编译的机制。make每编译一个源文件时会生成一个.flags文件。例如编译mm/swap.c时,会在相同的目录下生成.swap.o.flags文件。它本质上也是 Makefile文件,其代码的功能是测试当前的编译选项与上次相比是否作过改动,若改动过就将自己对应的目标文件加入 FILES_FLAGS_UP_TO_DATE列表。生成这个文件是供下一次编译用的。下次编译时,当make进入某个子目录,会搜索其中的.flags 文件,如果存在则将它们嵌入(第309-312行)。Make会得到FILES_FLAGS_UP_TO_DATE列表,它的内容是不需要更新的目标。于是,系统从编译对象表中删除它们,从而得到FILES_FLAGS_CHANGED列表(第314-317行),这个列表的内容就是需要更新的目标。

  Main-makefile的第281-289行起定义vmlinux的生成(链接)规则。vmlinux 是由 HEAD、main.o、 version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。其中,CORE_FILES、NETWORKS、 DRIVERS 和LIBS在main-makefile中定义,由 arch-makefiles扩充。而HEAD在arch-makefiles 中定义(例如arch/arm/Makefile的第193行)。HEAD列表中的目标文件需要最先被链接到 vmlinux 中。
  链接脚本(*.lds文件)是通过*.lds.in文件生成的,对于arm,就是vmlinux-armo.lds.in。arch/arm/Makefile的第269-274行定义了.lds的生成规则。vmlinux-armo.lds.in实际上是一个链接脚本模板,只不过相关参数没有确定。 arch/arm/Makefile的第274行就是完成“填空”工作(为TEXTADDR、DATAADDR等赋上具体的值),并把完整的信息保存到 arch/arm/vmlinux.lds中。 
 
一、Makefile 初探

Linux的内核配置文件有两个,一个是隐含的.config文件,嵌入到主Makefile中;另一个是include/linux/autoconf.h,嵌入到各个c源文件中,它们由make config、make menuconfig、make xconfig这些过程创建。

几乎所有的源文件都会通过linux/config.h而嵌入autoconf.h,如果按照通常方法建立文件依赖关系(.depend),只要更新过autoconf.h,就会造成所有源代码的重新编绎。

为了优化make过程,减少不必要的重新编绎,Linux开发了专用的mkdep工具,用它来取代gcc来生成.depend文件。mkdep在处理源文件时,忽略linux/config.h这样的头文件,识别源文件宏指令中具有"CONFIG_"特征的行。

例如,如果有"#ifdef CONFIG_SMP"这样的行,它就会在.depend文件中输出$(wildcard /usr/src/linux/include/config/smp.h)。

include/config/下的文件是另一个工具split-include从autoconf.h中生成,它利用autoconf.h中的CONFIG_标记,生成与mkdep相对应的文件。例如,如果autoconf.h中有"#undef CONFIG_SMP"这一行,它就生成include/config/smp.h文件,内容为"#undef CONFIG_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。

三、模块的版本化处理

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

例如对于kernel/ksyms.c,make用以下命令生成对应的ksyms.ver:
gcc -E -D__KERNEL__ 
-D__GENKSYMS__ ksyms.c 
| /sbin/genksyms -k 2.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_kmalloc 93d4cfe6 
#define kmalloc _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时戳文件。在make dep时,如果其.stamp文件比源文件旧才重新生成.ver文件,否则只是更新.stamp文件时戳。另外,在生成.ver和modversions.h文件时,make都会比较新文件和旧文件的内容,保持它们修改时间为最旧。 
四、Rules.make的注释 
[code:1:974578564b] 
# 
# This file contains rules which are shared between multiple Makefiles. 
# 

# 
# False targets. 
# 
#  
.PHONY: dummy  

# 
# Special variables which should not be exported 
# 
# 取消这些变量通过环境向make子进程传递。 
unexport EXTRA_AFLAGS 
# as 的开关 
unexport EXTRA_CFLAGS 
# cc 的开关 
unexport EXTRA_LDFLAGS 
# ld 的开关 
unexport EXTRA_ARFLAGS 
# ar 的开关 
unexport SUBDIRS 
#  
unexport SUB_DIRS 
# 编绎内核需进入的子目录,
等于subdir-y 
unexport ALL_SUB_DIRS 
# 所有的子目录 
unexport MOD_SUB_DIRS 
# 编绎模块需进入的子目录 
unexport O_TARGET 
# ld合并的输出对象 
unexport ALL_MOBJS
# 所有的模块名 

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

# 
# Get things started. 
# 
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-)) 
# 
# Common rules 
# 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -