01-内核编译与配置.md 30 KB


title: "Linux内核编译与配置" tags:

  • 嵌入式Linux
  • 内核编译
  • 内核配置
  • Kconfig
  • Makefile
  • I.MX6ULL created: 2026-09-17 updated: 2026-09-17 pdf_ref: "正点原子I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第三十五章 Linux内核顶层Makefile详解" ---

一、Linux内核概述

1.1 内核版本

Linux内核版本号由三个数字组成:主版本.次版本.修订号。例如Linux 4.1.15,其中:

  • 4:主版本号,重大架构变更时递增
  • 1:次版本号,奇数表示开发版,偶数表示稳定版
  • 15:修订号,bug修复和小功能更新

查看内核版本:

uname -r
# 输出示例:4.1.15-gb39c1d0

内核源码获取(教材 35.1):Linux 由 Linux 基金会管理与发布,官网为 https://www.kernel.org,最新稳定版可直接在官网下载。NXP 从官网下载某个版本的内核,移植到自己的 CPU 上并测试通过后,再开放给 NXP CPU 的开发者。本章移植使用 NXP 提供的源码,路径为:

开发板光盘->1、例程源码->4、NXP 官方原版 Uboot 和 Linux->
    linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

正点原子出厂源码路径为:

开发板光盘->1、例程源码->3、正点原子 Uboot 和 Linux 出厂源码->
    linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2

注意:正点原子出厂系统在持续更新,压缩包名字可能不同,以实际为准。

1.2 内核源码结构

编译后的 Linux 源码目录(教材表 35.3.1)主要文件夹与文件如下:

linux-4.1.15/
├── arch/           # 架构相关目录(arm、arm64、x86 等)
├── block/          # 块设备相关目录(SD卡、EMMC、NAND、硬盘)
├── crypto/         # 加密相关目录(crc、crc32、md4、md5、hash)
├── Documentation/  # 文档相关目录
├── drivers/        # 驱动相关目录(学习重点)
├── firmware/       # 固件相关目录
├── fs/             # 文件系统相关目录(ext2/ext4/f2fs 等)
├── include/        # 头文件相关目录
├── init/           # 初始化相关目录(内核启动初始化代码)
├── ipc/            # 进程间通信相关目录
├── kernel/         # 内核核心相关目录
├── lib/            # 库函数相关目录
├── mm/             # 内存管理相关目录
├── net/            # 网络相关目录
├── samples/        # 例程相关目录
├── scripts/        # 脚本相关目录(编译时大量使用)
├── security/       # 安全相关目录
├── sound/          # 音频处理相关目录
├── tools/          # 工具相关目录
├── usr/            # 与 initramfs 相关的目录
├── virt/           # 提供虚拟机技术(KVM)
├── .config         # 最终使用的配置文件(编译生成)
├── Kbuild          # Makefile 会读取此文件
├── Kconfig         # 图形化配置界面的配置文件
├── Makefile        # Linux 顶层 Makefile
├── README          # 编译说明与源码目录说明(建议仔细阅读)
├── System.map      # 符号表(编译生成)
├── vmlinux         # 未压缩的 ELF 格式 Linux 文件(编译生成)
└── vmlinux.o       # 编译生成的 vmlinux.o

arch/arm 为例,其下还有 bootcommonconfigsmach-xxx 等子目录,分别用于控制系统引导、系统调用、动态调频、主频设置等:

  • arch/arm/configs/ — 各平台的默认配置文件 xxx_defconfig,其中 imx_v7_defconfig 即 I.MX6U-ALPHA 开发板的默认配置;
  • arch/arm/boot/dts/ — 对应开发平台的设备树文件;
  • arch/arm/boot/ — 保存编译出来的 ImagezImage 镜像,zImage 就是最终要用的 Linux 镜像;
  • arch/arm/mach-xxx/ — 相应平台的驱动和初始化文件,如 mach-imx 就是 I.MX 系列 CPU 的驱动与初始化文件。

1.3 内核配置系统

Linux内核使用Kconfig配置系统,主要文件包括:

  • Kconfig:配置项定义
  • Makefile:编译规则
  • defconfig:默认配置文件

配置流程:Kconfigmenuconfig.config → 编译

二、内核配置系统

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

2.1 Kconfig语法

Kconfig是Linux内核的配置描述语言,定义在各子目录的Kconfig文件中。

基本语法结构

config 选项名称
    类型(bool/tristate/int/hex/string)
    "提示信息"
    默认值
    依赖关系
    帮助文本

配置项类型

类型 说明 取值
bool 布尔类型 y或n
tristate 三态类型 y、n或m(模块)
int 整数类型 数字
hex 十六进制 0x格式数字
string 字符串 任意字符串

常用关键字

# 定义配置项
config LED_DRIVER
    bool "LED Driver"
    default y

# 三态类型(可编译为模块)
config USB_STORAGE
    tristate "USB storage support"
    default m

# 整数类型
config NR_CPUS
    int "Maximum number of CPUs"
    range 2 8
    default 4

# 依赖关系
config HIGHMEM
    bool "High Memory Support"
    depends on ARM

# 反向依赖
config FB
    bool "Support for frame buffer"
    depends on !S390

# 选择依赖
config SND_SOC
    tristate "ALSA SoC support"
    select SND_PCM

# 多选依赖
config EXT4_FS
    tristate "Ext4 file system"
    depends on BLOCK

菜单结构

# 创建菜单
menu "Device Drivers"
    source "drivers/Kconfig"
endmenu

# 创建子菜单
menu "Network device support"
    menu "Ethernet driver support"
        source "drivers/net/ethernet/Kconfig"
    endmenu
endmenu

# 条件菜单
if ARCH_ARM
    menu "ARM specific configuration"
    endmenu
endif

2.2 配置项依赖关系

依赖关系是Kconfig的核心机制,确保配置选项的逻辑正确性。

# depends on - 必须满足
config MODULE_UNLOAD
    bool "Module unloading"
    depends on MODULES

# ! 取反
config LOCALVERSION
    string "Local version - append to kernel release"
    depends on !COMPILE_TEST

# || 或
config SND
    tristate "Sound card support"
    depends on AUDIT || !AUDIT

# && 与
config X86_64
    bool "64-bit kernel"
    depends on 64BIT && X86

# 选择依赖(自动选择被依赖项)
config SND_SOC
    tristate "ALSA SoC support"
    select SND_PCM

# 反向选择
config SND_SOC_I2S
    tristate "Generic I2S driver"
    select SND_SOC

2.3 menuconfig使用

make menuconfig是基于ncurses的图形配置界面。

# 基本使用
make menuconfig

# 交叉编译配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 使用外部分支配置
make ARCH=arm menuconfig O=out/

配置界面操作:

  • y:编译进内核
  • m:编译为模块
  • n:不编译
  • ?:查看帮助
  • /:搜索配置项
  • Esc Esc:返回上级

三、顶层Makefile详解

3.1 版本信息

顶层Makefile一开始(第 1~4 行)就定义了 Linux 内核的版本号:

VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 15
EXTRAVERSION =

可以看出,Linux 内核版本号为 4.1.15。教材 35.5 只列出了上面 4 行;KERNELVERSION 由这 4 个变量拼接而成,其完整定义(摘自同一份顶层 Makefile)为:

KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

3.2 MAKEFLAGS设置

# 递归编译标志
MAKEFLAGS += -rR --include-dir=$(CURDIR)

-r禁止内置规则,-R禁止内置变量,提高编译效率。

3.3 输出控制

# V=1 显示详细编译命令
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

export quiet Q KBUILD_VERBOSE

使用方法:

  • make V=0:静默输出(默认)
  • make V=1:显示完整编译命令

3.4 静默编译

# make -s 静默模式
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

3.5 输出目录配置

# O=xxx 指定输出目录
ifeq ($(KBUILD_SRC),)
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
endif

使用方法:

make O=out/linux    # 编译输出到out/linux目录

3.6 代码检查

# C=1 检查需要重新编译的文件
# C=2 检查所有源文件
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif

3.7 外部模块编译

# M=dir 编译外部模块
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif

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

# 外部模块时,目标为modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

使用方法:

make M=/path/to/module    # 编译外部模块

3.8 目录架构与源码目录

ifeq ($(KBUILD_SRC),)
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
srctree := ..
else
srctree := $(KBUILD_SRC)
endif
endif
objtree := .
src := $(srctree)
obj := $(objtree)

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

export srctree objtree VPATH

3.9 架构与交叉编译配置

# 架构类型
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)

实际使用时需要修改为:

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-

或者通过命令行指定:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xxx_defconfig

3.10 工具链变量

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

3.11 头文件路径

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

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)

3.12 导出变量

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

3.13 调用 scripts/Kbuild.include

顶层Makefile会包含文件 scripts/Kbuild.include(教材 35.5 第 9 点):

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

其中定义了后面反复使用的 buildif_changed 等函数,例如:

build := -f $(srctree)/scripts/Makefile.build obj

3.14 Makefile.build 脚本分析

make xxx_defconfig 配置内核时,实际执行的是下面两条命令,它们都会用到 scripts/Makefile.build(教材 35.5.2):

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

(1)scripts_basic 目标

scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

展开后即 @make -f ./scripts/Makefile.build obj=scripts/basicMakefile.build 读取 scripts/basic/Makefile,其默认目标 __build 中只有 always 有效:

__build: scripts/basic/fixdep scripts/basic/bin2c
	@:

所以 scripts_basic 的作用就是编译出 scripts/basic/fixdepscripts/basic/bin2c 两个主机工具

(2)%config 目标

顶层Makefile中有 config%config 两个目标:

config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

make xxx_defconfig%config 匹配。Makefile.build 会读取 scripts/kconfig/Makefile,其中的规则为:

%_defconfig: $(obj)/conf
	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

展开后为:

@scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig

%_defconfig 依赖 scripts/kconfig/conf,因此会先编译 scripts/kconfig/conf.c 生成 conf 工具,再由它将默认配置输出为内核根目录下的 .config 文件。

四、内核配置方法

4.1 make xxx_defconfig

首次编译前需要使用make xxx_defconfig配置内核。教材提供的 I.MX6U-ALPHA 默认配置文件为 arch/arm/configs/imx_v7_defconfig

# 使用默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig

顶层Makefile中与之匹配的目标(教材示例代码 35.5.1.1):

config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

此时 config-targets=1mixed-targets=0dot-config=1%config 第 534 行会 include arch/$(SRCARCH)/MakefileSRCARCH=arm),这个文件很重要,zImageuImage 等镜像就是由它生成的。

执行流程:

make xxx_defconfig
    ↓
scripts_basic (编译fixdep、bin2c工具)
    ↓
scripts/kconfig/conf --defconfig=arch/arm/configs/xxx_defconfig Kconfig
    ↓
生成.config文件

完整的一次编译(教材 35.2)通常先清理再配置,最后编译:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

注意:distclean 会连带删除 .config 等配置信息。若已通过图形界面配置了内核但还没保存新的配置文件,则要慎用该脚本,否则配置会被全部删除。

4.2 make menuconfig

图形化配置界面:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

配置文件位置:

  • 输入:arch/arm/configs/xxx_defconfig
  • 输出:.config

4.3 默认配置

# 使用架构默认配置
make ARCH=arm defconfig

# 使用厂商提供的配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig

4.4 自定义配置

# 方式1:基于现有配置修改
make menuconfig
# 在菜单中修改配置,保存后生成.config

# 方式2:手动编辑.config
vim .config
make olddefconfig  # 更新配置

# 方式3:备份和恢复配置
cp .config .config.backup
make defconfig
cp .config.backup .config
make olddefconfig

五、内核编译

5.0 编译准备与初次编译

安装 lzop(教材 35.2):编译内核前需要先在 Ubuntu 上安装 lzop 库,否则编译会失败:

sudo apt-get install lzop

否则会提示 recipe for target 'arch/arm/boot/compressed/piggy.lzo' failed(lzop 未找到),安装后重新编译即可。

正点原子编译脚本 mx6ull_alientek_emmc.sh

#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
  • 第 2 行 make distclean:清理工程,每次运行脚本都会先清理;
  • 第 3 行 make xxx_defconfig:配置工程;
  • 第 4 行 make menuconfig:打开图形配置界面,不需要每次配置时可删除此行;
  • 第 5 行 make:编译 Linux 源码。

图形配置界面和 U-Boot 一样,本实验不需要任何配置,直接按两下 ESC 键退出,退出后会自动开始编译。

编译完成后:

  • arch/arm/boot/zImage — 要用的 Linux 镜像文件;
  • arch/arm/boot/dts/*.dtb — 很多设备树文件。

5.1 编译命令

# 编译zImage
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage

# 编译所有(包括模块)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all

# 使用16线程并行编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

5.2 编译目标生成

# make all 目标
all: vmlinux

# BOOT_TARGETS定义(arch/arm/Makefile)
BOOT_TARGETS = zImage Image xipImage bootpImage uImage

$(BOOT_TARGETS): vmlinux
    $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

BOOT_TARGETS 依赖 vmlinux,所以编译 zImage 时会先编译出 vmlinux。以 zImage 为例,第 316 行命令展开后为:

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

即再次借助 scripts/Makefile.build 完成从 vmlinuxzImage 的转换。使用 makemake allmake zImage 均可编译出 zImage

5.3 vmlinux生成流程

# 第 905~907 行:导出给 scripts/link-vmlinux.sh 使用
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

# 第 912 行:vmlinux依赖
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

# 第 915 行:链接命令
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)

# 第 920 行:vmlinux目标
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
    +$(call if_changed,link-vmlinux)

其中:

  • KBUILD_LDS = arch/arm/kernel/vmlinux.lds,是整个 Linux 的链接脚本;
  • $(head-y) 定义在 arch/arm/Makefilehead-y := arch/arm/kernel/head$(MMUEXT).o,使能 MMU 时 MMUEXT 为空,故 head-y = arch/arm/kernel/head.o
  • init-y/drivers-y/net-y 由顶层Makefile用 patsubst 把目录名替换为 %/built-in.o
  • libs-y 的值为 arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
  • core-yarch/arm/Makefile 中被追加了 arch/arm/kernel、mm、common、probes、net、crypto、firmware、mach-imx 等目录。

最终链接的实际命令LDFLAGS_vmlinux=-p --no-undefined -X --pic-veneer --build-id):

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

scripts/link-vmlinux.sh 中的 vmlinux_link()${lds} 为链接脚本、以 ${KBUILD_VMLINUX_INIT}${KBUILD_VMLINUX_MAIN} 为输入文件完成最终链接。

vmlinux组成部分:

组成 说明 路径
head-y 启动代码 arch/arm/kernel/head.o
init-y 初始化代码 init/built-in.o
core-y 核心代码 kernel/mm/fs/ipc等
libs-y 库函数 lib/lib.a lib/built-in.o
drivers-y 设备驱动 drivers/sound/firmware
net-y 网络协议 net/built-in.o

5.4 内核镜像类型

镜像 大小 说明
vmlinux ~16MB ELF格式,未压缩
Image ~12MB 去除调试信息
zImage ~6MB gzip压缩
uImage ~6MB U-Boot专用格式

转换关系:

vmlinux → objcopy → Image → gzip → zImage → mkimage → uImage

教材 35.5.5 的详细说明:

  1. vmlinux:编译出来的最原始内核文件,未压缩,ELF 格式,正点原子源码编译出来约 16MB;
  2. Image:Linux 内核镜像文件,仅包含可执行的二进制数据,即用 objcopy 取消掉 vmlinux 中的符号表等信息;没有压缩,保存在 arch/arm/boot 目录下,约 12MB;
  3. zImage:经过 gzip 压缩后的 Image,约 6MB;
  4. uImage:老版本 U-Boot 专用的镜像文件,是在 zImage 前面加了一个长度为 64 字节的“头”,头信息描述了镜像文件的类型、加载位置、生成时间、大小等。新版本 U-Boot 已经支持直接启动 zImage,所以 uImage 已很少使用。

5.5 增量编译与清理

# 编译后修改代码,直接执行(只重编被修改的文件)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

# 清理编译产物(保留 .config)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean

# 完全清理(删除所有生成文件 + .config 及备份文件)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mrproper

# 在 mrproper 基础上再删除编辑器备份与补丁文件(教材编译脚本使用)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean

5.6 built-in.o 文件编译生成过程

教材 35.5.4 指出,vmlinux-deps 中除链接脚本外,其余都是各子目录下的 built-in.o.a 文件。顶层Makefile中:

# 第 937 行:vmlinux-deps 依赖 vmlinux-dirs
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

# 第 889 行:vmlinux-dirs 保存生成 vmlinux 所需的源码目录
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
             $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
             $(net-y) $(net-m) $(libs-y) $(libs-m)))

# 第 946~947 行:进入每个目录执行编译
$(vmlinux-dirs): prepare scripts
	$(Q)$(MAKE) $(build)=$@

$(vmlinux-dirs) 规则的命令展开后就是为 initusrarch/arm/kernelkernelmmfsdriverslib 等每个目录各执行一次:

@make -f ./scripts/Makefile.build obj=<目录名>

每个目录下生成的 built-in.oscripts/Makefile.build 中的 builtin-target 定义:

ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif

$(builtin-target): $(obj-y) FORCE
	$(call if_changed,link_o_target)

cmd_link_o_target 使用 $(LD) ... -r -o $@ 把该目录下所有 obj-y 指定的 .o 文件链接成一个 built-in.o。最终所有 built-in.o.a 文件被链接成 ELF 可执行文件 vmlinux

5.7 编译输出目录

使用 O=xxx 可把编译产生的过程文件输出到指定目录,源码目录保持干净:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=out/linux imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=out/linux -j16

六、内核模块编译

6.1 内置模块

在Kconfig中使用obj-y编译进内核:

# drivers/leds/Kconfig
config LEDS_GPIO
    tristate "GPIO LED driver"
    depends on LEDS_CLASS
    default y

# drivers/leds/Makefile
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o

6.2 外部模块

# 编译外部模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- M=/path/to/module modules

# 安装模块到指定目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- M=/path/to/module \
    INSTALL_MOD_PATH=/path/to/rootfs modules_install

6.3 模块Makefile示例

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第三十五章内容,为扩展知识。教材仅在 35.5 第 7 点讲解 make M=dir 的模块编译机制,完整的模块 Makefile 模板见第四十章。

# 外部模块Makefile
obj-m := mydriver.o
mydriver-objs := main.o init.o

KERNEL_DIR := /path/to/linux-4.1.15
CROSS_COMPILE := arm-linux-gnueabihf-
ARCH := arm

all:
    make -C $(KERNEL_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
        M=$(PWD) modules

clean:
    make -C $(KERNEL_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
        M=$(PWD) clean

6.4 模块安装

# 将模块复制到内核模块目录
cp mydriver.ko /lib/modules/4.1.15/kernel/drivers/

# 生成模块依赖
depmod -a

# 加载模块
insmod mydriver.ko

# 查看已加载模块
lsmod

七、跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

7.1 I.MX6ULL vs STM32 vs RK3568内核差异

对比项 I.MX6ULL STM32MP1 RK3568
内核版本 4.1.15 5.4 5.10
架构 ARM Cortex-A7 ARM Cortex-A7 ARM Cortex-A55
位数 32位 32位 64位
配置文件 imx_v7_defconfig stm32mp1_defconfig rockchip_linux_defconfig
设备树 imx6ull-14x14-evk.dts stm32mp157c-ev1.dts rk3568-evb1-v10.dts
内核大小 ~5MB ~6MB ~8MB
启动方式 U-Boot+zImage U-Boot+zImage U-Boot+zImage

7.2 跨平台编译注意事项

# I.MX6ULL (ARM 32位)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig

# STM32MP1 (ARM 32位)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- stm32mp1_defconfig

# RK3568 (ARM 64位)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip_linux_defconfig

7.3 驱动差异

驱动类型 I.MX6ULL STM32MP1 RK3568
GPIO gpio-mxc gpio-stm32 gpio-rockchip
I2C i2c-imx i2c-stm32 i2c-rk3x
SPI spi-imx spi-stm32 spi-rockchip
UART serial-imx serial-stm32 serial-rockchip

八、面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

Q1: 请描述Linux内核编译的完整流程

答案:

  1. 配置阶段:使用make xxx_defconfigmake menuconfig生成.config文件
  2. 准备阶段:编译scripts/basic工具(fixdep、bin2c)
  3. 架构配置:读取arch/arm/Makefile设置架构相关变量
  4. 编译源码:递归编译各子目录,生成built-in.o和模块
  5. 链接阶段:使用scripts/link-vmlinux.sh链接生成vmlinux
  6. 镜像生成:将vmlinux转换为Image、zImage等可引导镜像

Q2: Kconfig中的depends on和select有什么区别?

答案:

  • depends on:配置项被依赖,必须满足条件才能启用

    config HIGHMEM
      bool "High Memory Support"
      depends on ARM  # 只有ARM架构才能选择
    
  • select:配置项被选中时自动选择被依赖项

    config SND_SOC
      tristate "ALSA SoC support"
      select SND_PCM  # 选中时自动选中SND_PCM
    

Q3: vmlinux是如何生成的?涉及哪些关键变量?

答案: vmlinux通过链接脚本和链接命令生成:

  • KBUILD_VMLINUX_INIT:启动代码和初始化代码
    • head-y:arch/arm/kernel/head.o
    • init-y:init/built-in.o
  • KBUILD_VMLINUX_MAIN:核心功能模块
    • core-y:kernel/mm/fs/ipc等
    • libs-y:lib/lib.a lib/built-in.o
    • drivers-y:drivers/sound/firmware
    • net-y:net/built-in.o
  • 链接脚本:arch/arm/kernel/vmlinux.lds
  • 链接命令:scripts/link-vmlinux.sh调用LD完成链接

Q4: make menuconfig和make xxx_defconfig有什么区别?

答案:

  • make xxx_defconfig:使用预定义的默认配置文件快速配置,适合初始配置

    make ARCH=arm imx_v7_defconfig  # 使用IMX6ULL默认配置
    
  • make menuconfig:提供图形界面手动修改配置,适合定制化需求

    make ARCH=arm menuconfig  # 打开配置菜单
    
  • 推荐流程:先用defconfig快速配置,再用menuconfig精细调整

Q5: 如何交叉编译Linux内核并烧录到开发板?

答案:

# 1. 进入内核源码目录
cd /path/to/linux-4.1.15

# 2. 配置内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 3. 编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

# 4. 编译模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

# 5. 安装模块到rootfs
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
    INSTALL_MOD_PATH=/path/to/rootfs modules_install

# 6. 复制zImage到TFTP目录
cp arch/arm/boot/zImage /tftpboot/

# 7. 复制设备树到TFTP目录
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /tftpboot/

# 8. 在U-Boot中设置启动参数
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv
run bootcmd

相关知识

  • [[02-Linux内核启动流程]]
  • [[03-设备树详解]]
  • [[04-内核模块开发]]
  • [[05-Bootloader配置]]

参考资料


内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第三十五章 Linux内核顶层Makefile详解