[TOC]
arch/arm/Makefile: 32位ARM架构的构建总指挥
arch/arm/Makefile
是 Linux 内核 Kbuild 系统中专门为 32 位 ARM 架构服务的顶级 Makefile。它不是一个独立的可执行脚本,而是被主 Makefile
包含(include)进去的。它的核心职责是定义 ARM 架构独有的构建规则、配置编译选项,并将 Kconfig 中选择的平台配置转化为实际的构建命令。
可以将其视为一个“总指挥官”,它接收来自 .config
文件的战略指令,然后向编译器、链接器等“部队”下达精确的战术命令,以确保最终能为特定的 ARM 平台构建出正确、高效的内核。
一、 核心角色与职责
定制化主构建流程: 内核的主
Makefile
定义了通用的构建目标(如vmlinux
,modules
)。arch/arm/Makefile
则对这些目标进行“重载”或“定制”,添加 ARM 架构特有的依赖和命令。配置翻译官: 将
.config
文件中由 Kconfig 系统生成的高层配置选项(例如CONFIG_ARCH_BCM2835
代表树莓派,CONFIG_CPU_V7
代表 ARMv7 架构)翻译成底层的编译器和链接器参数。架构特性开关: 根据配置启用或禁用 ARM 架构的特定功能,如 Thumb-2 指令集、NEON 浮点单元、硬件虚拟化等,并通过
CFLAGS
或AFLAGS
将这些选择传递给编译器。构建产物定义: 明确定义 ARM 平台的主要构建产物,最核心的是内核镜像(
zImage
,uImage
)和设备树二进制文件(.dtb
s)。
二、 关键任务详解
1. 工具链与编译选项配置
这是 arch/arm/Makefile
最基础也是最重要的任务。它会根据 .config
文件来组装一系列的编译标志。
- CPU 架构和类型:
- 根据
CONFIG_CPU_*
选项,设置-march=
和-mcpu=
标志。例如,如果选择了CONFIG_CPU_V7
, 它会添加类似-march=armv7-a
的标志,指示编译器按照 ARMv7-A 架构的指令集进行编译。
- 根据
- 指令集模式:
- 根据
CONFIG_ARM_THUMB
或CONFIG_ARM_THUMB2_KERNEL
,决定是使用 ARM 模式(-marm
)还是 Thumb/Thumb-2 模式(-mthumb
)来编译内核。Thumb-2 能有效减小代码体积。
- 根据
- 浮点单元 (FPU):
- 根据
CONFIG_VFP
和CONFIG_NEON
,设置-mfpu=
标志,例如-mfpu=neon
或-mfpu=vfpv3
,以启用硬件浮点或 SIMD 指令的支持。
- 根据
- 通用标志:
- 它还会添加一些 ARM 平台通用的标志,如
-mapcs-frame
(控制栈帧格式)、-msoft-float
(如果配置为软浮点)。
- 它还会添加一些 ARM 平台通用的标志,如
2. 内核镜像目标生成
ARM 平台有其特定的内核镜像格式。arch/arm/Makefile
定义了如何生成它们。
zImage
: 这是最常见的压缩内核镜像格式。arch/arm/Makefile
定义了一个zImage
目标,它的依赖是arch/arm/boot/compressed/vmlinux
。构建zImage
的实际命令和逻辑大部分位于arch/arm/boot/Makefile
中,但顶层的arch/arm/Makefile
负责触发这个目标。uImage
: 这是专为 U-Boot 引导加载程序设计的镜像格式。它是在zImage
的基础上添加了一个 64 字节的头部信息。arch/arm/Makefile
同样定义了uImage
目标,并会调用mkimage
工具来生成它。vmlinux
: 这是未压缩的、包含调试信息的 ELF 格式内核。arch/arm/Makefile
会确保在链接vmlinux
时使用正确的链接器脚本 (vmlinux.lds
) 和内存基地址。
3. 设备树编译 (DTB)
现代 ARM 系统使用设备树来向内核描述硬件信息。
dtbs
目标:arch/arm/Makefile
定义了一个dtbs
目标。当你执行make dtbs
时,Kbuild 系统会查找.config
文件中所有被选中的平台,然后找到对应的.dts
(Device Tree Source) 文件。dtb-y
变量: 它会收集所有需要编译的设备树文件名到一个名为dtb-y
的变量中。例如,如果配置了树莓派,dtb-y
就会包含bcm2835-rpi-b.dtb
等。- 调用
dtc
: 最终,它会为dtb-y
中的每一个.dts
文件调用设备树编译器 (dtc
),将它们编译成二进制的.dtb
文件,并输出到arch/arm/boot/dts/
目录下。
4. 平台与 SoC 选择
ARM 拥有海量的 SoC 和平台。arch/arm/Makefile
通过 Kconfig
系统来管理这种复杂性。
- 条件包含: Makefile 中充满了
ifeq ($(CONFIG_ARCH_OMAP2PLUS),y)
这样的条件语句。这些语句会根据.config
的选择,包含特定平台的Makefile
片段或设置特定的变量。 core-y
和plat-y
: 它会根据平台选择,将特定的目录(如mach-omap2/
或plat-samsung/
)添加到构建列表中,确保对应平台的驱动和核心代码被编译进去。
5. 链接器脚本管理
TEXT_OFFSET
: 这是一个至关重要的变量,定义了内核镜像在物理内存中的链接基地址。对于不同的 ARM 平台,这个地址是不同的。arch/arm/Makefile
会根据CONFIG_ARCH_*
的选择,为TEXT_OFFSET
设置一个默认值。这个值最终会传递给链接器脚本arch/arm/kernel/vmlinux.lds
,指导链接器将代码段放置在正确的地址。
三、 一次典型的构建流程
假设你为树莓派(BCM2835)构建内核,执行命令 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs modules
:
- 主
Makefile
发现ARCH=arm
,于是include arch/arm/Makefile
。 arch/arm/Makefile
开始执行。它读取.config
文件,发现CONFIG_ARCH_BCM2835=y
。- 设置编译选项: 它根据
.config
中的 CPU 类型(如 Cortex-A7)和特性,设置好CFLAGS
,例如-march=armv7-a -mfpu=neon-vfpv4
。 - 确定链接地址: 它为
TEXT_OFFSET
设置一个适用于树莓派的值(例如0x8000
)。 - 开始编译: Kbuild 系统开始递归编译源码树。当编译 ARM 相关代码时,都会使用上面设置好的
CFLAGS
。 - 处理
zImage
目标:- Kbuild 发现需要构建
zImage
。 arch/arm/Makefile
中的规则指示 Kbuild 首先确保vmlinux
被正确链接。- 然后,构建流程进入
arch/arm/boot/
目录,那里的Makefile
会负责压缩vmlinux
并与自解压代码打包,最终在arch/arm/boot/
目录下生成zImage
文件。
- Kbuild 发现需要构建
- 处理
dtbs
目标:- Kbuild 发现需要构建
dtbs
。 arch/arm/Makefile
将bcm2835-rpi-*.dtb
等文件名加入dtb-y
列表。- Kbuild 对列表中的每个条目,在
arch/arm/boot/dts/
目录中找到对应的.dts
文件,并调用dtc
进行编译,将结果输出到arch/arm/boot/dts/
。
- Kbuild 发现需要构建
- 处理
modules
目标: 编译所有被配置为模块 (CONFIG_*=m
) 的驱动,并生成.ko
文件。
四、 总结
arch/arm/Makefile
是连接 Kconfig 配置系统 与 实际编译过程 的核心桥梁。它将抽象的配置选项转化为具体、精确的构建指令,处理了 ARM 架构的高度多样性和复杂性。理解这个文件的工作原理,对于定制 ARM Linux 内核、移植到新硬件平台或解决复杂的编译问题至关重要。
include/asm-generic/vmlinux.lds.h
arch/arm/Makefile
生成vmlinux.lds
预处理:
- vmlinux.lds.S 文件首先会经过 C 预处理器(cpp)的处理。预处理器会处理文件中的宏定义、条件编译指令(如
#ifdef
、#include
等),并生成一个纯文本的链接脚本文件vmlinux.lds
。
- vmlinux.lds.S 文件首先会经过 C 预处理器(cpp)的处理。预处理器会处理文件中的宏定义、条件编译指令(如
编译命令:
- 在内核构建过程中,Makefile 会调用 GCC 或 Clang 编译器来处理这个文件。具体的命令类似于:其中:
1
$(CC) -E -P -o vmlinux.lds vmlinux.lds.S
$(CC)
是编译器命令(如gcc
或clang
)。-E
选项表示只运行预处理器,不进行编译。-P
选项表示去掉预处理器生成的行号信息。-o vmlinux.lds
指定输出文件名为vmlinux.lds
。- vmlinux.lds.S 是输入文件。
- 在内核构建过程中,Makefile 会调用 GCC 或 Clang 编译器来处理这个文件。具体的命令类似于:
生成
vmlinux.lds
:- 经过预处理后,
vmlinux.lds.S
文件中的所有宏定义和条件编译指令都会被展开和处理,生成最终的vmlinux.lds
文件。这个文件是一个纯文本文件,包含了链接器需要的所有指令,用于定义内核镜像的内存布局。
- 经过预处理后,
链接阶段:
- 最终,生成的
vmlinux.lds
文件会被传递给链接器(如ld
),用于链接内核各个部分,生成最终的内核镜像文件 vmlinux。
- 最终,生成的
通过这种方式,vmlinux.lds.S
文件中的内容经过预处理后生成 vmlinux.lds
,再由链接器使用这个链接脚本来生成最终的内核镜像。
CONFIG_CPU_BIG_ENDIAN
1 | ifeq ($(CONFIG_CPU_BIG_ENDIAN),y) |
arch/arm/include/asm/vmlinux.lds.h
ARM_CPU_DISCARD ARM_CPU_KEEP 热插拔CPU时需要保持或丢弃的段
1 |
CONFIG_HOTPLUG_CPU 热插拔CPU
- CONFIG_HOTPLUG_CPU 是一个内核配置选项,用于启用或禁用 CPU 热插拔功能。热插拔允许在运行时添加或移除 CPU,而不需要重启系统。
- 在 ARM 架构的 Linux 内核中,.ARM.exidx 和 .ARM.extab 段用于异常处理和栈回溯(unwinding)。这些段包含了异常索引表(exception index table)和异常表(exception table),它们对于处理异常和执行栈回溯是必需的。
- 当启用 CONFIG_HOTPLUG_CPU 配置选项时,内核需要支持 CPU 的热插拔功能,这意味着在运行时可以动态地添加或移除 CPU。为了支持这种动态变化,内核必须确保在 CPU 被移除或添加时,异常处理和栈回溯机制能够正确工作。这就需要保留与 CPU 相关的异常处理信息。
- 具体来说,.ARM.exidx.cpuexit.text 和 .ARM.extab.cpuexit.text 段包含了与 CPU 退出(cpuexit)相关的异常处理信息和栈回溯信息。当 CPU 被热插拔时,这些信息是必需的,以确保系统能够正确处理与该 CPU 相关的异常和栈回溯。因此,当启用 CONFIG_HOTPLUG_CPU 时,这些段不会被丢弃。
ARM_EXIT_KEEP ARM_EXIT_DISCARD
1 |
CONFIG_SMP_ON_UP 在单处理器系统上启用SMP
- SMP 内核包含在非 SMP 处理器上失败的指令。启用此选项允许内核修改自身以使这些说明安全。 禁用它会允许大约 1K 的空间储蓄。
CONFIG_JUMP_LABEL 启用跳转标签优化,以减少条件分支的开销
- 此选项启用透明分支优化,该优化创建某些几乎总是 true 或几乎总是 false 的分支条件甚至比在 kernel 中执行更便宜。
- 某些对性能敏感的内核代码(如跟踪点、调度程序功能、网络代码和 KVM 都有这样的分支,并包含对此优化技术的支持。
- 如果检测到编译器支持 “asm goto”,内核将仅使用 nop 编译此类分支指令。当 condition 标志切换为 true 时,nop 将转换为 jump 指令来执行条件指令块。
- 此技术降低了分支预测的开销和压力的处理器,通常会使内核更快。更新的情况较慢,但这种情况总是非常罕见的。
ARM_DISCARD arm丢弃的段
ARM_DISCARD
是一个宏定义,用于在链接脚本中指定哪些段应该被丢弃或忽略。这个宏通常用于内核的链接脚本中,以确保某些不需要的段不会被包含在最终的内核映像中。
1 |
解压部分代码的lds
arch/arm/boot/compressed/vmlinux.lds.S
CONFIG_CPU_ENDIAN_BE8 大端模式
- 大端模式修改顺序
1 |
SANITIZER_DISCARDS 无害丢弃
1 |
PATCHABLE_DISCARDS 可修补的丢弃
1 | /* 实际配置决定了 init/exit 部分是否 |
CONFIG_HAVE_DYNAMIC_FTRACE_NO_PATCHABLE 控制动态函数跟踪(ftrace)功能的行为
- 它决定了是否使用不可修补的函数条目。
COMMON_DISCARDS 公共丢弃
1 |
内核的lds
arch/arm/kernel/vmlinux.lds.S
CONFIG_XIP_KERNEL 选择不同的lds.s文件
1 |
jiffies 取低32位使用
- 这个定义在链接脚本中不会直接生成可见的输出段或内容,因为它只是定义了一个符号,并没有分配内存或生成代码段。因此,在最终生成的 vmlinux.lds 文件中,你不会看到显式的 jiffies 定义。
- 链接脚本的主要作用是定义内核镜像的内存布局和各个段的排列方式,而符号定义(如 jiffies)通常用于在链接过程中为特定地址或变量提供别名或偏移量。这些符号在链接过程中会被解析和使用,但不会直接出现在最终的链接脚本输出中。
1 |
|