03-Linux内核裁剪与编译.md 34 KB


title: Linux内核裁剪与编译 tags: [Linux内核, 内核编译, Makefile, 内核移植, 设备树, ARM] created: 2026-09-17 updated: 2026-09-17 pdf_ref:

  • "40_第三十五章 Linux内核顶层Makefile详解.pdf"
  • "42_第三十七章 Linux内核移植.pdf" ---

Linux内核裁剪与编译

关联知识: [[06-系统移植与启动/01-U-Boot命令与使用]] | [[06-系统移植与启动/02-U-Boot启动流程]] | [[06-系统移植与启动/04-根文件系统构建]]

编译 Linux 内核和编译 U-Boot 的流程高度相似:先 make xxx_defconfig 选定默认配置,再 make 生成镜像。区别在于 Linux 内核多出一套 Kconfig 配置系统、一个庞大的 drivers/ 驱动目录和独立编译的设备树(DTS/DTB)。本章以 NXP linux-imx-4.1.15 源码和正点原子 I.MX6U-ALPHA(EMMC 版)为主线,串起内核概述、配置、裁剪、顶层 Makefile、编译、设备树六个环节。


一、Linux内核概述

1.1 内核版本选择

内核源码由 Linux 基金会管理与发布,官网 https://www.kernel.org。开发中并不追求最新版,而是跟随半导体厂商的 BSP 版本

来源 版本 / 文件 说明
NXP 官方原版 linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2 NXP 移植到 i.MX 系列后的 BSP
正点原子出厂 linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2 已适配 I.MX6U-ALPHA,含编译脚本

为什么用 4.1.15:NXP 对 i.MX6ULL 的 BSP 长期维护在这个版本,外设驱动、设备树最完整;4.x 与 5.x 在核心机制上没有本质区别,5.x 更多是新增平台和外设。中途换版本意味着设备树、驱动全部重调,得不偿失。

1.2 内核源码目录结构

mindmap
  root((Linux内核源码))
    arch/ 体系结构与设备树
    drivers/ 设备驱动
    fs/ 文件系统
    kernel/ 核心调度
    mm/ 内存管理
    net/ 网络协议栈
    include/ 头文件
    init/ 内核初始化
    scripts/ 构建脚本
    Makefile Kconfig 顶层构建
linux-imx-4.1.15/
+-- arch/            # 架构相关,arch/arm 下有 boot、configs、mach-xxx
+-- block/           # 块设备管理
+-- crypto/          # 加密算法(crc、md5、hash)
+-- Documentation/   # 官方文档,查模块/驱动架构的第一去处
+-- drivers/         # 驱动(i2c、gpio、net、mmc…),学习重点
+-- firmware/        # 固件
+-- fs/              # 文件系统(ext4、f2fs、nfs、yaffs2)
+-- include/         # 头文件
+-- init/            # 内核启动初始化代码
+-- ipc/             # 进程间通信
+-- kernel/          # 内核核心代码
+-- lib/             # 公共库函数
+-- mm/              # 内存管理
+-- net/             # 网络协议栈
+-- samples/         # 示例代码
+-- scripts/         # 编译脚本(kconfig、Makefile.build、link-vmlinux.sh)
+-- security/        # 安全模块
+-- sound/           # 音频子系统
+-- tools/           # 编译工具
+-- usr/             # initramfs 相关
+-- virt/            # 虚拟化(KVM)
+-- .config          # 最终配置(编译生成)
+-- Kbuild           # 部分 Makefile 会读取
+-- Kconfig          # menuconfig 图形界面的配置源
+-- Makefile         # 顶层 Makefile
+-- System.map       # 符号表(编译生成)
+-- vmlinux          # 未压缩 ELF(编译生成)

重点关注的子目录:

目录 作用 关键点
arch/arm/configs/ 各平台默认配置 imx_v7_defconfigimx_v7_mfg_defconfig
arch/arm/boot/ 编译产物 ImagezImage
arch/arm/boot/dts/ 设备树源码与产物 imx6ull-alientek-emmc.dts/.dtb
arch/arm/mach-xxx/ 平台初始化 mach-imx 为 i.MX 系列
drivers/ 驱动源码 按类型分类,如 drivers/netdrivers/mmc
scripts/kconfig/ 配置工具 conf 负责生成 .config
scripts/Makefile.build 子目录构建入口 每个目录的 built-in.o 由它生成

1.3 内核配置系统

文件 角色 是否该改
Kconfig 配置菜单的“源”,描述每个配置项的名称、类型、依赖 加新驱动时才改
.config 最终生效的配置,make 编译时读取 机器生成,不手改
xxx_defconfig 精简版默认配置,存于 arch/arm/configs/ 手改或 savedefconfig 导出
include/generated/autoconf.h .config 自动生成,供 C 代码 #ifdef 使用 自动生成

三者的转换关系:

Kconfig(菜单定义) + xxx_defconfig(默认值)
        |  make menuconfig / make xxx_defconfig
        v
      .config(最终配置)
        |  自动生成
        v
include/generated/autoconf.h(C 代码可见的宏)

1.4 内核构建系统(Kbuild)

Kbuild 是 Linux 内核的构建框架,不是单个文件,而是一套约定:

  • 顶层 Makefile 负责版本号、交叉编译变量、目标分发(allzImagedtbsmodules)。
  • scripts/Kbuild.include 提供 buildif_changedecho-cmd 等通用函数。
  • scripts/Makefile.build 是每个子目录的真正执行者,读取子目录的 Makefile(或 Kbuild)。
  • 子目录用 obj-y(编译进内核)、obj-m(编译成模块)声明源文件。

    # drivers/net/phy/Makefile 片段:按配置决定是否编译
    obj-$(CONFIG_SMSC_PHY) += smsc.o
    

.configCONFIG_SMSC_PHY=y 时,smsc.c 被编译进内核;=m 时编译为模块 smsc.ko


二、内核配置方法

2.1 make menuconfig 交互式配置

# 在源码根目录执行,菜单界面基于 ncurses
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

界面结构:顶部为标题,中间为可折叠菜单树,底部为操作提示。配置项前的符号含义:

符号 含义
[ ] 布尔项,未选
[ * ] 布尔项,选中(编进内核)
< > 三态项,未选
< * > 三态项,=y(编进内核)
< M > 三态项,=m(编译成模块)
(value) 数值 / 字符串项
-*- 被其他项强制选中,不可改

2.2 导航技巧

按键 作用
/ 上下移动
/ 切换底部按钮(<Select> <Exit> <Help> <Save> <Load>
Enter 进入子菜单,或切换选中状态
Y / N / M 直接设为 =y / 不选 / =m
空格 在三态之间循环
/ 全局搜索配置项关键字(定位驱动最快的方式)
? 查看当前项帮助:类型、依赖、config
Esc Esc 返回上一级 / 退出主菜单

定位一个驱动:按 / 输入关键字(如 SMSC),结果会显示其 CONFIG_* 名称和菜单路径,据此逐层进入勾选。

2.3 常用配置项

驱动类

配置项 路径 用途
CONFIG_SMSC_PHY Device Drivers → Network device support → PHY Device support → Drivers for SMSC PHYs LAN8720A 网络 PHY(V2.4 以前底板)
CONFIG_SOC_IMX6ULL System Type → Freescale i.MX i.MX6ULL 平台支持
CONFIG_MMC_SDHCI / CONFIG_MMC_SDHCI_ESDHC_IMX Device Drivers → MMC/SD uSDHC/EMMC 控制器
CONFIG_SERIAL_IMX Device Drivers → Character devices → Serial i.MX UART,控制台 ttymxc0 依赖它

文件系统与根文件系统类

配置项 用途
CONFIG_EXT4_FS ext4 文件系统,EMMC 根文件系统常用
CONFIG_NFS_FS NFS 客户端
CONFIG_ROOT_NFS 支持 NFS 作为根文件系统
CONFIG_DEVTMPFS / CONFIG_DEVTMPFS_MOUNT 自动挂载 devtmpfs,省去手建 /dev

网络与调频类

配置项 用途
CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND 默认调频策略为 ondemand(省电,产品推荐)
CONFIG_CPU_FREQ_GOV_PERFORMANCE performance 策略(开发期高性能)
CONFIG_FEC i.MX 内部 MAC 网卡驱动(fec_main.c

2.4 配置保存与导出

menuconfig 里改的是内存中的配置,退出前必须保存,否则前功尽弃。两种方式:

方式一:图形界面保存<Save> 按钮)

方向键移动到 <Save> → 回车 → 输入文件名(可带相对路径)
例如:arch/arm/configs/imx_alientek_emmc_defconfig

方式二:命令行导出(推荐,可脚本化)

# 把当前 .config 精简后导出为 defconfig(去掉无意义的默认项)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- savedefconfig
# 生成 defconfig,人工核对后复制到 arch/arm/configs/
cp defconfig arch/arm/configs/imx_alientek_emmc_defconfig

# 或直接粗暴地把 .config 另存为 defconfig
cp .config arch/arm/configs/imx_alientek_emmc_defconfig

注意:make clean 会删除 .configmake mrproper / distclean 删得更彻底。改完图形配置且验证稳定后,务必先 savedefconfig 固化,再执行清理。

2.5 .config 文件格式

#
# Automatically generated file; DO NOT EDIT.
#
CONFIG_ARM=y                                  # =y 编进内核
CONFIG_SMSC_PHY=y                             # 选中该驱动
CONFIG_MMC_BLOCK=m                            # =m 编译成模块
# CONFIG_ARCH_MULTI_V6 is not set             # 显式未选中(屏蔽)
CONFIG_CMDLINE="console=ttymxc0,115200"       # 字符串
  • CONFIG_xxx=y:编译进内核镜像。
  • CONFIG_xxx=m:编译为 .ko 模块。
  • # CONFIG_xxx is not set:显式关闭。屏蔽某个功能时不能直接删行,要写成这种注释形式,否则 Kconfig 会用默认值把它重新打开。

三、内核裁剪

3.1 裁剪原则与策略

裁剪的目标是在功能完整的前提下减小 zImage、缩短启动时间、降低内存占用。原则:

  1. 先跑通,再裁剪:用官方 defconfig 编译并能正常启动后,再逐项瘦身。
  2. 按需保留:只保留目标产品真正用到的驱动、文件系统、协议。
  3. 优先模块化:暂时不确定的功能设为 =m,需要时 insmod,而不是直接砍掉。
  4. 小步验证:每裁一批就启动验证一次,便于定位是哪一项裁坏了。
  5. 关闭调试信息:量产版本必须去掉 debug,否则镜像可能翻倍。

3.2 驱动裁剪

drivers/ 分类逐类清理,只保留板载外设:

类别 可裁剪项 保留项(I.MX6U-ALPHA 为例)
字符设备 无用键盘、触摸屏、串口扩展 CONFIG_SERIAL_IMX(控制台)
块设备 无关的 SCSI、NVMe、RAID CONFIG_MMC_SDHCI_ESDHC_IMX(EMMC/SD)
网络设备 千兆网卡型号一箩筐 CONFIG_FEC + CONFIG_SMSC_PHY
图形/显示 未接屏幕时的 DRM/FB 全部关掉 按需保留 LCDIF
无线/USB 未使用的 WiFi/蓝牙/USB 主机 按需

定位方法:menuconfig 里按 / 搜关键字,或直接在 drivers/ 下找对应 Kconfig,看它的 depends on

3.3 文件系统裁剪

根文件系统用什么,就留什么文件系统:

# 典型裁剪(EMMC + NFS 调试)
CONFIG_EXT4_FS=y          # EMMC 根文件系统
# CONFIG_EXT2_FS is not set
# CONFIG_EXT3_FS is not set
CONFIG_NFS_FS=y           # 调试阶段网络挂载
CONFIG_ROOT_NFS=y
# CONFIG_F2FS_FS is not set
# CONFIG_BTRFS_FS is not set
# CONFIG_XFS_FS is not set

文件系统代码体积可观,关掉一整族未用文件系统通常能省下上百 KB。

3.4 调试信息裁剪

这是收益最大的一项:

配置项 说明 量产建议
CONFIG_DEBUG_INFO 生成 DWARF 调试信息,体积巨大 关闭
CONFIG_DEBUG_INFO_REDUCED 精简调试信息 关闭
CONFIG_KALLSYMS 符号解析,panic 时打印函数名 权衡,调试期保留
CONFIG_FRAME_POINTER 栈回溯辅助 关闭
CONFIG_MAGIC_SYSRQ SysRq 调试键 逐项确认后关闭
CONFIG_PROFILING 性能剖析 关闭

一般不用 strip 去处理 zImage——gzip 压缩本身会丢弃不可达信息,真正占体积的是未裁剪的驱动和调试宏,从源头配置才是正道。

3.5 模块化设计:=y vs =m

对比项 =y(built-in) =m(module)
编译产物 链接进 vmlinux/zImage 独立 .ko 文件
加载时机 内核启动即生效 insmod/modprobe 按需加载
体积影响 常驻内存 占用文件系统空间,用时才占内存
启动速度 启动慢(初始化所有内置驱动) 启动快(只加载必要模块)
适用场景 启动必需(存储、控制台、根 FS) 可选外设、调试驱动、多产品复用

判断标准:内核启动到挂载根文件系统所必需的 → =y;业务外设、偶尔使用、需要热插拔的 → =m。正点原子移植时 CONFIG_SMSC_PHY=y 是刻意编进内核的,因为网络要在挂载 NFS 根文件系统之前就绪,不能等模块加载。


四、顶层Makefile详解

4.1 Makefile 结构分析

顶层 Makefile 与 U-Boot 高度相似(U-Boot 参考了 Linux),前 600 行几乎一致,可按功能分段:

# ---- 1. 版本号 ----
VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 15
EXTRAVERSION =
# 合起来就是 4.1.15

# ---- 2. MAKEFLAGS ----
MAKEFLAGS += -rR --include-dir=$(CURDIR)

# ---- 3. 命令输出:make V=1 打印完整命令 ----
ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
# KBUILD_VERBOSE=1 → quiet=, Q= ;否则 quiet=quiet_, Q=@

# ---- 4. 静默输出:make -s ----
# 编译时只输出简短信息

# ---- 5. 输出目录:make O=/tmp/build ----
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

# ---- 6. 代码检查:make C=1/C=2(sparse)----
ifeq ("$(origin C)", "command line")
  KBUILD_CHECKSRC = $(C)
endif

# ---- 7. 外部模块:make M=drivers/foo ----
ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

常用命令行开关汇总:

命令 作用
make V=1 打印完整编译命令
make -s 静默编译
make O=/out 产物输出到独立目录
make C=1 检查需重编译文件的 sparse 警告
make C=2 检查全部源码
make M=dir 只编译指定外部模块

4.2 设置目标架构与交叉编译器

# 顶层 Makefile 第 252~253 行(原始)
ARCH            ?= $(SUBARCH)
CROSS_COMPILE   ?= $(CONFIG_CROSS_COMPILE:"%"=%)

两种用法,二选一:

# 用法一:命令行传入(推荐,不改源码)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage
# 用法二:直接写进顶层 Makefile 第 252~253 行,之后 make 即可
ARCH            ?= arm
CROSS_COMPILE   ?= arm-linux-gnueabihf-

?= 表示命令行已定义时不覆盖,因此两种方式可共存,但命令行优先级更高。

4.3 交叉编译工具变量

# 顶层 Makefile 第 353~361 行
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

设置 CROSS_COMPILE=arm-linux-gnueabihf- 后,上面每个变量自动展开为对应工具,如 CC = arm-linux-gnueabihf-gcc。头文件路径由 USERINCLUDE(UAPI)和 LINUXINCLUDE(内核源码)两组变量拼接,供所有子 Makefile 使用。

4.4 vmlinux 生成流程

make 的默认目标是 _all,它依赖 allall 又依赖 vmlinux

_all: all
all: vmlinux

vmlinux 的依赖由六大变量收集,它们最终都变成各目录下的 built-in.o / .a

# 第 905~912 行
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
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

各变量展开结果(ARM、开启 MMU):

head-y     = arch/arm/kernel/head.o
init-y     = init/built-in.o
core-y     = usr/built-in.o arch/arm/vfp/built-in.o arch/arm/kernel/built-in.o \
             arch/arm/mm/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
libs-y     = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
drivers-y  = drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y      = net/built-in.o

built-in.o 的来源:scripts/Makefile.buildbuiltin-target := $(obj)/built-in.o,用 LD -r 把该目录所有 .o 合并:

$(builtin-target): $(obj-y) FORCE
	$(call if_changed,link_o_target)
# 展开后:arm-linux-gnueabihf-ld -r -o built-in.o <该目录所有 .o>

最终链接由 scripts/link-vmlinux.sh 完成,链接脚本为 arch/arm/kernel/vmlinux.lds

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

4.5 目标生成规则与镜像对比

arch/arm/Makefile 定义了各类启动镜像目标:

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

即所有镜像都先依赖 vmlinux,再由 scripts/Makefile.build 逐级转换:

flowchart LR
  A["vmlinux<br/>ELF + 符号表<br/>~16MB"] -->|objcopy 去符号| B["Image<br/>裸二进制<br/>~12MB"]
  B -->|gzip 压缩| C["zImage<br/>自解压镜像<br/>~6MB"]
  C -->|mkimage + 64B 头| D["uImage<br/>老式 U-Boot 专用"]
镜像 格式 大小(参考) 用途
vmlinux ELF,含符号表 ~16MB 调试、生成 System.map
Image objcopy 后的裸二进制 ~12MB 不压缩内核镜像
zImage gzip 压缩的 Image ~6MB 最常用bootz 启动
uImage zImage + 64 字节头 略大于 zImage 老版本 U-Boot 的 bootm

常用目标:

目标 生成物
make all / make vmlinux + zImage + 模块
make zImage arch/arm/boot/zImage
make dtbs 仅设备树 arch/arm/boot/dts/*.dtb
make modules .ko 模块
make M=drivers/foo 仅编译指定外部模块

五、内核编译命令

5.1 清理命令

命令 清理范围 是否删 .config
make clean 大部分编译产物(.obuilt-in.o、镜像) 会删(4.1.15 行为)
make mrproper 所有编译产物 + 配置文件 + 备份 会删
make distclean mrproper + 编辑器备份、补丁文件 会删
make defconfig 用架构默认配置重新生成 .config 覆盖

distclean 最彻底。正点原子的编译脚本每次开头都执行 distclean,所以图形界面配置完一定要先 savedefconfig 固化,否则脚本一跑,配置全没。

5.2 默认配置

# i.MX6ULL EVK 官方默认配置(支持 MfgTool 烧写)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_mfg_defconfig

# 正点原子 EMMC 版配置(在 mfg 基础上裁剪 V6、适配底板)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig

5.3 图形配置

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

5.4 并行编译

# -jN 中 N 一般取 CPU 核数或 2 倍核数
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

# 编译前需安装 lzop,否则压缩 piggy 时会失败
sudo apt-get install 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_alientek_emmc_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
chmod 777 mx6ull_alientek_emmc.sh   # 赋可执行权限
./mx6ull_alientek_emmc.sh           # 执行;menuconfig 中按两次 Esc 退出后自动编译

5.5 编译输出分析

编译成功后产物分布:

arch/arm/boot/zImage                     # 内核镜像
arch/arm/boot/dts/imx6ull-alientek-emmc.dtb   # 设备树
vmlinux                                  # ELF 内核(根目录)
System.map                               # 符号表(根目录)
drivers/.../*.ko                         # 模块

下载并启动(U-Boot 命令行):

# bootargs 需正确指定根文件系统
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'

tftp 80800000 zImage                      # 内核下载到 DDR 0x80800000
tftp 83000000 imx6ull-alientek-emmc.dtb   # 设备树下载到 0x83000000
bootz 80800000 - 83000000                 # bootz <kernel> <ramdisk> <dtb>

根文件系统缺失时的典型报错:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

这说明内核已经启动成功,只是 VFS 挂不上根文件系统——检查 bootargs 中的 root= 是否正确,或根文件系统分区是否为空。

5.6 常见编译错误排查

错误现象 原因 解决
recipe for target 'arch/arm/boot/compressed/piggy.lzo' failed 未安装 lzop sudo apt-get install lzop
arm-linux-gnueabihf-gcc: command not found 交叉工具链未安装/未加 PATH 安装工具链并配置环境变量
修改配置后配置项“复活” 直接删了 .config 的行 改成 # CONFIG_xxx is not set
make clean 后配置丢失 clean 会删 .config savedefconfig 固化
驱动模块加载失败 / insmod 报版本不符 内核与模块配置不一致 用同一份 .config 重编内核与模块
编译到一半卡死或内存不足 -j 过大 降低并行度,如 -j4

六、设备树编译

6.1 设备树与 dtc

设备树(Device Tree)用 .dts 描述板级硬件,由 dtc(Device Tree Compiler)编译为 .dtb 供内核解析。ARM 平台上内核依赖 .dtb 才能识别外设。

后缀 含义
.dts 板级设备树源码,一个板子一个
.dtsi SoC 级公共定义,被 .dts include
.dtb 编译后的二进制,U-Boot 通过 bootz 传给内核
.dtbo 设备树 overlay(插件)

6.2 设备树 Makefile 配置

新增一个板子的设备树,必须把 .dtb 挂进 arch/arm/boot/dts/Makefile 的对应配置项,否则 make dtbs 不会编译它:

# arch/arm/boot/dts/Makefile 第 400 行起
dtb-$(CONFIG_SOC_IMX6ULL) += \
	imx6ull-14x14-evk.dtb \
	imx6ull-14x14-evk-emmc.dtb \
	imx6ull-alientek-emmc.dtb \        # 新增的正点原子 EMMC 版设备树
	imx6ull-9x9-evk.dtb

新增步骤:

cd arch/arm/boot/dts
cp imx6ull-14x14-evk.dts imx6ull-alientek-emmc.dts   # 以官方 EVK 为模板复制
# 编辑 Makefile,在 dtb-$(CONFIG_SOC_IMX6ULL) 中加入 imx6ull-alientek-emmc.dtb

6.3 设备树源文件结构

/dts-v1/;
#include "imx6ull.dtsi"                       // SoC 级公共定义
#include "imx6ull-14x14-evk.dtsi"

/ {
    model = "Alientek i.MX6ULL ALPHA EMMC";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    chosen {
        bootargs = "console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw";
    };
};

/* 追加/覆盖 usdhc2 节点:EMMC 使用 8 线并关闭 1.8V */
&usdhc2 {
    pinctrl-names = "default", "state_100mhz", "state_200mhz";
    pinctrl-0 = <&pinctrl_usdhc2_8bit>;
    pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
    pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
    bus-width = <8>;
    non-removable;
    no-1-8-v;
    status = "okay";
};

编写规则:.dtsi 定义 SoC 通用部分,.dts&label { ... } 覆盖或追加板级差异;status = "okay"/"disabled" 控制节点开关。

6.4 设备树编译命令

# 随内核一起编译(会同时生成 zImage 与所有配置的 dtb)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all

# 只编译设备树(改 dts 后最常用,速度快)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

# 单独编译指定 dtb
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx6ull-alientek-emmc.dtb

产物在 arch/arm/boot/dts/ 下。若想独立使用 dtc:

# dts → dtb
dtc -I dts -O dtb -o out.dtb imx6ull-alientek-emmc.dts
# dtb → dts(反编译,排查问题时有用)
dtc -I dtb -O dts -o out.dts imx6ull-alientek-emmc.dtb

七、跨平台对比

三款常见嵌入式平台的内核差异,移植时思路相通、细节不同:

对比项 I.MX6ULL(本节) STM32MP157 RK3568
内核架构 ARM Cortex-A7(单核) Cortex-A7 ×2 + M4 Cortex-A55 ×4
内核版本 4.1.15(NXP BSP) 5.4 / 5.10(ST BSP) 4.19 / 5.10(Rockchip BSP)
交叉编译器 arm-linux-gnueabihf- arm-linux-gnueabihf-(A7) aarch64-linux-gnu-(64 位)
默认配置 imx_v7_mfg_defconfig stm32mp15_defconfig rockchip_linux_defconfig
设备树风格 一板一 .dts,依 imx6ull.dtsi stm32mp151.dtsi,多用 overlay rk3568.dtsirk3568-<board>.dts
内核镜像 zImage zImage / uImage Image(64 位无 zImage)
启动方式 bootz + dtb bootz/bootm + dtb booti + dtb(64 位)
内核来源 NXP linux-imx ST linux-stm32mp Rockchip linux-rockchip
网络 PHY 适配 需改 dts + smsc.c/fec_main.c 通用 PHY 驱动基本够用 通常 dts 配置即可
典型场景 工控、HMI、学习入门 工业控制(A7+M4 异构) 边缘计算、AI 网关、多屏

核心差异点:

  • 32 位 vs 64 位:I.MX6ULL / STM32MP1 用 arm-linux-gnueabihf-zImage;RK3568 是 ARMv8,用 aarch64-linux-gnu-Image,启动命令是 booti
  • BSP 成熟度:三家的板级差异都固化在设备树里,因此移植 = 换 defconfig + 换/改设备树 + 调驱动,流程一致。
  • 主频与调频:I.MX6ULL 支持 198/396/528/792MHz 多档调频,验证时用 cpufreq 目录下的 scaling_governorscaling_available_frequencies 观察。

八、面试精选

Q1:vmlinuxImagezImageuImage 有什么区别?生成链是怎样的?

vmlinux 是编译出的原始 ELF 文件,含符号表,约 16MB,不直接用于启动;Image 是用 objcopy 去掉符号表等信息的裸二进制,约 12MB,未压缩;zImage 是 gzip 压缩后的镜像,约 6MB,是我们最常用的;uImage 是老版 U-Boot 专用格式,在 zImage 前加 64 字节头(描述类型、加载地址、时间、大小)。生成链:vmlinux --objcopy--> Image --gzip--> zImage --mkimage--> uImage。镜像目标都先依赖 vmlinux,由 scripts/Makefile.build 逐级转换。

Q2:CONFIG_xxx=y=m 的区别是什么?怎么选?

=y 编进内核(built-in),链接进 vmlinux,启动即生效、常驻内存;=m 编译为独立 .ko 模块,按需 insmod/modprobe 加载。判断标准:内核启动到挂载根文件系统所必需的(控制台串口、存储控制器、根文件系统驱动)用 =y;可选外设、调试驱动、多产品复用外设用 =m。注意 clean 会删 .config,改配置后要 savedefconfig 固化。

Q3:顶层 Makefile 是如何收集所有子目录、最终链接出 vmlinux 的?

顶层用六个变量收集目录:head-yarch/arm/kernel/head.o)、init-ycore-ylibs-ydrivers-ynet-y,它们在 arch/arm/Makefile 中按配置追加,再经 patsubst 转换为各目录的 built-in.o/.ascripts/Makefile.build 在每个目录把 obj-y 列出的 .old -r 合并成 built-in.o。最后 scripts/link-vmlinux.sh 按链接脚本 arch/arm/kernel/vmlinux.ldsKBUILD_VMLINUX_INIT + KBUILD_VMLINUX_MAIN 链接成 vmlinux

Q4:内核裁剪的思路是什么?从哪里入手收益最大?

思路是“先跑通再裁剪,按需保留,模块优先,小步验证”。收益排序:① 关闭 CONFIG_DEBUG_INFO 等调试项(体积影响最大);② 只保留板载驱动,砍掉无关网卡/显示/无线;③ 只保留实际使用的文件系统(EMMC 用 ext4,调试加 NFS);④ 不确定的功能先设 =m 而非删除。裁剪后用 ls -lh arch/arm/boot/zImage 对比体积,并启动验证。

Q5:把一个半导体厂的 BSP 移植到自研板子,完整步骤是什么?网络驱动为什么是重点?

① 在源码中找可参考的官方开发板(如 I.MX6ULL EVK);② 编译出其 zImage.dtb;③ 用这两个文件在自研板上启动;④ 启动成功后,新增自研板 defconfig(复制官方 defconfig,如屏蔽 CONFIG_ARCH_MULTI_V6=y)和设备树(复制 EVK .dts 并加入 dts Makefile);⑤ 修改差异驱动(主频、EMMC、网络);⑥ 构建根文件系统。网络驱动是重点,因为后续驱动开发普遍依赖 NFS/tftp 网络调试,网络不通会很难受。处理器内部 MAC + 外部 PHY 的方案,重点改三处:复位引脚、PHY 地址、PHY 复位时序(如 LAN8720A 的软复位、SR8201F 复位后延时 200ms),并确保 CONFIG_SMSC_PHY 等被编进内核。


参考

  • 40_第三十五章 Linux内核顶层Makefile详解.pdf
  • 42_第三十七章 Linux内核移植.pdf
  • 关联:[[06-系统移植与启动/01-U-Boot命令与使用]] | [[06-系统移植与启动/02-U-Boot启动流程]] | [[06-系统移植与启动/04-根文件系统构建]]