1. 课程导学与环境准备:Linux驱动开发全景图

大家好,我是你们这门课的讲师。做了十几年嵌入式Linux,从裸机驱动一路折腾到复杂SoC,踩过的坑比代码还多。今天咱们先不急着敲键盘,把全景图铺开,把环境搭好。这一步走稳了,后面才跑得快。

1.1 Linux驱动开发全景图

先问大家一个问题:你写一个驱动,到底在写什么?

说白了,驱动就是操作系统和硬件之间的翻译官。上层应用调用open/read/write,驱动负责把这些操作翻译成硬件寄存器读写、中断处理、DMA搬运。整个过程分成三层:

  • 应用层:用户空间程序,通过系统调用访问设备
  • 内核层:驱动模块,运行在内核空间,处理硬件交互
  • 硬件层:物理设备,寄存器、中断控制器、总线接口

我刚开始学驱动时,总觉得内核层很神秘。后来发现,其实内核就是给你搭好了一套框架——字符设备框架、平台设备框架、设备树机制。你只需要填自己的硬件逻辑就行。

核心认知:驱动开发 = 理解硬件手册 + 套用内核框架 + 调试到跑通。三者缺一不可。

Linux驱动开发全景图 应用层 用户程序 open/read/write 内核层 字符设备框架 平台设备驱动 设备树解析 硬件层 寄存器/中断/DMA 系统调用 读写寄存器 交叉编译链 arm-linux-gnueabihf- 存储分区 uboot/kernel/rootfs 调试优化 printk/ftrace/perf 课程核心模块 驱动移植 → 工具链配置 存储分区 → 调试技巧 性能优化 → 实战项目

1.2 课程目标与学习路径

这门课不是讲理论,是讲怎么把驱动跑起来。30章内容,我按实战路线设计的:

阶段 章节 核心产出
基础准备 1-3章 环境搭建、工具链配置、第一个驱动模块
驱动移植 4-12章 字符设备、平台驱动、设备树、中断、DMA
存储与分区 13-18章 Flash分区、UBIFS、块设备驱动
调试优化 19-25章 printk/ftrace/perf、内存泄漏、性能调优
综合实战 26-30章 完整项目:摄像头驱动移植与优化

我的建议:别跳着学。每一章的实验环境都是基于前一章搭建的。我见过太多人直接跳到设备树那章,结果连模块加载都报错,回头补基础更浪费时间。

1.3 开发板选型建议

选开发板这事,我踩过不少坑。早期用某款国产板子,资料全是英文手册的机翻版,一个GPIO配置折腾了两天。后来换了主流板子,社区活跃,遇到问题一搜就有答案。

我个人推荐以下三款,覆盖不同预算和需求:

开发板 SoC 价格区间 适合场景
Raspberry Pi 4B BCM2711 (Cortex-A72) ¥300-400 入门、社区资源丰富
NXP i.MX8M Mini EVK i.MX8M Mini (Cortex-A53) ¥800-1200 工业级、Yocto/BSP完整
TI AM335x BeagleBone Black AM3358 (Cortex-A8) ¥400-600 工控、RT-Linux、PRU

避坑指南:我曾经图便宜买了一块某宝上的“全志H3核心板”,结果厂家提供的BSP是3.4内核,设备树都不支持。后来自己移植主线内核,花了整整一周。选板子,一定要确认厂家是否提供主线内核支持,或者社区是否有活跃维护。

1.4 虚拟机与Ubuntu安装

开发环境我建议用虚拟机,方便快照和回滚。Windows下用VMware Workstation,Linux下用VirtualBox都行。我个人习惯用VMware,稳定,快照功能好用。

Ubuntu版本选20.04 LTS或22.04 LTS。别追新,LTS版本稳定,软件源兼容性好。

安装步骤很简单,但有几个关键点要注意:

  1. 磁盘分配:至少给80GB,驱动编译、内核源码、交叉工具链都很占空间。我见过有人只给20GB,编译到一半磁盘满了。
  2. 内存分配:4GB起步,8GB更舒服。编译内核时内存不够会卡死。
  3. CPU核心:分配2-4核,make -jN时能加速。
  4. 网络模式:用桥接模式,方便开发板和虚拟机互通。

关键配置:安装完成后,执行以下命令更新软件源并安装基础开发工具:

sudo apt update
sudo apt upgrade -y
sudo apt install build-essential git vim curl wget \
  libncurses-dev flex bison openssl libssl-dev \
  dkms libelf-dev libudev-dev libpci-dev libiberty-dev \
  autoconf automake device-tree-compiler -y

1.5 必备工具链安装

交叉编译链是驱动移植的核心工具。你想想看,在x86的PC上编译ARM架构的内核和驱动,没有交叉编译链根本做不到。

我推荐两种方式安装:

方式 命令/步骤 适用场景
apt直接安装 sudo apt install gcc-arm-linux-gnueabihf 快速开始,版本较旧但稳定
Linaro官方工具链 下载gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz 版本新,支持更多特性

我个人习惯用Linaro的,因为版本可控,项目里统一用7.5.0,不会因为apt源更新导致编译行为变化。

安装Linaro工具链的步骤:

# 下载
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

# 解压到 /opt
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt

# 添加环境变量
echo 'export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin' >> ~/.bashrc
source ~/.bashrc

# 验证
arm-linux-gnueabihf-gcc --version

验证小技巧:执行 arm-linux-gnueabihf-gcc -v 看到版本信息后,再编译一个hello.c试试:arm-linux-gnueabihf-gcc hello.c -o hello.arm,然后用 file hello.arm 查看,输出应该是 ELF 32-bit LSB executable, ARM。这样才算真正装好了。

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

环境搭好了,咱们写个最简单的驱动模块验证一下。这个模块什么都不做,只打印一条日志。

// hello_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.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");
MODULE_DESCRIPTION("A simple Hello World driver");

编译这个模块需要内核源码树。下载内核源码并配置:

# 下载内核源码(以5.10为例)
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

# 配置内核(使用默认配置)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig

# 编译内核(第一次编译较慢,-jN根据CPU核心数调整)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

然后编译驱动模块:

# 在hello_drv.c所在目录创建Makefile
obj-m += hello_drv.o

all:
    make -C /path/to/linux-5.10.100 M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

clean:
    make -C /path/to/linux-5.10.100 M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean

常见错误:如果编译时报错 Kernel configuration is invalid,说明内核没编译过。先执行 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig 再编译。我曾经在这上面浪费了半小时,后来发现是忘了先配置内核。

编译成功后,会生成 hello_drv.ko。把这个文件传到开发板上,执行:

insmod hello_drv.ko
dmesg | tail -5
# 应该看到:Hello, Linux Driver World!

看到这条日志,说明你的环境全部就绪了。嗯,到这里,第一章的准备工作就完成了。

本章核心收获:

  • 理解了Linux驱动开发的三层架构
  • 选好了开发板并搭建了虚拟机环境
  • 安装了交叉编译链并验证了可用性
  • 成功编译并加载了第一个驱动模块