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的启动流程是这样的:

  1. 上电后,BootROM加载u-boot.elf到OCM(片上内存)
  2. U-Boot初始化DDR,从SD卡或QSPI Flash读取uImage和devicetree.dtb
  3. 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 本章知识体系

下面这张图,把整个编译流程串起来了。你可以把它当成一个检查清单,每一步做完,打个勾。

Zynq Linux内核编译与启动文件生成流程 Zynq Linux内核编译与启动文件生成流程 1. 内核配置 make menuconfig 2. 内核编译 make uImage 3. 设备树编译 make dtbs 产出物 uImage Linux内核镜像 devicetree.dtb 设备树二进制 BOOT.BIN FSBL + U-Boot SD卡 / QSPI Flash 启动介质 启动流程:BootROM → FSBL → U-Boot → 加载内核 + 设备树 → 启动Linux

这张图从左到右展示了本章的三个核心步骤,以及最终产出物怎么组合成启动介质。你可以把它打印出来贴在工位上,每次编译时对照着走一遍,不容易漏步骤。