1. 驱动开发环境搭建与内核编译

做嵌入式Linux驱动开发,第一步就是把环境搭好。我见过不少新手,代码写得挺漂亮,结果卡在编译环境上折腾好几天。说白了,环境搭得顺不顺,直接决定了你后面调试的效率。

这一章,我们就从零开始,把交叉编译环境、内核源码、编译流程、设备树、网络调试环境全部搞定。嗯,跟着我的节奏来,一步都不要跳。

1.1 搭建交叉编译环境

为什么需要交叉编译?因为你的开发机(x86架构)和目标板(ARM/RISC-V等)的指令集不一样。你想想看,在PC上编译出来的程序,ARM芯片根本跑不了。

我个人习惯用 Linaro GCC 工具链,稳定且社区支持好。以 ARM Cortex-A7 为例,安装步骤很简单:

# 下载 ARM 交叉编译工具链
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/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
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
小提示: 我建议把环境变量写到 ~/.bashrc 里,然后 source ~/.bashrc。这样每次打开终端就不用重新设了。曾经有个同事每次编译前都要手动 export,后来发现是忘了加进配置文件。

验证是否安装成功:

arm-linux-gnueabihf-gcc --version

如果能看到版本号,说明环境已经就绪。

1.2 获取与配置 Linux 内核源码

内核源码的获取方式有两种:从 kernel.org 下载官方主线版本,或者从芯片厂商(如 NXP、TI、Rockchip)获取 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

配置内核的方式有好几种,我列个表格对比一下:

配置命令 说明 适用场景
make menuconfig 基于 ncurses 的文本菜单界面 最常用,推荐新手
make defconfig 使用默认配置 快速验证编译环境
make xconfig 基于 Qt 的图形界面 需要图形桌面的开发机
make oldconfig 基于现有 .config 更新 升级内核版本时使用

对于嵌入式开发,我习惯先加载芯片厂商提供的 defconfig,再手动微调:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
注意: 配置内核时,一定要确认 ARCH 和 CROSS_COMPILE 变量正确。我曾经因为忘记设 ARCH=arm,结果编译出来的是 x86 的内核,烧到板子上直接起不来。

1.3 内核编译与模块编译

配置完成后,就可以编译了。内核编译分为两部分:内核镜像 和 内核模块。

# 编译内核镜像(zImage)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage

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

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

# 安装模块到指定目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=./modules modules_install

-j4 表示用 4 个线程并行编译,具体数值根据你电脑的 CPU 核心数来定。我第一次编译内核时用了 -j1,等了快半小时才完事。后来改成 -j8,十分钟就搞定了。

编译完成后,你会得到以下关键文件:

  • arch/arm/boot/zImage — 内核镜像
  • arch/arm/boot/dts/xxx.dtb — 设备树二进制文件
  • modules/lib/modules/5.10.100/ — 内核模块目录

1.4 设备树编译与加载

设备树(Device Tree)是嵌入式 Linux 中描述硬件信息的核心机制。说白了,它就是告诉内核:你的板子上有哪些外设、它们挂在哪里、怎么配置。

设备树源文件(.dts)需要编译成二进制文件(.dtb)才能被内核使用:

# 编译单个设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 或者通过内核编译系统
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- myboard.dtb

设备树的加载方式有两种:

  1. U-Boot 加载:U-Boot 从存储介质读取 .dtb 文件,传递给内核。
  2. 内核内置:将设备树直接编译进内核镜像,适合固定硬件平台。

我一般用 U-Boot 加载的方式,因为调试时只需要替换 .dtb 文件,不用重新编译内核。

核心要点: 设备树是驱动开发的基础。如果设备树写错了,驱动加载时要么找不到设备,要么资源冲突。我曾经在调试 I2C 驱动时,因为设备树里中断号写错了一位,花了整整一天才定位到问题。

1.5 NFS 与 TFTP 网络调试环境搭建

嵌入式开发中,最痛苦的就是每次修改代码都要重新烧写整个系统。用 NFS 和 TFTP 可以大幅提升效率。

TFTP(Trivial File Transfer Protocol) 用于快速传输内核镜像和设备树文件。NFS(Network File System) 用于将开发机的文件系统挂载到目标板上,实现文件共享。

1.5.1 搭建 TFTP 服务器

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

# 配置 TFTP 目录(默认 /srv/tftp)
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp

# 将内核和设备树复制到 TFTP 目录
cp arch/arm/boot/zImage /srv/tftp/
cp arch/arm/boot/dts/myboard.dtb /srv/tftp/

在 U-Boot 中,通过以下命令从 TFTP 加载文件:

# 设置网络参数
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10

# 从 TFTP 加载内核
tftp 0x42000000 zImage

# 从 TFTP 加载设备树
tftp 0x43000000 myboard.dtb

# 启动内核
bootz 0x42000000 - 0x43000000

1.5.2 搭建 NFS 服务器

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

# 创建共享目录并解压根文件系统
sudo mkdir -p /srv/nfs/rootfs
sudo tar -xvf rootfs.tar.gz -C /srv/nfs/rootfs/

# 配置 NFS 导出
echo "/srv/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)" | sudo tee -a /etc/exports

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

在 U-Boot 中设置内核启动参数,使用 NFS 根文件系统:

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.10:/srv/nfs/rootfs rw ip=192.168.1.100:192.168.1.10:192.168.1.1:255.255.255.0::eth0:off'
bootz 0x42000000 - 0x43000000
调试技巧: 使用 NFS 后,你在开发机上修改驱动代码,重新编译模块,目标板直接就能用。我曾经调试一个 USB 驱动,一天内编译了 30 多次模块,要是每次都烧写,估计得疯掉。

1.6 本章知识体系总览

下面这张图把整个开发环境搭建的流程串起来了,你可以对照着检查自己有没有遗漏:

嵌入式Linux驱动开发环境搭建流程 嵌入式Linux驱动开发环境搭建流程 开发机(x86 Ubuntu) 交叉编译工具链(Linaro GCC) Linux 内核源码(配置 + 编译) zImage(内核镜像) .dtb(设备树) .ko(内核模块) TFTP 传输 TFTP 传输 NFS 共享 目标板(ARM 开发板) 流程:开发机 → 交叉编译 → 内核编译 → 网络传输 → 目标板运行

这张图把整个流程串起来了。从开发机开始,经过交叉编译工具链,配置编译内核源码,生成三个关键输出,再通过网络传输到目标板。每一步都环环相扣,缺一不可。

总结一下: 环境搭建是驱动开发的基础工程。交叉编译环境决定了你能不能编译,内核配置决定了你能不能跑,设备树决定了硬件能不能认,NFS/TFTP 决定了你调试快不快。把这四步走稳了,后面的驱动开发才能事半功倍。