2. 开发环境搭建:交叉编译工具链配置、内核源码获取与分支管理、QEMU模拟环境搭建、文件系统制作
嵌入式Linux驱动开发与原生PC开发最大的区别在于交叉编译与目标板模拟。本章将系统搭建一套完整的开发环境,涵盖工具链配置、内核源码管理、QEMU虚拟化平台构建以及根文件系统制作。这套环境将贯穿后续所有驱动移植实验。
2.1 交叉编译工具链配置
交叉编译工具链是嵌入式开发的基石。它运行在x86_64主机上,生成ARM架构的可执行代码。工具链的配置直接影响内核编译、驱动模块编译以及应用程序的二进制兼容性。
2.1.1 工具链选择与下载
推荐使用ARM官方维护的GNU Arm Embedded Toolchain,它经过严格测试,与主线内核兼容性最好。避免使用发行版仓库中过时的版本(如gcc-arm-linux-gnueabihf 4.8),否则可能遇到内核编译宏或内联汇编的兼容性问题。
| 工具链版本 | 适用内核范围 | GCC版本 | glibc版本 | 推荐度 |
|---|---|---|---|---|
| gcc-arm-10.2-2020.11 | v4.19 ~ v5.10+ | 10.2.1 | 2.31 | 强烈推荐 |
| gcc-arm-8.3-2019.03 | v4.14 ~ v5.4 | 8.3.1 | 2.28 | 推荐 |
| gcc-arm-7.2-2017.11 | v3.16 ~ v4.9 | 7.2.1 | 2.26 | 兼容旧内核 |
| apt安装(gcc-arm-linux-gnueabihf) | v4.9 ~ v5.4 | 7.5.0 (Ubuntu 20.04) | 2.27 | 仅用于快速验证 |
2.1.2 安装与路径配置
以下步骤以 gcc-arm-10.2-2020.11-x86_64-arm-linux-gnueabihf 为例,将其安装到 /opt/toolchains 目录。
# 1. 创建工具链目录
sudo mkdir -p /opt/toolchains
cd /opt/toolchains
# 2. 下载并解压(若网络受限,可先下载到本地再拷贝)
sudo wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/gcc-arm-10.2-2020.11-x86_64-arm-linux-gnueabihf.tar.xz
sudo tar -xf gcc-arm-10.2-2020.11-x86_64-arm-linux-gnueabihf.tar.xz
# 3. 添加至系统PATH(写入 ~/.bashrc 或 /etc/profile)
export PATH=/opt/toolchains/gcc-arm-10.2-2020.11-x86_64-arm-linux-gnueabihf/bin:$PATH
# 4. 验证安装
arm-linux-gnueabihf-gcc --version
# 输出应包含 "arm-linux-gnueabihf-gcc (GNU Toolchain for the A-profile Architecture 10.2-2020.11) 10.2.1 20201103"
$PATH 末尾,而应放在最前面,避免系统自带的x86_64 gcc被错误调用。建议在 ~/.bashrc 中显式 export,并在每个终端会话开始时 source ~/.bashrc。
2.1.3 工具链组成与测试
一个完整的交叉工具链包含编译器、汇编器、链接器、库以及调试工具。下表列出核心组件及其在内核编译中的角色:
| 可执行文件 | 作用 | 内核编译中的使用 |
|---|---|---|
arm-linux-gnueabihf-gcc |
C编译器 | 编译内核C代码、驱动模块 |
arm-linux-gnueabihf-ld |
链接器 | 链接vmlinux、模块.ko |
arm-linux-gnueabihf-objcopy |
二进制格式转换 | 生成zImage、uImage |
arm-linux-gnueabihf-objdump |
反汇编器 | 调试内核崩溃、分析驱动代码 |
arm-linux-gnueabihf-gdb |
调试器 | 通过QEMU的gdbstub调试内核 |
编写一个简单的测试程序,验证工具链能否正确生成ARM架构的可执行文件:
// test.c
#include <stdio.h>
int main(void)
{
printf("Cross toolchain works!\n");
return 0;
}
arm-linux-gnueabihf-gcc -static -o test test.c
file test
# 输出应包含 "ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked"
-static 选项生成静态链接的可执行文件,避免在目标板上缺少动态库时运行失败。后续制作文件系统时,我们会将必要的动态库拷贝到根文件系统中。
2.2 内核源码获取与分支管理
驱动移植的核心工作是对比不同内核版本的API差异。因此,我们需要同时管理多个内核版本的源码树,并能够快速切换分支进行编译和测试。
2.2.1 获取主线内核源码
从 kernel.org 获取官方主线内核源码,使用git克隆整个仓库,以便后续切换标签和分支。
# 创建内核源码工作目录
mkdir -p ~/linux-kernel
cd ~/linux-kernel
# 克隆主线内核(完整历史,约2.5GB)
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
# 查看所有标签(按版本排序)
git tag -l 'v3.*' | sort -V | tail -5
git tag -l 'v5.*' | sort -V | tail -5
# 检出我们需要的版本
git checkout v3.16 # 3.x 基线版本
git checkout v4.4 # 4.x 过渡版本
git checkout v5.10 # 5.x 目标版本
git clone --depth=1 --branch v5.10 只克隆单个标签的浅层副本。但后续无法使用 git log 查看历史提交,建议至少保留 --depth=1000。
2.2.2 分支管理策略
为每个实验创建独立的工作分支,避免污染主线标签。推荐的分支命名规范:
| 分支名称 | 基版本 | 用途 |
|---|---|---|
exp/gpio/v3.16 |
v3.16 | GPIO驱动移植实验(3.x基线) |
exp/gpio/v5.10 |
v5.10 | GPIO驱动移植实验(5.x目标) |
exp/i2c/v3.16 |
v3.16 | I2C驱动移植实验 |
exp/i2c/v5.10 |
v5.10 | I2C驱动移植实验 |
fix/xxx |
当前实验分支 | 修复编译错误或Bug |
# 从v3.16创建实验分支
git checkout v3.16
git checkout -b exp/gpio/v3.16
# 从v5.10创建实验分支
git checkout v5.10
git checkout -b exp/gpio/v5.10
# 查看当前分支列表
git branch -a
2.2.3 内核配置与编译
针对QEMU模拟的 vexpress-a9 平台,使用默认配置并启用必要的驱动选项。
# 设置环境变量(每次编译前执行)
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 使用vexpress默认配置
make vexpress_defconfig
# 进入菜单配置,添加调试符号和驱动选项
make menuconfig
# 建议开启:
# Kernel hacking ---> [*] Kernel debugging
# Kernel hacking ---> [*] Compile the kernel with debug info
# Device Drivers ---> [*] GPIO Support
# Device Drivers ---> [*] I2C Support
# 编译内核(使用-jN并行编译,N为CPU核心数)
make -j$(nproc) zImage
make -j$(nproc) modules
make -j$(nproc) dtbs
# 编译完成后,检查生成的文件
ls -lh arch/arm/boot/zImage
ls -lh arch/arm/boot/dts/vexpress-v2p-ca9.dtb
undefined reference to `__aeabi_unwind_cpp_pr0' 等链接错误,通常是因为工具链版本与内核版本不匹配。对于v3.16内核,建议使用gcc-arm-7.2或更低版本;对于v5.10内核,使用gcc-arm-10.2。如果必须使用高版本工具链编译旧内核,可以在 arch/arm/Makefile 中添加 KBUILD_CFLAGS += -fno-unwind-tables 临时规避。
2.3 QEMU模拟环境搭建
QEMU(Quick EMUlator)是嵌入式Linux开发中不可或缺的虚拟化工具。它能够模拟完整的ARM开发板,使我们无需硬件即可验证内核启动、驱动加载和应用程序运行。
2.3.1 QEMU安装与版本选择
Ubuntu 20.04 默认仓库中的 QEMU 版本为 4.2.1,足以支持 vexpress-a9 模拟。如果需要更精确的模拟特性(如 GICv3、PCIe RC),建议从源码编译 QEMU 5.2+。
# 安装QEMU及相关工具
sudo apt-get update
sudo apt-get install qemu-system-arm qemu-efi-arm qemu-utils
# 验证安装
qemu-system-arm --version
# 输出示例: QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.24)
2.3.2 启动内核与设备树
使用 QEMU 启动我们刚刚编译的内核,验证内核能否正常初始化并挂载根文件系统。
# 基本启动命令(不挂载文件系统,仅验证内核启动)
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ~/linux-kernel/linux/arch/arm/boot/zImage \
-dtb ~/linux-kernel/linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-append "console=ttyAMA0"
# 如果看到内核启动日志,最后停在 "Kernel panic - not syncing: VFS: Unable to mount root fs"
# 说明内核启动成功,只是缺少根文件系统。
-M vexpress-a9:指定模拟的开发板型号。-m 512M:分配512MB内存。-kernel:指定内核镜像路径。-dtb:指定设备树二进制文件。-nographic:不使用图形界面,串口输出到终端。-append "console=ttyAMA0":内核命令行参数,指定控制台为串口0。
2.3.3 网络与存储设备配置
为了后续驱动实验,需要为QEMU添加网络后端和SD卡存储模拟。
# 创建虚拟SD卡镜像(用于存储文件系统和驱动模块)
qemu-img create -f raw sd.img 512M
# 启动QEMU并挂载SD卡、启用网络
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ~/linux-kernel/linux/arch/arm/boot/zImage \
-dtb ~/linux-kernel/linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-drive file=sd.img,format=raw,if=sd \
-net nic -net user,hostfwd=tcp::2222-:22 \
-nographic \
-append "console=ttyAMA0 root=/dev/mmcblk0 rw"
| QEMU参数 | 作用 | 驱动实验中的用途 |
|---|---|---|
-drive file=sd.img,if=sd |
模拟SD卡控制器 | 块设备驱动、文件系统挂载 |
-net nic -net user |
模拟网卡(用户模式网络) | 网络驱动、NFS根文件系统 |
hostfwd=tcp::2222-:22 |
端口转发(主机2222→客户机22) | SSH登录、scp传输驱动模块 |
-s |
启动gdbstub(默认端口1234) | 内核调试、驱动单步执行 |
2.4 文件系统制作
根文件系统是Linux启动后挂载的第一个文件系统,它包含init程序、库文件、设备节点和必要的工具。对于驱动实验,我们需要一个轻量但功能完整的文件系统,支持模块加载、设备节点创建和网络功能。
2.4.1 使用BusyBox构建最小根文件系统
BusyBox是一个集成了数百个常用Unix命令的单一可执行文件,是嵌入式Linux文件系统的标准选择。
# 1. 下载BusyBox源码
cd ~
wget https://busybox.net/downloads/busybox-1.33.2.tar.bz2
tar -xf busybox-1.33.2.tar.bz2
cd busybox-1.33.2
# 2. 配置BusyBox(使用默认配置,启用静态编译)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# 在 Settings 中启用:
# [*] Build BusyBox as a static binary (no shared libs)
# 3. 编译并安装到临时目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install CONFIG_PREFIX=~/rootfs
# 4. 查看安装结果
ls -lh ~/rootfs/bin/busybox
~/rootfs/bin/busybox --help
2.4.2 完善文件系统目录结构
一个可启动的根文件系统需要包含以下目录和关键文件:
cd ~/rootfs
# 创建必要目录
mkdir -p dev etc init.d lib proc sys tmp var/log mnt
# 创建设备节点(静态方式,后续可使用devtmpfs自动创建)
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
sudo mknod dev/ttyAMA0 c 204 64
# 创建初始化脚本 etc/inittab
cat > etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
EOF
# 创建系统初始化脚本 etc/init.d/rcS
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
mkdir -p /dev/pts
mount -t devpts