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"内核模块开始,逐步深入到实际硬件控制、中断处理、设备树、并发锁等进阶主题。
1.2 内核模块概念
内核模块(Kernel Module)是Linux内核支持的一种动态加载机制,允许在系统运行时向内核插入或移除代码,而无需重新编译整个内核或重启系统。模块机制是嵌入式驱动开发的核心技术基础。
模块优势: 开发阶段无需反复编译完整内核;产品发布后可通过模块热修复或添加新功能;减少内核镜像体积,按需加载驱动。
一个标准内核模块包含以下关键要素:
- 模块入口/出口函数:
module_init()指定加载时执行的初始化函数;module_exit()指定卸载时的清理函数。 - 许可证声明:
MODULE_LICENSE("GPL")是必须的,否则内核会标记模块为"污染"。 - 作者与描述:
MODULE_AUTHOR()、MODULE_DESCRIPTION()提供元信息。 - 内核符号导出: 使用
EXPORT_SYMBOL()让其他模块调用本模块的函数。
模块与内核的关系可通过下图理解:
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 看到预期输出