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 环境搭建知识体系

下面这张图把整个环境搭建的脉络梳理清楚了。你照着这个流程走,基本不会迷路。

嵌入式Linux驱动开发环境搭建知识体系 宿主机(Ubuntu x86_64) 交叉编译工具链 NFS / TFTP 服务 内核源码树 工具链选择 Linaro / 芯片原厂 ARCH / CROSS_COMPILE 32位库依赖 网络服务配置 NFS: 挂载根文件系统 TFTP: uboot下载内核 no_root_squash / --create 内核编译 defconfig / menuconfig zImage + dtbs modules_install 目标板(ARM Cortex-A) 验证:insmod hello.ko → dmesg 关键原则 内核版本必须一致 ARCH/CROSS_COMPILE

我的经验:环境搭建这事儿,别想着一次搞定就万事大吉。实际开发中你会频繁切换内核版本、换工具链版本。我建议把不同版本的配置写成脚本,比如 setup-env-imx6ull.sh、setup-env-imx8mp.sh,切换时source一下就行。省得每次重新敲环境变量。

好了,到这步你的开发环境就齐活了。交叉编译工具链能编译ARM代码,NFS和TFTP让文件传输不再折腾,内核源码树也构建完成。接下来就可以正式开始驱动开发了——不过那是后面章节的事,咱们先把地基打牢。