1. Linux 驱动开发环境搭建:交叉编译工具链安装、内核源码树配置、NFS与TFTP服务搭建

嵌入式Linux驱动开发与原生Linux开发最大的区别在于交叉编译。目标板(ARM、RISC-V等)资源有限,无法运行完整的编译工具链,因此需要在性能强劲的宿主机(Host)上编译驱动模块,再部署到目标板(Target)上运行。本章将系统讲解开发环境的三大核心支柱:交叉编译工具链、内核源码树、以及网络文件系统(NFS)与TFTP服务。

环境概览: 本课程以 Ubuntu 22.04 LTS 为宿主机,目标板为 ARM Cortex-A7(基于 STM32MP157 平台),内核版本 Linux 5.10。所有操作均在此组合下验证,其他平台可类比调整。

1.1 交叉编译工具链安装

交叉编译工具链是一套运行在宿主机上、但生成目标板机器码的编译器、链接器、调试器及库文件的集合。安装方式主要有三种:

安装方式 适用场景 优点 缺点
发行版包管理器 快速验证、标准平台 命令简单,依赖自动解决 版本可能较旧,非标准架构支持有限
芯片厂商SDK 商业项目、特定芯片 针对性优化,包含板级支持包 体积大,安装步骤复杂
手动构建crosstool-NG 定制化需求、学习研究 高度可定制,理解工具链内部 编译耗时,配置繁琐

本课程采用发行版包管理器方式,快速搭建ARM交叉编译环境:

# 安装ARM交叉编译工具链(gcc、g++、binutils等)
sudo apt update
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

# 验证安装
arm-linux-gnueabihf-gcc --version
# 输出示例:arm-linux-gnueabihf-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
关键路径: 工具链安装后,可执行文件位于 /usr/bin/arm-linux-gnueabihf-*。驱动开发中常用 arm-linux-gnueabihf-objdump 反汇编模块,arm-linux-gnueabihf-objcopy 转换格式。

交叉编译工具链的命名规则为 arch-vendor-os-abi。以 arm-linux-gnueabihf 为例:

  • arm:目标架构
  • linux:操作系统
  • gnueabihf:使用GNU C库,支持硬件浮点(Hard Float)

下面通过一张SVG图展示交叉编译与本地编译的流程对比:

交叉编译 vs 本地编译流程对比 本地编译(Native) x86 GCC → x86 可执行文件 在 x86 宿主机上直接运行 交叉编译(Cross) x86 GCC → ARM 可执行文件 传输到 ARM 目标板上运行 交叉编译:宿主机架构 ≠ 目标架构,必须指定 --host 和 --target 参数

1.2 内核源码树配置

驱动模块本质上是内核的一部分,编译时必须依赖内核源码树提供的头文件、Makefile规则和配置信息。因此,必须准备一份与目标板运行内核版本完全一致的内核源码,并完成配置和编译。

1.2.1 获取与解压内核源码

# 从内核官网或芯片厂商获取源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.160.tar.xz
tar -xf linux-5.10.160.tar.xz
cd linux-5.10.160

1.2.2 配置内核

配置内核有三种方式,推荐使用基于厂商默认配置的增量配置:

配置命令 说明 使用场景
make ARCH=arm defconfig 使用架构默认配置 通用平台,快速开始
make ARCH=arm <board>_defconfig 使用厂商提供的板级配置 特定开发板,最常用
make ARCH=arm menuconfig 图形化菜单配置 手动调整内核功能开关
# 以STM32MP157为例,使用厂商默认配置
make ARCH=arm stm32mp1_defconfig

# 可选:进一步定制内核功能
make ARCH=arm menuconfig
常见错误: 编译驱动模块时出现 Cannot use CONFIG_CC_STACKPROTECTOR_STRONG 或 No rule to make target 'modules_prepare',通常是因为内核源码未完成 make modules_prepare 步骤。务必执行以下命令:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_prepare

1.2.3 编译内核与模块

# 编译内核镜像(耗时较长,可使用 -jN 并行编译)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage

# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

# 编译内核模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

编译完成后,内核源码树根目录下会生成 vmlinux(未压缩内核)、arch/arm/boot/zImage(压缩内核镜像)以及 arch/arm/boot/dts/*.dtb(设备树二进制文件)。

下面通过SVG图展示内核源码树配置与驱动编译的关系:

内核源码树配置与驱动编译关系 内核源码树 Kconfig Makefile 头文件 配置过程 make menuconfig 生成 .config 驱动模块 模块源码 .c 模块 Makefile 输出:.ko 内核模块文件 驱动模块编译依赖内核源码树的 .config、头文件和模块编译规则

1.3 NFS与TFTP服务搭建

驱动开发需要频繁将编译好的内核镜像、设备树、驱动模块传输到目标板并测试。NFS和TFTP是两种最常用的网络传输方式:

  • TFTP(Trivial File Transfer Protocol):UDP协议,用于引导阶段加载内核镜像和设备树。Bootloader(如U-Boot)内置TFTP客户端。
  • NFS(Network File System):TCP/IP协议,用于内核启动后挂载根文件系统,实现宿主机与目标板文件共享,方便模块热加载与调试。

1.3.1 搭建TFTP服务

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

# 配置TFTP服务
sudo vim /etc/default/tftpd-hpa
# 修改为以下内容:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"          # TFTP根目录
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"

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

# 重启服务
sudo systemctl restart tftpd-hpa
sudo systemctl enable tftpd-hpa

# 测试TFTP服务(本机回环测试)
echo "test" > /srv/tftp/test.txt
tftp localhost -c get test.txt
U-Boot中使用TFTP: 在目标板U-Boot命令行中执行:
setenv serverip 192.168.1.100   # 宿主机IP
setenv ipaddr 192.168.1.10     # 目标板IP
tftp 0xc2000000 zImage         # 加载内核到内存
tftp 0xc4000000 stm32mp157.dtb # 加载设备树
bootz 0xc2000000 - 0xc4000000  # 启动内核

1.3.2 搭建NFS服务

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

# 创建NFS共享目录(通常作为目标板的根文件系统)
sudo mkdir -p /srv/nfs/rootfs

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

# 应用配置
sudo exportfs -a

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

# 验证NFS挂载(本机测试)
sudo mount -t nfs localhost:/srv/nfs/rootfs /mnt
ls /mnt
sudo umount /mnt
权限避坑: no_root_squash 参数允许目标板以root身份访问共享目录,驱动模块加载和调试需要root权限。如果省略此参数,目标板上的root用户会被映射为nobody,导致insmod失败。

1.3.3 内核启动参数配置

目标板通过内核命令行参数指定NFS根文件系统。在U-Boot中设置:

setenv bootargs 'console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,v3,tcp ip=192.168.1.10:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'
saveenv
boot

参数说明:

参数 含义 示例值
root=/dev/nfs 使用NFS作为根文件系统 固定写法
nfsroot NFS服务器IP、共享路径、协议版本 192.168.1.100:/srv/nfs/rootfs,v3,tcp
ip 目标板IP:服务器IP:网关:子网掩码::网卡:自动配置 192.168.1.10:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off

下面通过SVG图展示NFS与TFTP在驱动开发流程中的协作关系:

NFS与TFTP在驱动开发中的协作流程 宿主机(Ubuntu) TFTP服务器 NFS服务器 以太网 目标板(ARM) U-Boot Linux内核 阶段1:U-Boot通过TFTP加载内核镜像和设备树 阶段2:内核启动后通过NFS挂载根文件系统,加载驱动模块 驱动模块热加载:insmod /mnt/drivers/my_driver.ko 修改驱动源码 → 宿主机编译 → 目标板NFS挂载目录直接更新 → 无需重启

1.4 环境验证清单

完成上述搭建后,建议逐项验证环境可用性:

验证项 验证命令 预期结果
交叉编译器 arm-linux-gnueabihf-gcc --version 显示版本号,无错误
内核配置 ls .config 文件存在,非空
内核编译 ls arch/arm/boot/zImage 文件存在
TFTP服务 tftp localhost -c get test.txt 成功下载文件
NFS服务 showmount -e localhost 显示共享目录列表
开发效率建议: 将以下环境变量写入 ~/.bashrc,避免每次编译重复输入:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export KERNEL_DIR=/home/user/linux-5.10.160
然后执行 source ~/.bashrc 生效。