1. 嵌入式Linux驱动开发环境搭建
做嵌入式Linux驱动开发,第一件事就是把环境搭好。这活儿看着琐碎,但一步错步步错。我见过太多新手卡在交叉编译工具链上,折腾一整天连个hello world都跑不起来。说白了,环境搭建就是给后续所有工作打地基,地基不稳,房子早晚塌。
本章核心目标:在宿主机(通常是Ubuntu)上搭建一套完整的嵌入式Linux驱动开发环境,包括交叉编译工具链、NFS/TFTP服务、内核源码树。最终能编译一个内核模块并下载到目标板运行。
1.1 交叉编译工具链配置
交叉编译,说白了就是在x86的PC上编译出ARM架构能跑的二进制文件。为什么不能直接在ARM板上编译?你想想看,ARM板那点CPU和内存,编译个内核得等到猴年马月。所以咱们得用PC的算力干ARM的活。
1.1.1 工具链的选择
我个人习惯用Linaro提供的GCC工具链,稳定且社区活跃。当然你也可以用芯片原厂提供的,比如NXP的、TI的,但Linaro的通用性更好。
| 工具链类型 | 适用平台 | 下载地址示例 |
|---|---|---|
| arm-linux-gnueabihf- | ARM Cortex-A系列(32位) | gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf |
| aarch64-linux-gnu- | ARM Cortex-A系列(64位) | gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu |
| arm-none-eabi- | 裸机/MCU开发 | gcc-arm-none-eabi-10.3-2021.10 |
注意:驱动开发必须用 arm-linux-gnueabihf- 这类带Linux前缀的工具链,不能用 arm-none-eabi-。后者没有Linux系统调用支持,编译出来的模块没法加载。
1.1.2 安装与配置
步骤其实很简单,解压、加PATH、验证。但我建议你把它装到 /opt 下,别放 /usr/local,方便多版本共存。
# 1. 解压工具链
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 2. 添加环境变量(写入 ~/.bashrc)
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 3. 使生效
source ~/.bashrc
# 4. 验证
arm-linux-gnueabihf-gcc --version
我曾经遇到过一个问题:明明PATH加对了,但编译时总提示找不到编译器。后来发现是64位系统没装32位库——Linaro工具链有些版本依赖32位库。解决办法很简单:
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386
1.2 NFS与TFTP服务搭建
驱动开发有个痛点:每次编译完都要把文件拷到SD卡或者U盘,再插到板子上,太慢了。NFS和TFTP就是解决这个问题的。NFS让板子直接挂载PC上的目录,TFTP让板子通过网络下载文件。
1.2.1 NFS服务配置
NFS用于挂载根文件系统或共享驱动模块。我习惯把开发目录直接共享出来,这样在PC上改完代码,板子上立即可用。
# 安装NFS服务器
sudo apt-get install nfs-kernel-server
# 创建共享目录
sudo mkdir -p /srv/nfs/rootfs
sudo chown nobody:nogroup /srv/nfs/rootfs
# 配置导出文件 /etc/exports
/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
# 重启服务
sudo systemctl restart nfs-kernel-server
# 验证
showmount -e localhost
小技巧:no_root_squash 这个选项很重要。不加的话,板子上root用户对共享目录没有写权限,驱动模块insmod时会报权限错误。我刚开始就栽在这上面过。
1.2.2 TFTP服务配置
TFTP主要用于uboot阶段下载内核和设备树。它比NFS轻量,但功能也简单——只能下载,不能上传。
# 安装TFTP服务器
sudo apt-get 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
把编译好的内核镜像 zImage 和设备树 .dtb 文件丢到 /srv/tftp 下,uboot里用 tftp 0x82000000 zImage 就能下载了。
1.3 内核源码树构建
驱动开发离不开内核源码树。你写的驱动模块需要依赖内核头文件和Makefile体系。这里有个关键点:内核源码树必须和你板子上跑的内核版本一致,否则模块加载会报 version magic 错误。
1.3.1 获取内核源码
我建议直接从芯片厂商的官方仓库拉取,比如NXP的linux-imx、TI的linux-omap。如果只是学习,用主线内核也行。
# 以NXP i.MX6ULL为例
git clone https://github.com/nxp-imx/linux-imx.git -b imx_4.1.15_2.0.0_ga
# 或者下载主线内核
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
tar -xvf linux-5.10.100.tar.xz
1.3.2 配置与编译内核
拿到源码后,先配置再编译。配置有两种方式:一是用厂商提供的默认配置,二是自己手动裁剪。
# 进入源码目录
cd linux-imx
# 使用默认配置
make imx_v7_defconfig
# 或者手动配置
make menuconfig
# 编译内核(-j4 表示4核并行编译)
make -j4 zImage
make -j4 dtbs
# 编译内核模块
make -j4 modules
# 安装内核模块到NFS目录
make modules_install INSTALL_MOD_PATH=/srv/nfs/rootfs
避坑指南:编译内核前一定要确认 ARCH 和 CROSS_COMPILE 环境变量已经设置好。我曾经忘了设 ARCH=arm,结果编译出来的是x86的内核,放到板子上直接启动不了。排查了半天才发现是架构不对。
1.3.3 编译第一个驱动模块
环境搭好了,咱们写个最简单的hello模块验证一下。
// hello.c
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, Embedded Linux!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, Embedded Linux!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
# Makefile
obj-m := hello.o
KDIR := /home/user/linux-imx
CROSS_COMPILE := arm-linux-gnueabihf-
ARCH := arm
all:
make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean
编译后得到 hello.ko,把它放到NFS共享目录,板子上执行:
# 在目标板上
mount -t nfs -o nolock 192.168.1.100:/srv/nfs/rootfs /mnt
insmod /mnt/hello.ko
dmesg | tail -5
看到 Hello, Embedded Linux! 就说明环境搭建成功了。
1.4 环境搭建知识体系
下面这张图把整个环境搭建的脉络梳理清楚了。你照着这个流程走,基本不会迷路。
我的经验:环境搭建这事儿,别想着一次搞定就万事大吉。实际开发中你会频繁切换内核版本、换工具链版本。我建议把不同版本的配置写成脚本,比如 setup-env-imx6ull.sh、setup-env-imx8mp.sh,切换时source一下就行。省得每次重新敲环境变量。
好了,到这步你的开发环境就齐活了。交叉编译工具链能编译ARM代码,NFS和TFTP让文件传输不再折腾,内核源码树也构建完成。接下来就可以正式开始驱动开发了——不过那是后面章节的事,咱们先把地基打牢。