1. 嵌入式Linux开发环境搭建

嵌入式Linux开发,说白了就是“交叉编译”。你想想看,我们的开发电脑是x86架构,而目标板通常是ARM、RISC-V或者MIPS。在x86上编译出ARM能跑的程序,这就是交叉编译的核心。

我个人习惯先把整个环境拆成三块:交叉编译工具链、网络服务(TFTP/NFS)、开发板启动流程。这三块搞定了,后面26章的路就好走了。

核心要点: 开发环境搭建不是一次性的。我建议你每换一个项目,都重新验证一遍这三块是否联通。我在项目中遇到过好几次,板子变砖了才发现TFTP目录配错了路径。

1.1 交叉编译工具链安装

工具链的选择,其实就两种:官方预编译版和自己用crosstool-ng编译。对于大多数项目,我建议直接用官方版,省时间。

工具链来源 适用场景 安装方式
Linaro GCC ARM 32位/64位通用开发 下载tar.xz,解压到/opt
ARM官方gcc-arm-none-eabi 裸机或RTOS开发 apt install / 手动解压
Buildroot / Yocto SDK 完整系统构建后配套 跟随构建系统生成
crosstool-ng自编译 特殊架构或定制glibc版本 ct-ng build

安装步骤其实很简单。以Linaro GCC 7.5为例:

# 下载工具链
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/

# 添加环境变量
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH

# 验证安装
arm-linux-gnueabihf-gcc --version

我的习惯: 把工具链路径写到 ~/.bashrc 或 /etc/profile.d/ 里,这样每次开终端就不用重新export了。另外,我建议用软链接管理多版本工具链,比如 /opt/arm-gcc -> /opt/gcc-linaro-7.5.0/。

1.2 TFTP/NFS服务器配置

这两个服务是嵌入式开发的“生命线”。TFTP用来下载内核和设备树,NFS用来挂载根文件系统。没有它们,你每次调试都得烧写Flash,效率太低了。

1.2.1 TFTP服务器

TFTP说白了就是一个极简的文件传输协议,没有认证,没有目录浏览,就是“你给我文件名,我给你文件”。

# 安装TFTP服务器(Ubuntu/Debian)
sudo apt install tftpd-hpa

# 配置文件 /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"        # 这就是你的文件存放目录
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"

# 创建目录并设置权限
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp

# 重启服务
sudo systemctl restart tftpd-hpa

我曾经踩过的坑: TFTP默认端口69需要root权限。如果你用非root用户启动服务,记得检查防火墙。还有,TFTP文件大小限制通常为32MB,内核太大的话会传输失败。解决办法是用 --blocksize 1468 选项加大块大小。

1.2.2 NFS服务器

NFS比TFTP复杂一点,但好处是开发板的根文件系统可以直接跑在主机上。改代码、编译、测试,一气呵成。

# 安装NFS服务器
sudo apt install nfs-kernel-server

# 编辑 /etc/exports,添加共享目录
/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)

# 创建根文件系统目录
sudo mkdir -p /srv/nfs/rootfs
# 把编译好的根文件系统解压到这里

# 重启NFS服务
sudo systemctl restart nfs-kernel-server

# 验证挂载(在开发板u-boot或内核命令行中)
# root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,vers=3

关键参数说明:

  • no_root_squash:允许开发板以root身份访问文件,调试时必备
  • vers=3:使用NFSv3,兼容性最好。NFSv4在某些老内核上会有问题
  • rw:读写权限,方便在板子上直接修改脚本

1.3 开发板启动流程

搞嵌入式Linux,你必须理解板子从上电到进入shell的完整过程。我见过太多人只会烧录,出了问题就抓瞎。

启动流程其实就四步:

  1. BootROM:芯片内部固件,初始化时钟、DDR,从启动介质加载SPL
  2. SPL (Secondary Program Loader):初始化DDR和存储控制器,加载u-boot
  3. u-boot:完整的bootloader,加载内核和设备树到内存,传递启动参数
  4. Kernel:解压、初始化硬件、挂载根文件系统、启动init进程
嵌入式Linux启动流程 BootROM 初始化时钟/DDR SPL 加载u-boot u-boot 加载内核/DTB Kernel 挂载根文件系统 各阶段关键动作: • BootROM:固化在芯片内部,不可修改。从SD卡/eMMC/NAND/SPI Flash读取SPL • SPL:通常只有几十KB,初始化DDR和存储控制器,加载完整的u-boot • u-boot:提供命令行交互,设置bootargs,通过TFTP/NFS/MMC加载内核 • Kernel:解压后初始化各子系统,最后执行/sbin/init

1.3.1 u-boot常用命令

u-boot是调试阶段打交道最多的组件。我建议你记住这几个命令:

命令 作用 示例
printenv 打印所有环境变量 查看bootargs、serverip
setenv 设置环境变量 setenv bootargs console=ttyS0,115200
tftp 通过TFTP下载文件 tftp 0x42000000 zImage
bootm 启动内核 bootm 0x42000000 - 0x43000000
nfs 通过NFS挂载根文件系统 nfs 0x42000000 serverip:/path

1.3.2 典型启动参数示例

# 从TFTP下载内核,NFS挂载根文件系统
setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,vers=3 rw ip=192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'

# 从MMC启动
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw'

# 保存环境变量
saveenv

# 启动
tftp 0x42000000 zImage
tftp 0x43000000 imx6ull-14x14-evk.dtb
bootm 0x42000000 - 0x43000000

我曾经踩过的坑: 第一次用NFS启动时,板子一直卡在“VFS: Unable to mount root fs”。查了半天,原来是NFS服务器上的根文件系统权限没设置对。记得检查 /etc/exports 里的 no_root_squash 选项,还有根文件系统里 /sbin/init 是否存在且可执行。

1.4 环境验证清单

搭建完环境后,我建议你按这个清单逐项验证:

  1. 工具链验证: 编译一个hello world,用 file 命令确认是ARM架构
  2. TFTP验证: 在开发板u-boot中执行 tftp 0x42000000 test.txt,确认能下载
  3. NFS验证: 在开发板u-boot中执行 nfs 0x42000000 serverip:/srv/nfs/rootfs/test.txt
  4. 完整启动验证: 用TFTP下载内核和DTB,用NFS挂载根文件系统,直到进入shell

我的建议: 第一次启动成功后,把u-boot的环境变量用 printenv 保存下来。以后换板子或者重刷固件,直接照着配就行,省得每次都要查文档。

嗯,环境搭建这部分就到这里。记住一句话:环境搭得好,调试没烦恼。后面我们就要开始真正的内核配置和设备树实战了。