2. 开发环境搭建:交叉编译工具链配置、内核源码树构建、QEMU模拟器与树莓派真实硬件环境搭建

嵌入式Linux驱动开发与普通应用程序开发最大的区别在于“交叉编译”与“目标硬件”的分离。本章将系统搭建三套可用的开发环境:纯QEMU模拟环境(用于快速验证)、树莓派真实硬件环境(用于最终部署)以及交叉编译工具链(连接宿主机与目标机的桥梁)。完成本章后,你将拥有一个可编译、可加载、可调试的完整驱动开发实验平台。

环境概览:宿主机使用 Ubuntu 22.04 LTS x86_64,目标机为 ARMv7(树莓派2/3)或 ARMv8(树莓派4)。所有工具链与内核源码均从官方源获取,保证可复现。

2.1 交叉编译工具链配置

交叉编译工具链是驱动开发的核心工具。它运行在x86_64宿主机上,但生成的二进制代码只能在ARM目标机上执行。我们选择Linaro提供的GCC工具链,它针对ARM Cortex-A系列处理器做了优化,且与主线内核兼容性最好。

2.1.1 工具链版本选择与下载

目标平台 工具链前缀 推荐版本 下载源
树莓派2/3 (ARMv7) arm-linux-gnueabihf- gcc-linaro-7.5.0-2019.12 Linaro官方Release
树莓派4 (ARMv8 32位兼容) aarch64-linux-gnu- gcc-arm-10.3-2021.07 ARM官方GNU-A工具链
QEMU模拟 (ARMv7) arm-linux-gnueabihf- 同上 同上
关键路径配置:下载后解压到 /opt/toolchains/ 目录,并将 bin/ 子目录加入 $PATH。建议在 ~/.bashrc 中永久添加:
export PATH=$PATH:/opt/toolchains/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

2.1.2 验证工具链可用性

配置完成后,执行以下命令验证工具链是否正常工作:

$ arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (Linaro GCC 7.5-2019.12) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.

$ arm-linux-gnueabihf-objdump --version
GNU objdump (GNU Binutils) 2.31.1

编写一个简单的C程序测试交叉编译:

// test.c
#include <stdio.h>
int main(void) {
    printf("Hello from ARM!\n");
    return 0;
}

// 交叉编译
$ arm-linux-gnueabihf-gcc -static -o test_arm test.c
$ file test_arm
test_arm: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped
常见错误:如果 file 命令显示 ELF 64-bit LSB x86-64,说明你使用了宿主机的gcc而非交叉编译器。请检查 $PATH 中交叉编译器路径是否在系统gcc之前。

2.2 内核源码树构建

驱动模块必须与目标内核版本完全匹配才能加载。我们采用“独立内核源码树”方式:下载与目标机相同版本的内核源码,配置并编译出内核模块编译所需的头文件与配置文件,但不编译完整内核镜像(QEMU环境除外)。

2.2.1 获取内核源码

从 kernel.org 获取主线内核,或从树莓派官方GitHub获取带树莓派补丁的内核:

# 主线内核(用于QEMU)
$ wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.76.tar.xz
$ tar -xf linux-6.1.76.tar.xz
$ cd linux-6.1.76

# 树莓派内核(用于真实硬件)
$ git clone --depth=1 --branch rpi-6.1.y https://github.com/raspberrypi/linux.git
$ cd linux

2.2.2 内核配置与编译

内核配置是构建过程中最容易出错的环节。我们使用 defconfig 生成基础配置,然后手动启用驱动开发所需的选项:

# 对于QEMU ARM环境
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 必须启用的选项(在menuconfig中搜索并设置)
# CONFIG_MODULES=y          —— 模块支持
# CONFIG_MODULE_UNLOAD=y    —— 模块卸载
# CONFIG_DEVTMPFS=y         —— 设备节点自动管理
# CONFIG_DEBUG_FS=y         —— 调试文件系统

# 编译内核与设备树(QEMU需要完整内核镜像)
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage -j$(nproc)
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
加速技巧:如果只需要编译模块,不需要完整内核镜像,可以只执行 make modules_prepare。这会生成 Module.symvers 和必要的头文件,耗时仅为完整编译的1/10。

2.2.3 内核源码树结构验证

编译完成后,确认以下关键文件存在:

$ ls -l arch/arm/boot/zImage
-rwxr-xr-x 1 user user 4.5M Mar 15 10:30 arch/arm/boot/zImage

$ ls -l arch/arm/boot/dts/vexpress-v2p-ca9.dtb
-rw-r--r-- 1 user user 12K Mar 15 10:30 arch/arm/boot/dts/vexpress-v2p-ca9.dtb

$ ls -l include/generated/autoconf.h
-rw-r--r-- 1 user user 58K Mar 15 10:30 include/generated/autoconf.h

下图展示了内核源码树中与驱动开发相关的核心目录结构:

Linux内核源码树核心目录结构(驱动开发视角) linux-6.1.76/ drivers/ include/ arch/ scripts/ char/ gpio/ i2c/ spi/ linux/ uapi/linux/ arm/ arm64/ 📌 驱动开发常用路径 • drivers/char/ —— 字符设备驱动(本项目主要工作目录) • include/linux/ —— 内核API头文件(ioctl、mutex、wait_queue等) • arch/arm/boot/dts/ —— 设备树源文件(硬件描述) • scripts/ —— 模块编译辅助脚本(如 checkpatch.pl 代码风格检查)

2.3 QEMU模拟器环境搭建

QEMU模拟器可以在没有真实硬件的情况下运行ARM Linux内核,是驱动开发初期调试和验证的利器。我们使用QEMU的“versatile express”开发板模拟(Cortex-A9 MPCore),它支持SMP、GIC中断控制器和足够的外设。

2.3.1 安装QEMU与依赖

$ sudo apt-get update
$ sudo apt-get install qemu-system-arm qemu-utils libncurses5-dev \
    bison flex libssl-dev bc

2.3.2 制作根文件系统

使用Buildroot制作最小根文件系统,包含busybox和必要的动态库:

# 下载Buildroot
$ wget https://buildroot.org/downloads/buildroot-2023.11.tar.gz
$ tar -xf buildroot-2023.11.tar.gz
$ cd buildroot-2023.11

# 配置为ARM vexpress目标
$ make qemu_arm_vexpress_defconfig
$ make -j$(nproc)

# 生成的根文件系统位于 output/images/rootfs.ext2
快速替代方案:如果不想编译Buildroot,可以直接下载预构建的ARM根文件系统镜像:
$ wget https://people.linaro.org/~alex.bennee/images/vexpress64-openembedded_minimal-armv8-20160311-447.img.xz
$ unxz vexpress64-openembedded_minimal-armv8-20160311-447.img.xz
注意此镜像为ARMv8,需使用 qemu-system-aarch64。

2.3.3 启动QEMU并验证

使用以下命令启动QEMU,挂载内核镜像、设备树和根文件系统:

$ qemu-system-arm \
    -M vexpress-a9 \
    -kernel linux-6.1.76/arch/arm/boot/zImage \
    -dtb linux-6.1.76/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
    -drive file=buildroot-2023.11/output/images/rootfs.ext2,if=sd,format=raw \
    -append "console=ttyAMA0,115200 root=/dev/mmcblk0 rw" \
    -serial stdio \
    -net nic -net user,hostfwd=tcp::2222-:22

启动成功后,QEMU终端会显示Linux启动日志,最终出现登录提示符(Buildroot默认用户root,无密码)。

验证驱动加载能力:在QEMU中执行以下命令,确认内核模块子系统正常工作:
# lsmod
Module                  Size  Used by    Not tainted
# uname -r
6.1.76
# cat /proc/devices | head -5
Character devices:
  1 mem
  2 pty
  3 ttyp

2.4 树莓派真实硬件环境搭建

树莓派环境用于最终部署和真实硬件验证。与QEMU不同,树莓派需要处理SD卡启动、UART串口调试和GPIO外设交互。

2.4.1 烧录系统与配置

使用Raspberry Pi Imager或手动烧录官方Raspberry Pi OS Lite(无桌面版,节省资源):

# 查看SD卡设备(通常是 /dev/sdX 或 /dev/mmcblkX)
$ lsblk

# 烧录系统镜像
$ sudo dd if=2024-03-15-raspios-bookworm-armhf-lite.img of=/dev/sdX bs=4M status=progress
$ sync

烧录完成后,在SD卡的boot分区创建 ssh 空文件以启用SSH,并编辑 config.txt 启用UART串口:

# 在 config.txt 末尾添加
enable_uart=1
dtoverlay=disable-bt  # 禁用蓝牙,释放UART0给GPIO 14/15

2.4.2 串口调试连接

使用USB转TTL串口线连接树莓派的GPIO引脚:

树莓派引脚 功能 串口线颜色(常见)
GPIO 14 (Pin 8) TXD (发送) 绿色
GPIO 15 (Pin 10) RXD (接收) 白色
GND (Pin 6) 地线 黑色

宿主机使用minicom或screen连接:

$ sudo apt-get install minicom
$ sudo minicom -D /dev/ttyUSB0 -b 115200

2.4.3 编译树莓派内核模块

在宿主机上使用交叉编译器编译驱动模块,然后通过SCP传输到树莓派:

# 在宿主机内核源码目录中编译模块
$ cd linux-rpi-6.1.y
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

# 假设驱动源码在 drivers/char/ 下
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- M=drivers/char/

# 传输到树莓派
$ scp drivers/char/my_driver.ko pi@192.168.1.100:/home/pi/
版本匹配检查:在树莓派上执行 uname -r 确认内核版本,然后检查模块的 vermagic 是否一致:
# 树莓派上
$ uname -r
6.1.21-v7+

# 宿主机上检查模块
$ modinfo my_driver.ko | grep vermagic
vermagic: 6.1.21-v7+ SMP mod_unload modversions ARMv7
如果版本不匹配,模块加载时会报 Invalid module format 错误。

2.5 环境验证清单

完成所有搭建后,使用以下清单逐项确认环境就绪:

开发环境验证流程 ① 工具链验证 ② 内核编译验证 ③ QEMU启动验证 ④ 树莓派启动验证 ⑤ 模块加载验证 ✅ 全部通过后,即可进入第3章:字符设备驱动框架与ioctl接口实现

2.5.1 逐项验证命令

  1. 工具链:arm-linux-gnueabihf-gcc -v 显示版本信息,无“command not found”错误。
  2. 内核编译:ls -l arch/arm/boot/zImage 文件存在且大小大于2MB。
  3. QEMU启动:执行QEMU命令后,终端输出内核启动日志,最终出现 buildroot login: 提示。
  4. 树莓派启动:串口终端显示树莓派启动日志,登录后执行 uname -a 确认内核版本。
  5. 模块加载:编写一个最简单的“hello world”模块,交叉编译后分别在QEMU和树莓派上执行 insmod 和 rmmod,无错误输出。
Hello World模块示例(用于最终验证):
// hello.c
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void) {
    printk(KERN_INFO "Hello, Linux Driver World!\n");
    return 0;
}

static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye, Linux Driver World!\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

// Makefile
obj-m := hello.o
KDIR := /path/to/your/kernel/tree
all:
    make -C $(KDIR) M=$(PWD) modules
clean:
    make -C $(KDIR) M=$(PWD) clean

2.6 本章小结

本章完成了驱动开发环境的全链路搭建,包括:

  • 交叉编译工具链:Linaro GCC 7.5,支持ARMv7硬浮点,配置了 $ARCH 和 $CROSS_COMPILE 环境变量。
  • 内核源码树:主线6.1.76(QEMU)和树莓派6.1.y分支,均已完成配置和编译。
  • QEMU模拟器:vexpress-a9开发板模拟,Buildroot根文件系统,支持网络转发和串口交互。
  • 树莓派真实硬件:系统烧录、UART串口调试、内核模块交叉编译与部署流程。