2. 开发环境搭建:交叉编译工具链安装、U-Boot与内核源码获取、TFTP/NFS服务配置、设备树概念入门
说实话,做嵌入式Linux开发,环境搭建这一步最容易被轻视。很多人急着写代码,结果编译都过不了,或者烧进去系统起不来,回头才发现是工具链版本不对、文件系统挂载路径写错了。我刚开始搞Zynq那会儿,就吃过这个亏——折腾了两天,最后发现是NFS权限没配好。
这一章,咱们把基础打牢。我会带着你一步步把环境搭起来,顺便聊聊我踩过的坑。
2.1 交叉编译工具链:为什么需要它?
你的PC是x86架构,Zynq的ARM核是ARMv7-A架构。PC上编译出来的程序,ARM跑不了。反过来,ARM上跑的程序,得用交叉编译工具链在PC上生成。
说白了,就是一套能在x86上生成ARM机器码的工具集。包括编译器、链接器、库文件等等。
2.1.1 获取与安装
我个人习惯用Linaro提供的GCC工具链,稳定且社区支持好。针对Zynq-7000系列,我们选arm-linux-gnueabihf- 这个前缀。
关键点: 工具链版本必须与Linux内核版本匹配。比如内核4.x,建议用gcc 7.x或8.x。版本差太多,编译时会出现奇怪的语法错误。
安装步骤很简单,解压后配置环境变量就行:
# 下载(以gcc-linaro-7.5.0为例)
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
# 解压到 /opt 目录
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 添加环境变量(写入 ~/.bashrc)
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
# 验证
arm-linux-gnueabihf-gcc --version
验证时如果看到版本信息,就说明装好了。我曾经遇到过PATH写错的情况,敲命令时提示“command not found”,检查一下路径里有没有多余的空格。
2.2 U-Boot与Linux内核源码获取
U-Boot是启动引导程序,内核是操作系统核心。这两者的源码,我们直接从官方仓库获取。
2.2.1 获取U-Boot源码
Zynq平台有专门的U-Boot分支,由Xilinx维护。我建议用这个分支,因为它包含了Zynq的板级配置和驱动。
git clone https://github.com/Xilinx/u-boot-xlnx.git
cd u-boot-xlnx
git checkout xilinx-v2020.2
为什么选v2020.2?这个版本对Zynq-7000支持最成熟,我项目里一直用,没出过问题。
2.2.2 获取Linux内核源码
同样,用Xilinx维护的内核分支:
git clone https://github.com/Xilinx/linux-xlnx.git
cd linux-xlnx
git checkout xlnx_rebase_v5.4_2020.2
小技巧: 如果你网络不好,可以先用wget下载压缩包,再解压。但用git的好处是方便切换分支和查看提交历史。
2.3 TFTP与NFS服务配置
开发阶段,我们不会每次都把镜像烧写到SD卡或QSPI Flash里。那样太慢了。TFTP用来通过网络加载内核和设备树,NFS用来挂载根文件系统。这样改完代码,重新编译,重启开发板就能看到效果。
2.3.1 TFTP服务器配置
TFTP(Trivial File Transfer Protocol)很简单,就是用来传文件的。我们在Ubuntu主机上装一个tftp-hpa:
sudo apt-get install tftp-hpa tftpd-hpa
sudo mkdir -p /tftpboot
sudo chmod 777 /tftpboot
配置文件在 /etc/default/tftpd-hpa,确保内容如下:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
重启服务:
sudo systemctl restart tftpd-hpa
把编译好的内核镜像 uImage 或 zImage 放到 /tftpboot 目录下,开发板就能通过U-Boot的 tftp 命令加载了。
注意: 防火墙可能会拦截TFTP的69端口。如果开发板连不上,先检查 sudo ufw status,必要时关掉防火墙或放行端口。
2.3.2 NFS服务器配置
NFS(Network File System)让开发板把主机的某个目录挂载为自己的根文件系统。这样文件系统在主机上修改,开发板立即可见。
sudo apt-get install nfs-kernel-server
sudo mkdir -p /srv/nfs/rootfs
sudo chmod 777 /srv/nfs/rootfs
编辑 /etc/exports,添加:
/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
重启NFS服务:
sudo exportfs -a
sudo systemctl restart nfs-kernel-server
把制作好的根文件系统(比如Buildroot或Yocto生成的)解压到 /srv/nfs/rootfs 里。开发板启动时,U-Boot会传参告诉内核从NFS挂载根文件系统。
2.4 设备树概念入门
设备树(Device Tree)是描述硬件信息的文件。以前Linux内核里硬编码了很多板级信息,换一块板子就得重新编译内核。有了设备树,硬件描述和内核代码分开了。你想想看,这多灵活。
设备树文件有两种后缀:
- .dts:设备树源文件,人类可读的文本格式。
- .dtb:设备树二进制文件,由dts编译生成,内核直接使用。
编译命令:
dtc -I dts -O dtb -o zynq-zed.dtb zynq-zed.dts
2.4.1 设备树的基本结构
一个简单的设备树片段长这样:
/dts-v1/;
#include "zynq-7000.dtsi"
/ {
model = "Zynq ZedBoard";
compatible = "xlnx,zynq-7000";
chosen {
bootargs = "console=ttyPS0,115200 root=/dev/nfs rw nfsroot=192.168.1.100:/srv/nfs/rootfs ip=192.168.1.10";
};
memory@0 {
device_type = "memory";
reg = <0x0 0x40000000>; // 1GB RAM
};
&uart1 {
status = "okay";
};
};
这里 chosen 节点传递了启动参数,memory 节点告诉内核内存大小和起始地址,&uart1 引用了芯片级文件里定义的UART外设并启用它。
核心概念: 设备树就是硬件的“身份证”。内核启动时读取它,才知道有哪些外设、怎么访问它们。写驱动时,我们经常通过设备树匹配设备,而不是硬编码地址。
2.4.2 设备树在Zynq启动流程中的位置
下面这张图展示了从U-Boot加载设备树到内核使用的完整流程:
流程很简单:U-Boot启动后,从SD卡或TFTP服务器读取 .dtb 文件,放到内存里,然后把地址传给内核。内核启动时解析设备树,初始化对应的外设驱动。
2.5 本章小结
这一章我们搭好了开发环境:交叉编译工具链、U-Boot和内核源码、TFTP/NFS服务,还了解了设备树的基本概念。这些是后续所有实验的基础。我建议你花点时间把每一步都验证通过,别急着往下走。环境稳了,后面写驱动才顺手。