之前的几篇学习笔记重点讲解了如何移植uboot到STM32MP157开发板上,从本章就开始学习如何移植Linux内核。 同uboot一样,在具体移植之前,先来学习一下Linux内核的顶层Makefile文件,因为顶层 Makefile控制着Linux内核的编译流程。
先编译一下正点原子STM32MP157开发板的出厂Linux内核。
编译内核之前需要先在Ubuntu上安装lzop库,否则内核编译会失败!命令如下:
sudo apt-get update //先更新在安装,防止安装的时候报错 sudo apt-get install lzop sudo apt-get install libssl-dev |
STM32MP1编译出来的Linux内核镜像文件为uImage,这是uboot所使用的内核镜像格式,通过在zImage镜像的前面添加0X40个字节的头部来得到uImage,这个需要mkimage工具来完成此工作。所以需要在Ubuntu下安装mkimage工具,输入如下命令:
sudo apt-get install u-boot-tools |
首先编译一下正点原子出厂的Linux源码,在Ubuntu下新建一个名为“alientek_linux”的目录存放正点原子出厂Linux源码,正点原子出厂Linux系统源码已经放到了开发板光盘中,就是linux-5.4.31-gb8d3ec3ac-v1.1.tar.bz2,将正点原子出厂linux系统源码拷贝到前面在Ubuntu下新建的“alientek_linux”目录下,拷贝完成以后使用如下命令解压缩:
cd alientek_linux //进入到alientek_linux目录 tar -vxjf linux-5.4.31-gb8d3ec3ac-v1.1.tar.bz2 //解压缩 |
解压完成后如下图所示:
在上图中Linux源码根目录下新建名为“stm32mp157d_atk.sh”的shell脚本,然后在这个shell脚本中输入如下所示内容:
示例代码15.1.1 stm32mp157d_atk.sh文件内容
1 #!/bin/sh
2 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- distclean
3 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp1_atk_defconfig
4 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig
5 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- uImage dtbs LOADADDR=0XC2000040 -j16
第2行,执行“make distclean”,清理工程,所以stm32mp157d_atk.sh每次都会清理一下工
程。如果通过图形界面配置了Linux,但是还没保存新的配置文件,那么就要慎重使用stm32mp157d_atk.sh编译脚本了,因为它会把你的配置信息都删除掉!
第3行,执行“make xxx_defconfig”,配置工程,这里使用的配置文件为stm32mp1_atk_defconfig。
第4行,执行“make menuconfig”,打开图形配置界面,对Linux进行配置,如果不想每次编译都打开图形配置界面的话可以将这一行删除掉。
第5行,编译Linux内核,后面的“uImage”表示编译uImage格式的Linux内核,“dtbs”表示编译设备树,LOADADDR表示Linux内核在DDR中的加载地址为0XC2000040。
stm32mp157d_atk.sh编译脚本每次执行都是先清理整个工程,然后在重新全编译,这个过程很费时间,如果电脑配置差的话要编译很久。除了第一次编译Linux源码,我们很少清理工程全编译的,后续驱动开发很少用“make distclean”来清理工程,都是直接“make uImage LOADADDR=0XC2000040”编译内核或者“make dtbs”编译设备树。
使用chmod给予stm32mp157d_atk.sh可执行权限,然后运行此shell脚本,命令如下:
chmod 777 stm32mp157d_atk.sh //给予可执行权限 ./stm32mp157d_atk.sh //执行编译脚本 |
编译的时候会弹出Linux图形配置界面,如下图所示:
Linux图形界面配置和uboot是一样的,这里不需要做任何配置,直接按两下“ESC”退出,退出后就会自动开始编译Linux,编译完成后如下图所示:
编译完成后就会在arch/arm/boot这个目录下生成一个uImage的文件,uImage就是要用的Linux镜像文件,如下图所示:
另外也会在arch/arm/boot/dts下生成很多.dtb文件,这些.dtb就是设备树文件,需要的是stm32mp157d-atk.dtb这个文件,如下图所示:
在编译脚本stm32mp157d_atk.sh里面,每条编译命令都指定ARCH和CROSS_COMPILE这两个变量的值,如果单独运行编译命令的话输入起来会很麻烦,可以直接在Linux内核源码的Makefile文件里面指定ARCH和CROSS_COMPILE这两个变量的值,打开Linux内核源码目录下的主Makefile,按照如下图所示设置ARCH和CROSS_COMPILE:
在主Makefile中添加ARCH和CROSS_COMPILE以后,stm32mp157d_atk.sh就可以简化为:
示例代码15.1.2 简化后的stm32mp157d_atk.sh文件内容
1 #!/bin/sh
2 make distclean
3 make stm32mp1_atk_defconfig
4 make menuconfig
5 make uImage dtbs LOADADDR=0XC2000040 -j16
如果要单独编译uImage的话可以使用如下命令:
make uImage LOADADDR=0XC2000040 |
单独编译设备树可以使用如下命令:
make dtbs |
可以看出,Linux的编译过程基本和uboot一样,都要先执行“make xxx_defconfig”来配置一下,然后在执行“make uImage”进行编译,如果需要使用图形界面配置的话就执行“make menuconfig”。
将正点原子提供的Linux源码进行解压,完成后的目录如下图所示:
上图就是正点原子提供的未编译的Linux源码目录文件,在分析Linux之前一定要先在Ubuntu中编译一下Linux,因为编译过程会生成一些文件,而生成的这些恰恰是分析Linux不可或缺的文件。编译完成以后使用tar压缩命令对其进行压缩并使用Filezilla软件将压缩后的Linux源码拷贝到Windows下。
编译后的Linux目录如下图所示:
上图中重要的文件夹或文件的含义如下图所示:
上图中的很多文件夹和文件并不需要很在意,需要关注的重点文件夹和文件如下,会进行详细讲解。
这个目录是和架构有关的目录,比如arm、arm64、avr32、x86等等架构。每种架构都对应
一个目录,在这些目录中又有很多子目录,比如boot、common、configs等等,以arch/arm为例,其子目录如下图所示:
上图是arch/arm的一部分子目录,这些子目录用于控制系统引导、系统调用、动态调频、主频设置等。arch/arm/configs目录是不同平台的默认配置文件:xxx_defconfig,如下图所示:
在arch/arm/configs中就包含有STM32MP157开发板的默认配置文件:stm32mp1_atk_defconfig,执行“make stm32mp1_atk_defconfig”即可完成配置。arch/arm/boot/dts目录里面是对应开发平台
的设备树文件。
arch/arm/boot目录下会保存编译出来的Image和uImage镜像文件,而uImage就是要用的linux镜像文件。
arch/arm/mach-xxx目录分别为相应平台的驱动和初始化文件,比如mach-stm32目录里面就是STM32系列CPU的驱动和初始化文件。
block是Linux下块设备目录,像SD卡、EMMC、NAND、硬盘等存储设备就属于块设备,block目录中存放着管理块设备的相关文件。
crypto目录里存放着加密文件,比如常见的crc、crc32、md4、md5、hash等加密算法。
此目录里面存放着Linux相关的文档,如果要想了解Linux某个功能模块或驱动架构的功能,就可以在Documentation目录中查找有没有对应的文档。
驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如drivers/i2c就是I2C相关驱动目录,drivers/gpio就是GPIO相关的驱动目录,这是学习的重点。
此目录存放文件系统,比如fs/ext2、fs/ext4、fs/f2fs等,分别是ext2、ext4和f2fs等文件系统。
头文件目录。
此目录存放Linux内核启动时的初始化代码。
IPC为进程间通信,ipc目录是进程间通信的具体实现代码。
Linux内核代码。
lib是库的意思,lib目录都是一些公用的库函数。
此目录存放内存管理相关代码。
此目录存放网络相关代码。
此目录存放一些示例代码文件。
脚本目录,Linux编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录中。
此目录存放安全相关的文件。
此目录存放音频相关驱动文件,音频驱动文件并没有存放到drivers目录中,而是单独的目录。
此目录存放一些编译的时候使用到的工具。
此目录存放与 initramfs有关的代码。
此目录存放虚拟机相关文件。
跟uboot一样,.config保存着Linux最终的配置信息,编译Linux的时候会读取此文件中的配置信息。最终根据配置信息来选择编译Linux哪些模块,哪些功能。
有些Makefile会读取此文件。
图形化配置界面的配置文件。
Linux顶层Makefile文件,可以好好研读一下。
此文件详细讲解了如何编译Linux源码,以及Linux源码的目录信息,可以看一下此文件。
关于Linux源码目录就分析到这里,接下来分析一下Linux的顶层Makefile。
创建VScode的Linux内核工程。
Linux的顶层Makefile和uboot的顶层Makefile非常相似,因为uboot参考了Linux,前602行几乎一样,所以前面部分大致看一下就行了。
1、版本号
顶层Makefile一开始就是Linux内核的版本号,如下所示:
可以看出,Linux内核版本号为5.4.31。
2、MAKEFLAGS变量
3、命令输出
Linux编译的时候也可以通过“V=1”来输出完整的命令,这个和uboot一样,相关代码如下所示:
4、静默输出
Linux编译的时候使用“make -s”就可以实现静默编译,编译的时候就不会打印任何信息,同uboot一样,相关代码如下:
5、设置编译结果输出目录
Linux编译的时候使用“O=xxx”即可将编译产生的过程文件输出到指定的目录中,相关代码如下:
6、代码检查
Linux也支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile中的代码如下:
7、模块编译
Linux允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile中的代码如下:
外部模块编译过程和uboot基本类似,最终导出srctree、objtree和VPATH这三个变量的值,其中srctree=.,也就是当前目录objtree同样为“.”。
8、设置目标架构和交叉编译器
同uboot一样,Linux编译的时候需要设置目标板架构ARCH和交叉编译器CROSS_COMPILE,在顶层Makefile中代码如下:
这里只定义了ARCH,CROSS_COMPILE需要编译的时候手动输入值。为了方便,一般直接在顶层Makefile中设置ARCH和CROSS_COMPILE,直接将其设置为对应的架构和编译器。比如正点原子至今的教程都是将ARCH设置为arm,CROSS_COMPILE设置为arm-none-linux-gnueabihf-,如下所示:
示例代码15.3.9 顶层Makefile代码段
360 ARCH ?= arm
361 CROSS_COMPILE ?= arm-none-linux-gnueabihf-
设置好后就可以使用如下命令编译Linux:
make xxx_defconfig //使用默认配置文件配置 Linux
make menuconfig //启动图形化配置界面
make -j16 //编译 Linux
9、调用scripts/Kbuild.include文件
同uboot一样, Linux顶层Makefile也会调用文件scripts/Kbuild.include,顶层Makefile相应代码如下:
10、交叉编译工具变量设置
顶层Makefile中其他和交叉编译器有关的变量设置如下:
LA、LD、CC等这些都是交叉编译器使用的工具。
11、头文件路径设置
顶层Makefile定义了两个变量保存头文件路径:USERINCLUDE和LINUXINCLUDE,相关代码如下:
第442-447行的USERINCLUDE是UAPI相关的头文件路径,第451-456行的LINUXINCLUDE是Linux内核源码的头文件路径。重点来看一下LINUXINCLUDE,其中SRACARCH=arm。因此,将USERINCLUDE和LINUXINCLUDE展开以
后为:
12、导出变量
顶层Makefile会导出很多变量给子Makefile使用,导出的这些变量如下:
第一次编译Linux之前都要先使用“make xxx_defconfig”配置Linux内核,在顶层Makefile中有“%config”这个目标,如下所示:
示例代码15.3.1.1 顶层Makefile代码段
560 include arch/$(SRCARCH)/Makefile
561 export KBUILD_DEFCONFIG KBUILD_KCONFIG CC_VERSION_TEXT
562
563 config: outputmakefile scripts_basic FORCE
564 $(Q)$(MAKE) $(build)=scripts/kconfig $@
565
566 %config: outputmakefile scripts_basic FORCE
567 $(Q)$(MAKE) $(build)=scripts/kconfig $@
第560行引用arch/arm/Makefile这个文件,这个文件很重要,因为zImage、uImage等这些文件就是由arch/arm/Makefile来生成的。
第561行导出变量KBUILD_DEFCONFIG、KBUILD_KCONFIG、CC_VERSION_TEXT。
第563行,没有目标与之匹配,因此不执行。
第566行,“make xxx_defconfig”与目标 “%config”匹配,因此执行。“%config”依赖scripts_basic、 outputmakefile和FORCE,“%config”真正有意义的依赖就只有 scripts_basic,scripts_basic的规则如下:
示例代码15.3.1.2 顶层Makefile代码段
499 scripts_basic:
500 $(Q)$(MAKE) $(build)=scripts/basic
501 $(Q)rm -f .tmp_quiet_recordmcount
build定义在文件scripts/Kbuild.include中,值为build := -f $(srctree)/scripts/Makefile.build obj,因此将示例代码15.3.1.2展开就是:
scripts_basic: @make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有 @,视配置而定 @rm -f . tmp_quiet_recordmcount //也可以没有 @ |
接着回到示例代码15.3.1.1的目标“%config”处,内容如下:
%config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@ |
将命令展开就是:
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig |
@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
接下来一次分析一下:
scripts_basic目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文
件scripts/Makefile.build,有如下代码:
示例代码15.3.2.1 Makefile.build代码段
40 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
41 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
42 include $(kbuild-file)
将kbuild-dir展开后为:
kbuild-dir=./scripts/basic |
将kbuild-file展开后为:
kbuild-file= ./scripts/basic/Makefile |
最后将42行展开,即:
include ./scripts/basic/Makefile |
继续分析scripts/Makefile.build,代码如下:
示例代码15.3.2.2 Makefile.build代码段
488 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
489 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
490 $(subdir-ym) $(always)
491 @:
__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个依赖的具体内容如下:
builtin-target = lib-target = extra-y = subdir-ym = 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这两个软件。
%config目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig。Makefile.build会读取scripts/kconfig/Makefile中的内容,此文件有如下所示内容:
示例代码15.3.2.3 scripts/kconfig/Makefile代码段
89 %_defconfig: $(obj)/conf
90 $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
目标%_defconfig与xxx_defconfig匹配,所以会执行这条规则,将其展开就是:
%_defconfig: scripts/kconfig/conf @ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig |
%_defconfig依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c生成conf这个软件。此软件就会将%_defconfig中的配置输出到.config文件中,最终生成Linux kernel根目录下的.config文件。
使用命令“make xxx_defconfig”配置好Linux内核以后就可以使用“make”或者“make all”命令进行编译。顶层Makefile有如下代码:
第15行,_all是默认目标,如果使用命令“make”编译Linux的话此目标就会被匹配。
第577行,如果KBUILD_EXTMOD为空的话578行的代码成立。
第578行,默认目标_all依赖all。
第631行,目标all依赖vmlinux,所以接下来的重点就是vmlinux!
顶层Makefile中有如下代码:
示例代码15.3.3.2 顶层Makefile代码段
1037 export KBUILD_VMLINUX_OBJS := $(head-y) $(init-y) $(core-y) \
1038 $(libs-y2)$(drivers-y) $(net-y) $(virt-y)
1039 export KBUILD_VMLINUX_LIBS := $(libs-y1)
1040 export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
1041 export LDFLAGS_vmlinux
1042 # used by scripts/Makefile.package
1043 export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux- alldirs)) LICENSES arch include scripts tools)
1044
1045 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS)
1046
1047 # Recurse until adjust_autoksyms.sh is satisfied
1048 PHONY += autoksyms_recursive
1049 ifdef CONFIG_TRIM_UNUSED_KSYMS
1050 autoksyms_recursive: descend modules.order
1051 $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \
1052 "$(MAKE) -f $(srctree)/Makefile vmlinux"
1053 endif
......
1075 vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
1076 +$(call if_changed,link-vmlinux)
从第1075行可以看出目标vmlinux依赖scripts/link-vmlinux.sh、autoksyms_recursive、$(vmlinux-deps)和FORCE。第1045行定义了vmlinux-deps,值为:
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS) |
第1037行,KBUILD_VMLINUX_OBJS := $(head-y) $(init-y) $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)。
第1039行,KBUILD_VMLINUX_LIBS := $(libs-y1)。
第1040行,KBUILD_LDS:=arch/$(SRCARCH)/kernel/vmlinux.lds,其中SRCARCH=arm,因此 KBUILD_LDS= arch/arm/kernel/vmlinux.lds。
第1049行,没有定义宏CONFIG_TRIM_UNUSED_KSYMS,所以 autoksyms_recursive为空的。
综上所述,vmlinux的依赖为:scripts/link-vmlinux.sh、$(head-y)、$(init-y)、$(core-y) 、$(libs-y2) 、$(drivers-y) 、$(net-y)、$(virt-y)、$(libs-y1)、arch/arm/kernel/vmlinux.lds和FORCE。
第1076行的命令用于链接生成vmlinux。
重点来看一下$(head-y)、$(init-y)、$(core-y) 、$(libs-y2) 、$(drivers-y) 、$(net-y)、$(virt-y)、$(libs-y1)这八个变量的值。
head-y定义在文件arch/arm/Makefile中,内容如下:
示例代码15.3.3.3 arch/arm/Makefile代码段
144 head-y := arch/arm/kernel/head$(MMUEXT).o
当不使能MMU的时候MMUEXT=-nommu,如果使能MMU的话为空,因此head-y最终的值为:
head-y = arch/arm/kernel/head.o |
在顶层Makefile中有如下代码:
示例代码15.3.3.4 顶层Makefile代码段
618 init-y := init/
619 drivers-y := drivers/ sound/
620 drivers-$(CONFIG_SAMPLES) += samples/
621 net-y := net/
......
1028 init-y := $(patsubst %/, %/built-in.a, $(init-y))
1030 drivers-y:= $(patsubst %/, %/built-in.a, $(drivers-y))
1031 net-y := $(patsubst %/, %/built-in.a, $(net-y))
从示例代码15.3.3.4可知,init-y、drivers-y和net-y最终的值为:
init-y = init/built-in.o drivers-y = drivers/built-in.a sound/built-in.a samples/built-in.a net-y = net/built-in.o |
libs-y基本和init-y一样,在顶层Makefile中存在如下代码:
示例代码15.3.3.5 顶层Makefile代码段
622 libs-y := lib/
......
1032 libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
1033 libs-y2 := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))
根据示例代码15.3.3.5可知,“libs-y1=lib/lib.a”,“libs-y2=lib/built-in.o”,这个只正确了一部分,因为在arch/arm/Makefile中会向libs-y中追加一些值,代码如下:
示例代码15.3.3.6 arch/arm/Makefile代码段
297 libs-y := arch/arm/lib/ $(libs-y)
arch/arm/Makefile将libs-y的值改为了:arch/arm/lib $(libs-y),展开以后为:
libs-y = arch/arm/lib lib/ |
因此根据示例代码15.3.3.5的第1032和1033行可知,libs-y1和libs-y2最终应该为:
libs-y1 = arch/arm/lib/lib.a lib/lib.a libs-y2 = arch/arm/lib/built-in.o lib/built-in.o |
virt-y在顶层Makefile中有如下代码:
示例代码15.3.3.7 顶层Makefile代码段
624 virt-y := virt/
......
1034 virt-y := $(patsubst %/, %/built-in.a, $(virt-y))
从示例代码15.3.3.7可知,virt-y最终的值为:
virt-y= virt/built-in.a |
core-y和init-y也一样,在顶层Makefile中有如下代码:
示例代码15.3.3.8 顶层Makefile代码段
623 core-y := usr/
......
1015 core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
但是在arch/arm/Makefile中会对core-y进行追加,代码如下:
示例代码15.3.3.9 arch/arm/Makefile代码段
276 core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/
277 # Put arch/arm/fastfpe/ to use this.
278 core-$(CONFIG_FPE_FASTFPE) += $(patsubst $(srctree)/%,%,$(wildcard $(srctree)/arch/arm/fastfpe/))
279 core-$(CONFIG_VFP) += arch/arm/vfp/
280 core-$(CONFIG_XEN) += arch/arm/xen/
281 core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/
282 core-$(CONFIG_VDSO) += arch/arm/vdso/
283
284 # If we have a machine-specific directory, then include it in the build.
285 core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
286 core-y += arch/arm/probes/
287 core-y += arch/arm/net/
288 core-y += arch/arm/crypto/
289 core-y += $(machdirs) $(platdirs)
第276-282行根据不同的配置向core-y追加不同的值,比如使能VFP的话就会在.config中有CONFIG_VFP=y这一行,那么core-y就会追加“arch/arm/vfp/”。
第285-289行就是对core-y直接追加的值。
在顶层Makefile中有如下一行:
示例代码15.3.3.10 顶层Makefile代码段
1029 core-y := $(patsubst %/, %/built-in.a, $(core-y))
经过上述代码的转换,最终core-y的值为:
core-y = usr/built-in.a arch/arm/vfp/built-in.a arch/arm/vdso/built-in.a arch/arm/kernel/built-in.a arch/arm/mm/built-in.a arch/arm/common/built-in.a arch/arm/probes/built-in.a arch/arm/net/built-in.a arch/arm/crypto/built-in.a arch/arm/mach-aspeed/built-in.a arch/arm/mach-milbeaut/built-in.a arch/arm/mach-stm32/built-in.a kernel/built-in.a certs/built-in.a mm/built-in.a fs/built-in.a ipc/built-in.a security/built-in.a crypto/built-in.a block/built-in.a |
关于head-y 、init-y、core-y、libs-y2、drivers-y、net-y、virt-y和libs-y1这8个变量就讲解到这里。这些变量都是一些built-in.o或.a等文件,这个和uboot一样,都是**将相应目录中的源码文件进行编译,然后在各自目录下生成built-in.o文件,有些生成了.a库文件。最终将这些built-in.o和.a文件进行链接即可形成ELF格式的可执行文件,也就是vmlinux!**但是链接是需要链接脚本的,vmlinux的依赖arch/arm/kernel/vmlinux.lds就是整个Linux的链接脚本。
示例代码15.3.3.2第1076行的命令“+$(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中,如下所示:
示例代码15.3.3.11 scripts/Kbuild.include代码段
217 if_changed = $(if $(any-prereq)$(cmd-check), \
218 $(cmd); \
219 printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
any-prereq用于检查依赖文件是否有变化,如果依赖文件有变化那么any-prereq就不为空,否则就为空。cmd-check用于检查命令是否有变化,如果没有变化那么cmd-check就为空。当命令或者依赖有变化的话就执行命令。
第219行,打印命令执行过程。比如示例代码15.3.3.2中第1076行“(call if_changed,link-vmlinux)”,这行命令用于链接vmlinux。在这里$@就是“link-vmlinux”,因此cmd_$@表示执
行cmd_link-vmlinux的内容。cmd_link-vmlinux在顶层Makefile中有如下所示定义:
示例代码15.3.3.12 顶层Makefile代码段
1071 cmd_link-vmlinux = \
1072 $(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux);\
1073 $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
第1071行就是cmd_link-vmlinux的值,其中CONFIG_SHELL=sh,$<表示目标vmlinux的第一个依赖文件,根据示例代码15.3.3.2可知,这个文件为scripts/link-vmlinux.sh。LD= arm-none-linux-gnueabihf-ld -EL,KBUILD_LDFLAGS= -EL。LDFLAGS_vmlinux的值由顶层Makefile和arch/arm/Makefile这两个文件共同决定,最终LDFLAGS_vmlinux=–no-undefined -X --pic-veneer --build-id。因此cmd_link-vmlinux最终的值为:
cmd_link-vmlinux = sh scripts/link-vmlinux.sh arm-none-linux-gnueabihf-ld -EL--no-undefined -X --pic-veneer --build-id |
cmd_link-vmlinux会调用scripts/link-vmlinux.sh这个脚本来链接出vmlinux!在link-vmlinux.sh中有如下所示代码:
示例代码15.3.3.13 scripts/link-vmlinux.sh代码段
58 # Link of vmlinux
59 # ${1} - output file
60 # ${2}, ${3}, ... - optional extra .o files
61 vmlinux_link()
62 {
63 local lds="${objtree}/${KBUILD_LDS}"
64 local output=${1}
65 local objects
66
67 info LD ${output}
68
69 # skip output file argument
70 shift
71
72 if [ "${SRCARCH}" != "um" ]; then
73 objects="--whole-archive \
74 ${KBUILD_VMLINUX_OBJS} \
75 --no-whole-archive \
76 --start-group \
77 ${KBUILD_VMLINUX_LIBS} \
78 --end-group \
79 ${@}"
80
81 ${LD} ${KBUILD_LDFLAGS} ${LDFLAGS_vmlinux} \
82 -o ${output} \
83 -T ${lds} ${objects}
84 else
85 objects="-Wl,--whole-archive \
86 ${KBUILD_VMLINUX_OBJS} \
87 -Wl,--no-whole-archive \
88 -Wl,--start-group \
89 ${KBUILD_VMLINUX_LIBS} \
90 -Wl,--end-group \
91 ${@}"
92
93 ${CC} ${CFLAGS_vmlinux} \
94 -o ${output} \
95 -Wl,-T,${lds} \
96 ${objects} \
97 -lutil -lrt -lpthread
98 rm -f linux
99 fi
100 }
......
305 vmlinux_link vmlinux "${kallsymso}" ${btf_vmlinux_bin_o}
vmliux_link就是最终链接出vmlinux的函数,第72行判断SRCARCH是否等于“um”,如果不相等的话就执 73-83行的代码。因为SRCARCH=arm,因此条件成立,执行73-83行的代码。重点看一下81-83行,这三行代码就应该很熟悉了!就是普通的链接操作,连接脚本为lds=./arch/arm/kernel/vmlinux.lds。
第305行调用 vmlinux_link函数来链接出vmlinux。
使用命令“make V=1”编译Linux,会有如下图所示的编译信息:
至此基本理清了make的过程,重点就是将各个子目录下的built-in.o、.a等文件链接在一起,最终生成vmlinux这个ELF格式的可执行文件。链接脚本为arch/arm/kernel/vmlinux.lds,链接过程是由shell脚本scripts/link-vmlinux.s来完成的。接下来的问题就是这些子目录下的built-in.o、.a等文件又是如何编译出来的呢?
根据示例代码15.3.3.2第1075行可知,vmliux依赖vmlinux-deps,而vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS),KBUILD_LDS是链接脚本,这里不考虑,剩下的KBUILD_VMLINUX_OBJS和KBUILD_VMLINUX_LIBS就是各个子目录下的built-in.o、.a等文件 。最终vmlinux-deps的值如下:
vmlinux-deps = arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o init/built-in.a usr/built-in.a arch/arm/vfp/built-in.a arch/arm/vdso/built-in.a arch/arm/kernel/built-in.a arch/arm/mm/built-in.a arch/arm/common/built-in.a arch/arm/probes/built-in.a arch/arm/net/built-in.a arch/arm/crypto/built-in.a arch/arm/mach-aspeed/built-in.a arch/arm/mach-milbeaut/built-in.a arch/arm/mach-stm32/built-in.a kernel/built-in.a certs/built-in.a mm/built-in.a fs/built-in.a ipc/built-in.a security/built-in.a crypto/built-in.a block/built-in.a arch/arm/lib/built-in.a lib/built-in.a drivers/built-in.a sound/built-in.a samples/built-in.a net/built-in.a virt/built-in.a arch/arm/lib/lib.a lib/lib.a |
除了arch/arm/kernel/vmlinux.lds以外,其他都是要编译链接生成的。在顶层Makefile中有
如下代码:
示例代码15.3.4.1 顶层Makefile代码段
1082 $(sort $(vmlinux-deps)): descend ;
sort是排序函数,用于对vmlinux-deps的字符串列表进行排序,并且去掉重复的单词。可以看出vmlinux-deps依赖descend,descend也定义在顶层Makefile中,定义如下:
示例代码15.3.4.2 顶层Makefile代码段
1025 build-dirs := $(vmlinux-dirs)
......
1688 descend: $(build-dirs)
1689 $(build-dirs): prepare
1690 $(Q)$(MAKE) $(build)=$@ \
1691 single-build=$(if $(filter-out $@/, $(single-no-ko)),1) \
1692 need-builtin=1 need-modorder=1
可以看出descend又依赖build-dirs,从1025行可以看出,build-dirs就是vmlinux-dirs。vmlinux-dirs看名字就知道和目录有关,此变量保存着生成vmlinux所需源码文件的目录,值如下:
示例代码15.3.4.2中第1689行,目标build-dirs依赖prepare,这个依赖不重要。重点看一下第1690行的命令。build前面已经说了,值为“-f ./scripts/Makefile.build obj”,因此将1690行的命令展开就是:
@ make -f ./scripts/Makefile.build obj=$@ |
$@表示目标文件,也就是build-dirs的值,而build-dirs就是vmlinux-dirs,将vmlinux-dirs中的这些目录全部带入到命令中,结果如下:
@ make -f ./scripts/Makefile.build obj=init @ make -f ./scripts/Makefile.build obj=usr @ make -f ./scripts/Makefile.build obj=arch/arm/vfp @ make -f ./scripts/Makefile.build obj=arch/arm/vdso @ make -f ./scripts/Makefile.build obj=arch/arm/kernel @ make -f ./scripts/Makefile.build obj=arch/arm/mm @ make -f ./scripts/Makefile.build obj=arch/arm/common @ make -f ./scripts/Makefile.build obj=arch/arm/probes @ make -f ./scripts/Makefile.build obj=arch/arm/net …… @ make -f ./scripts/Makefile.build obj=virt |
这些命令运行过程其实都是一样的,就以“@ make -f ./scripts/Makefile.build obj=init”这个命令为例,讲解一下详细的运行过程。这里又要用到 Makefile.build这个脚本了,此脚本默认目标为__build,再来看一下,__build目标对应的规则如
下:
示例代码15.3.4.4 scripts/Makefile.build代码段
488 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
489 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
490 $(subdir-ym) $(always)
491 @:
当只编译Linux内核镜像文件,也就是使用“make uImage”编译的时候,KBUILD_BUILTIN=1,KBUILD_MODULES为空。“make”命令是会编译所有的东西,包括Linux内核镜像文件和一些模块文件。如果只编译Linux内核镜像的话,__build目标简化为:
__build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always) @: |
重点来看一下builtin-target这个依赖,builtin-target同样定义在文件scripts/Makefile.build中,定义如下:
示例代码15.3.4.5 scripts/Makefile.build代码段
69 ifneq ($(strip $(real-obj-y) $(need-builtin)),)
70 builtin-target := $(obj)/built-in.a
71 endif
第70行就是builtin-target变量的值,为“$(obj)/built-in.o”,这就是这些built-in.o的来源了。要生成built-in.o,要求real-obj-y和need-builtin这些变量不能全部为空。
前面几小节重点是讲vmlinux是如何编译出来的,vmlinux是ELF格式的文件,但是在实际中不会使用vmlinux,而是使用zImage或uImage这样的Linux内核镜像文件。
1、vmlinux是编译出来的最原始的内核文件,是未压缩的,比如正点原子提供的Linux源码编译出来的vmlinux差不多有260多MB,如下图所示:
2、Image是Linux内核镜像文件,但是Image仅包含可执行的二进制数据。 Image就是使用objcopy取消掉vmlinux中的一些其他信息,比如符号表。但是Image是没有压缩过的,Image保存在arch/arm/boot目录下,其大小大概在19MB左右,如下图所示:
相比vmlinux的260MB,Image缩小到了19MB。
3、zImage是经过gzip压缩后的Image,经过压缩以后其大小大概在8MB左右,如下图所示:
4、uImage是老版本uboot专用的镜像文件,uImage是在zImage前面加了一个长度为64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息,uImage大小如下图所示:
上图中zImage大小为8133168字节,uImage大小为8133232字节,差距就是8133232-8133168=64字节,这64字节就是头部信息。
使用“make”、“make all”、“make uImage”这些命令就可以编译出uImage镜像,在arch/arm/Makefile中有如下代码:
第333行,变量BOOT_TARGETS包含zImage,Image,xipImage,bootpImage和uImage。
第341行,BOOT_TARGETS依赖vmlinux,因此如果使用“make uImage”编译Linux内核的话,首先肯定要先编译出vmlinux。
第342行,具体的命令,比如要编译uImage,那么命令展开以后如下所示:
@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/uImage @: |
就是使用scripts/Makefile.build文件来完成vmlinux到uImage的转换。
关于Linux顶层Makefile就讲解到这里,基本和uboot的顶层Makefile一样,重点在于vmlinux的生成。最后将vmlinux压缩成最常用的uImage或zImage等文件。
Linux内核的顶层Makefile的前半部分和uboot的就是一样的,可以去参考uboot的顶层Makefile详解,来进行学习,或者知道一个框架就可以了。
Linux内核的Makefile区别在于,关注的重点是vmlinux的生成。
首先会使用“make xxx_defconfig”配置Linux内核,最终展开就可以得到,最后执行的命令如下所示:
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig |
而配置的时候,有两行命令会执行脚本scripts/Makefile.build:
@make -f ./scripts/Makefile.build obj=scripts/basic @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig |
scripts_basic目标的作用就是编译出scripts/basic/fixdep和scripts/basic/bin2c这
两个软件。
%config目标会读取%_defconfig,这个会跟xxx_defconfig匹配,%_defconfig依赖scripts/kconfig/conf,会编译scripts/kconfig/conf.c生成conf软件,把%_defconfig中配置输出到.config,最终生成Linux kernel根目录下.config。
以上均与uboot的解读类似。
“make”的默认目标是_all,_all依赖all,all依赖vmlinux。
vmlinux依赖如下:scripts/link-vmlinux.sh、$(head-y)、$(init-y)、$(core-y) 、$(libs-y2) 、$(drivers-y) 、$(net-y)、$(virt-y)、$(libs-y1)、arch/arm/kernel/vmlinux.lds和FORCE。
那8个变量展开后如下:
head-y = arch/arm/kernel/head.o init-y = init/built-in.o drivers-y = drivers/built-in.a sound/built-in.a samples/built-in.a net-y = net/built-in.o libs-y1 = arch/arm/lib/lib.a lib/lib.a libs-y2 = arch/arm/lib/built-in.o lib/built-in.o virt-y= virt/built-in.a core-y = usr/built-in.a arch/arm/vfp/built-in.a arch/arm/vdso/built-in.a arch/arm/kernel/built-in.a arch/arm/mm/built-in.a arch/arm/common/built-in.a arch/arm/probes/built-in.a arch/arm/net/built-in.a arch/arm/crypto/built-in.a arch/arm/mach-aspeed/built-in.a arch/arm/mach-milbeaut/built-in.a arch/arm/mach-stm32/built-in.a kernel/built-in.a certs/built-in.a mm/built-in.a fs/built-in.a ipc/built-in.a security/built-in.a crypto/built-in.a block/built-in.a |
arch/arm/kernel/vmlinux.lds就是整个Linux的链接脚本,调用if_changed进行链接,最终执行cmd_link_vmlinux,而他展开后的值就是:
cmd_link-vmlinux = sh scripts/link-vmlinux.sh arm-none-linux-gnueabihf-ld -EL--no-undefined -X --pic-veneer --build-id cmd_link-vmlinux |
也就是调用scripts/link-vmlinux脚本链接vmlinux,最终调用的是里面的vmlinux_link函数链接vmlinux。
最后用的是vmlinux编译后,经过objcopy处理并gzip压缩,添加头信息的uImage镜像文件。