1. 课程导论与开发环境准备

1.1 嵌入式Linux驱动开发概述

嵌入式Linux驱动开发是连接硬件外设与Linux内核的桥梁。驱动程序作为内核的一部分,负责管理特定硬件设备,向上层应用提供统一的操作接口。在嵌入式系统中,驱动开发直接决定了硬件功能的可用性、实时性与功耗表现。

核心认知: 嵌入式Linux驱动开发 ≠ 应用软件开发。驱动运行在内核空间,拥有最高CPU特权级,任何错误都可能导致系统崩溃或数据损坏。开发者必须理解内核API、并发同步、内存管理、硬件寄存器操作等底层知识。

嵌入式Linux驱动按设备类型与通信方式,主要分为以下三类:

驱动类型 设备模型 典型设备 文件操作接口
字符设备驱动 字符设备 GPIO、UART、I2C、SPI open/read/write/ioctl
块设备驱动 块设备 eMMC、NAND Flash、SD卡 submit_bio/request
网络设备驱动 网络接口 以太网PHY、WiFi、CAN net_device_ops/sk_buff

本课程以字符设备驱动为主线,从最简单的"Hello World"内核模块开始,逐步深入到实际硬件控制、中断处理、设备树、并发锁等进阶主题。

嵌入式Linux驱动开发全景图 用户空间应用程序 (App / Library) 系统调用接口 (syscall / VFS / socket) 内核空间 — 设备驱动 字符设备驱动 块设备驱动 网络设备驱动 GPIO / UART / I2C / SPI eMMC / NAND / SD Ethernet / WiFi / CAN 硬件抽象层 (HAL / 设备树 / 寄存器映射) 物理硬件 (SoC / 外设芯片 / 总线)

1.2 内核模块概念

内核模块(Kernel Module)是Linux内核支持的一种动态加载机制,允许在系统运行时向内核插入或移除代码,而无需重新编译整个内核或重启系统。模块机制是嵌入式驱动开发的核心技术基础。

模块优势: 开发阶段无需反复编译完整内核;产品发布后可通过模块热修复或添加新功能;减少内核镜像体积,按需加载驱动。

一个标准内核模块包含以下关键要素:

  • 模块入口/出口函数: module_init() 指定加载时执行的初始化函数;module_exit() 指定卸载时的清理函数。
  • 许可证声明: MODULE_LICENSE("GPL") 是必须的,否则内核会标记模块为"污染"。
  • 作者与描述: MODULE_AUTHOR()、MODULE_DESCRIPTION() 提供元信息。
  • 内核符号导出: 使用 EXPORT_SYMBOL() 让其他模块调用本模块的函数。

模块与内核的关系可通过下图理解:

内核模块生命周期 .ko 模块文件 insmod 加载 内核空间 module_init() 注册设备 / 创建文件 运行中 (提供服务) rmmod 卸载 module_exit() 清理 关键命令:insmod → 加载模块;rmmod → 卸载模块;lsmod → 列出已加载模块;modinfo → 查看模块信息 模块依赖:modprobe 自动处理依赖,推荐生产环境使用

1.3 开发环境搭建

嵌入式Linux驱动开发采用宿主机-目标板交叉开发模式。宿主机(通常是x86 PC)上安装交叉编译工具链,编译生成目标板(ARM/RISC-V/MIPS等架构)可执行的内核模块,然后通过调试接口部署到目标板运行。

1.3.1 硬件环境

角色 典型设备 操作系统 职责
宿主机 (Host) x86 PC / 虚拟机 Ubuntu 22.04 / Debian 12 编写代码、交叉编译、调试分析
目标板 (Target) ARM开发板 (i.MX6/STM32MP1/RK3568) 嵌入式Linux (Yocto/Buildroot) 运行内核模块、验证驱动功能
连接方式 USB串口 / 以太网 / JTAG — 传输文件、内核日志、调试

1.3.2 交叉编译工具链安装

交叉编译工具链包含针对目标架构的编译器、链接器、库文件及头文件。以ARM Cortex-A7目标板为例,安装步骤:

# 下载ARM交叉编译工具链(以gcc-arm-11.2为例)
wget https://developer.arm.com/-/media/Files/downloads/gnu/11.2-2022.02/binrel/gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf.tar.xz

# 解压到指定目录
sudo tar -xf gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf.tar.xz -C /opt/

# 添加环境变量
echo 'export PATH=$PATH:/opt/gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf/bin' >> ~/.bashrc
source ~/.bashrc

# 验证安装
arm-none-linux-gnueabihf-gcc --version
重要: 交叉编译工具链版本必须与目标板内核版本匹配。工具链的glibc版本、内核头文件版本不一致会导致编译出的模块无法加载或运行时崩溃。建议从板卡厂商或Yocto SDK获取官方工具链。

1.3.3 内核源码与构建

编译内核模块需要目标板对应的内核源码树,且必须预先完成编译,生成 Module.symvers 和 include/generated/autoconf.h 等依赖文件。

# 获取内核源码(以Linux 5.10为例)
git clone -b linux-5.10.y git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux

# 配置内核(使用目标板的defconfig或现有.config)
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- your_board_defconfig

# 编译内核与模块(-jN 并行编译)
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- -j4

# 编译内核模块
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- modules

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

1.3.4 第一个内核模块:Hello World

编写一个最简单的内核模块,验证开发环境是否正常工作:

/* hello.c */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Embedded Linux Driver!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Embedded Linux Driver!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Course");
MODULE_DESCRIPTION("A simple Hello World kernel module");

对应的 Makefile:

# Makefile
obj-m := hello.o

KERNELDIR ?= /path/to/your/kernel/source
CROSS_COMPILE ?= arm-none-linux-gnueabihf-
CC := $(CROSS_COMPILE)gcc

all:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) clean

编译与部署流程:

# 宿主机编译
make

# 将hello.ko传输到目标板(通过scp或U盘)
scp hello.ko root@192.168.1.100:/root/

# 目标板加载模块
insmod hello.ko

# 查看内核日志
dmesg | tail -5
# 输出: [ 1234.5678] Hello, Embedded Linux Driver!

# 卸载模块
rmmod hello.ko
dmesg | tail -5
# 输出: [ 1235.1234] Goodbye, Embedded Linux Driver!
调试技巧: 使用 dmesg -w 实时监控内核日志;使用 lsmod 查看模块加载状态;使用 modinfo hello.ko 查看模块元信息。如果模块加载失败,dmesg 会给出具体错误原因(如版本不匹配、符号未定义等)。

1.3.5 开发环境检查清单

在开始后续课程前,请确认以下环境准备就绪:

  • 宿主机: Ubuntu 22.04+,已安装 build-essential、git、flex、bison、libncurses-dev
  • 交叉工具链: 已安装并加入PATH,gcc --version 输出目标架构信息
  • 内核源码: 已下载并完成编译,Module.symvers 文件存在
  • 目标板: 可正常启动Linux,网络或串口连接可用
  • 验证: 成功编译并加载 hello.ko 模块,dmesg 看到预期输出