Linux Kernel Makefile简析 之 make zImage

本文将简要分析Linux Kernel编译zImage的过程。读者需具备GNU Make、Bash Shell、Python脚

本、编译器、链接器等方面的基础知识。虽然重点是分析kernel的构建过程,但是也会顺带的分析一些

其他的小的知识点。我们坐车去远行,欣赏沿途的风景,并不会妨碍我们最终抵达我们的目的地,不是

吗?

先描述一下具体开发环境:

. host os: ubuntu 14.04 server LTS

. cross toolchain: crosstool-ng生成的交叉编译器

. target: tegra, jeston-tk1

. kernel: 3.10.40

在开始分析之前,先做一些必要的说明:

. 分析过程中,所有文件路径,都是相对于kernel源码根目录的。

好了,接下来开始分析zImage的编译过程。

启动终端,在kernel源码根目录下运行命令. envsetup.sh设置编译环境。envsetup.sh的内容如

下:

#/bin/bash
#
# filename: envsetup.sh

export ARCH=arm
export CROSS_COMPILE=~/tegra/tk1/crosstool/crosstool-ng/install/bin/arm-cortex_a9-linux-gnueabi-

然后运行命令make zImage O=output编译kernel。make读取kernel源码根目录下的Makefile,看到

如下代码片断:

图1

这里会先判断KBUILD_SRC的值是否为空,因为此时 KBUILD_SRC 尚未定义,所以这个判断是成的。

接着将KBUILD_OUTPUT赋为从命令行传过来的变量O的值,即:

KBUILD_OUTPUT := $(O) # KBUILD_OUTPUT := output

附带的变量saved-output也被赋成了同样的值output,然后将KBUILD_OUTPUT的值更新为output目录的完

整路径。KBUILD_OUTPUT是用来存储编译产生的输出的目录。

图2

这里是很重要的片段,是进入构建最终目标的开端。上面图2中的代码片段此时被展开为:

图3

请注意,其中/path/to/output/current/path都不是真实的目录名,具体的值依赖于kernel源码根目录的实际路径。/current/path在此时的值为kernel源码根目录所在的路径。

终于,我们看到了zImage这个我们编译的终极目标,它是依赖于sub-make的。但请记住,make在看

到zImage的产生规则以及其依赖目标的规则后,并不会马上去执行这两条规则,而只是先建立zImage和

sub-make的依赖关系。为什么会这样呢?原来make的执行过程是一个2遍扫描过程,这类似于一些编译器

的编译过程,并不是一次到位,而是分为几个阶段。make在第一遍扫描的时候,只是会去创建变量(并

展开某些类型变量)、建立目标依赖关系树,而只有在第二遍的时候才会去跟根据规则去进行目标的创

建过程。所以在这里zImage以及sub-make规则都不会被马上执行,而只是在make内部作了相应记录,而

后继续往后扫描Makefile剩下的内容。

这里有一个很重要的变量skip-makefile,被赋值为1,这个变量和我们接下来的分析密切相关。我

们继续看这个Makefile剩下的内容:

图4

这里用...省略了很多内容,中间的内容非常重要且核心,但对我们当前的第1遍是会被跳过的,因

为此时skip-makefile的值为1,所以ifeq ($(skip-makefile),)是不成立的,其间包含的内容自然也被

跳过。

FORCE这条特殊的空规则,在kernel所有的Makefile中,随时可以看到它的身影,所以有必要简单的

解释一下它的用途。FORCE常见于一条规则依赖列表的末尾,作用是保证依赖它的目标总是被更新。原因

FORCE不对应任何实际文件(.PHONY伪目标),且不依赖任何其标,make会认为这类目标总是要被更新

的,相应依赖它的目标自然也要被更新。

打起精神,我们的重头戏马上要登场了。make完成对kernel顶层Makefile的第1编扫描后,接下来进

入make对Makefile处理的第2阶段,根据阶段1构建的依赖关系树开始一步步地构建我们的终极目标

zImage。起点是图2所示的位置,我们看图2内容展开后的图3zImage依赖于sub-make,所以sub-make

条规则。我们来看sub-make规则的展开后的内容:

图5

这条规则到底做了什么呢?这条规则完成了4个动作:

. make将自己的当前目录切换到了/path/to/output

设置KBUILD_SRC为当前目录(即kernel源码根目录)

. make将重新读取kernel的顶层Makefile(也就是我们当前正在执行的Makefile)

. 构造目标依旧是zImage

到这里你可能产生了疑问,怎么又回到了我们正在执行的Makefile呢?这不是死循环了,没完没了

了吗?事实并非如此。我们一步步来解释为什么。执行sub-make规则的语句后,make重新从头开始读取

kernel的顶层Makefile,时光流逝,make的脚步又重新走到了图1所示的代码片段处,这里的条件分支判

KBUILD_SRC的值是否为空。记得吗?就在刚刚,我们重新进入Makefile之前,已经设置了KBUILD_SRC

的值为kernel顶层Makefile所在的路径(也是kernel源码根目录所在的路径),所以这个对KBUILD_SRC判空的分支是不成立的,自然分支里面的语句也不会被执行,因此和第1遍执行这个Makefile时有了不同的执行路径。

make继续前进到了图4所示的代码片段处,要根据skip-makefile的值决定我们接下来要去向哪里。

而这一次,skip-makefile并没有被定义,所以ifeq ($(skip-makefile),)的判定成立,因此make将执行

被该条件分支包含的代码片段,这将我们整个顶层Makefile分析的核心内容。这里面的内容很多,我们

将逐段进行分析,对代码片段截取也是逐段截图。

图6

    这段代码工作很简单,完成了几个变量的设置。在我的开发环境下,这些变量分别得到了如下的

值(至于如何得到的,就请看官自己分析了):

srctree = /path/to/kernel/source/base
objtree = /path/to/kernel/source/base/output
src = /path/to/kernel/source/base
obj = /path/to/kernel/source/base/output
VPATH = /path/to/kernel/source/base
SUBARCH = x86

值得注意的是,srctree objtree VPATH被export了。继续往下看:

图7(上接图6)

这段代码定义了一些变量,将得到如下所列的值:

ARCH = arm
CROSS_COMPILE = ~/tegra/tk1/crosstool/crosstool-ng/install/bin/arm-cortex_a9-linux-gnueabi-
UTS_MACHINE = arm
SRCARCH = arm
hdr-arch = arm
KCONFIG_CONFIG = .config
CONFIG_SHELL = /bin/bash

这些变量的重要性,我想也不用我多说了吧。路漫漫其修远兮,革命尚未成功,我们继续赶路吧。

(未完待续)

时间: 2024-10-09 17:20:12

Linux Kernel Makefile简析 之 make zImage的相关文章

Linux系统启动流程简析

在日常生活中,我们开机的操作一般为按下电源键,等待系统自己起来就好了.这开机的过程看似简单,但其中却包含着十分复杂的各种小过程.以Linux为例,其流程为下图所示: 一.POST 首先,先介绍下BIOS和POST的概念. BIOS:Basic Input Output System,即基本输入输出系统,它是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序.开机后自检程序和系统自启动程序,它可从CMOS中读写系统设置的具体信息.其主要功能是为计算机提供最底层

Linux目录结构简析

Linux继承了unix操作系统结构清晰的特点.在linux下的文件结构非常有条理.但是,上述的优点只有在对linux相当熟悉时,才能体会到.现在,虫虫就把linux下的目录结构简单介绍一下. /vmlinuz 我们已经知道,每一个linux都有一个内核(vmlinuz),我们在这个内核上添加上可以完成各种特定功能的模块,每个模块就体现在 linux中各种不同的目录上.当然,各种不同的发行套件,其目录有细小的差别,但主要结构都是一样的.我们还要将linux的功能模块和各种应用程序结合起来,这样,

linux装载可执行程序简析

朱宇轲 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 linux中主要的可执行文件为ELF文件,我们可以将它装载到自己的程序中,这次我们就将分析linux装载可执行程序的过程. 首先明确一点,装载可执行程序有两种方式:静态链接与动态链接.所谓静态链接,就是在程序执行之前完成所有链接工作,组成一个可执行文件,放到内存执行.这样做的缺点是,当有多个文件要链接同一份可执行文件时,内存

Linux Kernel文件系统写I/O流程代码分析(一)

Linux Kernel文件系统写I/O流程代码分析(一) 在Linux VFS机制简析(二)这篇博客上介绍了struct address_space_operations里底层文件系统需要实现的操作,实际编码过程中发现不是那么清楚的知道这里面的函数具体是干啥,在什么时候调用.尤其是写IO相关的操作,包括write_begin, write_end, writepage, writepages, direct_IO以及set_page_dirty等函数指针. 要搞清楚这些函数指针,就需要纵观整个

编译android的linux kernel goldfish

https://source.android.com/source/building-kernels.html $ export PATH=/home/hzh/oldhome/learn/android-4.2.2/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin:${PATH} $ export ARCH=arm $ export SUBARCH=arm $ export CROSS_COMPILE=arm-eabi- $ make ARCH=arm g

Linux网络性能优化方法简析

Linux网络性能优化方法简析 2010-12-20 10:56 赵军 IBMDW 字号:T | T 性能问题永远是永恒的主题之一,而Linux在网络性能方面的优势则显而易见,这篇文章是对于Linux内核中提升网络性能的一些优化方法的简析,以让我们去后台看看魔术师表演用的盒子,同时也看看内核极客们是怎样灵活的,渐进的去解决这些实际的问题. AD:2014WOT全球软件技术峰会北京站 课程视频发布 对于网络的行为,可以简单划分为 3 条路径:1) 发送路径,2) 转发路径,3) 接收路径,而网络性

Linux下df与du命令输出区别简析

PS:前些时间有童鞋问我,为什么他的服务器里用df和du命令查询的文件大小显示不一样.其实这两个命令查询原理是不一样的,简析如下: 1.正常情况下,df和du输出结果都会有差距 du -sh命令通过将指定文件系统中所有的目录.符号链接和文件使用的块数累加得到该文件系统使用的总块数: 而df命令通过查看文件系统磁盘块分配图得出总块数与剩余块数. 文件系统分配其中的一些磁盘块用来记录它自身的一些数据,如i节点,磁盘分布图,间接块,超级块等.这些数据对大多数用户级的程序来说是不可见的,通常称为Meta

Linux Kernel的Makefile与Kconfig文件的语法

https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt Introduction ------------ The configuration database is a collection of configuration options organized in a tree structure: +- Code maturity level options | +- Prompt for developme

Linux系统启动过程及其修复过程简析

Linux组成 Linux: kernel+rootfs kernel: 进程管理.内存管理.网络管理.驱动程序.文件系统.安全功能 rootfs:程序和glibc 库:函数集合, function, 调用接口(头文件负责描述) 过程调用:procedure,无返回值 函数调用:function 程序:二进制执行文件 内核设计流派: 单内核(monolithic kernel):Linux 把所有功能集成于同一个程序 微内核(micro kernel):Windows, Solaris 每种功能