[TOC]

arch/arm/Makefile: 32位ARM架构的构建总指挥

arch/arm/Makefile 是 Linux 内核 Kbuild 系统中专门为 32 位 ARM 架构服务的顶级 Makefile。它不是一个独立的可执行脚本,而是被主 Makefile 包含(include)进去的。它的核心职责是定义 ARM 架构独有的构建规则、配置编译选项,并将 Kconfig 中选择的平台配置转化为实际的构建命令

可以将其视为一个“总指挥官”,它接收来自 .config 文件的战略指令,然后向编译器、链接器等“部队”下达精确的战术命令,以确保最终能为特定的 ARM 平台构建出正确、高效的内核。


一、 核心角色与职责

  1. 定制化主构建流程: 内核的主 Makefile 定义了通用的构建目标(如 vmlinux, modules)。arch/arm/Makefile 则对这些目标进行“重载”或“定制”,添加 ARM 架构特有的依赖和命令。

  2. 配置翻译官: 将 .config 文件中由 Kconfig 系统生成的高层配置选项(例如 CONFIG_ARCH_BCM2835 代表树莓派,CONFIG_CPU_V7 代表 ARMv7 架构)翻译成底层的编译器和链接器参数。

  3. 架构特性开关: 根据配置启用或禁用 ARM 架构的特定功能,如 Thumb-2 指令集、NEON 浮点单元、硬件虚拟化等,并通过 CFLAGSAFLAGS 将这些选择传递给编译器。

  4. 构建产物定义: 明确定义 ARM 平台的主要构建产物,最核心的是内核镜像(zImage, uImage)和设备树二进制文件(.dtbs)。


二、 关键任务详解

1. 工具链与编译选项配置

这是 arch/arm/Makefile 最基础也是最重要的任务。它会根据 .config 文件来组装一系列的编译标志。

  • CPU 架构和类型:
    • 根据 CONFIG_CPU_* 选项,设置 -march=-mcpu= 标志。例如,如果选择了 CONFIG_CPU_V7, 它会添加类似 -march=armv7-a 的标志,指示编译器按照 ARMv7-A 架构的指令集进行编译。
  • 指令集模式:
    • 根据 CONFIG_ARM_THUMBCONFIG_ARM_THUMB2_KERNEL,决定是使用 ARM 模式(-marm)还是 Thumb/Thumb-2 模式(-mthumb)来编译内核。Thumb-2 能有效减小代码体积。
  • 浮点单元 (FPU):
    • 根据 CONFIG_VFPCONFIG_NEON,设置 -mfpu= 标志,例如 -mfpu=neon-mfpu=vfpv3,以启用硬件浮点或 SIMD 指令的支持。
  • 通用标志:
    • 它还会添加一些 ARM 平台通用的标志,如 -mapcs-frame (控制栈帧格式)、-msoft-float (如果配置为软浮点)。

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-yplat-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

  1. Makefile 发现 ARCH=arm,于是 include arch/arm/Makefile
  2. arch/arm/Makefile 开始执行。它读取 .config 文件,发现 CONFIG_ARCH_BCM2835=y
  3. 设置编译选项: 它根据 .config 中的 CPU 类型(如 Cortex-A7)和特性,设置好 CFLAGS,例如 -march=armv7-a -mfpu=neon-vfpv4
  4. 确定链接地址: 它为 TEXT_OFFSET 设置一个适用于树莓派的值(例如 0x8000)。
  5. 开始编译: Kbuild 系统开始递归编译源码树。当编译 ARM 相关代码时,都会使用上面设置好的 CFLAGS
  6. 处理 zImage 目标:
    • Kbuild 发现需要构建 zImage
    • arch/arm/Makefile 中的规则指示 Kbuild 首先确保 vmlinux 被正确链接。
    • 然后,构建流程进入 arch/arm/boot/ 目录,那里的 Makefile 会负责压缩 vmlinux 并与自解压代码打包,最终在 arch/arm/boot/ 目录下生成 zImage 文件。
  7. 处理 dtbs 目标:
    • Kbuild 发现需要构建 dtbs
    • arch/arm/Makefilebcm2835-rpi-*.dtb 等文件名加入 dtb-y 列表。
    • Kbuild 对列表中的每个条目,在 arch/arm/boot/dts/ 目录中找到对应的 .dts 文件,并调用 dtc 进行编译,将结果输出到 arch/arm/boot/dts/
  8. 处理 modules 目标: 编译所有被配置为模块 (CONFIG_*=m) 的驱动,并生成 .ko 文件。

四、 总结

arch/arm/Makefile 是连接 Kconfig 配置系统实际编译过程 的核心桥梁。它将抽象的配置选项转化为具体、精确的构建指令,处理了 ARM 架构的高度多样性和复杂性。理解这个文件的工作原理,对于定制 ARM Linux 内核、移植到新硬件平台或解决复杂的编译问题至关重要。

include/asm-generic/vmlinux.lds.h

arch/arm/Makefile

生成vmlinux.lds

  1. 预处理

    • vmlinux.lds.S 文件首先会经过 C 预处理器(cpp)的处理。预处理器会处理文件中的宏定义、条件编译指令(如 #ifdef#include 等),并生成一个纯文本的链接脚本文件 vmlinux.lds
  2. 编译命令

    • 在内核构建过程中,Makefile 会调用 GCC 或 Clang 编译器来处理这个文件。具体的命令类似于:
      1
      $(CC) -E -P -o vmlinux.lds vmlinux.lds.S
      其中:
      • $(CC) 是编译器命令(如 gccclang)。
      • -E 选项表示只运行预处理器,不进行编译。
      • -P 选项表示去掉预处理器生成的行号信息。
      • -o vmlinux.lds 指定输出文件名为 vmlinux.lds
      • vmlinux.lds.S 是输入文件。
  3. 生成 vmlinux.lds

    • 经过预处理后,vmlinux.lds.S 文件中的所有宏定义和条件编译指令都会被展开和处理,生成最终的 vmlinux.lds 文件。这个文件是一个纯文本文件,包含了链接器需要的所有指令,用于定义内核镜像的内存布局。
  4. 链接阶段

    • 最终,生成的 vmlinux.lds 文件会被传递给链接器(如 ld),用于链接内核各个部分,生成最终的内核镜像文件 vmlinux。

通过这种方式,vmlinux.lds.S 文件中的内容经过预处理后生成 vmlinux.lds,再由链接器使用这个链接脚本来生成最终的内核镜像。

CONFIG_CPU_BIG_ENDIAN

1
2
3
4
5
6
7
8
9
ifeq ($(CONFIG_CPU_BIG_ENDIAN),y)
KBUILD_CPPFLAGS += -mbig-endian
CHECKFLAGS += -D__ARMEB__
KBUILD_LDFLAGS += -EB
else
KBUILD_CPPFLAGS += -mlittle-endian
CHECKFLAGS += -D__ARMEL__
KBUILD_LDFLAGS += -EL
endif

arch/arm/include/asm/vmlinux.lds.h

ARM_CPU_DISCARD ARM_CPU_KEEP 热插拔CPU时需要保持或丢弃的段

1
2
3
4
5
6
7
#ifdef CONFIG_HOTPLUG_CPU
#define ARM_CPU_DISCARD(x)
#define ARM_CPU_KEEP(x) x
#else
#define ARM_CPU_DISCARD(x) x
#define ARM_CPU_KEEP(x)
#endif

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
2
3
4
5
6
7
8
#if (defined(CONFIG_SMP_ON_UP) && !defined(CONFIG_DEBUG_SPINLOCK)) || \
defined(CONFIG_GENERIC_BUG) || defined(CONFIG_JUMP_LABEL)
#define ARM_EXIT_KEEP(x) x
#define ARM_EXIT_DISCARD(x)
#else
#define ARM_EXIT_KEEP(x)
#define ARM_EXIT_DISCARD(x) x
#endif

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
2
3
4
5
6
7
8
9
10
11
12
13
#define ARM_DISCARD							\
*(.ARM.exidx.exit.text) \
*(.ARM.extab.exit.text) \
*(.ARM.exidx.text.exit) \
*(.ARM.extab.text.exit) \
ARM_CPU_DISCARD(*(.ARM.exidx.cpuexit.text)) \
ARM_CPU_DISCARD(*(.ARM.extab.cpuexit.text)) \
ARM_EXIT_DISCARD(EXIT_TEXT) \
ARM_EXIT_DISCARD(EXIT_DATA) \
EXIT_CALL \
ARM_MMU_DISCARD(*(.text.fixup)) \
ARM_MMU_DISCARD(*(__ex_table)) \
COMMON_DISCARDS

解压部分代码的lds

arch/arm/boot/compressed/vmlinux.lds.S

CONFIG_CPU_ENDIAN_BE8 大端模式

  • 大端模式修改顺序
1
2
3
4
5
6
7
8
#ifdef CONFIG_CPU_ENDIAN_BE8
#define ZIMAGE_MAGIC(x) ( (((x) >> 24) & 0x000000ff) | \
(((x) >> 8) & 0x0000ff00) | \
(((x) << 8) & 0x00ff0000) | \
(((x) << 24) & 0xff000000) )
#else
#define ZIMAGE_MAGIC(x) (x)
#endif

SANITIZER_DISCARDS 无害丢弃

1
2
3
4
5
6
7
8
9
10
11
12
#if defined(CONFIG_GCOV_KERNEL) || defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KCSAN)
# ifdef CONFIG_CONSTRUCTORS
# define SANITIZER_DISCARDS \
DISCARD_EH_FRAME
# else
# define SANITIZER_DISCARDS \
*(.init_array) *(.init_array.*) \
DISCARD_EH_FRAME
# endif
#else
# define SANITIZER_DISCARDS
#endif

PATCHABLE_DISCARDS 可修补的丢弃

1
2
3
4
5
6
7
8
9
10
11
/* 实际配置决定了 init/exit 部分是否
* 作为文本/数据处理,或者它们可以被丢弃(
* 经常发生在运行时)
*/
#ifndef CONFIG_HAVE_DYNAMIC_FTRACE_NO_PATCHABLE
#define KEEP_PATCHABLE KEEP(*(__patchable_function_entries))
#define PATCHABLE_DISCARDS
#else
#define KEEP_PATCHABLE
#define PATCHABLE_DISCARDS *(__patchable_function_entries)
#endif

CONFIG_HAVE_DYNAMIC_FTRACE_NO_PATCHABLE 控制动态函数跟踪(ftrace)功能的行为

  • 它决定了是否使用不可修补的函数条目。

COMMON_DISCARDS 公共丢弃

1
2
3
4
5
6
7
8
9
10
#define COMMON_DISCARDS							\
SANITIZER_DISCARDS \
PATCHABLE_DISCARDS \
*(.discard) \
*(.discard.*) \
*(.export_symbol) \
*(.no_trim_symbol) \
*(.modinfo) \
/* ld.bfd warns about .gnu.version* even when not emitted */ \
*(.gnu.version*) \

内核的lds

arch/arm/kernel/vmlinux.lds.S

CONFIG_XIP_KERNEL 选择不同的lds.s文件

1
2
3
4
#ifdef CONFIG_XIP_KERNEL
#include "vmlinux-xip.lds.S"
#else
#endif

jiffies 取低32位使用

  • 这个定义在链接脚本中不会直接生成可见的输出段或内容,因为它只是定义了一个符号,并没有分配内存或生成代码段。因此,在最终生成的 vmlinux.lds 文件中,你不会看到显式的 jiffies 定义。
  • 链接脚本的主要作用是定义内核镜像的内存布局和各个段的排列方式,而符号定义(如 jiffies)通常用于在链接过程中为特定地址或变量提供别名或偏移量。这些符号在链接过程中会被解析和使用,但不会直接出现在最终的链接脚本输出中。
1
2
3
4
5
#ifndef __ARMEB__
jiffies = jiffies_64;
#else
jiffies = jiffies_64 + 4;
#endif