搜索
bottom↓
回复: 0

【正点原子FPGA连载】第十六章Linux内核顶层Makefile详解--摘自【正点原子】领航者 ZYNQ 之linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-9-1 10:51:37 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-9-1 16:49 编辑


1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:
QQ群头像.png             
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第十六章Linux内核顶层Makefile详解



前几章我们重点讲解了如何移植uboot到领航者开发板上,从本章开始我们就开始学习如何移植Linux内核。同uboot一样,在具体移植之前,我们先来学习一下Linux内核的顶层Makefile文件,因为顶层Makefile控制着Linux内核的编译流程。



1.1Linux内核获取
关于Linux的起源以及发展历史,这里就不啰嗦了,网上相关的介绍太多了。即使写到这里也只是水一下教程页数而已,没有任何实际的意义。有限的时间还是放到有意义的事情上吧,Linux由Linux基金会管理与发布,Linux官网为https://www.kernel.org,所以你想获取最新的Linux版本就可以在这个网站上下载,网站界面如下图所示:
十六章372.png

图 27.1.1 linux官网

从上图可以看出最新的稳定版Linux已经到了5.1.4,大家没必要追新,因为4.x版本的Linux和5.x版本没有本质上的区别,5.x更多的是加入了一些新的平台、新的外设驱动而已。
Xilinx会从https://www.kernel.org下载某个版本的Linux内核,然后将其移植到自己的芯片平台上,测试成功以后就会将其开放给Xilinx的芯片平台开发者。开发者下载Xilinx提供的Linux内核,然后将其移植到自己的产品上。后面的移植我们使用Xilinx提供的Linux源码,Xilinx提供的Linux源码已经放到了开发板光盘中,路径为:ZYNQ开发板资料盘(A盘)\4_SourceCode\ZYNQ_7020\3_Embedded_Linux\资源文件\kernel\linux-xlnx-xilinx-v2018.3.tar.gz。
1.2Linux内核编译初次编译
先看一下如何编译Linux源码,这里编译一下领航者开发板移植好的Linux源码,已经放到了开发板光盘中,路径为:ZYNQ开发板资料盘(A盘)\4_SourceCode\ZYNQ_7020\3_Embedded_Linux\资源文件\kernel\ linux-4.14.0-atk-v2018.3.tar.gz。可以在Ubuntu中新建一个名为“alientek_linux”的文件夹,然后将linux- linux-4.14.0-atk-v2018.3.tar.gz这个压缩包拷贝到新建的alientek_linux文件夹中并解压。此处我们使用20.1节下载的linux内核。进入到下载到内核目录处,查看内核目录结构,命令如下:
  1. cd ~/work/petalinux/linux-4.14/
  2. ls
复制代码

可以看到Linux源码根目录如下图所示:
十六章1222.png

图 27.2.1正点原子提供的Linux源码根目录

其中oe-logs和oe-workdir是Petalinux工具生成的链接目录,不是linux内核的源码目录。
现在我们讲解一下如何编译出对应的Linux镜像文件。新建名为“zynq.sh”的shell脚本,然后在这个shell脚本里面输入如下所示内容:
示例代码 zynq.sh文件内容
  1. 1 #!/bin/sh
  2. 2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
  3. 3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xilinx_zynq_defconfig
  4. 4 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
  5. 5 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
复制代码

        第2行,执行“make distclean”,清理工程,所以zynq.sh每次都会清理一下工程。如果通过图形界面配置了Linux,但是还没保存新的配置文件,那么就要慎重使用zynq.sh编译脚本了,因为它会把你的配置信息都删除掉。
第3行,执行“make xxx_defconfig”,配置工程。
第4行,执行“make menuconfig”,打开图形配置界面,对Linux进行配置,如果不想每次编译都打开图形配置界面的话可以将这一行删除掉。
第5行,执行“make”,编译Linux源码。
可以看出,Linux的编译过程基本和uboot一样,都要先执行“make xxx_defconfig”来配置一下,然后在执行“make”进行编译。如果需要使用图形界面配置的话就执行“make menuconfig”。
使用chmod给予zynq.sh可执行权限,然后运行此shell脚本,命令如下:
  1. ./zynq.sh
复制代码

编译的时候会弹出Linux图形配置界面,如下图所示:
十六章2146.png

图 27.2.2 Linux图形配置界面

Linux的图行界面配置和uboot是一样的,这里我们不需要做任何的配置,直接按两下ESC键退出,退出图形界面以后会自动开始编译Linux。等待编译完成,完成以后如下图所示:
十六章2300.png

图 27.2.3 Linux编译完成

编译完成以后就会在arch/arm/boot这个目录下生成一个叫做zImage的文件,zImage就是我们要用的Linux镜像文件。另外也会在arch/arm/boo/dts下生成很多.dtb文件,这些.dtb就是设备树文件,比如zynq-altk.dtb就是领航者开发板对应的设备树。
1.3Linux工程目录分析
将正点原子提供的Linux源码进行解压,解压完成以后的目录如下图所示:
十六章2556.png

图 27.3.1未编译的Linux源码目录

上图就是正点原子提供的未编译的Linux源码目录文件,我们在分析Linux之前一定要先在Ubuntu中编译一下Linux,因为编译过程会生成一些文件,而生成的这些恰恰是分析Linux不可或缺的文件。编译后的Linux目录如下图所示:
十六章2740.png

图 27.3.2编译后的Linux目录

上图中重要的文件夹或文件的含义如下表所示:
表 26.2.2.1 Linux目录

2622.png

26221.png

上表中的很多文件夹和文件我们都不需要去关心,我们要关注的文件夹或文件如下:
1、arch目录
这个目录是和架构有关的目录,比如arm、arm64、avr32、x86等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如boot、common、configs等等,以arch/arm为例,其子目录如下图所示:
十六章4066.png

图 27.3.3 arch/arm子目录

上图是arch/arm的一部分子目录,这些子目录用于控制系统引导、系统调用、动态调频、主频设置等。arch/arm/configs目录是不同平台的默认配置文件:xxx_defconfig,如下图所示:
十六章4233.png

图 27.3.4配置文件

在arch/arm/configs中就包含有领航者开发板的默认配置文件:xilinx_zynq_defconfig,执行“make xilinx_zynq_defconfig”即可完成配置。arch/arm/boot/dts目录里面是对应开发平台的设备树文件,正点原子领航者开发板对应的设备树文件如下图所示:
十六章4446.png

图 27.3.5正点原子领航者开发板对应的设备树

arch/arm/boot目录中有编译出来的Image和zImage镜像文件,而zImage就是我们要用的linux镜像文件。
arch/arm/mach-xxx目录分别为相应平台的驱动和初始化文件,比如mach-zynq目录里面就是ZYNQ系列CPU的驱动和初始化文件。
2、block目录
block是Linux下块设备目录,像SD卡、EMMC、NAND、硬盘等存储设备就属于块设备,block目录中存放着管理块设备的相关文件。
3、crypto目录
crypto目录里面存放着加密文件,比如常见的crc、crc32、md4、md5、hash等加密算法。
4、Documentation目录
此目录里面存放着Linux相关的文档,如果要想了解Linux某个功能模块或驱动架构的功能,就可以在Documentation目录中查找有没有对应的文档。
5、drivers目录
设备驱动程序目录,此目录根据驱动类型的不同,分门别类进行整理,比如drivers/i2c就是I2C相关驱动目录,drivers/gpio就是GPIO相关的驱动目录,这是我们学习的重点。
6、firmware目录
此目录用于存放固件。
7、fs目录
此目录存放文件系统相关代码,比如fs/ext2、fs/ext4、fs/f2fs等,分别是ext2、ext4和f2fs等文件系统。
8、include目录
头文件目录。
9、init目录
此目录存放Linux内核启动的时候初始化代码。
10、ipc目录
IPC为进程间通信,ipc目录是进程间通信的具体实现代码。
11、kernel目录
Linux内核代码。与平台相关的部分代码放在arch/*/kernel目录下,其中*代表各种处理器平台
12、lib目录
lib是库的意思,lib目录都是一些公用的库函数。
13、mm目录
此目录存放与平台无关的内存管理代码,与平台相关的内存管理代码放在arch/*/mm目录下。
14、net目录
此目录存放网络相关代码。
15、samples目录
此目录存放一些示例代码文件。
16、scripts目录
脚本目录,Linux编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录中。
17、security目录
此目录存放安全相关的文件。
18、sound目录
此目录存放音频相关驱动文件,音频驱动文件并没有存放到drivers目录中,而是单独的目录。
19、tools目录
此目录存放一些编译的时候使用到的工具。
20、usr目录
此目录存放与initramfs有关的代码。
21、virt目录
此目录存放虚拟机相关文件。
22、.config文件
根uboot一样,.config保存着Linux最终的配置信息,编译Linux的时候会读取此文件中的配置信息。最终根据配置信息来选择编译Linux哪些模块,哪些功能。
23、Kbuild文件
有些Makefile会读取此文件。
24、Kconfig文件
图形化配置界面的配置文件。
25、Makefile文件
Linux顶层Makefile文件,建议好好阅读一下此文件。
26、README文件
此文件详细讲解了如何编译Linux源码,以及Linux源码的目录信息,建议仔细阅读一下此文件。
关于Linux源码目录就分析到这里,接下来分析一下Linux的顶层Makefile。


1.4VSCode工程创建
在分析Linux的顶层Makefile之前,先创建VSCode工程,创建过程和uboot一样。创建好以后将文件.vscode/settings.json改为如下所示内容:
示例代码 settings.json文件内容
  1. 1 {
  2. 2     "search.exclude": {
  3. 3     "**/node_modules": true,
  4. 4     "**/bower_components": true,
  5. 5     "**/*.o":true,
  6. 6     "**/*.su":true,
  7. 7     "**/*.cmd":true,
  8. 8     "Documentation":true,
  9. 9
  10. 10     /* 屏蔽不用的架构相关的文件 */
  11. 11     "arch/alpha":true,
  12. 12     "arch/arc":true,
  13. 13     "arch/arm64":true,
  14. 14     "arch/avr32":true,
  15. 15     "arch/[b-z]*":true,
  16. 16     "arch/arm/plat*":true,
  17. 17     "arch/arm/mach-[a-y]*":true,
  18. 18     "arch/arm/mach-zx":true,
  19. 19
  20. 20     /* 屏蔽不用的配置文件 */
  21. 21     "arch/arm/configs/[a-w]*":true,
  22. 22     "arch/arm/configs/[y-z]*":true,
  23. 23
  24. 24     /* 屏蔽不用的DTB文件 */
  25. 25     "arch/arm/boot/dts/[a-y]*":true,
  26. 26     "arch/arm/boot/dts/.*":true,
  27. 27     },
  28. 28
  29. 29     "files.exclude": {
  30. 30     "**/.git": true,
  31. 31     "**/.svn": true,
  32. 32     "**/.hg": true,
  33. 33     "**/CVS": true,
  34. 34     "**/.DS_Store": true,
  35. 35     "**/*.o":true,
  36. 36     "**/*.su":true,
  37. 37     "**/*.cmd":true,
  38. 38     "Documentation":true,
  39. 39
  40. 40     /* 屏蔽不用的架构相关的文件 */
  41. 41     "arch/alpha":true,
  42. 42     "arch/arc":true,
  43. 43     "arch/arm64":true,
  44. 44     "arch/avr32":true,
  45. 45     "arch/[b-z]*":true,
  46. 46     "arch/arm/plat*":true,
  47. 47     "arch/arm/mach-[a-y]*":true,
  48. 48     "arch/arm/mach-zx":true,
  49. 49     
  50. 50                 /* 屏蔽不用的配置文件 */
  51. 51     "arch/arm/configs/[a-w]*":true,
  52. 52     "arch/arm/configs/[y-z]*":true,
  53. 53
  54. 54     /* 屏蔽不用的DTB文件 */
  55. 55     "arch/arm/boot/dts/[a-y]*":true,
  56. 56     "arch/arm/boot/dts/.*":true,
  57. 57     }
  58. 58 }
复制代码

创建好VSCode工程以后就可以开始分析Linux的顶层Makefile了。
1.5顶层Makefile详解
Linux的顶层Makefile和uboot的顶层Makefile非常相似,因为uboot参考了Linux,前610行几乎一样,所以前面部分我们大致看一下就行了。
1、版本号
顶层Makefile一开始就是Linux内核的版本号,如下所示:
示例代码 顶层Makefile代码段
  1. 1 # SPDX-License-Identifier: GPL-2.0
  2. 2 VERSION = 4
  3. 3 PATCHLEVEL = 14
  4. 4 SUBLEVEL = 0
  5. 5 EXTRAVERSION =
复制代码

可以看出,Linux内核版本号为4.14.0。
2、MAKEFLAGS变量
MAKEFLAGS变量设置如下所示:
示例代码 顶层Makefile代码段
  1. 17 MAKEFLAGS += -rR --include-dir=$(CURDIR)
复制代码

3、命令输出
Linux编译的时候也可以通过“V=1”来输出完整的命令,这个和uboot一样,相关代码如下所示:
示例代码 顶层Makefile代码段
  1. 70   ifeq ("$(origin V)", "command line")
  2. 71     KBUILD_VERBOSE = $(V)
  3. 72   endif
  4. 73   ifndef KBUILD_VERBOSE
  5. 74     KBUILD_VERBOSE = 0
  6. 75   endif
  7. 76   
  8. 77   ifeq ($(KBUILD_VERBOSE),1)
  9. 78     quiet =
  10. 79     Q =
  11. 80   else
  12. 81     quiet=quiet_
  13. 82     Q = @
  14. 83   endif
复制代码

4、静默输出
Linux编译的时候使用“make -s”就可实现静默编译,编译的时候就不会打印任何的信息,同uboot一样,相关代码如下:
示例代码 顶层Makefile代码段
  1. 85   # If the user is running make -s (silent mode), suppress echoing of
  2. 86   # commands
  3. 87
  4. 88   ifneq ($(findstring s,$(filter-out --%,$(MAKEFLAGS))),)
  5. 89     quiet=silent_
  6. 90     tools_silent=s
  7. 91   endif
  8. 92   
  9. 93   export quiet Q KBUILD_VERBOSE
复制代码

5、设置编译结果输出目录
Linux编译的时候使用“O=xxx”即可将编译产生的过程文件输出到指定的目录中,相关代码如下:
示例代码 顶层Makefile代码段
  1. 112  ifeq ($(KBUILD_SRC),)
  2. 113  
  3. 114  # OK, Make called in directory where kernel src resides
  4. 115  # Do we want to locate output files in a separate directory?
  5. 116  ifeq ("$(origin O)", "command line")
  6. 117    KBUILD_OUTPUT := $(O)
  7. 118  endif
复制代码

        6、代码检查
Linux也支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile中的代码如下:
示例代码 顶层Makefile代码段
  1. 172  ifeq ("$(origin C)", "command line")
  2. 173    KBUILD_CHECKSRC = $(C)
  3. 174  endif
  4. 175  ifndef KBUILD_CHECKSRC
  5. 176    KBUILD_CHECKSRC = 0
  6. 177  endif
复制代码

7、模块编译
Linux允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile中的代码如下:
示例代码 顶层Makefile代码段
  1. 179  # Use make M=dir to specify directory of external module to build
  2. 180  # Old syntax make ... SUBDIRS=$PWD is still supported
  3. 181  # Setting the environment variable KBUILD_EXTMOD take precedence
  4. 182  ifdef SUBDIRS
  5. 183    KBUILD_EXTMOD ?= $(SUBDIRS)
  6. 184  endif
  7. 185  
  8. 186  ifeq ("$(origin M)", "command line")
  9. 187    KBUILD_EXTMOD := $(M)
  10. 188  endif
  11. 189  
  12. 190  # If building an external module we do not care about the all: rule
  13. 191  # but instead _all depend on modules
  14. 192  PHONY += all
  15. 193  ifeq ($(KBUILD_EXTMOD),)
  16. 194  _all: all
  17. 195  else
  18. 196  _all: modules
  19. 197  endif
  20. 198  
  21. 199  ifeq ($(KBUILD_SRC),)
  22. 200          # building in the source tree
  23. 201          srctree := .
  24. 202  else
  25. 203          ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
  26. 204                  # building in a subdirectory of the source tree
  27. 205                  srctree := ..
  28. 206          else
  29. 207                  srctree := $(KBUILD_SRC)
  30. 208          endif
  31. 209  endif
  32. 210  objtree        := .
  33. 211  src        := $(srctree)
  34. 212  obj        := $(objtree)
  35. 213  
  36. 214  VPATH      := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
  37. 215  
  38. 216  export srctree objtree VPATH
复制代码

外部模块编译过程和uboot也一样,最终导出srctree、objtree和VPATH这三个变量的值,其中srctree=.,也就是当前目录,objtree同样为“.”。
8、设置目标架构和交叉编译器
同uboot一样,Linux编译的时候需要设置目标板架构ARCH和交叉编译器CROSS_COMPILE,在顶层Makefile中代码如下:
示例代码 顶层Makefile代码段
  1. 251  ARCH       ?= $(SUBARCH)
  2. 252  CROSS_COMPILE  ?= $(CONFIG_CROSS_COMPILE:"%"=%)
复制代码

有时为了方便,可以直接修改顶层Makefile中的ARCH和CROSS_COMPILE,将其设置为对应的架构和编译器,比如本教程可以将ARCH设置为arm,CROSS_COMPILE设置为arm-linux-gnueabihf-,如下所示:
示例代码 顶层Makefile代码段
  1. 251  ARCH       ?= arm
  2. 252  CROSS_COMPILE  ?=  arm-linux-gnueabihf-
复制代码

设置好以后就可以使用如下命令编译Linux了:
  1. make xxx_defconfig                //使用默认配置文件配置Linux
  2. make menuconfig                        //启动图形化配置界面
  3. make -j16                                //编译Linux
复制代码

9、调用scripts/Kbuild.include文件
同uboot一样,Linux顶层Makefile也会调用文件scripts/Kbuild.include,顶层Makefile相应代码如下:
示例代码 顶层Makefile代码段
  1. 345  # We need some generic definitions (do not try to remake the file).
  2. 346  scripts/Kbuild.include: ;
  3. 347  include scripts/Kbuild.include
复制代码

10、交叉编译工具变量设置
顶层Makefile中其他和交叉编译器有关的变零设置如下:
示例代码 顶层Makefile代码段
  1. 349  # Make variables (CC, etc...)
  2. 350  AS     = $(CROSS_COMPILE)as
  3. 351  LD     = $(CROSS_COMPILE)ld
  4. 352  CC     = $(CROSS_COMPILE)gcc
  5. 353  CPP        = $(CC) -E
  6. 354  AR     = $(CROSS_COMPILE)ar
  7. 355  NM     = $(CROSS_COMPILE)nm
  8. 356  STRIP      = $(CROSS_COMPILE)strip
  9. 357  OBJCOPY        = $(CROSS_COMPILE)objcopy
  10. 358  OBJDUMP        = $(CROSS_COMPILE)objdump
复制代码

LA、LD、CC等这些都是交叉编译器所使用的工具。
11、头文件路径变量
顶层Makefile定义了两个变量保存头文件路径:USERINCLUDE和LINUXINCLUDE,相关代码如下:
示例代码 顶层Makefile代码段
  1. 380  # Use USERINCLUDE when you must reference the UAPI directories only.
  2. 381  USERINCLUDE    := \
  3. 382         -I$(srctree)/arch/$(hdr-arch)/include/uapi \
  4. 383         -I$(objtree)/arch/$(hdr-arch)/include/generated/uapi \
  5. 384         -I$(srctree)/include/uapi \
  6. 385         -I$(objtree)/include/generated/uapi \
  7. 386                  -include $(srctree)/include/linux/kconfig.h
  8. 387  
  9. 388  # Use LINUXINCLUDE when you must reference the include/ directory.
  10. 389  # Needed to be compatible with the O= option
  11. 390  LINUXINCLUDE    := \
  12. 391         -I$(srctree)/arch/$(hdr-arch)/include \
  13. 392         -I$(objtree)/arch/$(hdr-arch)/include/generated \
  14. 393         $(if $(KBUILD_SRC), -I$(srctree)/include) \
  15. 394         -I$(objtree)/include \
  16. 395         $(USERINCLUDE)
复制代码

第380~386行的USERINCLUDE是UAPI相关的头文件路径,第390~395行的LINUXINCLUDE是Linux内核源码的头文件路径。重点来看下LINUXINCLUDE,其中srctree=.,objtree=.,hdr-arch=arm,KBUILD_SRC为空,因此,将USERINCLUDE和LINUXINCLUDE展开以后为:
  1. USERINCLUDE    := \
  2.                 -I./arch/arm/include/uapi \
  3.                 -I./arch/arm/include/generated/uapi \
  4.                 -I./include/uapi \
  5.                 -I./include/generated/uapi \
  6.         -include ./include/linux/kconfig.h

  7. LINUXINCLUDE    := \
  8.                 -I./arch/arm/include \
  9.                 -I./arch/arm/include/generated \
  10.                 -I./include \
  11.                 -I./arch/arm/include/uapi \
  12.                 -I./arch/arm/include/generated/uapi \
  13.                 -I./include/uapi \
  14.                 -I./include/generated/uapi \
  15.         -include ./include/linux/kconfig.h
复制代码

12、导出变量
顶层Makefile会导出很多变量给子Makefile使用,导出的这些变量如下:
示例代码 顶层Makefile代码段
  1. 417  export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
  2. 418  export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
  3. 419  export CPP AR NM STRIP OBJCOPY OBJDUMP HOSTLDFLAGS HOST_LOADLIBES
  4. 420  export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
  5. 421  export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
  6. 422  
  7. 423  export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
  8. 424  export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KCOV CFLAGS_KASAN CFLAGS_UBSAN
  9. 425  export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
  10. 426  export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
  11. 427  export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
  12. 428  export KBUILD_ARFLAGS
复制代码

1.5.1 make xxx_defconfig过程
第一次编译Linux之前都要先使用“make xxx_defconfig”配置Linux内核,在顶层Makefile中有“%config”这个目标,如下所示:
示例代码 27.5.117.5.1顶层Makefile代码段
  1. 491  config-targets := 0
  2. 492  mixed-targets  := 0
  3. 493  dot-config     := 1
  4. 494  
  5. 495  ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
  6. 496     ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
  7. 497         dot-config := 0
  8. 498     endif
  9. 499  endif
  10. 500  
  11. 501  ifeq ($(KBUILD_EXTMOD),)
  12. 502          ifneq ($(filter config %config,$(MAKECMDGOALS)),)
  13. 503                  config-targets := 1
  14. 504                  ifneq ($(words $(MAKECMDGOALS)),1)
  15. 505                          mixed-targets := 1
  16. 506                  endif
  17. 507          endif
  18. 508  endif
  19. 509  # install and modules_install need also be processed one by one
  20. 510  ifneq ($(filter install,$(MAKECMDGOALS)),)
  21. 511          ifneq ($(filter modules_install,$(MAKECMDGOALS)),)
  22. 512             mixed-targets := 1
  23. 513          endif
  24. 514  endif
  25. 515  
  26. 516  ifeq ($(mixed-targets),1)
  27. 517  # ===========================================================================
  28. 518  # We're called with mixed targets (*config and build targets).
  29. 519  # Handle them one by one.
  30. 520  
  31. 521  PHONY += $(MAKECMDGOALS) __build_one_by_one
  32. 522  
  33. 523  $(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one
  34. 524     @:
  35. 525  
  36. 526  __build_one_by_one:
  37. 527     $(Q)set -e; \
  38. 528     for i in $(MAKECMDGOALS); do \
  39. 529         $(MAKE) -f $(srctree)/Makefile $i; \
  40. 530     done
  41. 531  
  42. 532  else
  43. 533  ifeq ($(config-targets),1)
  44. 534  # ===========================================================================
  45. 535  # *config targets only - make sure prerequisites are updated, and descend
  46. 536  # in scripts/kconfig to make the *config target
  47. 537  
  48. 538  # Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
  49. 539  # KBUILD_DEFCONFIG may point out an alternative default configuration
  50. 540  # used for 'make defconfig'
  51. 541  include arch/$(SRCARCH)/Makefile
  52. 542  export KBUILD_DEFCONFIG KBUILD_KCONFIG
  53. 543  
  54. 544  config: scripts_basic outputmakefile FORCE
  55. 545     $(Q)$(MAKE) $(build)=scripts/kconfig $@
  56. 546  
  57. 547  %config: scripts_basic outputmakefile FORCE
  58. 548     $(Q)$(MAKE) $(build)=scripts/kconfig $@
  59. 549  
  60. 550  else
  61. ......
  62. 571  endif # KBUILD_EXTMOD
复制代码

第491~514行和uboot一样,都是设置定义变量config-targets、mixed-targets和dot-config的值,最终这三个变量的值为:
  1. config-targets= 1
  2. mixed-targets= 0
  3. dot-config= 1
复制代码

因为config-targets=1,因此第541行~548行成立。第541行引用arch/arm/Makefile这个文件,这个文件很重要,以为zImage、uImage等这些文件就是由arch/arm/Makefile来生成的。
第542行导出变量KBUILD_DEFCONFIG KBUILD_KCONFIG。
第544行,没有目标与之匹配,因此不执行。
第547行,“make xxx_defconfig”与目标“%config”匹配,因此被执行。“%config”依赖scripts_basic、outputmakefile和FORCE,真正有意义的依赖是scripts_basic。scripts_basic的规则如下:
示例代码 27.5.217.5.2 顶层Makefile代码段
  1. 448  scripts_basic:
  2. 449     $(Q)$(MAKE) $(build)=scripts/basic
  3. 450     $(Q)rm -f .tmp_quiet_recordmcount
复制代码

build定义在文件scripts/Kbuild.include中,值为build := -f $(srctree)/scripts/Makefile.build obj,因此将上述示例代码展开就是:
  1. scripts_basic:
  2.         @make -f ./scripts/Makefile.build obj=scripts/basic        //也可以没有@,视配置而定
  3.         @rm -f . tmp_quiet_recordmcount                                             //也可以没有@
复制代码

接着回到Makefile的目标“%config”处,内容如下:
  1. %config: scripts_basic outputmakefile FORCE
  2.         $(Q)$(MAKE) $(build)=scripts/kconfig $@
复制代码

将命令展开就是:
  1. @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
复制代码

1.5.2Makefile.build脚本分析
从上一小节可知,“make xxx_defconfig“配置Linux的时候如下两行命令会执行脚本scripts/Makefile.build:
  1. @make -f ./scripts/Makefile.build obj=scripts/basic
  2. @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
复制代码

我们依次来分析一下:
1、scripts_basic目标对应的命令
scripts_basic目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文件scripts/Makefile.build,有如下代码:
示例代码 27.5.317.5.3  Makefile.build代码段
  1. 42 # The filename Kbuild has precedence over Makefile
  2. 43 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
  3. 44 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
  4. 45 include $(kbuild-file)
复制代码

将kbuild-dir展开后为:
  1. kbuild-dir=./scripts/basic
复制代码

将kbuild-file展开后为:
  1. kbuild-file= ./scripts/basic/Makefile
复制代码

最后将45行展开,即:
  1. include ./scripts/basic/Makefile
复制代码

继续分析scripts/Makefile.build,如下代码:
示例代码 27.5.417.5.4 Makefile.build代码段
  1. 96 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
  2. 97      $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
  3. 98      $(subdir-ym) $(always)
  4. 99     @:
复制代码

__build是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标__build。在顶层Makefile中,KBUILD_BUILTIN为1,KBUILD_MODULES为空,因此展开后目标__build为:
  1. __build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
  2.         @:
复制代码

可以看出目标__build有5个依赖:builtin-target、lib-target、extra-y、subdir-ym和always。这5个依赖的具体内容如下:
  1. builtin-target =
  2. lib-target =
  3. extra-y =
  4. subdir-ym =
  5. always = scripts/basic/fixdep scripts/basic/bin2c
复制代码

只有always有效,因此__build最终为:
  1. __build: scripts/basic/fixdep scripts/basic/bin2c
  2.         @:
复制代码

__build依赖于scripts/basic/fixdep和scripts/basic/bin2c,所以要先将scripts/basic/fixdep.c和scripts/basic/bin2c.c这两个文件编译成fixdep和bin2c。
综上所述,scripts_basic目标的作用就是编译scripts/basic/fixdep和scripts/basic/bin2c这两个文件。
2、 %config目标对应的命令
%config目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig,此命令会使用到的各个变量值如下:
  1. src= scripts/kconfig
  2. kbuild-dir = ./scripts/kconfig
  3. kbuild-file = ./scripts/kconfig/Makefile
  4. include ./scripts/kconfig/Makefile
复制代码

可以看出,Makefilke.build会读取scripts/kconfig/Makefile中的内容,此文件有如下所示内容:
示例代码 27.5.517.5.5 scripts/kconfig/Makefile代码段
  1. 112 %_defconfig: $(obj)/conf
  2. 113     $(Q)[        DISCUZ_CODE_983        ]lt; $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
复制代码

目标%_defconfig与 xxx_defconfig匹配,所以会执行这条规则,将其展开就是:
  1. %_defconfig: scripts/kconfig/conf
  2.         @ scripts/kconfig/conf  --defconfig=arch/arm/configs/%_defconfig  Kconfig
复制代码

%_defconfig依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c生成conf这个软件。此软件就会将%_defconfig中的配置输出到.config文件中,最终生成Linux kernel根目录下的.config文件。
1.5.3make过程
使用命令“make xxx_defconfig”配置好Linux内核以后就可以使用“make”或者“make all”命令进行编译。顶层Makefile有如下代码:
示例代码 27.5.617.5.6顶层Makefile代码段
  1. 120  # That's our default target when none is given on the command line
  2. 121  PHONY := _all
  3. 122  _all:
  4. ......
  5. 190  # If building an external module we do not care about the all: rule
  6. 191  # but instead _all depend on modules
  7. 192  PHONY += all
  8. 193  ifeq ($(KBUILD_EXTMOD),)
  9. 194  _all: all
  10. 195  else
  11. 196  _all: modules
  12. 197  endif
  13. ......
  14. 623  all: vmlinux
复制代码

        第122行,_all是默认目标,如果使用命令“make”编译Linux的话此目标就会被匹配。
第193行,如果KBUILD_EXTMOD为空的话194行的代码成立。
第194行,默认目标_all依赖all。
第623行,目标all依赖vmlinux,所以接下来的重点就是vmlinux。
顶层Makefile中有如下代码:
示例代码 27.5.717.5.7顶层Makefile代码段
  1. 966  # Externally visible symbols (used by link-vmlinux.sh)
  2. 967  export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
  3. 968  export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
  4. 969  export KBUILD_VMLINUX_LIBS := $(libs-y1)
  5. 970  export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds
  6. 971  export LDFLAGS_vmlinux
  7. 972  # used by scripts/package/Makefile
  8. 973  export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools)
  9. 974  
  10. 975  vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)
  11. 976  
  12. 977  # Include targets which we want to execute sequentially if the rest of the
  13. 978  # kernel build went well. If CONFIG_TRIM_UNUSED_KSYMS is set, this might be
  14. 979  # evaluated more than once.
  15. 980  PHONY += vmlinux_prereq
  16. 981  vmlinux_prereq: $(vmlinux-deps) FORCE
  17. 982  ifdef CONFIG_HEADERS_CHECK
  18. 983     $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
  19. 984  endif
  20. 985  ifdef CONFIG_GDB_SCRIPTS
  21. 986     $(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
  22. 987  endif
  23. 988  ifdef CONFIG_TRIM_UNUSED_KSYMS
  24. 989     $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \
  25. 990       "$(MAKE) -f $(srctree)/Makefile vmlinux"
  26. 991  endif
  27. 992  
  28. 993  # standalone target for easier testing
  29. 994  include/generated/autoksyms.h: FORCE
  30. 995     $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh true
  31. 996  
  32. 997  ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)
  33. 998  
  34. 999  # Final link of vmlinux with optional arch pass after final link
  35. 1000 cmd_link-vmlinux =                                                 \
  36. 1001    $(CONFIG_SHELL) [        DISCUZ_CODE_986        ]lt; $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) ;    \
  37. 1002    $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
  38. 1003
  39. 1004 vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE
  40. 1005    +$(call if_changed,link-vmlinux)
复制代码

        从第1004行可以看出目标vmlinux依赖scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE。第975行定义了vmlinux-deps,值为:
vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)
第967行,KBUILD_VMLINUX_INIT = $(head-y) $(init-y)。
第968行,KBUILD_VMLINUX_MAIN = $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)。
第968行,KBUILD_VMLINUX_LIBS = $(libs-y1)。
第970行,KBUILD_LDS = arch/$(SRCARCH)/kernel/vmlinux.lds,其中SRCARCH=arm,因此KBUILD_LDS= arch/arm/kernel/vmlinux.lds。
综上所述,vmlinux的依赖为:scripts/link-vmlinux.sh、$(head-y)、$(init-y)、$(core-y)、$(libs-y1)、$(libs-y2)、$(drivers-y)、$(net-y)、$(virt-y)、arch/arm/kernel/vmlinux.lds和FORCE。
第1005行的命令用于链接生成vmlinux。
重点来看下$(head-y)、$(init-y)、$(core-y)、$(libs-y1)、$(libs-y2)、$(drivers-y)和$(net-y)这七个变量的值。
1、head-y
head-y定义在文件arch/arm/Makefile中,内容如下:
示例代码 27.5.817.5.8 arch/arm/Makefile代码段
  1. 139 head-y      := arch/arm/kernel/head$(MMUEXT).o
复制代码

        当不使能MMU的话MMUEXT=-nommu,如果使能MMU的话为空,因此head-y最终的值为:
  1. head-y = arch/arm/kernel/head.o
复制代码

2、init-y、drivers-y和net-y
在顶层Makefile中有如下代码:
示例代码 27.5.917.5.9 顶层Makefile代码段
  1. 564  # Objects we will link into vmlinux / subdirs we need to visit
  2. 565  init-y     := init/
  3. 566  drivers-y  := drivers/ sound/ firmware/
  4. 567  net-y      := net/
  5. 958  init-y     := $(patsubst %/, %/built-in.o, $(init-y))
  6. 959  core-y     := $(patsubst %/, %/built-in.o, $(core-y))
  7. 960  drivers-y  := $(patsubst %/, %/built-in.o, $(drivers-y))
  8. 961  net-y      := $(patsubst %/, %/built-in.o, $(net-y))
复制代码

        从上述示例代码可知,init-y、libs-y、drivers-y和net-y最终的值为:
  1. init-y        = init/built-in.o
  2. drivers-y        = drivers/built-in.o  sound/built-in.o  firmware/built-in.o
  3. net-y        = net/built-in.o
复制代码

3、libs-y1和libs-y2
libs-y1和libs-y2基本和init-y一样,在顶层Makefile中存在如下代码:
示例代码 27.5.1017.5.10顶层Makefile代码段
  1. 568  libs-y     := lib/
  2. 962  libs-y1   := $(patsubst %/, %/lib.a, $(libs-y))
  3. 963  libs-y2   := $(filter-out %.a, $(patsubst %/, %/built-in.o, $(libs-y)))
复制代码

        根据上述示例代码可知,libs-y1应该等于“lib/lib.a”,libs-y2应该等于“lib/built-in.o”。这个只正确了一部分。因为在arch/arm/Makefile中向libs-y追加了一些值,代码如下:
  1. 300 libs-y              := arch/arm/lib/ $(libs-y)
复制代码

因此可知,libs-y1、libs-y2最终应该为:
  1. libs-y1 = arch/arm/lib/lib.a lib/lib.a
  2. libs-y2 = arch/arm/lib/built-in.o  lib/built-in.o
复制代码

4、core-y
core-y和init-y也一样,在顶层Makefile中有如下代码:
示例代码 27.5.1117.5.11 顶层Makefile代码段
  1. 569  core-y     := usr/
  2. 949  core-y     += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
复制代码

        但是在arch/arm/Makefile中会对core-y进行追加,代码如下:
示例代码 27.5.1217.5.12 arch/arm/Makefile代码段
  1. 283 core-$(CONFIG_FPE_NWFPE)    += arch/arm/nwfpe/
  2. 284 core-$(CONFIG_FPE_FASTFPE)  += $(FASTFPE_OBJ)
  3. 285 core-$(CONFIG_VFP)      += arch/arm/vfp/
  4. 286 core-$(CONFIG_XEN)      += arch/arm/xen/
  5. 287 core-$(CONFIG_KVM_ARM_HOST)     += arch/arm/kvm/
  6. 288 core-$(CONFIG_VDSO)     += arch/arm/vdso/
  7. 289
  8. 290 # If we have a machine-specific directory, then include it in the build.
  9. 291 core-y              += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
  10. 292 core-y              += arch/arm/probes/
  11. 293 core-y              += arch/arm/net/
  12. 294 core-y              += arch/arm/crypto/
  13. 295 core-y              += arch/arm/firmware/
  14. 296 core-y              += $(machdirs) $(platdirs)
复制代码

        第283~288行根据不同的配置向core-y追加不同的值,比如使能VFP的话就会在.config中有CONFIG_VFP=y这一行,那么core-y就会追加“arch/arm/vfp/”。
第291~296行就是对core-y直接追加的值。
在顶层Makefile中有如下一行:
示例代码 27.5.1317.5.13 顶层Makefile代码段
  1. 959  core-y     := $(patsubst %/, %/built-in.o, $(core-y))
复制代码

        经过上述代码的转换,最终core-y的值为:
  1. core-y =usr/built-in.o                                        arch/arm/vfp/built-in.o \
  2. certs/built-in.o                arch/arm/kernel/built-in.o \
  3. arch/arm/mm/built-in.o                         arch/arm/common/built-in.o \
  4. arch/arm/probes/built-in.o                arch/arm/net/built-in.o \
  5. arch/arm/crypto/built-in.o                 arch/arm/firmware/built-in.o \
  6. arch/arm/mach-zynq/built-in.o        kernel/built-in.o\
  7. mm/built-in.o                                     fs/built-in.o \
  8. ipc/built-in.o                                         security/built-in.o \
  9. crypto/built-in.o                                 block/built-in.o
复制代码

关于head-y、init-y、core-y、libs-y1、libs-y2、drivers-y和net-y这7个变量就讲解到这里。这些变量都是一些built-in.o或.a等文件,这个和uboot一样,都是将相应目录中的源码文件进行编译,然后在各自目录下生成built-in.o文件,有些生成了.a库文件。最终将这些built-in.o和.a文件进行链接即可形成ELF格式的可执行文件,也就是vmlinux。但是链接是需要连接脚本的,vmlinux依赖的arch/arm/kernel/vmlinux.lds就是整个Linux的链接脚本。
在示例代码 27.5.7的第1005行的命令“+$(call if_changed,link-vmlinux)”表示将$(call if_changed,link-vmlinux)的结果作为最终生成vmlinux的命令,前面的“+”表示该命令结果不可忽略。$(call if_changed,link-vmlinux)调用函数if_changed,link-vmlinux是函数if_changed的参数,函数if_changed定义在文件scripts/Kbuild.include中,如下所示:
示例代码 27.5.1417.5.14 scripts/Kbuild.include代码段
  1. 264 if_changed = $(if $(strip $(any-prereq) $(arg-check)),                \
  2. 265     @set -e;                                                     \
  3. 266     $(echo-cmd) $(cmd_$(1));                                      \
  4. 267     printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
复制代码

        any-prereq用于检查依赖文件是否有变化,如果依赖文件有变化那么any-prereq就不为空,否则就为空。arg-check用于检查参数是否有变化,如果没有变化那么arg-check就为空。
第265行,“@set -e”告诉bash,如果任何语句的执行结果不为true(也就是执行出错)的话就直接退出。
第266行,$(echo-cmd)用于打印命令执行过程,比如在链接vmlinux的时候就会输出“LINK vmlinux”。$(cmd_$(1))中的$(1)表示参数,也就是link-vmlinux,因此$(cmd_$(1))表示执行cmd_link-vmlinux的内容。cmd_link-vmlinux在顶层Makefile中有如下所示定义:
示例代码 27.5.1517.5.15顶层Makefile代码段
  1. 999  # Final link of vmlinux with optional arch pass after final link
  2. 1000 cmd_link-vmlinux =                                                 \
  3. 1001    $(CONFIG_SHELL) [        DISCUZ_CODE_999        ]lt; $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) ;    \
  4. 1002    $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
复制代码

        第1001~1002行就是cmd_link-vmlinux的值,其中CONFIG_SHELL=/bin/bash、$<表示目标vmlinux的第一个依赖文件,根据示例代码 27.5.7的第1004行可知,这个文件为scripts/link-vmlinux.sh。
LD= arm-linux-gnueabihf-ld -EL,LDFLAGS为空。LDFLAGS_vmlinux的值由顶层Makefile和arch/arm/Makefile这两个文件共同决定,最终LDFLAGS_vmlinux=-p --no-undefined -X --pic-veneer --build-id。第1002行的语句因为ARCH_POSTLINK为空,所以不起作用。因此cmd_link-vmlinux最终的值为:
  1. cmd_link-vmlinux = /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --no-undefined -X --pic-veneer --build-id
复制代码

cmd_link-vmlinux会调用scripts/link-vmlinux.sh脚本来链接出vmlinux文件。在scripts/link-vmlinux.sh中有如下所示代码:
示例代码 27.5.1617.5.16 scripts/link-vmlinux.sh代码段
  1. 90  # Link of vmlinux
  2. 91  # ${1} - optional extra .o files
  3. 92  # ${2} - output file
  4. 93  vmlinux_link()
  5. 94  {
  6. 95      local lds="${objtree}/${KBUILD_LDS}"
  7. 96      local objects
  8. 97  
  9. 98      if [ "${SRCARCH}" != "um" ]; then
  10. 99          if [ -n "${CONFIG_THIN_ARCHIVES}" ]; then
  11. 100             objects="--whole-archive            \
  12. 101                 built-in.o              \
  13. 102                 --no-whole-archive          \
  14. 103                 --start-group               \
  15. 104                 ${KBUILD_VMLINUX_LIBS}          \
  16. 105                 --end-group             \
  17. 106                 ${1}"
  18. 107         else
  19. 108             objects="${KBUILD_VMLINUX_INIT}         \
  20. 109                 --start-group               \
  21. 110                 ${KBUILD_VMLINUX_MAIN}          \
  22. 111                 ${KBUILD_VMLINUX_LIBS}          \
  23. 112                 --end-group             \
  24. 113                 ${1}"
  25. 114         fi
  26. 115
  27. 116         ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2}     \
  28. 117             -T ${lds} ${objects}
  29. 118     else
  30. ......
  31. 314 info LD vmlinux
  32. 315 vmlinux_link "${kallsymso}" vmlinux
复制代码

vmliux_link就是最终链接出vmlinux的函数,第98行判断SRCARCH是否等于“um”,如果不相等的话就执行99~117行的代码。因为SRCARCH=arm,因此条件成立,又因为CONFIG_THIN_ARCHIVES的值为“y”,长度不为0,所以执行第100~106行的代码。第116~117行的代码应该很熟悉了,就是普通的链接操作,连接脚本为lds= ./arch/arm/kernel/vmlinux.lds,需要链接的文件由变量KBUILD_VMLINUX_LIBS来决定,这个变量在本节中已经讲解过了。
第315行调用vmlinux_link函数来链接出vmlinux。
使用命令“make V=1”编译Linux,会有如下图所示的编译信息:
十六章38738.png

图 27.5.1 link-vmlinux.sh链接vmlinux的过程

至此我们基本理清了make的过程,重点就是将各个子目录下的built-in.o、.a等文件链接在一起,最终生成vmlinux这个ELF格式的可执行文件。链接脚本为arch/arm/kernel/vmlinux.lds,链接过程是由shell脚本scripts/link-vmlinux.s来完成的。接下来的问题就是这些子目录下的built-in.o、.a等文件又是如何编译出来的呢?
1.5.4built-in.o文件编译生成过程
根据示例代码 27.5.7的第1004行可知,vmliux依赖vmlinux-deps,而vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)。KBUILD_LDS是连接脚本,这里不考虑,剩下的KBUILD_VMLINUX_INIT、KBUILD_VMLINUX_MAIN和 KBUILD_VMLINUX_LIBS就是各个子目录下的built-in.o和.a等文件。最终vmlinux-deps的值如下:
  1. vmlinux-deps = arch/arm/kernel/vmlinux.lds        arch/arm/kernel/head.o
  2.                init/built-in.o                    usr/built-in.o
  3.                arch/arm/vfp/built-in.o            arch/arm/kernel/built-in.o
  4.                arch/arm/mm/built-in.o             arch/arm/common/built-in.o
  5.                arch/arm/probes/built-in.o         arch/arm/net/built-in.o
  6.                arch/arm/crypto/built-in.o         arch/arm/firmware/built-in.o
  7.                arch/arm/lib/lib.a                 arch/arm/mach-zynq/built-in.o
  8.                lib/lib.a                          kernel/built-in.o
  9.                certs/built-in.o                   mm/built-in.o
  10.                fs/built-in.o                      ipc/built-in.o
  11.                security/built-in.o                crypto/built-in.o
  12.                block/built-in.o                   arch/arm/lib/built-in.o
  13.                lib/built-in.o                     drivers/built-in.o
  14.                sound/built-in.o                   firmware/built-in.o
  15.                net/built-in.o                     virt/built-in.o
复制代码

除了arch/arm/kernel/vmlinux.lds以外,其他都是要编译链接生成的。在顶层Makefile中有如下代码:
示例代码 27.5.1717.5.17顶层Makefile代码段
  1. 1014 $(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
复制代码

sort是排序函数,用于对vmlinux-deps的字符串列表进行排序,并且去掉重复的单词。可以看出vmlinux-deps依赖vmlinux-dirs,vmlinux-dirs也在顶层Makefile中定义,定义如下:
示例代码 27.5.1817.5.18 顶层Makefile代码段
951  vmlinux-dirs   := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
952              $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
953              $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))
vmlinux-dirs看名字就知道和目录有关,此变量保存着生成vmlinux所需源码文件的目录,值如下:
vmlinux-dirs= init                    usr                    arch/arm/vfp
              arch/arm/kernel         arch/arm/mm           arch/arm/common
              arch/arm/probes         arch/arm/net           arch/arm/crypto
              arch/arm/firmware       drivers                arch/arm/mach-zynq
              arch/arm/lib            kernel                 certs
              mm                   fs                    ipc
              security               crypto                 block
              sound                 firmware               net
              lib                    virt
在顶层Makefile中有如下代码:
示例代码 27.5.1917.5.19顶层Makefile代码段
  1. 1022 PHONY += $(vmlinux-dirs)
  2. 1023 $(vmlinux-dirs): prepare scripts
复制代码

1024    $(Q)$(MAKE) $(build)=$@
目标vmlinux-dirs依赖prepare和scripts,这两个依赖就不去浪费时间分析了,重点看一下第1024行的命令。build前面已经说了,值为“-f ./scripts/Makefile.build obj”,因此将1024行的命令展开就是:
  1. @ make -f ./scripts/Makefile.build obj=$@
复制代码

$@表示目标文件,也就是vmlinux-dirs的值,将vmlinux-dirs中的这些目录全部带入到命令中,结果如下:
  1. @ make -f ./scripts/Makefile.build obj=init
  2. @ make -f ./scripts/Makefile.build obj=usr
  3. @ make -f ./scripts/Makefile.build obj=arch/arm/vfp
  4. @ make -f ./scripts/Makefile.build obj=arch/arm/kernel
  5. @ make -f ./scripts/Makefile.build obj=arch/arm/mm
  6. @ make -f ./scripts/Makefile.build obj=arch/arm/common
  7. @ make -f ./scripts/Makefile.build obj=arch/arm/probes
  8. @ make -f ./scripts/Makefile.build obj=arch/arm/net
  9. @ make -f ./scripts/Makefile.build obj=arch/arm/crypto
  10. @ make -f ./scripts/Makefile.build obj=arch/arm/firmware
  11. @ make -f ./scripts/Makefile.build obj=arch/arm/mach-zynq
  12. @ make -f ./scripts/Makefile.build obj=kernel
  13. @ make -f ./scripts/Makefile.build obj=certs
  14. @ make -f ./scripts/Makefile.build obj=mm
  15. @ make -f ./scripts/Makefile.build obj=fs
  16. @ make -f ./scripts/Makefile.build obj=ipc
  17. @ make -f ./scripts/Makefile.build obj=security
  18. @ make -f ./scripts/Makefile.build obj=crypto
  19. @ make -f ./scripts/Makefile.build obj=block
  20. @ make -f ./scripts/Makefile.build obj=drivers
  21. @ make -f ./scripts/Makefile.build obj=sound
  22. @ make -f ./scripts/Makefile.build obj=firmware
  23. @ make -f ./scripts/Makefile.build obj=net
  24. @ make -f ./scripts/Makefile.build obj=arch/arm/lib
  25. @ make -f ./scripts/Makefile.build obj=lib
复制代码

这些命令运行过程其实都是一样的,我们就以“@ make -f ./scripts/Makefile.build obj=init”这个命令为例,讲解一下详细的运行过程。这里又要用到Makefile.build这个脚本了,此脚本默认目标为__build,这个在27.5.2小节已经讲过了,我们再来看一下,__build目标对应的规则如下:
示例代码 27.5.2017.5.20 scripts/Makefile.build代码段
  1. 96 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
  2. 97      $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
  3. 98      $(subdir-ym) $(always)
  4. 99     @:
复制代码

当只编译Linux内核镜像文件,也就是使用“make zImage”编译的时候,KBUILD_BUILTIN=1,KBUILD_MODULES为空。“make”命令是会编译所有的东西,包括Linux内核镜像文件和一些模块文件。所以如果只编译Linux内核镜像的话,__build目标可简化为:
  1. __build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
  2.   @:
复制代码

重点来看一下builtin-target这个依赖,builtin-target同样定义在文件scripts/Makefile.build中,定义如下:
示例代码 27.5.2117.5.21 scripts/Makefile.build代码段
  1. 88 ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
  2. 89 builtin-target := $(obj)/built-in.o
  3. 90 endif
复制代码

第89行就是builtin-target变量的值,为“$(obj)/built-in.o”,这就是这些built-in.o的来源了。要生成built-in.o,要求obj-y、obj-m、obj-、subdir-m和lib-target这些变量不能全为空。最后一个问题:built-in.o是怎么生成的?在文件scripts/Makefile.build中有如下代码:
示例代码 27.5.2217.5.22 scripts/Makefile.build代码段
  1. 446 #
  2. 447 # Rule to compile a set of .o files into one .o file
  3. 448 #
  4. 449 ifdef builtin-target
  5. 450
  6. 451 ifdef CONFIG_THIN_ARCHIVES
  7. 452   cmd_make_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS)
  8. 453   cmd_make_empty_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS)
  9. 454   quiet_cmd_link_o_target = AR      $@
  10. 455 else
  11. 456   cmd_make_builtin = $(LD) $(ld_flags) -r -o
  12. 457   cmd_make_empty_builtin = rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS)
  13. 458   quiet_cmd_link_o_target = LD      $@
  14. 459 endif
  15. 460
  16. 461 # If the list of objects to link is empty, just create an empty built-in.o
  17. 462 cmd_link_o_target = $(if $(strip $(obj-y)),\
  18. 463               $(cmd_make_builtin) $@ $(filter $(obj-y), $^) \
  19. 464               $(cmd_secanalysis),\
  20. 465               $(cmd_make_empty_builtin) $@)
  21. 466
  22. 467 $(builtin-target): $(obj-y) FORCE
  23. 468     $(call if_changed,link_o_target)
  24. 469
  25. 470 targets += $(builtin-target)
  26. 471 endif # builtin-target
复制代码

第467行的目标就是builtin-target,依赖为obj-y,命令为“$(call if_changed,link_o_target)”,也就是调用函数if_changed,参数为link_o_target,其返回值就是具体的命令。前面讲过了if_changed,它会调用cmd_$(1)所对应的命令($(1)就是函数的第1个参数),在这里就是调用cmd_link_o_target所对应的命令,也就是第462~465行的命令。cmd_link_o_target使用LD将某个目录下的所有.o文件链接在一起,最终形成built-in.o。
1.5.5make zImage过程
1、vmlinux、Image,zImage、uImage的区别
前面几小节重点是讲vmlinux是如何编译出来的,vmlinux是ELF格式的文件,但是在实际中我们不会使用vmlinux,而是使用zImage或uImage这样的Linux内核镜像文件。那么vmlinux、zImage、uImage它们之间有什么区别呢?
vmlinux是编译出来的最原始的内核文件,是未压缩的,比如正点原子提供的Linux源码编译出来的vmlinux差不多有12MB,如下图所示:
十六章31179.png

图 27.5.2 vmlinux信息

Image是Linux内核镜像文件,但是Image仅包含可执行的二进制数据。Image就是使用objcopy去掉vmlinux中的一些信息,比如符号表什么的。但是Image是没有压缩过的,Image保存在arch/arm/boot目录下,其大小大概在11MB左右,如下图所示:
十六章38941.png

图 27.5.3 Image镜像信息

可见相比vmlinux的12MB,Image缩小了1MB,然而在嵌入式中,Image可能还是比较大,所以需要压缩。
zImage是经过gzip压缩后的Image,经过压缩以后其大小为3.9MB,如下图所示:
十六章39109.png

图 27.5.4 zImage镜像信息

uImage是老版本uboot专用的镜像文件,uImag是在zImage前面加了一个长度为64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的uboot已经支持了zImage启动,所以已经很少用到uImage了。
使用“make”、“make all”、“make zImage”这些命令就可以编译出zImage镜像,在arch/arm/Makefile中有如下代码:
示例代码 27.5.2317.5.23 arch/arm/Makefile代码段
  1. 327 BOOT_TARGETS    = zImage Image xipImage bootpImage uImage
  2. 335 $(BOOT_TARGETS): vmlinux
  3. 336   $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
  4. 337   @$(kecho) '  Kernel: $(boot)/$@ is ready'
复制代码

第327行,变量BOOT_TARGETS包含zImage,Image,xipImage等镜像文件。
第335行,BOOT_TARGETS依赖vmlinux,因此如果使用“make zImage”编译Linux内核的话,首先肯定要先编译出vmlinux。
第336行,具体的命令,比如要编译zImage,那么命令展开以后如下所示:
  1. @ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage
复制代码

看来又是使用scripts/Makefile.build文件来完成vmliux到zImage的转换。
关于Linux顶层Makefile就讲解到这里,基本和uboot的顶层Makefile一样,重点在于vmlinux的生成。最后将vmlinux压缩成我们最常用的zImage或uImage等文件。

阿莫论坛20周年了!感谢大家的支持与爱护!!

如果想吃一顿饺子,就得从冰箱里取出肉,剁馅儿,倒面粉、揉面、醒面,擀成皮儿,下锅……
一整个繁琐流程,就是为了出锅时那一嘴滚烫流油的热饺子。

如果这个过程,禁不住饿,零食下肚了,饺子出锅时也就不香了……《非诚勿扰3》
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-28 19:12

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表