--- 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修复和小功能更新 查看内核版本: ```bash 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` 为例,其下还有 `boot`、`common`、`configs`、`mach-xxx` 等子目录,分别用于控制系统引导、系统调用、动态调频、主频设置等: - `arch/arm/configs/` — 各平台的默认配置文件 `xxx_defconfig`,其中 `imx_v7_defconfig` 即 I.MX6U-ALPHA 开发板的默认配置; - `arch/arm/boot/dts/` — 对应开发平台的设备树文件; - `arch/arm/boot/` — 保存编译出来的 `Image` 和 `zImage` 镜像,`zImage` 就是最终要用的 Linux 镜像; - `arch/arm/mach-xxx/` — 相应平台的驱动和初始化文件,如 `mach-imx` 就是 I.MX 系列 CPU 的驱动与初始化文件。 ### 1.3 内核配置系统 Linux内核使用Kconfig配置系统,主要文件包括: - `Kconfig`:配置项定义 - `Makefile`:编译规则 - `defconfig`:默认配置文件 配置流程:`Kconfig` → `menuconfig` → `.config` → 编译 ## 二、内核配置系统 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 ### 2.1 Kconfig语法 Kconfig是Linux内核的配置描述语言,定义在各子目录的Kconfig文件中。 #### 基本语法结构 ```kconfig config 选项名称 类型(bool/tristate/int/hex/string) "提示信息" 默认值 依赖关系 帮助文本 ``` #### 配置项类型 | 类型 | 说明 | 取值 | | ---------- | -------- | --------------- | | `bool` | 布尔类型 | y或n | | `tristate` | 三态类型 | y、n或m(模块) | | `int` | 整数类型 | 数字 | | `hex` | 十六进制 | 0x格式数字 | | `string` | 字符串 | 任意字符串 | #### 常用关键字 ```kconfig # 定义配置项 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 ``` #### 菜单结构 ```kconfig # 创建菜单 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的核心机制,确保配置选项的逻辑正确性。 ```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的图形配置界面。 ```bash # 基本使用 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 内核的版本号: ```makefile VERSION = 4 PATCHLEVEL = 1 SUBLEVEL = 15 EXTRAVERSION = ``` 可以看出,Linux 内核版本号为 **4.1.15**。教材 35.5 只列出了上面 4 行;`KERNELVERSION` 由这 4 个变量拼接而成,其完整定义(摘自同一份顶层 Makefile)为: ```makefile KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION) ``` ### 3.2 MAKEFLAGS设置 ```makefile # 递归编译标志 MAKEFLAGS += -rR --include-dir=$(CURDIR) ``` `-r`禁止内置规则,`-R`禁止内置变量,提高编译效率。 ### 3.3 输出控制 ```makefile # 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 静默编译 ```makefile # 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 输出目录配置 ```makefile # O=xxx 指定输出目录 ifeq ($(KBUILD_SRC),) ifeq ("$(origin O)", "command line") KBUILD_OUTPUT := $(O) endif endif ``` **使用方法:** ```bash make O=out/linux # 编译输出到out/linux目录 ``` ### 3.6 代码检查 ```makefile # C=1 检查需要重新编译的文件 # C=2 检查所有源文件 ifeq ("$(origin C)", "command line") KBUILD_CHECKSRC = $(C) endif ifndef KBUILD_CHECKSRC KBUILD_CHECKSRC = 0 endif ``` ### 3.7 外部模块编译 ```makefile # 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 ``` **使用方法:** ```bash make M=/path/to/module # 编译外部模块 ``` ### 3.8 目录架构与源码目录 ```makefile 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 架构与交叉编译配置 ```makefile # 架构类型 ARCH ?= $(SUBARCH) CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%) ``` **实际使用时需要修改为:** ```makefile ARCH ?= arm CROSS_COMPILE ?= arm-linux-gnueabihf- ``` 或者通过命令行指定: ```bash make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xxx_defconfig ``` ### 3.10 工具链变量 ```makefile 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 头文件路径 ```makefile 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 导出变量 ```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 ``` ### 3.13 调用 scripts/Kbuild.include 顶层Makefile会包含文件 `scripts/Kbuild.include`(教材 35.5 第 9 点): ```makefile # We need some generic definitions (do not try to remake the file). scripts/Kbuild.include: ; include scripts/Kbuild.include ``` 其中定义了后面反复使用的 `build`、`if_changed` 等函数,例如: ```makefile build := -f $(srctree)/scripts/Makefile.build obj ``` ### 3.14 Makefile.build 脚本分析 `make xxx_defconfig` 配置内核时,实际执行的是下面两条命令,它们都会用到 `scripts/Makefile.build`(教材 35.5.2): ```bash @make -f ./scripts/Makefile.build obj=scripts/basic @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig ``` **(1)scripts_basic 目标** ```makefile scripts_basic: $(Q)$(MAKE) $(build)=scripts/basic $(Q)rm -f .tmp_quiet_recordmcount ``` 展开后即 `@make -f ./scripts/Makefile.build obj=scripts/basic`。`Makefile.build` 读取 `scripts/basic/Makefile`,其默认目标 `__build` 中只有 `always` 有效: ```makefile __build: scripts/basic/fixdep scripts/basic/bin2c @: ``` 所以 `scripts_basic` 的作用就是**编译出 `scripts/basic/fixdep` 和 `scripts/basic/bin2c` 两个主机工具**。 **(2)%config 目标** 顶层Makefile中有 `config` 和 `%config` 两个目标: ```makefile 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`,其中的规则为: ```makefile %_defconfig: $(obj)/conf $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig) ``` 展开后为: ```bash @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`: ```bash # 使用默认配置 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig ``` 顶层Makefile中与之匹配的目标(教材示例代码 35.5.1.1): ```makefile config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@ %config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@ ``` 此时 `config-targets=1`、`mixed-targets=0`、`dot-config=1`。`%config` 第 534 行会 `include arch/$(SRCARCH)/Makefile`(`SRCARCH=arm`),这个文件很重要,`zImage`、`uImage` 等镜像就是由它生成的。 **执行流程:** ``` make xxx_defconfig ↓ scripts_basic (编译fixdep、bin2c工具) ↓ scripts/kconfig/conf --defconfig=arch/arm/configs/xxx_defconfig Kconfig ↓ 生成.config文件 ``` 完整的一次编译(教材 35.2)通常先清理再配置,最后编译: ```bash 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 图形化配置界面: ```bash make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig ``` **配置文件位置:** - 输入:`arch/arm/configs/xxx_defconfig` - 输出:`.config` ### 4.3 默认配置 ```bash # 使用架构默认配置 make ARCH=arm defconfig # 使用厂商提供的配置 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig ``` ### 4.4 自定义配置 ```bash # 方式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 库,否则编译会失败: ```bash sudo apt-get install lzop ``` 否则会提示 `recipe for target 'arch/arm/boot/compressed/piggy.lzo' failed`(lzop 未找到),安装后重新编译即可。 **正点原子编译脚本** `mx6ull_alientek_emmc.sh`: ```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 编译命令 ```bash # 编译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 编译目标生成 ```makefile # 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 行命令展开后为: ```bash @make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage ``` 即再次借助 `scripts/Makefile.build` 完成从 `vmlinux` 到 `zImage` 的转换。使用 `make`、`make all`、`make zImage` 均可编译出 `zImage`。 ### 5.3 vmlinux生成流程 ```makefile # 第 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/Makefile`:`head-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-y` 在 `arch/arm/Makefile` 中被追加了 `arch/arm/kernel、mm、common、probes、net、crypto、firmware、mach-imx` 等目录。 **最终链接的实际命令**(`LDFLAGS_vmlinux=-p --no-undefined -X --pic-veneer --build-id`): ```bash /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 增量编译与清理 ```bash # 编译后修改代码,直接执行(只重编被修改的文件) 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中: ```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)` 规则的命令展开后就是为 `init`、`usr`、`arch/arm/kernel`、`kernel`、`mm`、`fs`、`drivers`、`lib` 等每个目录各执行一次: ```bash @make -f ./scripts/Makefile.build obj=<目录名> ``` 每个目录下生成的 `built-in.o` 由 `scripts/Makefile.build` 中的 `builtin-target` 定义: ```makefile 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` 可把编译产生的过程文件输出到指定目录,源码目录保持干净: ```bash 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`编译进内核: ```makefile # 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 外部模块 ```bash # 编译外部模块 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 # 外部模块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 模块安装 ```bash # 将模块复制到内核模块目录 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 跨平台编译注意事项 ```bash # 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_defconfig`或`make 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**:配置项被依赖,必须满足条件才能启用 ```kconfig config HIGHMEM bool "High Memory Support" depends on ARM # 只有ARM架构才能选择 ``` - **select**:配置项被选中时自动选择被依赖项 ```kconfig 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**:使用预定义的默认配置文件快速配置,适合初始配置 ```bash make ARCH=arm imx_v7_defconfig # 使用IMX6ULL默认配置 ``` - **make menuconfig**:提供图形界面手动修改配置,适合定制化需求 ```bash make ARCH=arm menuconfig # 打开配置菜单 ``` - **推荐流程**:先用defconfig快速配置,再用menuconfig精细调整 ### Q5: 如何交叉编译Linux内核并烧录到开发板? **答案:** ```bash # 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驱动开发指南V2.0.1》 - Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/ - Linux Kernel Build System: https://www.kernel.org/doc/html/latest/kbuild/ --- **内容来源**: 《I.MX6U嵌入式Linux驱动开发指南》第三十五章 Linux内核顶层Makefile详解