1. 课程导入
Linux 驱动开发是嵌入式、服务器、桌面系统与国产化平台中连接硬件与内核的桥梁。学习本课程,目标不是背 API,而是建立“内核对象—设备模型—驱动匹配—用户接口”的完整心智模型:能读懂内核源码中的驱动框架,能独立编写可加载、可卸载、可调试的字符设备驱动,并能用设备树、sysfs、procfs 等机制完成设备管理。
核心学习目标:理解 file_operations、cdev、device、driver、bus、class 等核心对象;掌握模块加载/卸载、设备注册、驱动匹配、用户空间访问四条主线;能定位 probe 未触发、节点未生成、open 失败等常见问题。
一、整体框架:核心对象与设备管理
Linux 设备模型可以概括为“总线—设备—驱动”三角关系。总线负责匹配,设备描述硬件资源,驱动提供操作方法。当设备与驱动匹配成功后,内核调用驱动的 probe 函数,驱动再向内核注册字符设备、创建类与设备节点,最终暴露给用户空间。下图展示了从内核模块到用户接口的完整对象关系。
二、开发环境搭建
驱动开发环境与普通应用开发不同,必须保证内核头文件版本与运行内核一致。推荐在 Ubuntu 或国产 Linux 发行版中安装 build-essential、linux-headers-$(uname -r) 与 make。若目标平台是 ARM 开发板,还需交叉编译工具链与目标内核源码树。
# 查看当前内核版本
uname -r
# 安装编译依赖(Ubuntu/Debian)
sudo apt update
sudo apt install build-essential linux-headers-$(uname -r) make git
# 交叉编译示例(ARM64)
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make -C /path/to/kernel/source M=$PWD modules
验证环境是否可用:进入任意内核模块目录执行 make,若生成 .ko 文件且无 “missing headers” 报错,说明头文件与编译器匹配成功。
三、第一个内核模块:最小可运行案例
内核模块是驱动开发的最小载体。下面是一个标准 Hello 模块,包含入口、出口与许可证声明。加载后通过 dmesg 观察日志,卸载后日志消失,这是后续所有驱动调试的基础手段。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void)
{
pr_info("hello_driver: module loaded\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("hello_driver: module unloaded\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("course");
MODULE_DESCRIPTION("First Linux kernel module");
对应的 Makefile 只需三行核心内容,利用内核构建系统完成编译:
obj-m += hello_driver.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
编译、加载、查看、卸载的完整操作序列如下:
make
sudo insmod hello_driver.ko
dmesg | tail -n 5
lsmod | grep hello_driver
sudo rmmod hello_driver
dmesg | tail -n 5
四、核心对象速查清单
| 对象 | 所在头文件 | 作用 | 典型 API |
|---|---|---|---|
| file_operations | linux/fs.h | 定义驱动对用户空间的操作方法 | open/read/write/ioctl/release |
| cdev | linux/cdev.h | 内核字符设备抽象 | cdev_init / cdev_add / cdev_del |
| dev_t | linux/types.h | 主次设备号 | MKDEV / MAJOR / MINOR |
| device | linux/device.h | 描述一个具体设备 | device_register / device_create |
| device_driver | linux/device.h | 描述驱动能力与匹配表 | driver_register / probe / remove |
| bus_type | linux/device.h | 总线匹配规则 | bus_register / match |
| class | linux/device.h | 设备分类,自动创建设备节点 | class_create / device_create |
五、常见误区与复盘
- 误区一:头文件版本不匹配。使用 /usr/src/linux-headers 与当前运行内核不一致,会导致模块加载时 “version magic” 报错。解决方法是安装与 uname -r 完全一致的头文件包。
- 误区二:忘记 MODULE_LICENSE。缺少许可证声明会污染内核并产生警告,部分内核还会拒绝加载非 GPL 模块。
- 误区三:在模块入口做耗时操作。module_init 应快速返回,硬件初始化、等待队列等应放到 probe 或工作队列中。
- 误区四:只写代码不看 dmesg。内核日志是驱动调试的第一现场,printk 分级、动态调试与 ftrace 都应尽早掌握。
卸载模块前务必确认没有进程正在使用对应设备节点,否则 rmmod 会返回 “Module is in use”。可用 lsof /dev/xxx 或 fuser 检查占用情况。