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

做驱动开发,第一件事不是写代码,而是搭环境。我见过太多人上来就撸代码,结果编译报错半天发现是工具链没配好——白白浪费时间。这一章,咱们把交叉编译工具链、内核源码树、NFS/TFTP网络启动这三块地基打牢。

1.1 交叉编译工具链配置

说白了,交叉编译就是在PC上编译出能在ARM板子上跑的程序。你想想看,你的开发机是x86架构,目标板是ARM架构,不用交叉编译,难道把PC扛到板子旁边去编译吗?

1.1.1 工具链的选择

我个人习惯用Linaro提供的GCC工具链,稳定且社区活跃。当然你也可以用芯片厂商提供的,比如NXP的、TI的。这里我列个对比:

工具链来源 适用平台 优点 缺点
Linaro GCC ARM Cortex-A系列 通用性强,更新快 对特定芯片优化不够
芯片厂商SDK 特定SoC 硬件优化好,BSP配套全 版本老旧,绑定厂商
Buildroot/Yocto 自定义系统 全套工具链+根文件系统 构建时间长,学习曲线陡

1.1.2 安装与配置步骤

以Linaro 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

# 解压到指定目录
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
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
小技巧: 把环境变量写到 ~/.bashrc 里,每次打开终端自动生效。我习惯再加一行 alias armcc='arm-linux-gnueabihf-gcc',少敲几个字符。

1.1.3 验证工具链

配好了得验证一下,别等到编译内核时报错才回头查:

arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc -v 2>&1 | grep "Target:"

输出应该看到 arm-linux-gnueabihf 字样。我曾经遇到一次,明明配了路径,但 --version 显示的却是x86的gcc——原来是系统自带的gcc优先级更高,PATH顺序搞反了。

注意: 不要用root用户安装工具链。普通用户装到 /opt 或 /usr/local 都行,但记得给目录加写权限。我吃过亏,sudo装完结果普通用户编译不了。

1.2 内核源码树构建

内核源码树,说白了就是Linux内核的完整代码目录结构。驱动编译需要它提供的头文件和Makefile规则。没有内核源码树,你连个hello world驱动都编译不出来。

1.2.1 获取内核源码

我建议直接从kernel.org下载主线内核,或者用芯片厂商提供的BSP内核。这里以Linux 5.10 LTS为例:

# 下载主线内核
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz

# 解压
tar -xvf linux-5.10.100.tar.xz
cd linux-5.10.100

1.2.2 配置内核

内核配置有三种方式,我列个表给你看:

配置方式 命令 适用场景
默认配置 make defconfig 快速开始,通用平台
芯片厂商配置 make <vendor>_defconfig 特定开发板,如 make imx_v7_defconfig
菜单配置 make menuconfig 手动裁剪内核模块

我个人习惯先用厂商的defconfig,再用menuconfig微调。嗯,这里要注意:menuconfig依赖ncurses库,没装的话先 sudo apt install libncurses-dev。

1.2.3 编译内核与模块

# 设置环境变量(如果还没设)
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 编译内核
make -j4 zImage

# 编译设备树
make -j4 dtbs

# 编译内核模块
make -j4 modules

-j4 表示用4个线程并行编译。我机器是8核的,经常用 -j$(nproc) 自动检测核心数。第一次编译内核大概要10-15分钟,后面增量编译就快多了。

关键输出文件:
  • arch/arm/boot/zImage — 内核镜像
  • arch/arm/boot/dts/<你的板子>.dtb — 设备树二进制文件
  • drivers/.../*.ko — 内核模块(.ko文件)

1.3 NFS/TFTP网络启动环境搭建

做驱动开发,最烦的就是每次修改都要烧写固件。用网络启动,板子从服务器加载内核和根文件系统,改完代码直接重启就行——省掉烧写的功夫,效率翻倍。

1.3.1 TFTP服务器配置

TFTP用来传输内核镜像和设备树文件。配置很简单:

# 安装TFTP服务器
sudo apt install tftpd-hpa

# 配置TFTP目录
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp

# 复制内核和设备树到TFTP目录
cp arch/arm/boot/zImage /srv/tftp/
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /srv/tftp/

配置文件在 /etc/default/tftpd-hpa,我一般改成这样:

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"

改完重启服务:sudo systemctl restart tftpd-hpa。

防火墙坑: TFTP用UDP 69端口。我有一回怎么都传不了文件,查了半天发现是ufw没放行。记得 sudo ufw allow 69/udp。

1.3.2 NFS服务器配置

NFS用来共享根文件系统。板子启动后,直接挂载PC上的目录作为根目录:

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

# 创建根文件系统目录(假设你已经用busybox或buildroot做好了)
sudo mkdir -p /srv/nfs/rootfs

# 配置导出
sudo vim /etc/exports
# 添加一行:
/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)

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

no_root_squash 这个选项很重要——它允许板子上的root用户拥有PC上root的权限。没有这个,你在板子上创建文件会报权限错误。

1.3.3 U-Boot启动参数设置

板子上电后,在U-Boot命令行设置启动参数。以i.MX6ULL为例:

# 设置IP地址(根据你的网络环境调整)
setenv serverip 192.168.1.100   # PC的IP
setenv ipaddr 192.168.1.101     # 板子的IP

# 设置TFTP加载内核和设备树
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-evk.dtb; bootz 80800000 - 83000000'

# 设置NFS根文件系统
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,vers=3 rw ip=192.168.1.101'

# 保存并启动
saveenv
boot

我第一次配这个的时候,忘了设 vers=3,结果NFS挂载失败。U-Boot默认用NFSv2,但现代服务器都跑v3/v4,得显式指定版本。

1.4 整体流程梳理

说了这么多,我画张图帮你理清整个环境搭建的脉络:

嵌入式Linux驱动开发环境搭建流程 嵌入式Linux驱动开发环境搭建流程 开发机(x86 PC / Ubuntu) 交叉编译工具链 arm-linux-gnueabihf-gcc 内核源码树 Linux 5.10 + 设备树 网络启动服务 TFTP + NFS 编译产物 zImage + .dtb + 根文件系统 目标板(ARM / U-Boot) 开发机编译 → TFTP传输内核/设备树 → NFS挂载根文件系统 → 板子网络启动

1.5 避坑指南

最后,我把这些年踩过的坑总结一下:

  • 工具链版本不匹配: 我曾经用7.5的工具链编译5.4的内核,结果某些内联汇编语法不兼容。后来我养成了习惯——查内核源码的 Documentation/process/changes.rst,看看推荐哪个gcc版本。
  • 内核配置遗漏: 网络启动需要开启 CONFIG_ROOT_NFS 和 CONFIG_IP_PNP。我第一次忘了配,板子启动到挂载根文件系统时直接卡死。
  • TFTP文件权限: TFTP目录下的文件必须是644权限,且所有者是tftp用户。我有一回 chmod 777 反而导致传输失败——因为TFTP出于安全考虑,不允许写全局可写的文件。
  • NFS版本协商: U-Boot的NFS客户端比较老,跟服务器协商版本时可能失败。我建议在 /etc/exports 里加上 vers=3,同时在U-Boot的bootargs里也指定 nfsvers=3。
调试小技巧: 如果网络启动失败,先在板子上 ping 一下PC的IP。ping不通就查网线、IP配置;ping通了再查TFTP和NFS。分层排查,别一头扎进内核代码里。

环境搭好了,后面写驱动、配设备树才有得玩。嗯,这套流程我每换一次开发板都要走一遍,熟练了也就十分钟的事。你照着做一遍,后面会省很多事。