Linux驱动与用户态通信实战:ioctl与mmap - 第2节

2.1 环境概览与工具链

搭建一套可用的 Linux 内核驱动开发与调试环境,是进入内核编程的第一步。本章将引导你完成从源码获取、内核编译、模块构建到 QEMU 虚拟机调试的全流程配置。整个环境基于 Ubuntu 22.04 LTS 主机,目标内核版本为 Linux 6.1 LTS。

环境组件清单
组件 版本/工具 用途
主机操作系统 Ubuntu 22.04 LTS (x86_64) 开发与编译平台
内核源码 Linux 6.1.y (LTS) 驱动编译与符号依赖
交叉编译链 gcc-arm-linux-gnueabihf ARM 目标架构编译
模拟器 QEMU 7.2+ 目标硬件仿真
根文件系统 Buildroot / Busybox 虚拟机磁盘镜像
调试器 GDB + gdb-multiarch 内核与模块调试
提示: 如果你使用 x86_64 主机直接调试 x86_64 内核,可以省略交叉编译链,使用本地 gcc 即可。本课程以 ARM 为目标架构,更贴近嵌入式场景。

2.2 获取 Linux 内核源码

推荐从 kernel.org 获取长期支持(LTS)版本,或使用 Git 克隆主线仓库。

2.2.1 方式一:下载压缩包

# 下载 Linux 6.1 LTS 源码
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

2.2.2 方式二:Git 克隆(推荐)

# 克隆稳定版仓库(浅克隆,节省空间)
git clone --depth=1 --branch v6.1.76 \
  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git \
  linux-6.1.76

cd linux-6.1.76
注意: 内核源码目录不要放在共享目录或 NTFS 文件系统下,否则编译时可能遇到权限或符号链接问题。建议放在 ext4 分区。

2.3 编译内核与设备树

编译内核前需要配置。我们使用 vexpress 开发板默认配置,该配置在 QEMU 中支持良好。

2.3.1 配置内核

# 加载 vexpress 默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
  vexpress_defconfig

# 可选:使用 menuconfig 进行微调
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

2.3.2 编译内核与设备树

# 编译内核(使用 -jN 加速,N 为 CPU 核心数)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

编译完成后,关键产出物如下:

  • zImage:压缩后的内核镜像,位于 arch/arm/boot/zImage
  • vexpress-v2p-ca9.dtb:设备树二进制文件,位于 arch/arm/boot/dts/vexpress-v2p-ca9.dtb
内核编译流程示意
内核编译流程 源码获取 配置 (defconfig) 编译内核 编译设备树 zImage + dtb 产出物:arch/arm/boot/zImage + arch/arm/boot/dts/vexpress-v2p-ca9.dtb

2.4 编译内核模块

驱动开发中,大部分模块以 可加载内核模块(LKM) 形式存在。编译模块需要内核源码树已编译过,且依赖 Module.symvers 文件。

2.4.1 编译内核模块

# 编译所有模块(如果配置中启用了模块)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

# 安装模块到临时目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
  INSTALL_MOD_PATH=./modules_install modules_install

2.4.2 编写并编译自定义模块

在课程后续章节中,你将编写自己的驱动模块。一个简单的 Makefile 模板如下:

# Makefile —— 用于编译外部内核模块
obj-m := hello_driver.o

KDIR := /path/to/linux-6.1.76
CROSS := arm-linux-gnueabihf-

all:
    make ARCH=arm CROSS_COMPILE=$(CROSS) -C $(KDIR) M=$(PWD) modules

clean:
    make ARCH=arm CROSS_COMPILE=$(CROSS) -C $(KDIR) M=$(PWD) clean
关键点: KDIR 必须指向已编译过的内核源码目录,否则缺少 Module.symvers 和 include/generated/ 等文件。

2.5 QEMU 虚拟机调试环境

QEMU 可以模拟 ARM vexpress-a9 开发板,配合 GDB 实现源码级调试。这是内核驱动开发中最常用的调试手段。

2.5.1 安装 QEMU

sudo apt install qemu-system-arm qemu-efi-arm

2.5.2 制作根文件系统

使用 Buildroot 快速生成 ext4 格式的根文件系统镜像:

# 下载 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 -j4

# 产出镜像:output/images/rootfs.ext4

2.5.3 启动 QEMU 虚拟机

qemu-system-arm \
  -M vexpress-a9 \
  -kernel /path/to/zImage \
  -dtb /path/to/vexpress-v2p-ca9.dtb \
  -drive file=/path/to/rootfs.ext4,if=sd,format=raw \
  -append "console=ttyAMA0 root=/dev/mmcblk0 rw" \
  -nographic \
  -net nic -net user,hostfwd=tcp::2222-:22

启动后,你将看到内核启动日志,最终进入 shell 提示符。

2.5.4 使用 GDB 调试内核

在 QEMU 中启用 GDB 服务器,主机侧连接调试:

# QEMU 端(添加 -s 参数,默认监听 :1234)
qemu-system-arm ... -s -S

# 主机端 GDB
gdb-multiarch vmlinux
(gdb) target remote :1234
(gdb) break start_kernel
(gdb) continue
注意: vmlinux 是未压缩的内核 ELF 文件,位于内核源码根目录,包含完整的符号表。调试模块时,需要使用 add-symbol-file 命令加载模块符号。

2.6 环境验证清单

完成上述步骤后,建议执行以下验证,确保环境可用:

  1. 内核编译验证: 检查 zImage 和 dtb 文件是否存在。
  2. 模块编译验证: 编写一个空的 hello_driver.c,执行 make 后生成 .ko 文件。
  3. QEMU 启动验证: 虚拟机正常进入 shell,uname -a 显示内核版本为 6.1.76。
  4. GDB 连接验证: 在 start_kernel 处断点命中,可以单步执行。
调试环境架构图
QEMU + GDB 调试架构 主机 (Ubuntu) GDB (gdb-multiarch) vmlinux + .ko 符号 TCP :1234 QEMU 虚拟机 ARM vexpress-a9 zImage + dtb GDB stub :1234 目标内核 驱动模块 (LKM) ioctl / mmap 接口 GDB 通过 TCP 连接 QEMU 内置的 GDB stub,实现内核源码级调试

2.7 常见问题与解决

问题 原因 解决方案
编译内核时提示 arm-linux-gnueabihf-gcc: not found 交叉编译链未安装 sudo apt install gcc-arm-linux-gnueabihf
QEMU 启动后卡在 Kernel panic - not syncing 根文件系统路径或格式错误 检查 -drive 参数和 root= 内核参数
GDB 无法连接 QEMU QEMU 未以 -s 参数启动 添加 -s 或手动指定 -gdb tcp::1234
模块插入时提示 Invalid module format 模块与内核版本不匹配 确保模块使用同一内核源码编译
建议: 将常用命令封装为 shell 脚本,例如 build_kernel.sh、run_qemu.sh,提高开发效率。