~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
众所周知,Linux内核是使用make命令来配置并编译的,那必然少不了Makefile。在内核目录树中我们可以看到内核编译系统的顶层Makefile文件。但是如此复杂、庞大的内核源码绝不可能使用一个或几个Makefile文件来完成配置编译,而是需要一套同样复杂、庞大,且为Linux内核定制的Makefile系统。她可以说是内核的一个子系统,是内核中比较特殊的一部分,几乎都是应用层的程序和脚本,但又和生成的内核二进制文件息息相关。编译不仅涉及本地编译,还涉及各个平台之间的交叉编译以及二进制文件格式处理等等。她是对Makefile在功能上的扩充,使其在配置编译Linux内核的时候更加灵活、高效和简洁。
尽管她是一个复杂的系统,但对绝大部分内核开发者来说只需要知道如何使用,而无需了解其中的细节。她对绝大部分内核开发者基本上是透明的,隐藏了大部分实现细节,有效地降低了开发者的负担,能使其能专注于内核开发,而不至于花费时间和精力在编译过程上。
以下我们就来简要的了解一下内核Makefile体系。
一、内核Makefile体系概述
其实内核Makefile体系的包含了Kconfig和Kbuild两个系统。她曾经的维护人是Sam
Ravnborg <sam@ravnborg.org>,现在的暂时没有查到。参考资料:
kbuild 更换维护者 作者:王聪(西邮神人,崇拜下)
Kconfig对应的是内核配置阶段,如你使用命令:make menuconfig,就是在使用Kconfig系统。Kconfig由以下三部分组成:
scripts/kconfig/*
|
Kconfig文件解析程序
|
kconfig
|
各个内核源代码目录中的kconfig文件
|
arch/$(ARCH)/configs/*_defconfig
|
各个平台的缺省配置文件
|
当Kconfig系统生成.config后,Kbuild会依据.config编译指定的目标。后面我会简单地对make
%config的流程进行情景分析,这里不必赘述。
Kbuild是内核Makefile体系重点,对应内核编译阶段,由5个部分组成:
顶层Makefile
|
根据不同的平台,对各类target分类并调用相应的规则Makefile生成目标
|
.config
|
内核配置文件
|
arch/$(ARCH)/Makefile
|
具体平台相关的Makefile
|
scripts/Makefile.*
|
通用规则文件,面向所有的Kbuild Makefiles,所起的作用可以从后缀名中得知。
|
各子目录下的Makefile文件
|
由其上层目录的Makefile调用,执行其上层传递下来的命令
|
而其中scripts目录下的编译规则文件和其目录下的C程序在整个编译过程起着重要的作用。列举如下:
文件名
|
作用
|
Kbuild.include
|
共用的定义文件,被许多独立的Makefile.*规则文件和顶层Makefile包含
|
Makefile.build
|
提供编译built-in.o, lib.a等的规则
|
Makefile.lib
|
负责归类分析obj-y、obj-m和其中的目录subdir-ym所使用的规则
|
Makefile.host
|
本机编译工具(hostprog-y)的编译规则
|
Makefile.clean
|
内核源码目录清理规则
|
Makefile.headerinst
|
内核头文件安装时使用的规则
|
Makefile.modinst
|
内核模块安装规则
|
Makefile.modpost
|
模块编译的第二阶段,由<module>.o和<module>.mod生成<module>.ko时使用的规则
|
顶层Makefile主要是负责完成vmlinux(内核文件)与*.ko(内核模块文件)的编译。顶层Makefile读取.config文件,并根据.config文件确定访问哪些子目录,并通过递归向下访问子目录的形式完成。顶层Makefile同时根据.config文件原封不动的包含一个具体架构的Makefile,其名字类似于arch/$(ARCH)/Makefile。该架构Makefile向顶层Makefile提供其架构的特别信息。
每一个子目录都有一个Makefile文件,用来执行从其上层目录传递下来的命令。子目录的Makefile也从.config文件中提取信息,生成内核编译所需的文件列表。
二、内核Makefile导读与情景分析
1、概述
上面简要介绍了内核Makefile的总体结构,但当我们打开顶层Makefile文件时还是因为她的复杂而觉得无从下手。但是内核Makefile就是Makefile,和最简单的Makefile遵循着同样的规则。所以只要我们静下心来分析,还是可以理解的。当然,在阅读内核的Makefile前,你必须对Makefile和shell脚本有一定的基础。
根据Makefile的执行规则,在分析Makefile时,首先必须确定一个目标,然后才能确定所有的依赖关系,最后根据更新情况决定是否执行相应的命令。所以要看懂内核Makefile的大致框架,我们首先要了解她里面所定义的目标。而内核Makefile所定义的目标基本上可以通过make
help打印出来(因为help本身就是顶层Makefile的一个目标,里面是打印帮助信息的“echo”命令)。
这些目标可以分为以下几个大类:
目标
|
常用目标举例
|
作用
|
配置
|
%config
|
config
|
启动Kconfig,以不同界面来配置内核。
|
menuconfig
|
xconfig
|
编译
|
all
|
编译vmlinux内核映像和内核模块
|
vmlinux
|
编译vmlinux内核映像
|
modules
|
编译内核模块
|
安装
|
headers_install
|
安装内核头文件/模块
|
modules_install
|
源码浏览
|
tags
|
生成代码浏览工具所需要的文件
|
TAGS
|
cscope
|
静态分析
|
checkstack
|
检查并分析内核代码
|
namespacecheck
|
headers_check
|
内核打包
|
%pkg
|
以不同的安装格式编译内核
|
文档转换
|
%doc
|
把kernel文档转成不同格式
|
构架相关
(以arm为例)
|
zImage
|
生成压缩的内核映像
|
uImage
|
生成压缩的u-boot可引导的内核映像
|
install
|
安装内核映像
|
其中的构架相关目标在顶层Makefile上并未出现,而是被包含在平台相关的Makefile(arch/$(ARCH)/Makefile)中。
2、情景分析
以下我们就来分析一个简单的目标(menuconfig),作为情景分析范例来演示一下内核Makefile的分析方法。
首先当我们在内核源码的根目录下执行make menuconfig命令时,根据规则,make程序读取顶层Makefile文件及其包含的Makefile文件,内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。make程序最终会调用规则:
config %config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig $@
|
调用的原因是我们指定的目标“menuconfig”匹配了“%config”。
她的依赖目标是scripts_basic和outputmakefile,以及FORCE。也就是说在完成了这3个依赖目标后,下面的两个命令才会执行以完成我们指定的目标“menuconfig”。
所以我们来看看这三个依赖目标实现的简要过程:
(1)scripts_basic
make程序会调用规则:
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
|
他没有依赖目标,所以直接执行了以下的指令,只要将指令展开,我们就知道make做了什么操作。其中比较不好展开的是$(build),她的定义在scripts/Kbuild.include中:
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
|
所以展开后是:
make -f scripts/Makefile.build obj= scripts/basic
|
也就是make解析执行scripts/Makefile.build文件,且参数obj= scripts/basic。而在解析执行scripts/Makefile.build文件的时候,scripts/Makefile.build又会通过解析传入参数来包含对应文件夹下的Makefile文件(scripts/basic/Makefile),从中获得需要编译的目标。
在确定这个目标以后,通过目标的类别来继续包含一些scripts/Makefile.*文件。例如scripts/basic/Makefile中内容如下:
hostprogs-y := fixdep docproc hash
always := $(hostprogs-y)
# fixdep is needed to compile other host programs
$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep
|
所以scripts/Makefile.build会包含scripts/Makefile.host。相应的语句如下:
# Do not include host rules unless needed
ifneq ($(hostprogs-y)$(hostprogs-m),)
include scripts/Makefile.host
endif
|
此外scripts/Makefile.build会包含include scripts/Makefile.lib等必须的规则定义文件,在这些文件的共同作用下完成对scripts/basic/Makefile中指定的程序编译。
由于Makefile.build的解析执行牵涉了多个Makefile.*文件,过程较为复杂,碍于篇幅无法一条一条指令的分析,兴趣的读者可以自行分析。
(2)outputmakefile
make程序会调用规则:
PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif
|
从这里我们可以看出:outputmakefile是当KBUILD_SRC不为空(指定O=dir,编译输出目录和源代码目录分开)时,在输出目录建立Makefile时才执行命令的,
如果我们在源码根目录下执行make menuconfig命令时,这个目标是空的,什么都不做。
如果我们指定了O=dir时,就会执行源码目录下的scripts/mkmakefile,用于在指定的目录下产生一个Makefile,并可以在指定的目录下开始编译。
(3)FORCE
这是一个在内核Makefile中随处可见的伪目标,她的定义在顶层Makefile的最后:
是个完全的空目标,但是为什么要定义一个这样的空目标,并让许多目标将其作为依赖目标呢?原因如下:
正因为FORCE是一个没有命令或者依赖目标,不可能生成相应文件的伪目标。当make执行此规则时,总会认为FORCE不存在,必须完成这个目标,所以她是一个强制目标。也就是说:规则一旦被执行,make就认为它的目标已经被执行并更新过了。当她作为一个规则的依赖时,由于依赖总被认为被更新过的,因此作为依赖所在的规则中定义的命令总会被执行。所以可以这么说:只要执行依赖包含FORCE的目标,其目标下的命令必被执行。
在make完成了以上3个目标之后,就开始执行下面的命令的,首先是
$(Q)mkdir -p include/linux include/config
|
这个很好理解,就是建立两个必须的文件夹。然后
$(Q)$(MAKE) $(build)=scripts/kconfig $@
|
这和我们上面分析的$(Q)$(MAKE) $(build)=结构相同,将其展开得到:
make -f scripts/Makefile.build obj=scripts/kconfigmenuconfig
|
所以这个指令的效果是使make解析执行scripts/Makefile.build文件,且参数obj=scripts/kconfig menuconfig。这样Makefile.build会包含对应文件夹下的Makefile文件(scripts/kconfig
/Makefile),并完成scripts/kconfig /Makefile下的目标:
menuconfig: $(obj)/mconf
$< $(Kconfig)
|
这个目标的依赖条件是$(obj)/mconf,通过分析可知她其实是对应以下规则:
mconf-objs := mconf.o zconf.tab.o $(lxdialog)
……
ifeq ($(MAKECMDGOALS),menuconfig)
hostprogs-y += mconf
endif
|
也就是编译生成本机使用的mconf程序。完成依赖目标后,通过scripts/kconfig/Makefile中对Kconfig的定义可知,最后执行:
mconfarch/$(SRCARCH)/Kconfig
|
而对于conf和xconf等都有类似的过程,所以总结起来:当make %config时,内核根目录的顶层Makefile会临时编译出scripts/kconfig中的工具程序conf/mconf/qconf等负责对arch/$(SRCARCH)/Kconfig文件进行解析。这个Kconfig又通过source标记调用各个目录下的Kconfig文件构建出一个Kconfig树,使得工具程序构建出整个内核的配置界面。在配置结束后,工具程序就会生成我们常见的.config文件。
三、在内核中添加自己的模块
虽然内核Makefile体系很是复杂,但是这种复杂带来的确是开发时的便利。其实内核Makefile体系之所以复杂,其中的一个原因就是为了方便扩展。对于一个开发者来要在内核中添加自己的一个驱动代码是非常简单的事情。
一般来说,对于一个新驱动代码的添加,驱动工程师只需要在内核源码的drivers目录的相应子目录下添加新设备驱动源码,并增加或修改该目录下的Kconfig和Makefile文件即可。
比如你已经写好了一个针对TI 的AM33XX芯片的LED的驱动程序,名为am33xx_led.c。
(1)将驱动源码am33xx_led.c等文件复制到linux-X.Y.Z/drivers/char目录。
(2)在该目录下的Kconfig文件中添加LED驱动的配置选项:
config AM33XX_LED
bool "Support for am33xx led drivers"
depends on SOC_OMAPAM33XX
default n
---help---
Say Y here if you want to support forAM33XXLED drivers.
|
(3)在该目录下的Makefile文件中添加对LED驱动的编译:
obj-$(CONFIG_AM33XX_LED) += am33xx_led.o
|
这样你就可以在make menuconfig的时候看到这个配置选项,并进行配置了。
当然,上面的例子只是一个意思,对于Kconfig文件和Makefile的详细语法,请参考内核文档:Documentation/kbuild/makefile.txt
四 、在内核Makefile上对读者的建议
这个复杂的Makefile体系体现了很多优秀程序共有的设计思想,对于我们今后的程序设计上有很多值得借鉴的地方。比如:模块化设计、简化编程接口,使得自行添加模块更加的简洁。阅读分析这样复杂的Makefile对于学习和编写Makefile和shell脚本有很好的参考价值。如果你正在学习Makefile的编写和阅读,那你可以耐心的分析一下内核的Makefile体系,只要你认真分析了一两个目标的实现,你会发现当你在阅读一些小软件的Makefile时已经是轻车熟路了。特别是现在很多芯片的开发包都是以SDK包的形式发布的,而这些软件包都是通过Makefile体系来实现自动编译和配置的,所以熟悉Makefile是每个Linux开发者都需要做到的。
相关推荐
很好Linux内核休息书籍 第一层次修炼的内容包括了前三章, 目的是希望您能够对 Linux 以及内核有个全面的认 识和了解,掌握分析 Linux 内核源代码的分析方法。 第 1 章主要介绍了 Linux 的 18 年成长史, 或许您会...
58-Make与Makefile在Linux内核体系编译中的应用
1.1.1 Linux版本 Linux内核的版本号可以从源代码的顶层目录下的Makefile中看到,比如2.6.29.1内核的Makefile中: VERSION = 2 PATCHLEVEL = 6 SUBLEVEL = 29 EXTRAVERSION = .1 其 中的“VERSION”和...
硬件部件的使用及编程(囊括了常见硬件,比如UART、I*IC、LCD等),UBoot、Linux内核的分析、配置和移植,根文件系统的构造(包括移植busybox、glibc、制作映象文件等),内核调试技术(比如添加kgdb补丁、栈回溯等),...
第2 章 LINUX 内核体系结构 13 21 LINUX 内核模式 13 22 LINUX 内核系统体系结构 14 23 LINUX 内核进程控制 15 24 LINUX 内核对内存的使用方法 16 25 LINUX 内核源代码的目录结构 18 26 内核系统与用户程序的关系 23...
根据龙芯平台 Linux 内核实际情况,将内核的源码文件分为三部分: 驱动部分、体系架构相关部分 以及公共部分。 a) 驱动部分: 包含内核 driver 目录下所有的文件。 b) 体系架构相关部分:内核源码中与体系架构相关的...
1.5.3 Linux内核源代码分析工具 14 习题1 15 第2章 内存寻址 17 2.1 内存寻址简介 17 2.1.1 Intel x86 CPU寻址方式的演变 18 2.1.2 IA32寄存器简介 19 2.1.3 物理地址、虚拟地址及线性地址 21 2.2 分段机制 22 2.2.1...
2.2 内核编译分析 28 2.2.1 编译配置 29 2.2.2 寻找第一个目标 32 2.2.3 prepare和scripts目标 38 2.2.4 递归编译各对象 41 2.2.5 链接vmlinux 44 2.2.6 制作bzImage 50 3 实模式下的内核代码 57 3.1 内核映像内存...
-国嵌内核驱动进阶班-1-3(Linux内核配置与编译).avi -国嵌内核驱动进阶班-1-4(Linux内核模块开发).avi -国嵌内核驱动进阶班-1-5(必修实验).avi -第2天(U-Boot移植) -国嵌内核驱动进阶班-2-1(嵌入式linux...
5.3 实验内容——创建Linux内核和文件系统 5.4 本章小结 5.5 思考与练习 第6章 文件I/O编程 6.1 Linux系统调用及用户编程接口(API) 6.1.1 系统调用 6.1.2 用户编程接口(API) 6.1.3 系统命令 6.2 Linux中文件及文件...
1.5 分析Linux内核源代码很有必要 14 1.5.1 源代码目录结构 14 1.5.2 浏览源代码的工具 16 1.5.3 为什么用汇编语言编写内核代码 17 1.5.4 Linux内核的显著特性 18 1.5.5 学习Linux内核的...
分析Linux内核的代码结构以及启动过程,并介绍如何移植到开发板上。 介绍嵌入式Linux文件系统的目录结构,然后构造嵌入式Linux文件系统。 嵌入式Linux驱动程序开发和移植。 嵌入式系统中的GUI介绍。 ...
第8章 嵌入式linux c语言基础——arm linux内核常见数据结构 225 8.1 链表 226 8.1.1 链表概述 226 8.1.2 单向链表 226 8.1.3 双向链表 233 8.1.4 循环链表 234 8.1.5 arm linux中链表使用实例 ...
├<1 Linux操作系统基础> │ ├01 - 说在前面的话1.mp4 │ ├02 - 说在前面的话2.mp4 │ ├03 - 说在前面的话3.mp4 │ ├04 - 说在前面的话4.mp4 │ ├05 - 计算机组成原理概述1 .mp4 │ ├06 - 计算机组成原理概述2...
5.3 实验内容——移植Linux内核 164 本章小结 165 思考与练习 165 第6章 文件I/O编程 166 6.1 Linux系统调用及用户编程接口(API) 166 6.1.1 系统调用 166 6.1.2 用户编程接口(API) 167 6.1.3 ...
第3章 嵌入式Linux内核、引导系统和文件系统36 3.1 Linux内核定制、裁剪和添加36 3.1.1 概述36 3.1.2 内核目录简介37 3.1.3 配置文件和配置工具37 3.1.4 内核的编译命令39 实验3.1 Linux内核裁剪与编译40 3.2...
第3章 嵌入式Linux内核、引导系统和文件系统36 3.1 Linux内核定制、裁剪和添加36 3.1.1 概述36 3.1.2 内核目录简介37 3.1.3 配置文件和配置工具37 3.1.4 内核的编译命令39 实验3.1 Linux内核裁剪与编译...
主要包括Linux的基本概念和操作,Linux的树型结构,Linux的文本编辑,Linux的安装和启动,用户管理,Shell编程技术,进程管理,C编译器,系统扩充,维护与监视,Linux的图形界面,网络的基本概念与设置,Linux在网络...
第3章 嵌入式Linux内核、引导系统和文件系统36 3.1 Linux内核定制、裁剪和添加36 3.1.1 概述36 3.1.2 内核目录简介37 3.1.3 配置文件和配置工具37 3.1.4 内核的编译命令39 实验3.1 Linux内核裁剪与编译40 3.2...