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
设备树的加载方式有两种:
- U-Boot 加载:U-Boot 从存储介质读取
.dtb文件,传递给内核。 - 内核内置:将设备树直接编译进内核镜像,适合固定硬件平台。
我一般用 U-Boot 加载的方式,因为调试时只需要替换 .dtb 文件,不用重新编译内核。
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
1.6 本章知识体系总览
下面这张图把整个开发环境搭建的流程串起来了,你可以对照着检查自己有没有遗漏:
这张图把整个流程串起来了。从开发机开始,经过交叉编译工具链,配置编译内核源码,生成三个关键输出,再通过网络传输到目标板。每一步都环环相扣,缺一不可。