1. Linux驱动开发绪论:内核模块基础、驱动角色定位、外设驱动模型全景图

1.1 驱动到底是什么?——先聊聊角色定位

很多初学者会把驱动想得很神秘。其实说白了,驱动就是一段运行在内核空间的代码,负责让操作系统认识并控制某个硬件设备。

我刚开始接触Linux驱动时,总觉得它跟应用程序差别很大。后来发现,驱动本质上也是在写代码,只不过它运行的环境更特殊——没有标准库、不能随便malloc、出了问题直接panic。嗯,这里要注意,驱动一旦崩溃,整个系统就挂了,不像用户态程序崩了还能重启。

驱动在内核中的角色,可以归纳为三点:

  • 硬件抽象层:把千奇百怪的硬件寄存器操作,封装成统一的接口
  • 数据通道:在用户态和硬件之间搬运数据,同时保证安全
  • 资源管家:管理中断、DMA、内存等硬件资源,防止冲突

核心观点:驱动不是"写出来的",而是"设计出来的"。好的驱动设计,能让硬件性能发挥到极致,同时保证系统稳定。

1.2 内核模块——驱动的"容器"

Linux驱动通常以内核模块的形式存在。模块可以动态加载和卸载,不需要重新编译整个内核。这个特性在嵌入式开发中特别有用——你想想看,产品已经量产了,突然发现某个外设驱动需要修复,总不能把整个内核重新烧一遍吧?

一个最简单的内核模块长这样:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init my_driver_init(void)
{
    printk(KERN_INFO "Hello, driver world!\n");
    return 0;
}

static void __exit my_driver_exit(void)
{
    printk(KERN_INFO "Goodbye, driver world!\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example module");

这段代码虽然简单,但包含了模块的骨架:入口函数、出口函数、许可证声明。我个人习惯在写每个模块时,都会把MODULE_DESCRIPTION写清楚,方便后续维护。

3. 模块的"生老病死"——生命周期管理

内核模块的生命周期分为几个阶段:

阶段 触发方式 执行内容
加载 insmod / modprobe module_init() 注册驱动、分配资源
运行 设备接入 / 应用调用 处理读写、中断、IOCTL等请求
卸载 rmmod module_exit() 释放资源、注销驱动
异常 内核崩溃 / 资源冲突 panic / oops,需要重启恢复

避坑指南:我曾经在卸载模块时忘记释放中断,结果下次加载时中断号已经被占用,系统直接死机。记住,module_exit()里一定要把module_init()申请的所有资源都释放干净,一个都不能少。

1.4 外设驱动模型全景图——从"裸奔"到"框架"

早期的Linux驱动,每个驱动都是"孤岛"。开发者需要自己处理设备发现、资源管理、电源管理等琐事。后来内核社区发现这样不行——太乱了,而且代码重复严重。

于是,Linux 2.6之后引入了设备驱动模型(Device Driver Model),核心思想就一句话:把硬件设备、驱动、总线三者解耦。

整个模型围绕三个核心对象展开:

  • device:代表一个物理或虚拟设备,包含资源信息(如寄存器地址、中断号)
  • driver:代表一段能操作某类设备的代码,包含probe/remove等回调
  • bus:代表设备与驱动之间的连接方式,负责匹配和绑定

下面这张图展示了它们之间的关系:

Linux外设驱动模型核心架构 总线(Bus) 负责匹配 device 与 driver 设备(device) 包含资源:地址、中断、DMA 驱动(driver) 包含回调:probe、remove 注册 注册 匹配(Match) 通过 name / of_match_table / id_table probe() 执行 初始化硬件、注册接口 核心流程:设备注册到总线 → 驱动注册到总线 → 总线匹配 → 匹配成功则调用驱动的probe() probe()中完成硬件初始化、注册字符设备/网络设备等操作

这个模型的好处很明显:

  • 代码复用:一个驱动可以支持多个同类型设备
  • 热插拔:设备插入时自动匹配驱动,无需手动干预
  • 电源管理:通过设备树或ACPI统一管理设备状态

个人经验:我在做嵌入式项目时,经常用设备树(Device Tree)来描述硬件资源。设备树本质上就是给驱动模型提供"设备信息"的载体。你只要在设备树里配好寄存器地址和中断号,驱动里通过platform_get_resource()就能拿到,省去了硬编码的麻烦。

1.5 驱动的分类——你属于哪一类?

Linux驱动按功能可以分为几大类。我习惯把它们分成"三大门派":

驱动类型 典型代表 核心接口
字符设备驱动 GPIO、UART、I2C open/read/write/ioctl
块设备驱动 eMMC、SD卡、NVMe request_queue / bio
网络设备驱动 以太网、WiFi、CAN net_device_ops / sk_buff

你想想看,这三种驱动在内核中的"待遇"完全不同。字符设备最简单,直接操作文件接口就行;块设备要处理复杂的I/O调度;网络设备则要跟协议栈打交道,复杂度最高。

1.6 本章小结——你该带走什么?

这一章我们聊了三个核心问题:

  • 驱动是内核与硬件之间的"翻译官"
  • 内核模块是驱动的载体,生命周期要管理好
  • 设备驱动模型把设备、驱动、总线解耦,让代码更优雅