搜索
bottom↓
回复: 0

【正点原子Linux连载】第三十五章Linux内核顶层Makefile详解--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-6-22 15:33:45 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-24 16:12 编辑

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html  
4)对正点原子Linux感兴趣的同学可以加群讨论:
876919289 QQ群头像.png
5)关注正点原子公众号,获取最新资料

100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png


第三十五章Linux内核顶层Makefile详解


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


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

图35.1.1 linux官网

        从图35.1.1可以看出最新的稳定版Linux已经到了5.1.4,大家没必要追新,因为4.x版本的Linux和5.x版本没有本质上的区别,5.x更多的是加入了一些新的平台、新的外设驱动而已。
        NXP会从https://www.kernel.org下载某个版本的Linux内核,然后将其移植到自己的CPU上,测试成功以后就会将其开放给NXP的CPU开发者。开发者下载NXP提供的Linux内核,然后将其移植到自己的产品上。本章的移植我们就使用NXP提供的Linux源码,NXP提供Linux源码已经放到了开发板光盘中,路径为:1、例程源码-》4、NXP官方原版Uboot和Linux-》linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2。
35.2 Linux内核编译初次编译
        先看一下如何编译Linux源码,这里编译一下I.MX6U-ALPHA开发板移植好的Linux源码,已经放到了开发板光盘中,路径为:1、例程源码-》3、正点原子修改后的Uboot和Linux-》linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2。在Ubuntu中新建名为“alientek_linux”的文件夹,然后将linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2这个压缩包拷贝到前面新建的alientek_linux文件夹中并解压,命令如下:
  1. tar -vxjf linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2
复制代码

        解压完成以后的Linux源码根目录如图35.2.1所示:
image004.jpg

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

        以EMMC核心板为例,讲解一下如何编译出对应的Linux镜像文件。新建名为“mx6ull_alientek_emmc.sh”的shell脚本,然后在这个shell脚本里面输入如下所示内容:
示例代码35.2.1 mx6ull_alientek_emmc.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- imx_v7_defconfig
  4. 4 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
  5. 5 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
复制代码

        使用chmod给予x6ull_alientek_emmc.sh可执行权限,然后运行此shell脚本,命令如下:
./mx6ull_alientek_emmc.sh
        编译的时候会弹出Linux图形配置界面,如图35.2.3所示:
image006.jpg

图35.2.3 Linux图形配置界面

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

图35.2.4 Linux编译完成

        编译完成以后就会在arch/arm/boot这个目录下生成一个叫做zImage的文件,zImage就是我们要用的Linux镜像文件。另外也会在arch/arm/boo/dts下生成很多.dtb文件,这些.dtb就是设备树文件。
        编译Linux内核的时候可能会提示“recipefortarget ‘arch/arm/boot/compressed/piggy.lzo’ failed”,如图35.2.5所示:
image010.gif

图35.2.5 lzop未找到

        图35.2.5中的错误提示lzop未找到,原因是没有安装lzop库,输入如下命令安装lzop库即可解决:
sudoapt-getinstalllzop
        lzop库安装完成以后在重新编译一下Linux内核即可。
        看一下编译脚本mx6ull_alientek_emmc.sh的内容,文件内容如下:
示例代码35.2.1 mx6ull_alientek_emmc.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- imx_v7_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行,执行“makedistclean”,清理工程,所以mx6ull_alientek_emmc.sh每次都会清理一下工程。如果通过图形界面配置了Linux,但是还没保存新的配置文件,那么就要慎重使用mx6ull_alientek_emmc.sh编译脚本了,因为它会把你的配置信息都删除掉!
        第3行,执行“makexxx_defconfig”,配置工程。
        第4行,执行“makemenuconfig”,打开图形配置界面,对Linux进行配置,如果不想每次编译都打开图形配置界面的话可以将这一行删除掉。
        第5行,执行“make”,编译Linux源码。
        可以看出,Linux的编译过程基本和uboot一样,都要先执行“makexxx_defconfig”来配置一下,然后在执行“make”进行编译。如果需要使用图形界面配置的话就执行“makemenuconfig”。
35.3 Linux工程目录分析
将正点原子提供的Linux源码进行解压,解压完成以后的目录如图35.3.1所示:
image012.gif

图35.3.1未编译的Linux源码目录

        图35.3.1就是正点原子提供的未编译的Linux源码目录文件,我们在分析Linux之前一定要先在Ubuntu中编译一下Linux,因为编译过程会生成一些文件,而生成的这些恰恰是分析Linux不可或缺的文件。编译完成以后使用tar压缩命令对其进行压缩并使用Filezilla软件将压缩后的uboot源码拷贝到Windows下。
        编译后的Linux目录如图35.3.2所示:
image014.gif

图35.3.2 编译后的Linux目录

        图35.3.2中重要的文件夹或文件的含义见表35.3.1所示:
类型        名字        描述        备注
文件夹        arch        架构相关目录。        Linux自带
        block        块设备相关目录。        
        crypto        加密相关目录。        
        Documentation        文档相关目录。        
        drivers        驱动相关目录。        
        firmeare        固件相关目录。        
        fs        文件系统相关目录。        
        include        头文件相关目录。        
        init        初始化相关目录。        
        ipc        进程间通信相关目录。        
        kernel        内核相关目录。        
        lib        库相关目录。        
        mm        内存管理相关目录。        
        net        网络相关目录。        
        samples        例程相关目录。        
        scripts        脚本相关目录。        
        security        安全相关目录。        
        sound        音频处理相关目录。        
        tools        工具相关目录。        
        usr        与initramfs相关的目录,用于生成initramfs。        
        virt        提供虚拟机技术(KVM)。        
文件        .config        Linux最终使用的配置文件。        编译生成的文件。
        .gitignore        git工具相关文件。        Linux自带
        .mailmap        邮件列表。        
        .missing-syscalls.d        。        编译生成的文件
        .tmp_xx        这是一系列的文件,作用目前笔者还不是很清楚。        编译生成的文件
        .version        好像和版本有关。        
        .vmlinux.cmd        cmd文件,用于连接生成vmlinux。        
        COPYING        版权声明。        Linux自带
        CREDITS        Linux贡献者。        
        Kbuild        Makefile会读取此文件。        
        Kconfig        图形化配置界面的配置文件。。        
        MAINTAINERS        维护者名单。        
        Makefile        Linux顶层Makefile        
        Module.xx
modules.xx        一系列文件,和模块有关。        编译生成的文件
        mx6ull_alientek_emmc.sh
mx6ull_alientek_nand.sh        正点原子提供的,Linux编译脚本。        正点原子提供
        README        Linux描述文件。        Linux自带
        REPORTING-BUGS        BUG上报指南        
        System.map        符号表。        编译生成的文件
        vmlinux        编译出来的、未压缩的ELF格式Linux文件        
        vmlinux.o        编译出来的vmlinux.o文件。        
表35.3.1 Linux目录
表35.3.1中的很多文件夹和文件我们都不需要去关心,我们要关注的文件夹或文件如下:
        1、arch目录
        这个目录是和架构有关的目录,比如arm、arm64、avr32、x86等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如boot、common、configs等等,以arch/arm为例,其子目录如图35.3.2所示:
image016.jpg

图35.3.2 arch/arm子目录

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

图35.3.3 配置文件

        在arch/arm/configs中就包含有I.MX6U-ALPHA开发板的默认配置文件:imx_v7_defconfig,执行“make imx_v7_defconfig”即可完成配置。arch/arm/boot/dts目录里面是对应开发平台的设备树文件,正点原子I.MX6U-ALPHA开发板对应的设备树文件如图35.3.4所示:
image020.gif

图35.3.4 正点原子I.MX6U开发板对应的设备树

        arch/arm/boot目录下会保存编译出来的Image和zImage镜像文件,而zImage就是我们要用的linux镜像文件。
        arch/arm/mach-xxx目录分别为相应平台的驱动和初始化文件,比如mach-imx目录里面就是I.MX系列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内核代码。
        12、lib目录
        lib是库的意思,lib目录都是一些公用的库函。
        13、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。
35.4 VSCode工程创建
        在分析Linux的顶层Makefile之前,先创建VSCode工程,创建过程和uboot一样。创建好以后将文件.vscode/settings.json改为如下所示内容:
示例代码35.4.1.1 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-h]*":true,
  18. 18"arch/arm/mach-[n-z]*":true,
  19. 19"arch/arm/mach-i[n-z]*":true,
  20. 20"arch/arm/mach-m[e-v]*":true,
  21. 21"arch/arm/mach-k*":true,
  22. 22"arch/arm/mach-l*":true,
  23. 23
  24. 24/* 屏蔽排除不用的配置文件 */
  25. 25"arch/arm/configs/[a-h]*":true,
  26. 26"arch/arm/configs/[j-z]*":true,
  27. 27"arch/arm/configs/imo*":true,
  28. 28"arch/arm/configs/in*":true,
  29. 29"arch/arm/configs/io*":true,
  30. 30"arch/arm/configs/ix*":true,
  31. 31
  32. 32/* 屏蔽掉不用的DTB文件 */
  33. 33"arch/arm/boot/dts/[a-h]*":true,
  34. 34"arch/arm/boot/dts/[k-z]*":true,
  35. 35"arch/arm/boot/dts/in*":true,
  36. 36"arch/arm/boot/dts/imx1*":true,
  37. 37"arch/arm/boot/dts/imx7*":true,
  38. 38"arch/arm/boot/dts/imx2*":true,
  39. 39"arch/arm/boot/dts/imx3*":true,
  40. 40"arch/arm/boot/dts/imx5*":true,
  41. 41"arch/arm/boot/dts/imx6d*":true,
  42. 42"arch/arm/boot/dts/imx6q*":true,
  43. 43"arch/arm/boot/dts/imx6s*":true,
  44. 44"arch/arm/boot/dts/imx6ul-*":true,
  45. 45"arch/arm/boot/dts/imx6ull-9x9*":true,
  46. 46"arch/arm/boot/dts/imx6ull-14x14-ddr*":true,
  47. 47},
  48. 48"files.exclude":{
  49. 49"**/.git": true,
  50. 50"**/.svn": true,
  51. 51"**/.hg": true,
  52. 52"**/CVS": true,
  53. 53"**/.DS_Store": true,
  54. 54"**/*.o":true,
  55. 55"**/*.su":true,
  56. 56"**/*.cmd":true,
  57. 57"Documentation":true,
  58. 58
  59. 59/* 屏蔽不用的架构相关的文件 */
  60. 60"arch/alpha":true,
  61. 61"arch/arc":true,
  62. 62"arch/arm64":true,
  63. 63"arch/avr32":true,
  64. 64"arch/[b-z]*":true,
  65. 65"arch/arm/plat*":true,
  66. 66"arch/arm/mach-[a-h]*":true,
  67. 67"arch/arm/mach-[n-z]*":true,
  68. 68"arch/arm/mach-i[n-z]*":true,
  69. 69"arch/arm/mach-m[e-v]*":true,
  70. 70"arch/arm/mach-k*":true,
  71. 71"arch/arm/mach-l*":true,
  72. 72
  73. 73/* 屏蔽排除不用的配置文件 */
  74. 74"arch/arm/configs/[a-h]*":true,
  75. 75"arch/arm/configs/[j-z]*":true,
  76. 76"arch/arm/configs/imo*":true,
  77. 77"arch/arm/configs/in*":true,
  78. 78"arch/arm/configs/io*":true,
  79. 79"arch/arm/configs/ix*":true,
  80. 80
  81. 81/* 屏蔽掉不用的DTB文件 */
  82. 82"arch/arm/boot/dts/[a-h]*":true,
  83. 83"arch/arm/boot/dts/[k-z]*":true,
  84. 84"arch/arm/boot/dts/in*":true,
  85. 85"arch/arm/boot/dts/imx1*":true,
  86. 86"arch/arm/boot/dts/imx7*":true,
  87. 87"arch/arm/boot/dts/imx2*":true,
  88. 88"arch/arm/boot/dts/imx3*":true,
  89. 89"arch/arm/boot/dts/imx5*":true,
  90. 90"arch/arm/boot/dts/imx6d*":true,
  91. 91"arch/arm/boot/dts/imx6q*":true,
  92. 92"arch/arm/boot/dts/imx6s*":true,
  93. 93"arch/arm/boot/dts/imx6ul-*":true,
  94. 94"arch/arm/boot/dts/imx6ull-9x9*":true,
  95. 95"arch/arm/boot/dts/imx6ull-14x14-ddr*":true,
  96. 96}
  97. 97}
复制代码

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

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

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

        4、静默输出
        Linux编译的时候使用“make-s”就可实现静默编译,编译的时候就不会打印任何的信息,同uboot一样,相关代码如下:
示例代码35.5.4 顶层Makefile代码段
  1. 87 ifneq ($(filter 4.%,$(MAKE_VERSION)),)   # make-4
  2. 88 ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  3. 89   quiet=silent_
  4. 90 endif
  5. 91else                 # make-3.8x
  6. 92 ifneq ($(filter s%-s%,$(MAKEFLAGS)),)
  7. 93   quiet=silent_
  8. 94 endif
  9. 95 endif
  10. 96
  11. 97 export quiet Q KBUILD_VERBOSE
复制代码

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

        6、代码检查
        Linux也支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile中的代码如下:
示例代码35.5.6 顶层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中的代码如下:
示例代码35.5.7 顶层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. 195else
  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. 202else
  25. 203         ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
  26. 204                 # building in a subdirectory of the source tree
  27. 205                 srctree :=..
  28. 206else
  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中代码如下:
示例代码35.5.8 顶层Makefile代码段
  1. 252 ARCH                        ?= $(SUBARCH)
  2. 253 CROSS_COMPILE           ?= $(CONFIG_CROSS_COMPILE:"%"=%)
复制代码

        为了方便,一般直接修改顶层Makefile中的ARCH和CROSS_COMPILE,直接将其设置为对应的架构和编译器,比如本教程将ARCH设置为为arm,CROSS_COMPILE设置为arm-linux-gnueabihf-,如下所示:
示例代码35.5.9 顶层Makefile代码段
252 ARCH                        ?=arm
253 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相应代码如下:
示例代码35.5.10 顶层Makefile代码段
  1. 348 # We need some generic definitions (do not try to remake the file).
  2. 349 scripts/Kbuild.include:;
  3. 350        include scripts/Kbuild.include
复制代码

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

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

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

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

        12、导出变量
        顶层Makefile会导出很多变量给子Makefile使用,导出的这些变量如下:
示例代码35.5.13 顶层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
  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_KASAN
  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
复制代码

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

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

        因为config-targets=1,因此第534行~541行成立。第534行引用arch/arm/Makefile这个文件,这个文件很重要,因为zImage、uImage等这些文件就是由arch/arm/Makefile来生成的。
        第535行导出变量KBUILD_DEFCONFIG KBUILD_KCONFIG。
        第537行,没有目标与之匹配,因此不执行。
        第540行,“makexxx_defconfig”与目标“%config”匹配,因此执行。“%config”依赖scripts_basic、outputmakefile和FORCE,“%config”真正有意义的依赖就只有scripts_basic,scripts_basic的规则如下:
示例代码35.5.1.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,因此将示例代码35.5.1.2展开就是:
  1. scripts_basic:
  2.         @make -f ./scripts/Makefile.build obj=scripts/basic        //也可以没有@,视配置而定
  3.         @rm -f . tmp_quiet_recordmcount                                        //也可以没有@
复制代码

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

        将命令展开就是:
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
35.5.2 Makefile.build脚本分析
从上一小节可知,“makexxx_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,有如下代码:
示例代码35.5.2.1 Makefile.build代码段
  1. 41 # The filename Kbuild has precedence over Makefile
  2. 42 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
  3. 43 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
  4. 44 include $(kbuild-file)
复制代码

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

        将kbuild-file展开后为:
kbuild-file= ./scripts/basic/Makefile
最后将59行展开,即:
  1. include./scripts/basic/Makefile
复制代码

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

        __build是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标__build。在顶层Makefile中,KBUILD_BUILTIN为1,KBUILD_MODULES为空,因此展开后目标__build为:
__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
        @:
        可以看出目标__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最终为:
__build: scripts/basic/fixdep scripts/basic/bin2c
        @:
        __build依赖于scripts/basic/fixdep和scripts/basic/bin2c,所以要先将scripts/basic/fixdep和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
复制代码

可以看出,Makefile.build会读取scripts/kconfig/Makefile中的内容,此文件有如下所示内容:
示例代码35.5.2.3 scripts/kconfig/Makefile代码段
  1. 113%_defconfig: $(obj)/conf
  2. 114     $(Q)[        DISCUZ_CODE_1108        ]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文件中,最终生成Linuxkernel根目录下的.config文件。
35.5.3 make过程
        使用命令“makexxx_defconfig”配置好Linux内核以后就可以使用“make”或者“makeall”命令进行编译。顶层Makefile有如下代码:
示例代码35.5.3.1 顶层Makefile代码段
  1. 125 PHONY := _all
  2. 126 _all:
  3. ......
  4. 192 PHONY += all
  5. 193 ifeq ($(KBUILD_EXTMOD),)
  6. 194 _all: all
  7. 195else
  8. 196 _all: modules
  9. 197 endif
  10. ......
复制代码

608 all: vmlinux
        第126行,_all是默认目标,如果使用命令“make”编译Linux的话此目标就会被匹配。
        第193行,如果KBUILD_EXTMOD为空的话194行的代码成立。
        第194行,默认目标_all依赖all。
        第608行,目标all依赖vmlinux,所以接下来的重点就是vmlinux!
        顶层Makefile中有如下代码:
示例代码35.5.3.2 顶层Makefile代码段
  1. 904 # Externally visible symbols (used by link-vmlinux.sh)
  2. 905 export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
  3. 906 export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
  4. 907 export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds
  5. 908 export LDFLAGS_vmlinux
  6. 909 # used by scripts/pacmage/Makefile
  7. 910 export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools virt)
  8. 911
  9. 912 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
  10. 913
  11. 914 # Final link of vmlinux
  12. 915       cmd_link-vmlinux = $(CONFIG_SHELL) [        DISCUZ_CODE_1111        ]lt; $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
  13. 916 quiet_cmd_link-vmlinux = LINK    $@
  14. 917
  15. 918 # Include targets which we want to
  16. 919 # execute if the rest of the kernel build went well.
  17. 920 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
  18. 921 ifdef CONFIG_HEADERS_CHECK
  19. 922     $(Q)$(MAKE)-f $(srctree)/Makefile headers_check
  20. 923 endif
  21. 924 ifdef CONFIG_SAMPLES
  22. 925     $(Q)$(MAKE) $(build)=samples
  23. 926 endif
  24. 927 ifdef CONFIG_BUILD_DOCSRC
  25. 928     $(Q)$(MAKE) $(build)=Documentation
  26. 929 endif
  27. 930 ifdef CONFIG_GDB_SCRIPTS
  28. 931     $(Q)ln -fsn `cd $(srctree)&&/bin/pwd`/scripts/gdb/vmlinux-gdb.py
  29. 932 endif
  30. 933+$(call if_changed,link-vmlinux)
复制代码

        从第920行可以看出目标vmlinux依赖scripts/link-vmlinux.sh $(vmlinux-deps) FORCE。第912行定义了vmlinux-deps,值为:
vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
        第905行,KBUILD_VMLINUX_INIT= $(head-y) $(init-y)。
        第906行,KBUILD_VMLINUX_MAIN = $(core-y) $(libs-y) $(drivers-y) $(net-y)。
        第907行,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-y) 、$(drivers-y) 、$(net-y)、arch/arm/kernel/vmlinux.lds和FORCE。
        第933行的命令用于链接生成vmlinux。
        重点来看一下$(head-y) 、$(init-y)、$(core-y) 、$(libs-y) 、$(drivers-y) 和$(net-y)这六个变量的值。
        1、head-y
        head-y定义在文件arch/arm/Makefile中,内容如下:
示例代码35.5.3.3 arch/arm/Makefile代码段
135 head-y      := arch/arm/kernel/head$(MMUEXT).o
        当不使能MMU的话MMUEXT=-nommu,如果使能MMU的话为空,因此head-y最终的值为:
  1. head-y = arch/arm/kernel/head.o
  2.         2、init-y、drivers-y和net-y
复制代码

        在顶层Makefile中有如下代码:
示例代码35.5.3.4 顶层Makefile代码段
  1. 558 init-y              := init/
  2. 559 drivers-y           := drivers/ sound/ firmware/
  3. 560 net-y               := net/
  4. ......
  5. 896 init-y              := $(patsubst %/, %/built-in.o, $(init-y))
  6. 898 drivers-y           := $(patsubst %/, %/built-in.o, $(drivers-y))
  7. 899 net-y               := $(patsubst %/, %/built-in.o, $(net-y))
  8.         从示例代码35.5.3.4可知,init-y、libs-y、drivers-y和net-y最终的值为:
  9. init-y        = init/built-in.o
  10. drivers-y        = drivers/built-in.o  sound/built-in.o  firmware/built-in.o
  11. net-y        = net/built-in.o
复制代码

        3、libs-y
        libs-y基本和init-y一样,在顶层Makefile中存在如下代码:
示例代码35.5.3.5 顶层Makefile代码段
  1. 561 libs-y      := lib/
  2. ......
  3. 900 libs-y1     := $(patsubst %/, %/lib.a, $(libs-y))
  4. 901 libs-y2     := $(patsubst %/, %/built-in.o, $(libs-y))
  5. 902 libs-y      := $(libs-y1) $(libs-y2)
复制代码

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

        arch/arm/Makefile将libs-y的值改为了:arch/arm/lib $(libs-y),展开以后为:
  1. libs-y = arch/arm/lib lib/
复制代码

        因此根据示例代码35.5.3.5的第900~902行可知,libs-y最终应该为:
  1. libs-y = arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o
复制代码

        4、core-y
        core-y和init-y也一样,在顶层Makefile中有如下代码:
示例代码35.5.3.7 顶层Makefile代码段
  1. 532 core-y            := usr/
  2. ......
  3. 887 core-y           += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
  4.         但是在arch/arm/Makefile中会对core-y进行追加,代码如下:
  5. 示例代码35.5.3.8 arch/arm/Makefile代码段
  6. 269 core-$(CONFIG_FPE_NWFPE)        += arch/arm/nwfpe/
  7. 270 core-$(CONFIG_FPE_FASTFPE)        += $(FASTFPE_OBJ)
  8. 271 core-$(CONFIG_VFP)                += arch/arm/vfp/
  9. 272 core-$(CONFIG_XEN)                += arch/arm/xen/
  10. 273 core-$(CONFIG_KVM_ARM_HOST)        += arch/arm/kvm/
  11. 274 core-$(CONFIG_VDSO)                += arch/arm/vdso/
  12. 275
  13. 276 # If we have a machine-specific directory, then include it in the build.
  14. 277 core-y                 += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
  15. 278 core-y                   += arch/arm/probes/
  16. 279 core-y                    += arch/arm/net/
  17. 280 core-y                    += arch/arm/crypto/
  18. 281 core-y                    += arch/arm/firmware/
  19. 282 core-y                    += $(machdirs) $(platdirs)
复制代码

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

        经过上述代码的转换,最终core-y的值为:
  1. core-y =         usr/built-in.o                                        arch/arm/vfp/built-in.o \
  2. arch/arm/vdso/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-imx/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-y 、drivers-y和net-y这6个变量就讲解到这里。这些变量都是一些built-in.o或.a等文件,这个和uboot一样,都是将相应目录中的源码文件进行编译,然后在各自目录下生成built-in.o文件,有些生成了.a库文件。最终将这些built-in.o和.a文件进行链接即可形成ELF格式的可执行文件,也就是vmlinux!但是链接是需要连接脚本的,vmlinux的依赖arch/arm/kernel/vmlinux.lds就是整个Linux的链接脚本。
        示例代码35.5.3.2第933行的命令“+$(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中,如下所示:
示例代码35.5.3.10 scripts/Kbuild.include代码段
  1. 247 if_changed = $(if $(strip $(any-prereq) $(arg-check)),        \
  2. 248     @set -e;        \
  3. 249     $(echo-cmd) $(cmd_$(1));        \
  4. 250     printf '%s\n''cmd_$@ := $(make-cmd)'> $(dot-target).cmd)
复制代码

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

        第915行就是cmd_link-vmlinux的值,其中CONFIG_SHELL=/bin/bash,$<表示目标vmlinux的第一个依赖文件,根据示例代码35.5.3.2可知,这个文件为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。因此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!在link-vmlinux.sh中有如下所示代码:
示例代码35.5.3.12 scripts/link-vmlinux.sh代码段
  1. 51 vmlinux_link()
  2. 52{
  3. 53  local lds="${objtree}/${KBUILD_LDS}"
  4. 54
  5. 55if["${SRCARCH}"!="um"]; then
  6. 56      ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux}-o ${2}                  \
  7. 57-T ${lds} ${KBUILD_VMLINUX_INIT}                     \
  8. 58--start-group ${KBUILD_VMLINUX_MAIN}--end-group ${1}
  9. 59else
  10. 60      ${CC} ${CFLAGS_vmlinux}-o ${2}                              \
  11. 61-Wl,-T,${lds} ${KBUILD_VMLINUX_INIT}                 \
  12. 62-Wl,--start-group                                    \
  13. 63               ${KBUILD_VMLINUX_MAIN}                      \
  14. 64-Wl,--end-group                                     \
  15. 65-lutil ${1}
  16. 66      rm -f linux
  17. 67  fi
  18. 68}
  19. ......
复制代码
  1. 216 info LD vmlinux
  2. 217 vmlinux_link "${kallsymso}" vmlinux
复制代码

        vmliux_link就是最终链接出vmlinux的函数,第55行判断SRCARCH是否等于“um”,如果不相等的话就执行56~58行的代码。因为SRCARCH=arm,因此条件成立,执行56~58行的代码。这三行代码就应该很熟悉了!就是普通的链接操作,连接脚本为lds= ./arch/arm/kernel/vmlinux.lds,需要链接的文件由变量KBUILD_VMLINUX_INIT和KBUILD_VMLINUX_MAIN来决定,这两个变量在示例代码35.5.3.2中已经讲解过了。
第217行调用vmlinux_link函数来链接出vmlinux。
使用命令“make V=1”编译Linux,会有如图35.5.3.1所示的编译信息:
image022.jpg

图35.5.3.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等文件又是如何编译出来的呢?
35.5.4 built-in.o文件编译生成过程
        根据示例代码35.5.3.2第920行可知,vmliux依赖vmlinux-deps,而vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN),KBUILD_LDS是连接脚本,这里不考虑,剩下的KBUILD_VMLINUX_INIT和KBUILD_VMLINUX_MAIN就是各个子目录下的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/vdso/built-in.o \
  4. arch/arm/kernel/built-in.o                         arch/arm/mm/built-in.o \
  5. arch/arm/common/built-in.o                         arch/arm/probes/built-in.o \
  6. arch/arm/net/built-in.o                                 arch/arm/crypto/built-in.o \
  7. arch/arm/firmware/built-in.o                         arch/arm/mach-imx/built-in.o \
  8. kernel/built-in.o                                         mm/built-in.o \
  9. fs/built-in.o                                                 ipc/built-in.o \
  10. security/built-in.o                                         crypto/built-in.o\
  11. block/built-in.o                                         arch/arm/lib/lib.a\
  12. lib/lib.a                                                         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
复制代码

        除了arch/arm/kernel/vmlinux.lds以外,其他都是要编译链接生成的。在顶层Makefile中有如下代码:
示例代码35.5.4.1 顶层Makefile代码段
937 $(sort $(vmlinux-deps)): $(vmlinux-dirs);
        sort是排序函数,用于对vmlinux-deps的字符串列表进行排序,并且去掉重复的单词。可以看出vmlinux-deps依赖vmlinux-dirs,vmlinux-dirs也定义在顶层Makefile中,定义如下:
示例代码35.5.4.2 顶层Makefile代码段
889 vmlinux-dirs   := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
890              $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
891              $(net-y) $(net-m) $(libs-y) $(libs-m)))
        vmlinux-dirs看名字就知道和目录有关,此变量保存着生成vmlinux所需源码文件的目录,值如下:
  1. vmlinux-dirs = init                         usr                                         arch/arm/vfp \
  2.                         arch/arm/vdso         arch/arm/kernel                 arch/arm/mm \
  3.                         arch/arm/common        arch/arm/probes                 arch/arm/net \
  4.                         arch/arm/crypto         arch/arm/firmware                 arch/arm/mach-imx\
  5.                         kernel                         mm                                 fs \
  6.                         ipc                                 security                                crypto \
  7.                         block                         drivers                                 sound \
  8.                         firmware                 net                                         arch/arm/lib \
  9. lib
复制代码

        在顶层Makefile中有如下代码:
示例代码35.5.4.3 顶层Makefile代码段
946 $(vmlinux-dirs): prepare scripts
947     $(Q)$(MAKE) $(build)=$@
        目标vmlinux-dirs依赖prepare和scripts,这两个依赖不去浪费时间了,重点看一下第947行的命令。build前面已经说了,值为“-f ./scripts/Makefile.build obj”,因此将947行的命令展开就是:
@ 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/vdso
  5. @ make -f ./scripts/Makefile.build obj=arch/arm/kernel
  6. @ make -f ./scripts/Makefile.build obj=arch/arm/mm
  7. @ make -f ./scripts/Makefile.build obj=arch/arm/common
  8. @ make -f ./scripts/Makefile.build obj=arch/arm/probes
  9. @ make -f ./scripts/Makefile.build obj=arch/arm/net
  10. @ make -f ./scripts/Makefile.build obj=arch/arm/crypto
  11. @ make -f ./scripts/Makefile.build obj=arch/arm/firmware
  12. @ make -f ./scripts/Makefile.build obj=arch/arm/mach-imx
  13. @ make -f ./scripts/Makefile.build obj=kernel
  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,这个在35.5.2小节已经讲过了,我们再来看一下,__build目标对应的规则如下:
示例代码35.5.4.4 scripts/Makefile.build代码段
  1. 94 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
  2. 95   $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
  3. 96   $(subdir-ym) $(always)
  4. 97  @:
复制代码

        当只编译Linux内核镜像文件,也就是使用“makezImage”编译的时候,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中,定义如下:
示例代码35.5.4.5 scripts/Makefile.build代码段
  1. 86 ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
  2. 87 builtin-target := $(obj)/built-in.o
  3. 88 endif
复制代码

        第87行就是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中有如下代码:
示例代码35.5.4.6 顶层Makefile代码段
  1. 325 #
  2. 326 # Rule to compile a set of .o files into one .o file
  3. 327 #
  4. 328 ifdef builtin-target
  5. 329 quiet_cmd_link_o_target = LD      $@
  6. 330 # If the list of objects to link is empty, just create an empty built-in.o
  7. 331 cmd_link_o_target = $(if $(strip $(obj-y)),\
  8. 332               $(LD) $(ld_flags)-r -o $@ $(filter $(obj-y), $^) \
  9. 333               $(cmd_secanalysis),\
  10. 334               rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)
  11. 335
  12. 336 $(builtin-target): $(obj-y) FORCE
  13. 337     $(call if_changed,link_o_target)
  14. 338
  15. 339 targets += $(builtin-target)
  16. 340 endif # builtin-target
复制代码

        第336行的目标就是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所对应的命令,也就是第331~334行的命令。cmd_link_o_target就是使用LD将某个目录下的所有.o文件链接在一起,最终形成built-in.o。
35.5.5 makezImage过程
1、vmlinux、Image,zImage、uImage的区别
        前面几小节重点是讲vmlinux是如何编译出来的,vmlinux是ELF格式的文件,但是在实际中我们不会使用vmlinux,而是使用zImage或uImage这样的Linux内核镜像文件。那么vmlinux、zImage、uImage他们之间有什么区别呢?
        ①、vmlinux是编译出来的最原始的内核文件,是未压缩的,比如正点原子提供的Linux源码编译出来的vmlinux差不多有16MB,如图35.5.5.1所示:
image024.jpg

图35.5.5.1 vmlinux信息

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

图35.5.5.2 Image镜像信息

        相比vmlinux的16MB,Image缩小到了12MB。
        ③、zImage是经过gzip压缩后的Image,经过压缩以后其大小大概在6MB左右,如图35.5.5.3所示:
image028.jpg

图35.5.5.3 zImage镜像信息

        ④、uImage是老版本uboot专用的镜像文件,uImag是在zImage前面加了一个长度为64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的uboot已经支持了zImage启动!所以已经很少用到uImage了,除非你用的很古老的uboot。
        使用“make”、“makeall”、“makezImage”这些命令就可以编译出zImage镜像,在arch/arm/Makefile中有如下代码:
示例代码35.5.5.1 顶层Makefile代码段
  1. 310 BOOT_TARGETS    = zImage Image xipImage bootpImage uImage
  2. ......
复制代码

  1. 315 $(BOOT_TARGETS): vmlinux
  2. 316     $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
复制代码

        第310行,变量BOOT_TARGETS包含zImage,Image,xipImage等镜像文件。
        第315行,BOOT_TARGETS依赖vmlinux,因此如果使用“makezImage”编译的Linux内核的话,首先肯定要先编译出vmlinux。
        第316行,具体的命令,比如要编译zImage,那么命令展开以后如下所示:
@ 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周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-26 01:30

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

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