第三章:Linux内核编译——获取源码、配置与部署

好,咱们进入正题。内核编译这件事,说白了就是给Zynq的ARM核准备一个操作系统内核。很多新手觉得这步很神秘,其实拆开来看,就三步:拿源码、配选项、编译部署。我在Zynq项目上折腾过不下几十次内核编译,踩过的坑能写满一页A4纸。今天我把这些经验都抖出来。

3.1 获取Linux内核源码

源码从哪来?两个主流选择:

  • Xilinx官方维护的版本:在GitHub上搜"xilinx-linux",这是最稳妥的选择。我习惯用这个,因为它对Zynq外设的支持最全。
  • 主线Linux内核:从kernel.org下载。好处是更新快,但你需要自己打Xilinx的补丁。

我个人建议用Xilinx版本。为什么?你想想看,Zynq的DMA、FPGA桥接这些外设,主线内核不一定有现成的驱动。Xilinx版本已经帮你配好了。

获取方式很简单:

git clone https://github.com/Xilinx/linux-xlnx.git
cd linux-xlnx
git checkout xilinx-v2023.1  # 选一个稳定版本

嗯,这里要注意版本号。我遇到过有人直接clone master分支,结果编译到一半报错。选一个带"xilinx-v"前缀的tag,这些是经过测试的。

3.2 配置内核选项

源码拿到了,接下来是配置。这一步最考验经验。配置不对,轻则功能缺失,重则系统起不来。

3.2.1 基础配置方法

Zynq有默认配置模板,省了不少事:

make ARCH=arm xilinx_zynq_defconfig

这条命令会生成一个基础配置。但它只是"能跑"的水平。你要做的是在此基础上裁剪和添加。

我习惯用menuconfig做微调:

make ARCH=arm menuconfig

界面里按"/"可以搜索选项。比如你要找"SPI"驱动,按"/"输入SPI,它会告诉你路径在哪。

我的小技巧:第一次配置时,先执行defconfig,然后make savedefconfig保存一份。之后每次改配置,都跟这份对比,避免误删关键选项。

3.2.2 必须开启的选项

根据我的项目经验,以下选项是Zynq平台的标配:

功能 配置路径 说明
UART Device Drivers → Character devices → Serial drivers → Xilinx UART 控制台输出,不开你连log都看不到
SD/MMC Device Drivers → MMC/SD/SDIO card support → Xilinx SDHCI 从SD卡启动系统必备
FPGA桥接 Device Drivers → FPGA Configuration Framework → Xilinx FPGA manager 动态加载FPGA bitstream
DMA引擎 Device Drivers → DMA Engine support → Xilinx DMA engines 高速数据传输,不开性能掉一半
曾经踩过的坑:我有一次忘了开FPGA manager,结果板子上电后FPGA加载失败,系统卡在uboot。查了两天才发现是内核配置漏了。所以建议你把常用外设的驱动都编进内核(不是模块),省得后面手忙脚乱。

3.2.3 设备树配置

配置完内核选项,别忘了设备树。Zynq的设备树文件在arch/arm/boot/dts/zynq-*.dts。你需要根据你的硬件修改:

// 修改内存大小
memory {
    device_type = "memory";
    reg = <0x0 0x40000000>;  // 1GB内存
};

// 添加自定义外设
&amba {
    my_ip: my_ip@43c00000 {
        compatible = "xlnx,my-ip-1.0";
        reg = <0x43c00000 0x10000>;
        interrupt-parent = <&intc>;
        interrupts = <0 59 4>;
    };
};

设备树这东西,说白了就是告诉内核"你的硬件长什么样"。地址、中断号、寄存器大小,一个都不能错。我习惯对照Vivado的地址映射表来写,这样最保险。

3.3 编译与部署

配置搞定,开始编译。这一步最吃机器性能,也最考验耐心。

3.3.1 编译命令

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make -j4 UIMAGE_LOADADDR=0x8000 uImage
make -j4 modules
make -j4 dtbs

解释一下:

  • -j4:用4个线程并行编译。我一般设成CPU核心数+1。
  • UIMAGE_LOADADDR=0x8000:Zynq的加载地址,固定值别改。
  • uImage:生成带uboot头的内核镜像。
  • modules:编译内核模块,后面要装到文件系统里。
  • dtbs:编译设备树,生成.dtb文件。

第一次编译大概要10-15分钟。如果只改了几个选项,用make -j4就行,它会增量编译,快很多。

常见错误及解决

  • 报"arm-linux-gnueabihf-gcc: not found" → 没装交叉编译器。装一下:sudo apt install gcc-arm-linux-gnueabihf
  • 报"mkimage: command not found" → 缺uboot工具。装:sudo apt install u-boot-tools
  • 报"undefined reference to xxx" → 配置有冲突。试试make clean后重新配置。

3.3.2 部署到Zynq板

编译完会生成三个关键文件:

  • arch/arm/boot/uImage:内核镜像
  • arch/arm/boot/dts/zynq-你的板子.dtb:设备树
  • arch/arm/boot/zImage:另一种格式的内核,有时也用

部署方式取决于你的启动介质:

SD卡启动

# 把SD卡插到电脑,挂载到/media/boot
cp arch/arm/boot/uImage /media/boot/
cp arch/arm/boot/dts/zynq-你的板子.dtb /media/boot/
sync
umount /media/boot

TFTP网络启动(调试时最方便):

# 在开发机上
cp arch/arm/boot/uImage /tftpboot/
cp arch/arm/boot/dts/zynq-你的板子.dtb /tftpboot/
# 在uboot命令行
tftp 0x8000 uImage
tftp 0x2A00000 zynq-你的板子.dtb
bootm 0x8000 - 0x2A00000

我个人调试时喜欢用TFTP。改完内核,编译,tftp加载,重启,全程不到30秒。SD卡要插拔,太慢了。

3.3.3 验证内核是否正常

板子启动后,看串口输出。正常的话你会看到:

Starting kernel ...
Booting Linux on physical CPU 0x0
Linux version 5.15.0-xilinx-v2023.1 ...
CPU: ARMv7 Processor [413fc090] revision 0 (ARMv7), cr=10c5387d
...
Freeing unused kernel memory: 1024K
Run /sbin/init as init process

如果卡在某一步,比如"Waiting for root device",通常是设备树里根文件系统配置不对。检查chosen节点的bootargs

chosen {
    bootargs = "console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootfstype=ext4";
};

我曾经因为root=/dev/mmcblk0p2写成了mmcblk0p1,系统死活起不来。这种低级错误,查起来最费时间。

3.4 总结与建议

内核编译这件事,做多了就熟了。我给你三个建议:

  1. 版本锁定:确定一个内核版本后,不要轻易升级。Zynq的外设驱动跟内核版本绑定很紧,升级可能带来兼容性问题。
  2. 配置备份:每次调通一个配置,把.config文件备份到Git里。我吃过亏,改了一堆配置后忘了保存,重来一遍想死的心都有。
  3. 从简到繁:第一次编译,用defconfig先跑通。确认能启动后,再慢慢加功能。别一上来就开一堆选项,出了问题你都不知道是哪步错了。

嗯,这一章就到这里。下一章我们讲根文件系统的构建,到时候你会看到内核和文件系统是怎么配合的。有问题随时翻翻这一章,编译命令和配置路径我都写得很清楚了。