title: Linux内核裁剪与编译 tags: [Linux内核, 内核编译, Makefile, 内核移植, 设备树, ARM] created: 2026-09-17 updated: 2026-09-17 pdf_ref:
关联知识: [[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 基金会管理与发布,官网 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 更多是新增平台和外设。中途换版本意味着设备树、驱动全部重调,得不偿失。
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_defconfig、imx_v7_mfg_defconfig |
arch/arm/boot/ |
编译产物 | Image、zImage |
arch/arm/boot/dts/ |
设备树源码与产物 | imx6ull-alientek-emmc.dts/.dtb |
arch/arm/mach-xxx/ |
平台初始化 | mach-imx 为 i.MX 系列 |
drivers/ |
驱动源码 | 按类型分类,如 drivers/net、drivers/mmc |
scripts/kconfig/ |
配置工具 | conf 负责生成 .config |
scripts/Makefile.build |
子目录构建入口 | 每个目录的 built-in.o 由它生成 |
| 文件 | 角色 | 是否该改 |
|---|---|---|
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 代码可见的宏)
Kbuild 是 Linux 内核的构建框架,不是单个文件,而是一套约定:
Makefile 负责版本号、交叉编译变量、目标分发(all、zImage、dtbs、modules)。scripts/Kbuild.include 提供 build、if_changed、echo-cmd 等通用函数。scripts/Makefile.build 是每个子目录的真正执行者,读取子目录的 Makefile(或 Kbuild)。子目录用 obj-y(编译进内核)、obj-m(编译成模块)声明源文件。
# drivers/net/phy/Makefile 片段:按配置决定是否编译
obj-$(CONFIG_SMSC_PHY) += smsc.o
当 .config 里 CONFIG_SMSC_PHY=y 时,smsc.c 被编译进内核;=m 时编译为模块 smsc.ko。
# 在源码根目录执行,菜单界面基于 ncurses
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
界面结构:顶部为标题,中间为可折叠菜单树,底部为操作提示。配置项前的符号含义:
| 符号 | 含义 |
|---|---|
[ ] |
布尔项,未选 |
[ * ] |
布尔项,选中(编进内核) |
< > |
三态项,未选 |
< * > |
三态项,=y(编进内核) |
< M > |
三态项,=m(编译成模块) |
(value) |
数值 / 字符串项 |
-*- |
被其他项强制选中,不可改 |
| 按键 | 作用 |
|---|---|
↑ / ↓ |
上下移动 |
← / → |
切换底部按钮(<Select> <Exit> <Help> <Save> <Load>) |
Enter |
进入子菜单,或切换选中状态 |
Y / N / M |
直接设为 =y / 不选 / =m |
空格 |
在三态之间循环 |
/ |
全局搜索配置项关键字(定位驱动最快的方式) |
? |
查看当前项帮助:类型、依赖、config 名 |
Esc Esc |
返回上一级 / 退出主菜单 |
定位一个驱动:按
/输入关键字(如SMSC),结果会显示其CONFIG_*名称和菜单路径,据此逐层进入勾选。
驱动类
| 配置项 | 路径 | 用途 |
|---|---|---|
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) |
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会删除.config;make mrproper/distclean删得更彻底。改完图形配置且验证稳定后,务必先savedefconfig固化,再执行清理。
#
# 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 会用默认值把它重新打开。裁剪的目标是在功能完整的前提下减小 zImage、缩短启动时间、降低内存占用。原则:
defconfig 编译并能正常启动后,再逐项瘦身。=m,需要时 insmod,而不是直接砍掉。按 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。
根文件系统用什么,就留什么文件系统:
# 典型裁剪(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。
这是收益最大的一项:
| 配置项 | 说明 | 量产建议 |
|---|---|---|
CONFIG_DEBUG_INFO |
生成 DWARF 调试信息,体积巨大 | 关闭 |
CONFIG_DEBUG_INFO_REDUCED |
精简调试信息 | 关闭 |
CONFIG_KALLSYMS |
符号解析,panic 时打印函数名 | 权衡,调试期保留 |
CONFIG_FRAME_POINTER |
栈回溯辅助 | 关闭 |
CONFIG_MAGIC_SYSRQ |
SysRq 调试键 | 逐项确认后关闭 |
CONFIG_PROFILING |
性能剖析 | 关闭 |
一般不用
strip去处理 zImage——gzip压缩本身会丢弃不可达信息,真正占体积的是未裁剪的驱动和调试宏,从源头配置才是正道。
=y vs =m| 对比项 | =y(built-in) |
=m(module) |
|---|---|---|
| 编译产物 | 链接进 vmlinux/zImage |
独立 .ko 文件 |
| 加载时机 | 内核启动即生效 | insmod/modprobe 按需加载 |
| 体积影响 | 常驻内存 | 占用文件系统空间,用时才占内存 |
| 启动速度 | 启动慢(初始化所有内置驱动) | 启动快(只加载必要模块) |
| 适用场景 | 启动必需(存储、控制台、根 FS) | 可选外设、调试驱动、多产品复用 |
判断标准:内核启动到挂载根文件系统所必需的 → =y;业务外设、偶尔使用、需要热插拔的 → =m。正点原子移植时 CONFIG_SMSC_PHY=y 是刻意编进内核的,因为网络要在挂载 NFS 根文件系统之前就绪,不能等模块加载。
顶层 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 |
只编译指定外部模块 |
# 顶层 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-
?= 表示命令行已定义时不覆盖,因此两种方式可共存,但命令行优先级更高。
# 顶层 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 使用。
make 的默认目标是 _all,它依赖 all,all 又依赖 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.build 中 builtin-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
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 |
仅编译指定外部模块 |
| 命令 | 清理范围 | 是否删 .config |
|---|---|---|
make clean |
大部分编译产物(.o、built-in.o、镜像) |
会删(4.1.15 行为) |
make mrproper |
所有编译产物 + 配置文件 + 备份 | 会删 |
make distclean |
mrproper + 编辑器备份、补丁文件 |
会删 |
make defconfig |
用架构默认配置重新生成 .config |
覆盖 |
distclean最彻底。正点原子的编译脚本每次开头都执行distclean,所以图形界面配置完一定要先savedefconfig固化,否则脚本一跑,配置全没。
# 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
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# -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 退出后自动编译
编译成功后产物分布:
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= 是否正确,或根文件系统分区是否为空。
| 错误现象 | 原因 | 解决 |
|---|---|---|
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 |
设备树(Device Tree)用 .dts 描述板级硬件,由 dtc(Device Tree Compiler)编译为 .dtb 供内核解析。ARM 平台上内核依赖 .dtb 才能识别外设。
| 后缀 | 含义 |
|---|---|
.dts |
板级设备树源码,一个板子一个 |
.dtsi |
SoC 级公共定义,被 .dts include |
.dtb |
编译后的二进制,U-Boot 通过 bootz 传给内核 |
.dtbo |
设备树 overlay(插件) |
新增一个板子的设备树,必须把 .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
/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" 控制节点开关。
# 随内核一起编译(会同时生成 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.dtsi,rk3568-<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 网关、多屏 |
核心差异点:
arm-linux-gnueabihf- 和 zImage;RK3568 是 ARMv8,用 aarch64-linux-gnu- 和 Image,启动命令是 booti。cpufreq 目录下的 scaling_governor、scaling_available_frequencies 观察。Q1:vmlinux、Image、zImage、uImage 有什么区别?生成链是怎样的?
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-y(arch/arm/kernel/head.o)、init-y、core-y、libs-y、drivers-y、net-y,它们在 arch/arm/Makefile 中按配置追加,再经 patsubst 转换为各目录的 built-in.o/.a。scripts/Makefile.build 在每个目录把 obj-y 列出的 .o 用 ld -r 合并成 built-in.o。最后 scripts/link-vmlinux.sh 按链接脚本 arch/arm/kernel/vmlinux.lds 把 KBUILD_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详解.pdf42_第三十七章 Linux内核移植.pdf