Linux内核中makefile有什么作用?深入解析makefile工作过程和原理

Linux内核中Makefile 的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成 linux 内核二进制文件。
由于 Linux 内核源代码是按照树形结构组织的,所以 Makefile 也被分布在目录树中。
Linux 内核中的 Makefile 以及与 Makefile 直接相关的文件有:Makefile:顶层 Makefile,是整个内核配置、编译的总体控制文件。
config:内核配置文件,包含由用户选择的配置选项,用来存放内核配置后的结果(如 make config)。
arch/*/Makefile:位于各种 CPU 体系目录下的 Makefile,如 arch/arm/Makefile,是针对特定平台的 Makefile。
各个子目录下的 Makefile:比如 drivers/Makefile,负责所在子目录下源代码的管理。
Rules.make:规则文件,被所有的 Makefile 使用。
用户通过 make config 配置后,产生了 .config。
顶层 Makefile 读入 .config 中的配置选择。
顶层 Makefile 有两个主要的任务:产生 vmlinux 文件和内核模块(module)。
为了达到此目的,顶层 Makefile 递归的进入到内核的各个子目录中,分别调用位于这些子目录中的 Makefile。
至于到底进入哪些子目录,取决于内核的配置。
在顶层 Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定 CPU 体系结构下的 Makefile,这个 Makefile 中包含了平台相关的信息。
位于各个子目录下的 Makefile 同样也根据 .config 给出的配置信息,构造出当前配置下需要的源文件列表,并在文件的最后有 include $(TOPDIR)/Rules.make。
Rules.make 文件起着非常重要的作用,它定义了所有 Makefile 共用的编译规则。
比如,如果需要将本目录下所有的 c 程序编译成汇编代码,需要在 Makefile 中有以下的编译规则:  %.s: %.c    $(CC) $(CFLAGS) -S $< -o $@有很多子目录下都有同样的要求,就需要在各自的 Makefile 中包含此编译规则,这会比较麻烦。
而 Linux 内核中则把此类的编译规则统一放置到 Rules.make 中,并在各自的 Makefile 中包含进了 Rules.make(include Rules.make),这样就避免了在多个 Makefile 中重复同样的规则。
对于上面的例子,在 Rules.make 中对应的规则为:    %.s: %.c    $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F)) $(CFLAGS_$@) -S $< -o $@Makefile 中的变量顶层 Makefile 定义并向环境中输出了许多变量,为各个子目录下的 Makefile 传递一些信息。
有些变量,比如 SUBDIRS,不仅在顶层 Makefile 中定义并且赋初值,而且在 arch/*/Makefile 还作了扩充。
常用的变量有以下几类:1) 版本信息 版本信息有:VERSION,PATCHLEVEL, SUBLEVEL, EXTRAVERSION,KERNELRELEASE。
版本信息定义了当前内核的版本,比如 VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXATAVERSION=-rmk7,它们共同构成内核的发行版本KERNELRELEASE:2.4.18-rmk72) CPU 体系结构:ARCH在顶层 Makefile 的开头,用 ARCH 定义目标 CPU 的体系结构,比如 ARCH:=arm 等。
许多子目录的 Makefile 中,要根据 ARCH 的定义选择编译源文件的列表。
3) 路径信息:TOPDIR, SUBDIRSTOPDIR 定义了 Linux 内核源代码所在的根目录。
例如,各个子目录下的 Makefile 通过 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。
SUBDIRS 定义了一个目录列表,在编译内核或模块时,顶层 Makefile 就是根据 SUBDIRS 来决定进入哪些子目录。
SUBDIRS 的值取决于内核的配置,在顶层 Makefile 中 SUBDIRS 赋值为 kernel drivers mm fs net ipc lib;根据内核的配置情况,在 arch/*/Makefile 中扩充了 SUBDIRS 的值,参见4)中的例子。
4) 内核组成信息:HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBSLinux 内核文件 vmlinux 是由以下规则产生的: vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs$(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o \    --start-group \    x$(CORE_FILES) \    $(DRIVERS) \    $(NETWORKS) \    $(LIBS) \    --end-group \    -o vmlinux可以看出,vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。
这些变量(如 HEAD)都是用来定义连接生成 vmlinux 的目标文件和库文件列表。
其中,HEAD在arch/*/Makefile 中定义,用来确定被最先链接进 vmlinux 的文件列表。
比如,对于 ARM 系列的 CPU,HEAD 定义为:HEAD := arch/arm/kernel/head-$(PROCESSOR).o \    arch/arm/kernel/init_task.o表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被链接到 vmlinux 中。
PROCESSOR 为 armv 或 armo,取决于目标 CPU。
CORE_FILES,NETWORK,DRIVERS 和 LIBS 在顶层 Makefile 中定义,并且由 arch/*/Makefile 根据需要进行扩充。
CORE_FILES 对应着内核的核心文件,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,这些是组成内核最为重要的文件。
同时,arch/arm/Makefile 对 CORE_FILES 进行了扩充:  # arch/arm/Makefile    # If we have a machine-specific directory, then include it in the build.    MACHDIR         := arch/arm/mach-$(MACHINE)    ifeq ($(MACHDIR),$(wildcard $(MACHDIR)))    SUBDIRS         += $(MACHDIR)    CORE_FILES      := $(MACHDIR)/$(MACHINE).o $(CORE_FILES)    endif    HEAD            := arch/arm/kernel/head-$(PROCESSOR).o \                       arch/arm/kernel/init_task.o    SUBDIRS         += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe    CORE_FILES      := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES)    LIBS            := arch/arm/lib/lib.a $(LIBS)    5) 编译信息:CPP, CC, AS, LD, AR,CFLAGS,LINKFLAGS在 Rules.make 中定义的是编译的通用规则,具体到特定的场合,需要明确给出编译环境,编译环境就是在以上的变量中定义的。
针对交叉编译的要求,定义了 CROSS_COMPILE。
比如:    CROSS_COMPILE = arm-linux-    CC = $(CROSS_COMPILE)gcc    LD = $(CROSS_COMPILE)ld    ......CROSS_COMPILE 定义了交叉编译器前缀 arm-linux-,表明所有的交叉编译工具都是以 arm-linux- 开头的,所以在各个交叉编译器工具之前,都加入了 $(CROSS_COMPILE),以组成一个完整的交叉编译工具文件名,比如 arm-linux-gcc。
CFLAGS 定义了传递给 C 编译器的参数。
 LINKFLAGS 是链接生成 vmlinux 时,由链接器使用的参数。
LINKFLAGS 在 arm/*/Makefile 中定义,比如:    # arch/arm/Makefile    LINKFLAGS :=-p -X -T arch/arm/vmlinux.lds6) 配置变量CONFIG_*  .config 文件中有许多的配置变量等式,用来说明用户配置的结果。
例如 CONFIG_MODULES=y 表明用户选择了 Linux 内核的模块功能。
.config 被顶层 Makefile 包含后,就形成许多的配置变量,每个配置变量具有确定的值:y 表示本编译选项对应的内核代码被静态编译进 Linux 内核;m 表示本编译选项对应的内核代码被编译成模块;n 表示不选择此编译选项;如果根本就没有选择,那么配置变量的值为空。
    Rules.make 变量前面讲过,Rules.make 是编译规则文件,所有的 Makefile 中都会包括 Rules.make。
Rules.make 文件定义了许多变量,最为重要是那些编译、链接列表变量。
O_OBJS,L_OBJS,OX_OBJS,LX_OBJS:本目录下需要编译进 Linux 内核 vmlinux 的目标文件列表,其中 OX_OBJS 和 LX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。
M_OBJS,MX_OBJS:本目录下需要被编译成可装载模块的目标文件列表。
同样,MX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。
 O_TARGET,L_TARGET:每个子目录下都有一个 O_TARGET 或 L_TARGET,Rules.make 首先从源代码编译生成 O_OBJS 和 OX_OBJS 中所有的目标文件,然后使用 $(LD) -r 把它们链接成一个 O_TARGET 或 L_TARGET。
O_TARGET 以 .o 结尾,而 L_TARGET 以 .a 结尾。
子目录 Makefile子目录 Makefile 用来控制本级目录以下源代码的编译规则。
我们通过一个例子来讲解子目录 Makefile 的组成:    #    # Makefile for the linux kernel.    #    # All of the (potential) objects that export symbols.    # This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'.    export-objs := tc.o    # Object file lists.    obj-y        :=    obj-m        :=    obj-n        :=    obj-         :=    obj-$(CONFIG_TC) += tc.o    obj-$(CONFIG_ZS) += zs.o    obj-$(CONFIG_VT) += lk201.o lk201-map.o lk201-remap.o    # Files that are both resident and modular: remove from modular.    obj-m        := $(filter-out $(obj-y), $(obj-m))    # Translate to Rules.make lists.    L_TARGET := tc.a    L_OBJS       := $(sort $(filter-out $(export-objs), $(obj-y)))    LX_OBJS      := $(sort $(filter     $(export-objs), $(obj-y)))    M_OBJS       := $(sort $(filter-out $(export-objs), $(obj-m)))    MX_OBJS      := $(sort $(filter     $(export-objs), $(obj-m)))    include $(TOPDIR)/Rules.make a) 注释对 Makefile 的说明和解释,由#开始。
b) 编译目标定义类似于 obj-$(CONFIG_TC) += tc.o 的语句是用来定义编译的目标,是子目录 Makefile 中最重要的部分。
编译目标定义那些在本子目录下,需要编译到 Linux 内核中的目标文件列表。
为了只在用户选择了此功能后才编译,所有的目标定义都融合了对配置变量的判断。
前面说过,每个配置变量取值范围是:y,n,m 和空,obj-$(CONFIG_TC) 分别对应着 obj-y,obj-n,obj-m,obj-。
如果 CONFIG_TC 配置为 y,那么 tc.o 就进入了 obj-y 列表。
obj-y 为包含到 Linux 内核 vmlinux 中的目标文件列表;obj-m 为编译成模块的目标文件列表;obj-n 和 obj- 中的文件列表被忽略。
配置系统就根据这些列表的属性进行编译和链接。
export-objs 中的目标文件都使用了 EXPORT_SYMBOL() 定义了公共的符号,以便可装载模块使用。
在 tc.c 文件的最后部分,有 "EXPORT_SYMBOL(search_tc_card);",表明 tc.o 有符号输出。
这里需要指出的是,对于编译目标的定义,存在着两种格式,分别是老式定义和新式定义。
老式定义就是前面 Rules.make 使用的那些变量,新式定义就是 obj-y,obj-m,obj-n 和 obj-。
Linux 内核推荐使用新式定义,不过由于 Rules.make 不理解新式定义,需要在 Makefile 中的适配段将其转换成老式定义。
c) 适配段适配段的作用是将新式定义转换成老式定义。
在上面的例子中,适配段就是将 obj-y 和 obj-m 转换成 Rules.make 能够理解的 L_TARGET,L_OBJS,LX_OBJS,M_OBJS,MX_OBJS。
L_OBJS := $(sort $(filter-out $(export-objs), $(obj-y))) 定义了 L_OBJS 的生成方式:在 obj-y 的列表中过滤掉 export-objs(tc.o),然后排序并去除重复的文件名。
这里使用到了 GNU Make 的一些特殊功能,具体的含义可参考 Make 的文档(info make)。
d) include $(TOPDIR)/Rules.make

返回列表
上一篇:
下一篇: