I.MX6ULL_Linux_系统篇(19) kernel编译及Makefile分析

news2024/9/22 11:32:35

Linux 内核

Linux 由 Linux 基金会管理与发布, Linux 官网为 https://www.kernel.org,所以你想获取最新的Linux 版本就可以在这个网站上下载,网站界面如图所示:

从图中可以看出最新的稳定版 Linux 已经到了 6.2,NXP 会从 https://www.kernel.org 下载某个版本的 Linux 内核,然后将其移植到自己的 CPU上,测试成功以后就会将其开放给 NXP 的 CPU 开发者。开发者下载 NXP 提供的 Linux 内核,然后将其移植到自己的产品上。本章的移植我们就使用 NXP 提供的 Linux 源码, NXP 提供 Linux源码:linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

编译内核

编译内核之前需要先在 ubuntu 上安装 lzop 库,否则内核编译会失败!如果是新机器,也可能会缺少其他依赖,命令如下:

sudo apt-get install lzop

先看一下如何编译 Linux 源码,这里编译 I.MX6U-ALPHA 开发板移植好的 Linux 源码,将内核压缩包解压,命令如下:

tar -vxjf linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

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

 编译命令:

2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
4 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
5 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

第 2 行,执行“make distclean”,清理工程
第 3 行,执行“make xxx_defconfig”,配置工程
第 4 行,执行“make menuconfig”,打开图形配置界面,对 Linux 进行配置
第 5 行,执行“make”,编译 Linux 源码。
可以看出, Linux 的编译过程基本和 uboot 一样,都要先执行“make xxx_defconfig”来配置一下,然后再执行“make”进行编译。如果需要使用图形界面配置的话就执行“make menuconfig”。

编译完成以后就会在 arch/arm/boot 这个目录下生成一个叫做 zImage 的文件, zImage 就是我们要用的 Linux 镜像文件。另外也会在 arch/arm/boot/dts 下生成很多.dtb 文件,这些.dtb 就是
设备树文件。

Linux 目录分析

arch架构相关
block块设备相关
crypto加密相关
Documentation参考文档
drivers驱动
firmeare固件相关
fs文件系统相关
include头文件
init初始化相关
ipc进程间通信
kernel内核相关
lib
mm内存管理
net网络协议
samples例程相关
scripts脚本
security安全相关
sound音频处理乡相关
tools工具
usrinitramfs相关
virt虚拟技术KVM
.config配置文件
Kconfig图形化界面配置文件
Makefile顶层编译文件
vmlinux编译出来的。未压缩的ELF格式linux文件

arch 目录

这个目录是和架构有关的目录,比如 arm、 arm64、 avr32、 x86 等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如 boot、 common、 configs 等等,以 arch/arm 为
例,其子目录如图所示:

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

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

arch/arm/boot 目录下会保存编译出来的 Image 和 zImage 镜像文件,而 zImage 就是我们要用的 linux 镜像文件。arch/arm/mach-xxx 目录分别为相应平台的驱动和初始化文件,比如 mach-imx 目录里面就是 I.MX 系列 CPU 的驱动和初始化文件。

block 目录

block 是 Linux 下块设备目录,像 SD 卡、 EMMC、 NAND、硬盘等存储设备就属于块设备,block 目录中存放着管理块设备的相关文件。

crypto 目录

crypto 目录里面存放着加密文件,比如常见的 crc、 crc32、 md4、 md5、 hash 等加密算法。

Documentation 目录

此目录里面存放着 Linux 相关的文档,如果要想了解 Linux 某个功能模块或驱动架构的功能,就可以在 Documentation 目录中查找有没有对应的文档。

drivers 目录

驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如 drivers/i2c 就是 I2C相关驱动目录, drivers/gpio 就是 GPIO 相关的驱动目录,这是我们学习的重点。

firmware 目录

此目录用于存放固件。

fs 目录

此目录存放文件系统,比如 fs/ext2、 fs/ext4、 fs/f2fs 等,分别是 ext2、 ext4 和 f2fs 等文件系统。

include 目录

头文件目录

init 目录

此目录存放 Linux 内核启动的时候初始化代码。

ipc 目录

IPC 为进程间通信, ipc 目录是进程间通信的具体实现代码。

kernel 目录

Linux 内核代码。

lib 目录

lib 是库的意思, lib 目录都是一些公用的库函数。

mm 目录

此目录存放内存管理相关代码。

net 目录

此目录存放网络相关代码。

samples 目录

此目录存放一些示例代码文件。

scripts 目录

脚本目录, Linux 编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录中。

security 目录

此目录存放安全相关的文件。

sound 目录

此目录存放音频相关驱动文件,音频驱动文件并没有存放到 drivers 目录中,而是单独的目录。

tools 目录

此目录存放一些编译的时候使用到的工具。

usr 目录

此目录存放与 initramfs 有关的代码。

virt 目录

此目录存放虚拟机相关文件。

.config 文件

跟 uboot一样, .config保存着Linux 最终的配置信息,编译Linux 的时候会读取此文件中的配置信息。最终根据配置信息来选择编译 Linux 哪些模块,哪些功能。

Kbuild 文件

有些 Makefile 会读取此文件。

Kconfig 文件

图形化配置界面的配置文件。

Makefile 文件

Linux 顶层 Makefile 文件,建议好好阅读一下此文件。

MAKEFLAGS += -rR --include-dir=$(CURDIR)

README 文件

此文件详细讲解了如何编译 Linux 源码,以及 Linux 源码的目录信息,建议仔细阅读一下此文件。
关于 Linux 源码目录就分析到这里,接下来分析一下 Linux 的顶层 Makefile。

顶层 Makefile 详解

Linux 的顶层 Makefile 和 uboot 的顶层 Makefile 非常相似,因为 uboot 参考了 Linux,前 602行几乎一样,所以前面部分我们大致看一下就行了。

版本号

顶层 Makefile 一开始就是 Linux 内核的版本号,如下所示,可以看出, Linux 内核版本号为 4.1.15。

VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 15
EXTRAVERSION =

MAKEFLAGS 变量

MAKEFLAGS 变量设置如下所示:

MAKEFLAGS += -rR --include-dir=$(CURDIR)

命令输出

Linux 编译的时候也可以通过“V=1”来输出完整的命令,这个和 uboot 一样,相关代码如下所示:

ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

静默输出

Linux 编译的时候使用“make -s”就可实现静默编译,编译的时候就不会打印任何的信息,同 uboot 一样,相关代码如下:

# If the user is running make -s (silent mode), suppress echoing of
# commands

ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  quiet=silent_
endif
else					# make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
  quiet=silent_
endif
endif

export quiet Q KBUILD_VERBOSE

编译输出目录

Linux 编译的时候使用“O=xxx”即可将编译产生的过程文件输出到指定的目录中,相关代码如下:

# kbuild supports saving output files in a separate directory.
# To locate output files in a separate directory two syntaxes are supported.
# In both cases the working directory must be the root of the kernel src.
# 1) O=
# Use "make O=dir/to/store/output/files/"
#
# 2) Set KBUILD_OUTPUT
# Set the environment variable KBUILD_OUTPUT to point to the directory
# where the output files shall be placed.
# export KBUILD_OUTPUT=dir/to/store/output/files/
# make
#
# The O= assignment takes precedence over the KBUILD_OUTPUT environment
# variable.

# KBUILD_SRC is set on invocation of make in OBJ directory
# KBUILD_SRC is not intended to be used by the regular user (for now)
ifeq ($(KBUILD_SRC),)

# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

代码检查

Linux 也支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层 Makefile 中的代码如下:

# Call a source code checker (by default, "sparse") as part of the
# C compilation.
#
# Use 'make C=1' to enable checking of only re-compiled files.
# Use 'make C=2' to enable checking of *all* source files, regardless
# of whether they are re-compiled or not.
#
# See the file "Documentation/sparse.txt" for more details, including
# where to get the "sparse" utility.

ifeq ("$(origin C)", "command line")
  KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

模块编译

Linux允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile中的代码如下:

# Use make M=dir to specify directory of external module to build
# Old syntax make ... SUBDIRS=$PWD is still supported
# Setting the environment variable KBUILD_EXTMOD take precedence
ifdef SUBDIRS
  KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

ifeq ($(KBUILD_SRC),)
        # building in the source tree
        srctree := .
else
        ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
                # building in a subdirectory of the source tree
                srctree := ..
        else
                srctree := $(KBUILD_SRC)
        endif
endif
objtree		:= .
src		:= $(srctree)
obj		:= $(objtree)

VPATH		:= $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))

export srctree objtree VPATH

外部模块编译过程和 uboot 也一样,最终导出 srctree、 objtree 和 VPATH 这三个变量的值,其中 srctree=.,也就是当前目录, objtree 同样为“.”。

设置架构和交叉编译器

同 uboot一样, Linux编译的时候需要设置目标板架构ARCH 和交叉编译器 CROSS_COMPILE,在顶层 Makefile 中代码如下:

# Cross compiling and selecting different set of gcc/bin-utils
# ---------------------------------------------------------------------------
#
# When performing cross compilation for other architectures ARCH shall be set
# to the target architecture. (See arch/* for the possibilities).
# ARCH can be set during invocation of make:
# make ARCH=ia64
# Another way is to have ARCH set in the environment.
# The default ARCH is the host where make is executed.

# CROSS_COMPILE specify the prefix used for all executables used
# during compilation. Only gcc and related bin-utils executables
# are prefixed with $(CROSS_COMPILE).
# CROSS_COMPILE can be set on the command line
# make CROSS_COMPILE=ia64-linux-
# Alternatively CROSS_COMPILE can be set in the environment.
# A third alternative is to store a setting in .config so that plain
# "make" in the configured kernel build directory always uses that.
# Default value for CROSS_COMPILE is not to prefix executables
# Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile
ARCH		?= $(SUBARCH)
CROSS_COMPILE	?= $(CONFIG_CROSS_COMPILE:"%"=%)

调用 scripts/Kbuild.include 文件

同 uboot 一样, Linux 顶层 Makefile 也会调用文件 scripts/Kbuild.include,顶层 Makefile 相应代码如下:

# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
include scripts/Kbuild.include

交叉编译工具变量设置

顶层 Makefile 中其他和交叉编译器有关的变量设置如下:

# Make variables (CC, etc...)
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm
STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

头文件路径变量

顶层 Makefile 定义了两个变量保存头文件路径: USERINCLUDE 和 LINUXINCLUDE,相关代码如下:

# Use USERINCLUDE when you must reference the UAPI directories only.
USERINCLUDE    := \
		-I$(srctree)/arch/$(hdr-arch)/include/uapi \
		-Iarch/$(hdr-arch)/include/generated/uapi \
		-I$(srctree)/include/uapi \
		-Iinclude/generated/uapi \
                -include $(srctree)/include/linux/kconfig.h

# Use LINUXINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
LINUXINCLUDE    := \
		-I$(srctree)/arch/$(hdr-arch)/include \
		-Iarch/$(hdr-arch)/include/generated/uapi \
		-Iarch/$(hdr-arch)/include/generated \
		$(if $(KBUILD_SRC), -I$(srctree)/include) \
		-Iinclude \
		$(USERINCLUDE)

USERINCLUDE 是 UAPI 相关的头文件路径,LINUXINCLUDE 是 Linux 内核源码的头文件路径。重点来看一下 LINUXINCLUDE,其中srctree=., hdr-arch=arm, KBUILD_SRC 为空,因此,将 USERINCLUDE 和 LINUXINCLUDE 展开以后为:

USERINCLUDE := \
    -I./arch/arm/include/uapi \
    -Iarch/arm/include/generated/uapi \
    -I./include/uapi \
    -Iinclude/generated/uapi \
    -include ./include/linux/kconfig.h

LINUXINCLUDE := \
    -I./arch/arm/include \
    -Iarch/arm/include/generated/uapi \
    -Iarch/arm/include/generated \
    -Iinclude \
    -I./arch/arm/include/uapi \
    -Iarch/arm/include/generated/uapi \
    -I./include/uapi \
    -Iinclude/generated/uapi \
    -include ./include/linux/kconfig.h

导出变量

顶层 Makefile 会导出很多变量给子 Makefile 使用,导出的这些变量如下:

export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM STRIP OBJCOPY OBJDUMP
export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS

export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN
export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
export KBUILD_ARFLAGS

make xxx_defconfig 过程

第一次编译 Linux 之前都要使用“make xxx_defconfig”先配置 Linux 内核,在顶层 Makefile中有“%config”这个目标,如下所示:

第 490~507 行和 uboot 一样,都是设置定义变量 config-targets、 mixed-targets 和 dot-config的值,最终这三个变量的值为:

config-targets= 1
mixed-targets= 0
dot-config= 1

因为 config-targets=1,因此第 534 行~541 行成立。第 534 行引用 arch/arm/Makefile 这个文件,这个文件很重要,因为 zImage、 uImage 等这些文件就是由 arch/arm/Makefile 来生成的。
第 535 行导出变量 KBUILD_DEFCONFIG KBUILD_KCONFIG。
第 537 行,没有目标与之匹配,因此不执行。
第 540 行,“make xxx_defconfig”与目标“%config”匹配,因此执行。“%config”依赖scripts_basic、 outputmakefile 和 FORCE,“%config”真正有意义的依赖就只有 scripts_basic,scripts_basic 的规则如下:

build 定义在文件 scripts/Kbuild.include 中,值为 build := -f $(srctree)/scripts/Makefile.build obj,因此将命令展开就是:

scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount //也可以没有@

回到config%目标,将命令展开:

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

Makefile.build 分析

从上一小节可知,“ make xxx_defconfig“配置 Linux 的时候如下两行命令会执行脚本scripts/Makefile.build:

@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,有如下代码:

 将命令展开:

kbuild-dir=./scripts/basic
kbuild-file= ./scripts/basic/Makefile
include ./scripts/basic/Makefile

继续分析 scripts/Makefile.build,如下代码:

__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 目标对应的命令:

%config 目 标 对 应 的 命 令 为 : @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig,此命令会使用到的各个变量值如下:

src= scripts/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile

可以看出, Makefile.build 会读取 scripts/kconfig/Makefile 中的内容,此文件有如下所示内容:

%_defconfig: $(obj)/conf
    $(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 过程

使用命令“make xxx_defconfig”配置好 Linux 内核以后就可以使用“make”或者“make all”命令进行编译。顶层 Makefile 有如下代码:

 第 126 行, _all 是默认目标,如果使用命令“make”编译 Linux 的话此目标就会被匹配。

第 193 行,如果 KBUILD_EXTMOD 为空的话 194 行的代码成立。
第 194 行,默认目标_all 依赖 all。

 第 608 行,目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux!

从第 920 行可以看出目标 vmlinux 依赖 scripts/link-vmlinux.sh $(vmlinux-deps) FORCE。第912 行定义了 vmlinux-deps。

第 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)这六个变量的值。

head-y

head-y 定义在文件 arch/arm/Makefile 中,内容如下:

head-y := arch/arm/kernel/head$(MMUEXT).o

当不使能 MMU 的话 MMUEXT=-nommu,如果使能 MMU 的话为空,因此 head-y 最终的值为:

head-y = arch/arm/kernel/head.o

init-y、 drivers-y 和 net-y

在顶层 Makefile 中有如下代码:

558 init-y := init/
559 drivers-y := drivers/ sound/ firmware/
560 net-y := net/
......
896 init-y := $(patsubst %/, %/built-in.o, $(init-y))
898 drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
899 net-y := $(patsubst %/, %/built-in.o, $(net-y))

从示例代码可知, init-y、 drivers-y 和 net-y 最终的值为:

init-y = init/built-in.o
drivers-y = drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y = net/built-in.o

libs-y

libs-y 基本和 init-y 一样,在顶层 Makefile 中存在如下代码:

561 libs-y := lib/
......
900 libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
901 libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
902 libs-y := $(libs-y1) $(libs-y2)

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

286 libs-y := arch/arm/lib/ $(libs-y)

arch/arm/Makefile 将 libs-y 的值改为了: arch/arm/lib $(libs-y),展开以后为:

libs-y = arch/arm/lib lib/

因此 libs-y 最终应该为:

libs-y = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o

core-y

core-y 和 init-y 也一样,在顶层 Makefile 中有如下代码:

532 core-y := usr/
......
887 core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/

但是在 arch/arm/Makefile 中会对 core-y 进行追加,代码如下:

269 core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/
270 core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ)
271 core-$(CONFIG_VFP) += arch/arm/vfp/
272 core-$(CONFIG_XEN) += arch/arm/xen/
273 core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/
274 core-$(CONFIG_VDSO) += arch/arm/vdso/
275
276 # If we have a machine-specific directory, then include it in the build.
277 core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
278 core-y += arch/arm/probes/
279 core-y += arch/arm/net/
280 core-y += arch/arm/crypto/
281 core-y += arch/arm/firmware/
282 core-y += $(machdirs) $(platdirs)

第 269~274 行根据不同的配置向 core-y 追加不同的值,比如使能 VFP 的话就会在.config中有 CONFIG_VFP=y 这一行,那么 core-y 就会追加“arch/arm/vfp/”。
第 277~282 行就是对 core-y 直接追加的值。
在顶层 Makefile 中有如下一行:

897 core-y := $(patsubst %/, %/built-in.o, $(core-y))

经过上述代码的转换,最终 core-y 的值为:

core-y = usr/built-in.o arch/arm/vfp/built-in.o \
    arch/arm/vdso/built-in.o arch/arm/kernel/built-in.o \
    arch/arm/mm/built-in.o arch/arm/common/built-in.o \
    arch/arm/probes/built-in.o arch/arm/net/built-in.o \
    arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o \
    arch/arm/mach-imx/built-in.o kernel/built-in.o\
    mm/built-in.o fs/built-in.o \
    ipc/built-in.o security/built-in.o \
    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 的链接脚本。

vmlinux的命令 +$(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 中,如下所示:

247 if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
248 @set -e; \
249 $(echo-cmd) $(cmd_$(1)); \
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 中有如下所示定义:

914 # Final link of vmlinux
915 cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
916 quiet_cmd_link-vmlinux = LINK $@

第 915 行就是 cmd_link-vmlinux 的值,其中 CONFIG_SHELL=/bin/bash, $<表示目标 vmlinux的第一个依赖文件,这个文件为 scripts/link-vmlinux.sh。LD= arm-linux-gnueabihf-ld -EL, LDFLAGS 为空。 LDFLAGS_vmlinux 的值由顶层 Makefile 和arch/arm/Makefile 这两个文件共同决定,最终 LDFLAGS_vmlinux=-p --no-undefined -X --picveneer --build-id。因此 cmd_link-vmlinux 最终的值为:

cmd_link-vmlinux = /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --noundefined -X --pic-veneer --build-id

cmd_link-vmlinux 会调用 scripts/link-vmlinux.sh 这个脚本来链接出 vmlinux!在 linkvmlinux.sh 中有如下所示代码:

51 vmlinux_link()
52 {
53     local lds="${objtree}/${KBUILD_LDS}"
54
55     if [ "${SRCARCH}" != "um" ]; then
56         ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \
57         -T ${lds} ${KBUILD_VMLINUX_INIT} \
58         --start-group ${KBUILD_VMLINUX_MAIN} --end-group ${1}
59     else
60         ${CC} ${CFLAGS_vmlinux} -o ${2} \
61         -Wl,-T,${lds} ${KBUILD_VMLINUX_INIT} \
62         -Wl,--start-group \
63         ${KBUILD_VMLINUX_MAIN} \
64         -Wl,--end-group \
65         -lutil ${1}
66         rm -f linux
67     fi
68 }
......
216 info LD vmlinux
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 来决定,这两个变量在前面已经讲解过了。
第 217 行调用 vmlinux_link 函数来链接出 vmlinux。使用命令“make V=1”编译 Linux,会有如图所示的编译信息:

至此我们基本理清了 make 的过程,重点就是将各个子目录下的 built-in.o、 .a 等文件链接在一起,最终生成 vmlinux 这个 ELF 格式的可执行文件。链接脚本为 arch/arm/kernel/vmlinux.lds,
链接过程是由shell脚本scripts/link-vmlinux.s来完成的。接下来的问题就是这些子目录下的builtin.o、 .a 等文件又是如何编译出来的呢?

built-in.o 编译过程

根据示前面分析可知, 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 的值如下:

vmlinux-deps = arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o \
    init/built-in.o usr/built-in.o \
    arch/arm/vfp/built-in.o arch/arm/vdso/built-in.o \
    arch/arm/kernel/built-in.o arch/arm/mm/built-in.o \
    arch/arm/common/built-in.o arch/arm/probes/built-in.o \
    arch/arm/net/built-in.o arch/arm/crypto/built-in.o \
    arch/arm/firmware/built-in.o arch/arm/mach-imx/built-in.o \
    kernel/built-in.o mm/built-in.o \
    fs/built-in.o ipc/built-in.o \
    security/built-in.o crypto/built-in.o\
    block/built-in.o arch/arm/lib/lib.a\
    lib/lib.a arch/arm/lib/built-in.o\
    lib/built-in.o drivers/built-in.o \
    sound/built-in.o firmware/built-in.o \
    net/built-in.o

除了 arch/arm/kernel/vmlinux.lds 以外,其他都是要编译链接生成的。在顶层 Makefile 中有如下代码:

937 $(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

sort 是排序函数,用于对 vmlinux-deps 的字符串列表进行排序,并且去掉重复的单词。可以看出 vmlinux-deps 依赖 vmlinux-dirs, vmlinux-dirs 也定义在顶层 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 所需源码文件的目录,值如下:

vmlinux-dirs = init usr arch/arm/vfp \
    arch/arm/vdso arch/arm/kernel arch/arm/mm \
    arch/arm/common arch/arm/probes arch/arm/net \
    arch/arm/crypto arch/arm/firmware arch/arm/mach-imx\
    kernel mm fs \
    ipc security crypto \
    block drivers sound \
    firmware net arch/arm/lib \
    lib

在顶层 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 中的这些目录全部带入到命令中,结果如下:

@ 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=arch/arm/crypto
@ make -f ./scripts/Makefile.build obj=arch/arm/firmware
@ make -f ./scripts/Makefile.build obj=arch/arm/mach-imx
@ make -f ./scripts/Makefile.build obj=kernel
@ make -f ./scripts/Makefile.build obj=mm
@ make -f ./scripts/Makefile.build obj=fs
@ make -f ./scripts/Makefile.build obj=ipc
@ make -f ./scripts/Makefile.build obj=security
@ make -f ./scripts/Makefile.build obj=crypto
@ make -f ./scripts/Makefile.build obj=block
@ make -f ./scripts/Makefile.build obj=drivers
@ make -f ./scripts/Makefile.build obj=sound
@ make -f ./scripts/Makefile.build obj=firmware
@ make -f ./scripts/Makefile.build obj=net
@ make -f ./scripts/Makefile.build obj=arch/arm/lib
@ make -f ./scripts/Makefile.build obj=lib

这些命令运行过程其实都是一样的,我们就以“@ make -f ./scripts/Makefile.build obj=init”这个命令为例,讲解一下详细的运行过程。这里又要用到 Makefile.build 这个脚本了,此脚本默认目标为__build,重点来看一下 builtin-target 这个依赖, builtin-target 同样定义在文件 scripts/Makefile.build中,定义如下:

86 ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(libtarget)),)
87 builtin-target := $(obj)/built-in.o
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 中有如下代码:

325 #
326 # Rule to compile a set of .o files into one .o file
327 #
328 ifdef builtin-target
329 quiet_cmd_link_o_target = LD $@
330 # If the list of objects to link is empty, just create an empty built-in.o
331 cmd_link_o_target = $(if $(strip $(obj-y)),\
332 $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
333 $(cmd_secanalysis),\
334 rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)
335
336 $(builtin-target): $(obj-y) FORCE
337 $(call if_changed,link_o_target)
338
339 targets += $(builtin-target)
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。

make zImage 过程

vmlinux、 Image, zImage、 uImage 的区别
前面几小节重点是讲 vmlinux 是如何编译出来的, vmlinux 是 ELF 格式的文件,但是在实际中我们不会使用 vmlinux,而是使用 zImage 或 uImage 这样的 Linux 内核镜像文件。那么vmlinux、 zImage、 uImage 他们之间有什么区别呢?

①、 vmlinux 是编译出来的最原始的内核文件,是未压缩的,比如正点原子提供的 Linux 源码编译出来的 vmlinux 差不多有 16MB,如图所示:

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

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

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

310 BOOT_TARGETS = zImage Image xipImage bootpImage uImage
......
315 $(BOOT_TARGETS): vmlinux
316 $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

第 310 行,变量 BOOT_TARGETS 包含 zImage, Image, xipImage 等镜像文件。
第 315 行, BOOT_TARGETS 依赖 vmlinux,因此如果使用“make zImage”编译的 Linux 内核的话,首先肯定要先编译出 vmlinux。
第 316 行,具体的命令,比如要编译 zImage,那么命令展开以后如下所示:

@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/377524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python常用标准库-sys库一文详解

目录 前言 一、Sys库概述 二、Sys查看版本信息 1.sys.version获取Python版本信息 2.sys.api_version获取解释器中C的API版本 3.sys.getwindowsversion系统功能版本 4.sys.hexversion()获取Python解释器的版本值 5.sys.implementation获取当前正在运行的Python解释器的实现…

Redis源码---如何实现一个性能优异的Hash表

目录 前言 Redis 如何实现链式哈希&#xff1f; 什么是哈希冲突&#xff1f; 链式哈希如何设计与实现&#xff1f; Redis 如何实现 rehash&#xff1f; 什么时候触发 rehash&#xff1f; rehash 扩容扩多大&#xff1f; 渐进式 rehash 如何实现&#xff1f; 前言 Hash …

数据处理 |遍历所有文件夹及子目录文件夹方法总结与实例代码详解

深度学习中不可避免的数据预处理~1. glob.glob()方法 2. pathlib中的Path方法3. os.walk()方法1. glob.glob()方法 语法glob.glob(pathname)&#xff08;多指定文件类型&#xff0c;查找jpg,png,txt,json等&#xff09;缺点&#xff1a;查找文件较慢2. 路径操作库pathlib中的Pa…

【计算机三级网络技术】 第四篇 路由设计技术基础

文章目录一、分组转发二、路由选择1.理想的路由算法的基本特征2.路由算法的度量标准3.路由算法分类&#xff1a;4.IP路由选择与路由汇聚(重点)三、自治系统与Internet的路由选择协议1.自治系统2.路由选择协议的分类四、内部网关协议1.RIP的基本概念2.RIP的原理3.RIP的运行过程五…

Android Lmkd 低内存终止守护程序

一、低内存终止守护程序 Android 低内存终止守护程序 (lmkd) 进程可监控运行中的 Android 系统的内存状态&#xff0c;并通过终止最不必要的进程来应对内存压力大的问题&#xff0c;使系统以可接受的性能水平运行。 所有应用进程都是从zygote孵化出来的&#xff0c;记录在AMS…

Android问题笔记 - 打开Android Studio先弹出项目选择框

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断…

leetcode 427. Construct Quad Tree(构建四叉树)

刚看到题的时候是懵的&#xff0c;这也太长了。到底是要表达什么呢。 不妨把这个矩阵看成一个正方形的图片&#xff0c;想象你在处理图片&#xff0c;从整体逐步到局部。 刚开始看一整张图片&#xff0c;如果是全0或全1&#xff0c;这个就是叶子节点&#xff0c;怎么表达叶子节…

网络货运平台“降本提质引流增值”秘籍是什么?

2月24日&#xff0c;2022&#xff08;第五届&#xff09;中国网络货运平台年会在厦门举行&#xff0c;数据宝作为中物联副会长单位受邀参加峰会&#xff0c;数据宝轮值CEO肖斌发表题为“网络货运平台数字化创新应用实践分享”的主题分享。 据交通运输部统计&#xff0c;截止到2…

某建筑设计研究院“综合布线管理软件”应用实践

某建筑设计研究院有限公司&#xff08;简称“某院”&#xff09;隶属于国务院国资委直属的大型骨干科技型中央企业。“某院”前身为中央直属设计公司&#xff0c;创建于1952年。成立近70年来&#xff0c;始终秉承优良传统&#xff0c;致力于推进国内勘察设计产业的创新发展&…

CASENet中edge GT是如何产生的

1&#xff1a;首先下载cityscape数据集&#xff0c;包含两个大文件夹&#xff0c;具体的数据集介绍参考此链接。cityscape数据集解析 看一下gtFine子文件夹&#xff0c;另一个也是同理&#xff1a; 2&#xff1a;将下载好的数据集放到data_orig中&#xff0c;还有一个文件…

报错记录:element-admin框架打包后静态文件加载异常与登录异常解决方案

报错记录&#xff1a;element-admin框架打包后静态文件加载异常与登录异常解决方案 一、静态文件加载异常解决方案 二、登录异常解决方案 现象 element-admin如果用 electron 打包 是会有个问题&#xff1a; electron serve 运行没问题 electron build 打release 包 就会出错…

AST之path常用属性和方法总结笔记

文章目录1. path常用属性总结1.1 path.node1.2 path.scope1.3 path.parentPath1.4 path.parent1.5 path.container1.6 path.type1.7 path.key2. path常用方法总结2.1 path.toString2.2 path.replaceWith2.3 path.replaceWithMultiple2.4 path.remove2.5 path.insertBefore2.6 p…

TypeScript 常用知识

「 推荐一个学习 ts 基础的专栏&#xff0c;满满的干货&#xff1a;typeScript 」 1、为什么推荐使用 TypeScript 【】ts 是 js 的超集&#xff0c;包含 js 的所有元素 【】ts 通过对代码进行类型检查&#xff0c;可以帮助我们避免在编写 js 时经常遇到令人痛苦的错误 【】强…

第六节 方法

方法 方法是一种语法结构。 方法的作用&#xff1a; 1.提高代码的复用性 2.让程序逻辑更加清晰 方法定义的完整格式&#xff1a; 修饰符 返回值类型 方法名&#xff08;形参列表&#xff09;{ 方法体的代码&#xff08;需要执行的功能代码&#xff09; return 返回值&#xff…

Unity性能优化: 性能优化之内存篇

前言 本文和传统的内存优化不一样&#xff0c;不是讲如何降低内存占用&#xff0c;而是讲编程开发中要注意的内存问题以及一些内存技术的演变与原理。 对惹&#xff0c;这里有一个游戏开发交流小组&#xff0c;希望大家可以点击进来一起交流一下开发经验呀 1: Application进程…

maven项目无法解析插件

发现问题使用IDEA创建Maven项目时&#xff0c;报错无法解析插件 org.apache.maven.plugins:maven-clean-plugin这里使用的是IDEA捆绑的Maven插件解决方案查看Maven配置打开用户设置文件settings.xml&#xff0c;在其中加入如果该路径下没有此文件&#xff0c;可以自己创建一个。…

软件分析笔记02---Intermediate Representation

整体contents compiler &#xff08;source code ——> machine code&#xff09; non-trivial非平凡的 经过 语义分析->语法分析->类型检查等各种trivial的分析&#xff08;前端&#xff09;&#xff0c;生成中间代码IR->进行non-trivial的分析&#xff08;及静…

Linux 基础介绍-基础命令

文章目录01 学习目标02 Linux/Unix 操作系统简介2.1 Linux 操作系统的目标2.2 Linux 操作系统的作用2.3 Unix 家族历史2.4 Linux 家族历史2.5 Linux 和Unix 的联系2.6 Linux 内核介绍2.7 Linux 发行版本2.8 Unix/Linux 开发应用领域介绍03 Linux 目录结构3.1 Win 和Linux 文件系…

C++之入门之引用,内联函数

一、引用 1、引用的概念 在C中&#xff0c;引用的本质其实就是给一个已经存在的变量”起别名“。也就是说&#xff0c;引用与它所引用的对象共用一块空间。&#xff08;同一块空间的多个名字&#xff09; 就比如说&#xff0c;李逵又叫黑旋风&#xff0c;而黑旋风就是指李逵…

线程安全实例分析

一、变量的线程安全分析 成员变量和静态变量是否线程安全&#xff1f; ● 如果它们没有共享&#xff0c;则线程安全 ● 如果它们被共享了&#xff0c;根据它们的状态是否能够改变&#xff0c;又分两种情况 —— 如果只有读操作&#xff0c;则线程安全 —— 如果有读写操作&am…