2. 开发环境搭建:交叉编译工具链配置、Linux内核源码获取与编译、设备树(Device Tree)基础、QEMU模拟环境搭建与测试
说实话,做嵌入式Linux驱动开发,环境搭建这一步要是没走稳,后面全是坑。我见过太多新手一上来就急着写代码,结果编译报错半天找不到原因——最后发现是工具链版本不对。嗯,咱们先把地基打牢。
本章核心目标:搭建一套完整的嵌入式Linux驱动开发实验环境,包括交叉编译工具链、内核源码树、设备树编译工具以及QEMU模拟器。这套环境将贯穿整个课程。
2.1 交叉编译工具链配置
交叉编译,说白了就是在一台电脑(宿主机)上编译出能在另一台不同架构的电脑(目标机)上运行的程序。我们做ARM驱动开发,宿主机通常是x86_64的PC,目标机是ARM开发板或QEMU模拟的ARM系统。
我个人习惯使用Linaro提供的GCC工具链,稳定且社区支持好。以ARM 32位为例,我们选用gcc-arm-linux-gnueabihf。
2.1.1 下载与安装
# 下载Linaro ARM GCC工具链
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
# 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 添加环境变量
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
验证是否安装成功:
arm-linux-gnueabihf-gcc --version
如果看到版本信息,说明工具链已经就绪。
我的经验:建议把工具链路径永久写入 ~/.bashrc 或 ~/.zshrc,避免每次打开终端都要手动 export。我曾经因为忘记 export,浪费了整整一个下午排查一个“编译器找不到”的问题。
2.1.2 工具链组件说明
| 组件 | 用途 |
|---|---|
arm-linux-gnueabihf-gcc |
C语言交叉编译器 |
arm-linux-gnueabihf-g++ |
C++语言交叉编译器 |
arm-linux-gnueabihf-ld |
链接器 |
arm-linux-gnueabihf-objdump |
反汇编工具,调试驱动时非常有用 |
arm-linux-gnueabihf-objcopy |
格式转换工具,用于生成裸机镜像 |
2.2 Linux内核源码获取与编译
驱动开发离不开内核源码。你想想看,驱动本质上就是内核的一部分,你得知道内核怎么管理设备、怎么调度中断、怎么管理内存。
2.2.1 获取内核源码
我建议直接从kernel.org获取主线内核,或者使用LTS版本。本课程以Linux 5.10 LTS为例。
# 从kernel.org下载
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
# 解压
tar -xvf linux-5.10.100.tar.xz
cd linux-5.10.100
2.2.2 配置内核
内核配置是门学问。对于驱动开发,我们通常不需要编译整个内核,但需要内核源码树来编译驱动模块。
# 使用默认的ARM配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig
# 如果需要自定义配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
注意: menuconfig 需要安装 ncurses-dev 库。如果报错,执行 sudo apt-get install libncurses-dev。
2.2.3 编译内核
# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
# 编译内核模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
编译完成后,你会得到以下关键文件:
arch/arm/boot/zImage— 内核镜像arch/arm/boot/dts/vexpress-v2p-ca9.dtb— 设备树二进制文件drivers/目录下的.ko文件 — 内核模块
2.3 设备树(Device Tree)基础
设备树,说白了就是描述硬件信息的配置文件。以前的内核把硬件信息硬编码在代码里,换一块板子就得改代码。设备树把硬件描述和内核代码分开了,换板子只需要换一个 .dts 文件。
2.3.1 设备树文件结构
一个典型的设备树文件包含以下部分:
/dts-v1/;
/ {
model = "V2P-CA9";
compatible = "arm,vexpress-v2p-ca9", "arm,vexpress";
#address-cells = <1>;
#size-cells = <1>;
memory@60000000 {
device_type = "memory";
reg = <0x60000000 0x40000000>;
};
chosen {
bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0 rw";
};
amba {
#address-cells = <1>;
#size-cells = <1>;
uart@160000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x160000 0x1000>;
interrupts = <0 5 4>;
};
};
};
2.3.2 关键概念
| 概念 | 说明 |
|---|---|
compatible |
匹配驱动的关键字符串,驱动通过它找到对应的设备 |
reg |
描述设备的地址范围和大小 |
interrupts |
描述设备使用的中断号、触发方式等 |
status |
设备状态,常用 okay 或 disabled |
避坑指南:我曾经在调试一个I2C驱动时,设备树里写错了 reg 的地址,结果驱动死活加载不上。后来用 dtc -I dtb -O dts 反编译设备树,才发现地址少写了一个0。所以,写完设备树一定要反编译检查一遍。
2.3.3 编译设备树
# 将.dts编译为.dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译.dtb为.dts(调试用)
dtc -I dtb -O dts -o myboard.dts myboard.dtb
2.4 QEMU模拟环境搭建与测试
没有开发板怎么办?QEMU就是你的救星。它可以在PC上模拟一个完整的ARM系统,非常适合驱动开发的初期调试。
2.4.1 安装QEMU
sudo apt-get install qemu-system-arm
2.4.2 启动模拟系统
我们使用QEMU模拟vexpress-a9开发板,配合刚才编译的内核和设备树。
qemu-system-arm \
-M vexpress-a9 \
-kernel linux-5.10.100/arch/arm/boot/zImage \
-dtb linux-5.10.100/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-append "console=ttyAMA0,115200 root=/dev/mmcblk0 rw" \
-sd rootfs.ext3 \
-nographic
如果一切正常,你会看到内核启动日志,最后进入一个shell提示符。
验证驱动开发环境:进入系统后,执行 ls /dev/ 查看设备节点,执行 cat /proc/devices 查看已注册的设备驱动。这就是我们后续开发驱动的“试验田”。
2.4.3 常见问题与解决
- QEMU启动后黑屏:检查是否使用了
-nographic参数,或者串口控制台参数是否正确。 - 内核崩溃:通常是设备树与内核版本不匹配,重新编译dtb即可。
- 文件系统挂载失败:确认rootfs镜像路径正确,且格式为ext3。
2.5 本章知识体系
下面这张图展示了我们搭建的开发环境各组件之间的关系:
重要提醒:环境搭建完成后,建议创建一个快照或写一份环境配置文档。我见过太多同学在课程中途重装系统,结果环境配置全部丢失,又得从头再来。花10分钟记录一下,能省你一天的时间。
好了,环境搭建就到这里。工具链、内核、设备树、QEMU,这四个组件构成了我们驱动开发的基础设施。后面的章节,我们会在这个环境里一步步实现从最简单的字符设备驱动,到复杂的显示驱动。