3. 内核编译与设备树编译:Linux内核配置(menuconfig)、内核编译流程、设备树编译(dtc)、生成boot文件
好,到了第三章,咱们要开始动真格的了。前面两章我们把环境搭好、把源码拉下来,现在要做的,就是把Linux内核真正编译出来,生成能在Zynq上跑起来的boot文件。
这一章的内容,说白了就是三板斧:配置内核 → 编译内核 → 编译设备树。这三步走完,你手里就有了uImage、devicetree.dtb和u-boot.elf,Zynq的启动流程就靠它们了。
核心产出物清单:
uImage— 内核镜像,U-Boot能识别的格式devicetree.dtb— 编译后的设备树二进制文件u-boot.elf— 第一级bootloader(这里假设你已经单独编译好)
3.1 内核配置:menuconfig 的正确打开方式
我个人习惯,拿到一个新内核源码,第一件事不是直接make,而是先看看它默认的配置。Zynq平台有自己的一套默认配置,在arch/arm/configs/目录下,叫xilinx_zynq_defconfig。
# 先加载默认配置
make ARCH=arm xilinx_zynq_defconfig
# 然后打开图形化配置界面
make ARCH=arm menuconfig
这里有个坑,我刚开始做的时候踩过——一定要指定ARCH=arm。因为你的主机可能是x86_64,不指定的话,make会默认用本机架构,编译出来的东西根本跑不到ARM上。
menuconfig界面怎么用?说白了就是上下键移动、空格键选中、回车键进入子菜单。我建议你重点关注这几个地方:
| 配置项路径 | 说明 | 我的建议 |
|---|---|---|
System Type → Xilinx Zynq |
选择Zynq平台,勾选你的具体型号 | 必选,否则内核不认识你的硬件 |
Device Drivers → SPI/GPIO/I2C |
外设驱动支持 | 按需勾选,别贪多 |
File systems → EXT4 |
根文件系统类型 | 如果你用SD卡启动,这个必须开 |
Boot options → Kernel command line |
内核启动参数 | 这里要写对,不然起不来 |
小技巧:配置完成后,把.config文件备份一份。我每次改完配置都会cp .config config_my_zynq,万一改坏了还能回滚。
3.2 内核编译流程:从源码到uImage
配置搞定,接下来就是编译。Zynq平台用的是ARM架构,所以我们要交叉编译。命令其实不复杂:
# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- uImage
# 编译内核模块(如果你需要)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
# 安装模块到目标目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/path/to/rootfs modules_install
嗯,这里要注意几个点:
CROSS_COMPILE后面的横线不能丢,它表示前缀,make会自动补全后面的命令名。- 第一次编译会很慢,我建议用
-jN参数并行编译,N是你的CPU核心数+1。比如4核机器用make -j5。 - uImage和zImage的区别:uImage是U-Boot专用的镜像格式,它在zImage前面加了一个64字节的U-Boot头部。Zynq用U-Boot启动,所以必须编uImage。
我曾经踩过的坑:编译uImage时,make报错说mkimage命令找不到。这是因为mkimage是U-Boot的工具,不在系统PATH里。解决办法:export PATH=$PATH:/path/to/u-boot/tools,或者直接sudo apt install u-boot-tools。
编译完成后,你会在arch/arm/boot/目录下看到uImage文件。拿file命令看一眼:
$ file arch/arm/boot/uImage
uImage: u-boot legacy uImage, Linux, Linux/ARM, OS Kernel Image ...
看到这个输出,说明内核编译成功了。
3.3 设备树编译:dtc 工具的使用
设备树,说白了就是描述硬件信息的配置文件。Zynq的硬件信息——比如DDR地址、UART中断号、GPIO控制器——都写在.dts文件里。我们需要把它编译成.dtb二进制文件,内核启动时才能读懂。
设备树源文件在arch/arm/boot/dts/目录下。Zynq相关的文件命名规则是zynq-*.dts,比如zynq-zc702.dts、zynq-zed.dts。
# 编译单个设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq-zed.dtb
# 或者编译所有Zynq设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
编译出来的.dtb文件就在同一个目录下。你也可以用dtc工具手动编译:
# 手动编译设备树
dtc -I dts -O dtb -o zynq-zed.dtb zynq-zed.dts
# 反编译,把dtb转回dts(调试用)
dtc -I dtb -O dts -o zynq-zed.dts zynq-zed.dtb
个人经验:调试设备树时,我经常用反编译功能。有时候内核启动卡住了,我会把.dtb反编译成.dts,看看是不是地址写错了、中断号对不上。这比直接看二进制快多了。
3.4 生成boot文件:把碎片拼成启动链
好,现在我们有三个文件了:
uImage— 内核镜像zynq-zed.dtb— 设备树u-boot.elf— U-Boot(这个需要单独编译,不在本章范围)
Zynq的启动流程是这样的:
- 上电后,BootROM加载
u-boot.elf到OCM(片上内存) - U-Boot初始化DDR,从SD卡或QSPI Flash读取
uImage和devicetree.dtb - U-Boot把内核和设备树加载到DDR,跳转到内核入口
所以,你需要把这三个文件放到启动介质上。如果是SD卡启动,文件结构一般是:
SD卡根目录/
├── BOOT.BIN # 包含u-boot.elf和FSBL(第一级启动加载器)
├── uImage # 内核镜像
└── devicetree.dtb # 设备树
这里BOOT.BIN是用Xilinx的bootgen工具生成的,它把u-boot.elf和FSBL打包在一起。具体命令:
bootgen -image boot.bif -o BOOT.BIN -w
其中boot.bif是一个描述文件,告诉bootgen哪些文件要打包进去。内容大概长这样:
the_ROM_image:
{
[bootloader] ./fsbl.elf
./u-boot.elf
}
总结一下本章的产出物:
uImage— 编译好的Linux内核zynq-zed.dtb— 编译好的设备树BOOT.BIN— 包含FSBL和U-Boot的启动文件
这三个文件放到SD卡,插到Zynq开发板上,上电,你就能看到U-Boot的启动信息了。
3.5 本章知识体系
下面这张图,把整个编译流程串起来了。你可以把它当成一个检查清单,每一步做完,打个勾。
这张图从左到右展示了本章的三个核心步骤,以及最终产出物怎么组合成启动介质。你可以把它打印出来贴在工位上,每次编译时对照着走一遍,不容易漏步骤。