4. 交叉编译与开发环境搭建

内核移植,说白了就是让Linux内核跑在你手上的那块ARM板子上。很多人一上来就急着改代码,结果连内核怎么配置、怎么裁剪都没搞清楚。我刚开始做移植时也犯过这毛病,拿着个默认配置就编译,烧进去发现串口没输出,折腾了两天才发现是内核里根本没开对应UART驱动。

这一章,咱们把基础打牢。从获取源码开始,到配置、裁剪、编译,最后生成zImage或Image。每一步我都会把项目里踩过的坑指给你看。

本章核心目标:掌握Linux内核源码获取、menuconfig配置、ARM架构裁剪、编译生成zImage/Image的完整流程。

4.1 获取Linux内核源码

获取源码的方式有好几种,我按推荐顺序列出来:

方式 命令/地址 适用场景
kernel.org 官方主线 https://www.kernel.org/ 学习、新项目起步
git clone 主线仓库 git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 需要跟踪最新提交
SoC厂商提供BSP包 如NXP、TI、Rockchip官网 量产项目、有硬件支持
LTS长期支持版 kernel.org 上标记LTS的版本 产品稳定期、维护项目

我个人习惯用git clone主线仓库。为什么?因为方便切换分支、打补丁、回退版本。你想想看,如果从官网下载tar包,想换个版本还得重新下载,多麻烦。

小技巧:国内网络访问kernel.org慢的话,可以用清华、中科大的镜像源。比如:git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux.git

4.2 配置内核——make menuconfig

拿到源码后,第一步不是直接编译,而是配置。配置内核就像搭积木,你需要决定哪些功能要、哪些不要。

配置前,先看看你的ARM平台有没有现成的默认配置:

# 查看所有ARM相关默认配置
ls arch/arm/configs/

# 以vexpress开发板为例
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig

# 然后打开menuconfig做微调
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

menuconfig界面里,选项有三种状态:

  • [*] —— 编译进内核(built-in)
  • [M] —— 编译成模块(module)
  • [ ] —— 不编译

嗯,这里要注意:驱动移植阶段,我建议先把关键驱动编进内核,别用模块。为什么?因为模块需要文件系统加载,如果文件系统还没起来,驱动就加载不了,板子直接哑火。我在项目里吃过这个亏,那次串口驱动配成了模块,结果内核启动后根本没法交互,最后只能重新烧固件。

避坑指南:我曾经在配置网络驱动时,把PHY芯片驱动漏了,结果网卡能识别但死活ping不通。排查了两天才发现是PHY驱动没选上。所以配置完一定要检查依赖关系,用make ARCH=arm menuconfig里的"Show dependencies"功能。

4.3 ARM架构内核裁剪策略

裁剪不是乱删,得有策略。我总结了一套"三步裁剪法":

  1. 平台无关裁剪:关掉不需要的文件系统(如NTFS、XFS)、网络协议(如IPX、DECnet)、虚拟化功能
  2. ARM架构裁剪:只保留目标CPU型号,关掉其他ARM核的支持
  3. 板级裁剪:只保留板子上实际用到的外设驱动

下面这张图展示了裁剪的层次关系:

Linux内核裁剪层次结构 完整Linux内核源码 第一层:平台无关裁剪 第二层:ARM架构裁剪 第三层:板级外设裁剪 裁剪后的最小化内核 裁剪目标 减少镜像体积 加快启动速度 裁剪原则 最小必要功能 保留调试接口

具体到ARM架构,重点裁剪这几个地方:

配置路径 裁剪内容 节省空间
System Type → ARM system type 只保留目标CPU,如Cortex-A7 ~2MB
Kernel Features 关掉SMP(单核)、关掉PREEMPT(非实时) ~500KB
Device Drivers 只保留I2C、SPI、GPIO、UART等实际外设 ~5MB
File systems 只保留ext4、tmpfs、proc、sysfs ~3MB

经验之谈:裁剪后记得用make ARCH=arm savedefconfig生成最小配置文件。下次编译直接加载这个文件,省得每次都要重新选。我每个项目都会保留一份defconfig,方便团队复用。

4.4 编译生成zImage/Image

配置搞定后,编译就简单了。但有几个参数必须搞清楚:

# 编译生成Image(未压缩)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 Image

# 编译生成zImage(压缩后的镜像)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage

# 编译生成uImage(带uboot头的镜像)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 uImage

这三种镜像有什么区别?我整理了个对比表:

镜像类型 压缩方式 典型大小 启动方式
Image 未压缩 ~15MB 直接加载到内存运行
zImage gzip压缩 ~5MB 自解压,适合uboot引导
uImage zImage + uboot头 ~5.1MB uboot专用,带校验头

实际项目中,我大部分时候用zImage。为什么?因为uboot对zImage支持最好,而且压缩后体积小,烧录快。但要注意一点:如果你的板子内存很小(比如64MB以下),zImage自解压时会占用额外内存,这时候用Image更稳妥。

注意:编译时-j后面的数字别太大。我见过有人用-j32编译,结果内存爆了直接OOM。一般-j4或-j8就够了,除非你服务器配置特别高。

4.5 编译后的验证与常见问题

编译完成后,先别急着烧录。做两个快速验证:

  1. 检查镜像大小:ls -lh arch/arm/boot/zImage,如果比预期大很多,说明裁剪没到位
  2. 查看内核版本:strings arch/arm/boot/zImage | grep "Linux version",确认版本号正确

我遇到过最坑的问题,是编译出来的zImage烧进去后,uboot报"Wrong Image Format"。排查了半天,发现是交叉编译工具链版本不对。ARMv7的核用了ARMv5的工具链,指令集不兼容。

检查工具链:

# 查看工具链支持的架构
arm-linux-gnueabihf-gcc -v 2>&1 | grep --color=auto "Target"

# 查看内核实际使用的指令集
head -5 arch/arm/Makefile | grep "ARCH_TEXT"

另一个常见问题是配置了设备树但没编译进去。记得检查:

# 确认设备树是否被编译
ls -lh arch/arm/boot/dts/你的板子.dtb

# 如果没生成,检查配置
make ARCH=arm menuconfig
# 进入 Device Drivers → Device Tree and Open Firmware support
# 确保 CONFIG_OF 和 CONFIG_OF_FLATTREE 是 [*]

嗯,到这里,内核的获取、配置、裁剪、编译流程就走通了。你手头如果有ARM开发板,现在就可以动手试试。从make menuconfig开始,一步步裁剪,看看最终生成的zImage能小到什么程度。我做过一个极限裁剪实验,把Cortex-A7的内核裁到了2.3MB,启动时间从8秒降到了1.2秒——当然,那是把能关的全关了,只留了串口和GPIO。