2. Linux内核开发环境搭建:交叉编译工具链、内核源码获取与配置、QEMU模拟器与开发板环境

嵌入式Linux内核驱动开发与原生PC开发最大的区别在于目标架构不同。开发机(x86_64)与目标板(ARM/AArch64/RISC-V)的指令集不兼容,因此必须搭建一套完整的交叉编译环境,并配合模拟器或物理开发板进行验证。本章将系统讲解从零搭建这套环境的核心步骤与工具链选型。

环境全景图:开发机(Ubuntu 22.04 LTS)→ 交叉编译工具链 → 内核源码配置与编译 → QEMU模拟器运行/开发板烧录。

2.1 交叉编译工具链

交叉编译工具链由编译器(gcc)、汇编器(as)、链接器(ld)、库(glibc/musl)及调试器(gdb)组成,针对目标架构独立构建。常用获取方式有三种:

方式 命令/工具 适用场景 优点 缺点
发行版包管理器 apt install gcc-arm-linux-gnueabihf 快速原型验证 安装简单,系统集成好 版本较旧,目标架构有限
Linaro预编译工具链 从 releases.linaro.org 下载 ARMv7/ARMv8 主流开发 版本新,优化好,支持多库变体 需手动配置 PATH
crosstool-NG 自编译 ct-ng build 定制化/非主流架构 完全可控,可定制内核/库版本 编译耗时,学习曲线陡
Buildroot/Yocto SDK make sdk 产品级项目 与根文件系统、内核版本严格对齐 构建时间长,体积大
推荐选择:课程实验统一使用 Linaro gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf,目标架构 ARM Cortex-A7,硬浮点(hf)ABI,与后续 QEMU 模拟的 vexpress-a9 开发板完全兼容。

安装并验证工具链:

# 下载并解压
wget https://releases.linaro.org/components/toolchain/binaries/10.3-2021.07/arm-linux-gnueabihf/gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf.tar.xz
tar -xf gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf.tar.xz -C /opt/

# 添加至 PATH
export PATH=/opt/gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf/bin:$PATH

# 验证
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-objdump -V
避坑:交叉编译工具链的 sysroot 必须与目标板根文件系统的库版本一致。使用 Linaro 工具链时,默认 sysroot 位于工具链目录内的 arm-linux-gnueabihf/libc,编译内核模块时通常不需要额外指定,但编译用户态驱动测试程序时必须通过 --sysroot 显式指定。

2.2 内核源码获取与配置

Linux内核源码获取首选 kernel.org 的长期支持(LTS)版本。本课程使用 Linux 5.10 LTS,该版本对 ARM 嵌入式平台支持成熟,且 SPI 子系统接口稳定。

# 获取主线源码
git clone --depth 1 --branch v5.10 git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git linux-5.10
cd linux-5.10

# 或者直接下载 tarball
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.200.tar.xz
tar -xf linux-5.10.200.tar.xz

内核配置是环境搭建中最关键的环节。配置方法分为三个层级:

  1. 架构与板级默认配置:make ARCH=arm vexpress_defconfig —— 生成针对 ARM Versatile Express 平台的基线配置。
  2. menuconfig 微调:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig —— 按需开启 SPI 子系统、传感器驱动、设备树支持等。
  3. 碎片配置(.config)保存:make savedefconfig 生成精简 defconfig,便于版本管理。
必须开启的内核选项(SPI 驱动实验用):
CONFIG_SPI=y —— SPI 子系统核心
CONFIG_SPI_PL022=y —— ARM PrimeCell PL022 SPI 控制器驱动(vexpress 板载)
CONFIG_OF=y —— 设备树支持
CONFIG_SPI_SPIDEV=y —— SPI 用户态设备接口(调试用)

编译内核与设备树:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) dtbs
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) modules

编译产物:arch/arm/boot/zImage(内核镜像)、arch/arm/boot/dts/vexpress-v2p-ca9.dtb(设备树二进制)。

内核配置与编译流程 获取源码 配置 (defconfig) menuconfig 微调 编译 zImage 编译 dtbs 编译 modules QEMU / 开发板运行

2.3 QEMU模拟器环境

QEMU 支持全系统模拟(system mode),可在 x86_64 开发机上运行 ARM 内核与完整的根文件系统。本课程使用 vexpress-a9 开发板模型,该模型包含 PL022 SPI 控制器,完美适配 SPI 驱动实验。

安装 QEMU 并验证:

sudo apt install qemu-system-arm
qemu-system-arm --version

准备根文件系统(使用 Buildroot 生成最小 ext4 镜像):

# 使用预构建的最小根文件系统(包含 busybox、libc、SPI 测试工具)
wget https://example.com/rootfs.ext4   # 实际课程提供内部镜像
# 或使用 Buildroot 自行构建:
# git clone https://git.buildroot.net/buildroot
# cd buildroot; make qemu_arm_vexpress_defconfig; make

启动 QEMU 并挂载内核与设备树:

qemu-system-arm \
  -M vexpress-a9 \
  -kernel linux-5.10/arch/arm/boot/zImage \
  -dtb linux-5.10/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
  -drive file=rootfs.ext4,if=sd,format=raw \
  -append "console=ttyAMA0 root=/dev/mmcblk0 rw" \
  -serial stdio \
  -net nic -net user
调试技巧:QEMU 支持 -s(GDB stub 监听 tcp:1234)和 -S(启动时暂停等待调试器连接)。配合 arm-linux-gnueabihf-gdb vmlinux 可进行内核源码级调试,对 SPI 驱动中断处理、DMA 传输等场景的调试极为有效。
QEMU 系统模拟架构(vexpress-a9) 开发机 (x86_64 / Ubuntu) QEMU 系统模拟进程 (qemu-system-arm) Cortex-A9 CPU PL022 SPI 控制器 PL011 UART SD/MMC 控制器 Linux 内核 (zImage) + 根文件系统 (rootfs.ext4)

2.4 开发板环境(物理硬件)

当驱动需要在真实硬件上验证时序、电气特性或中断延迟时,必须使用物理开发板。本课程以 STM32MP157 和 Raspberry Pi 3B+ 作为参考平台,两者均具备 SPI 总线且文档丰富。

特性 STM32MP157 (DK2) Raspberry Pi 3B+ vexpress-a9 (QEMU)
CPU 架构 ARM Cortex-A7 (双核) ARM Cortex-A53 (四核) ARM Cortex-A9 (单核)
SPI 控制器 STM32 SPI (5路) BCM2837 SPI (2路) PL022 (1路)
调试接口 ST-Link + UART GPIO + UART GDB stub
内核加载方式 TFTP / SD 卡 SD 卡 -kernel 参数
适用阶段 产品级验证 原型验证 开发调试

开发板环境搭建要点:

  • 串口终端:使用 screen /dev/ttyUSB0 115200 或 minicom 连接板载 UART,获取内核启动日志。
  • 网络启动(TFTP/NFS):在开发机上配置 TFTP 服务器存放 zImage/dtb,NFS 导出根文件系统,开发板通过 Uboot 加载,实现快速迭代。
  • SPI 物理连接:注意电平匹配(3.3V vs 1.8V)、片选信号极性、SPI 模式(CPOL/CPHA)与传感器数据手册严格对应。
硬件调试警告:SPI 总线在物理板上容易因信号反射、地弹、电平不匹配导致通信失败。建议在 MOSI/MISO/SCLK 上串联 22Ω 电阻,并使用示波器测量时序。切勿在带电状态下插拔 SPI 设备,以免烧毁 GPIO 引脚。

2.5 环境验证:第一个 SPI 驱动模块

搭建完成后,编写一个最简单的 SPI 设备驱动模块,验证整个工具链与运行环境是否正常工作。

// hello_spi.c
#include <linux/module.h>
#include <linux/spi/spi.h>

static int __init hello_spi_init(void)
{
    pr_info("Hello SPI: environment is ready!\n");
    return 0;
}

static void __exit hello_spi_exit(void)
{
    pr_info("Goodbye SPI\n");
}

module_init(hello_spi_init);
module_exit(hello_spi_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SPI environment verification module");

编译与测试:

# 在内核源码树外编译
make -C /path/to/linux-5.10 M=$(pwd) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

# 将 .ko 复制到根文件系统,启动 QEMU 后加载
insmod hello_spi.ko
dmesg | tail -5
# 预期输出: [  xxx] Hello SPI: environment is ready!
验证清单:
✅ 交叉编译工具链可生成 ARM 目标文件(file hello_spi.ko 显示 ARM ELF)
✅ 内核编译无错误,zImage 与 dtb 生成成功
✅ QEMU 启动后内核控制台输出完整,网络/存储设备识别正常
✅ SPI 控制器驱动已加载(cat /sys/bus/spi/devices 显示 spi0.0 等节点)
✅ 内核模块插入与卸载无异常