构建
驱动开发方法
DeepSeek / Linux 驱动开发方法
现代 Linux 驱动开发是一个 “基于框架,填充回调函数” 的过程,核心在于理解内核提供的各种子系统、设备模型以及如何处理并发与硬件交互。
学习与实践路线图
- 基础准备:精通 C 语言,了解一些汇编和硬件知识。
- 从最简单的内核模块开始:编写一个 `Hello World` 模块,学习编译、加载、卸载。
- 学习字符设备驱动:创建一个虚拟的字符设备(如 mydev),实现 `open`, `read`, `write`,并在用户空间用 `cat`, `echo` 进行测试。
- 深入并发与同步:在你的虚拟设备中模拟资源竞争,并引入互斥锁来解决它。
- 学习中断处理:结合一个真实的硬件(如开发板上的一个按键)或模拟中断,实现顶半部和底半部。
- 掌握设备树:在一个嵌入式开发板上,将设备的硬件资源(如寄存器地址、中断号)从驱动代码中剥离,写入设备树文件(`.dts`)。
- 集成到特定框架:根据你的设备类型,选择一个合适的框架(如 I2C, SPI, Input 子系统等)进行实践。
- 阅读内核源码:大量阅读内核中已有驱动的源码,这是最好的学习资料。
推荐资源:
- 书籍:《Linux Device Drivers》(LDD3,虽旧但经典),《Essential Linux Device Drivers》
- 内核文档:`Documentation/driver-api/` 目录下的文档。
- 源码:Linux 内核源码 `drivers/` 目录下的例子。
开发 Linux 驱动主要有以下几种模式,从易到难,从抽象到具体:
用户空间驱动
概念:并非真正的“内核驱动”,而是将驱动逻辑实现在用户空间的应用程序中,通过操作系统提供的接口(如 `/dev/mem`, `sysfs`, `userspace I/O`)来访问硬件。
性质:
- 优点:开发调试简单(可以用 GDB),崩溃不会影响系统稳定性。
- 缺点:性能差,无法处理硬件中断,访问硬件受限。
场景:
- 对性能要求不高的简单设备。
- 原型开发和学习阶段。
- 不想或无法加载内核模块的环境。
内核模块
概念:将驱动编译成内核模块,其以 .ko 为扩展名。模块可以在系统运行时动态地加载到内核或从内核卸载,而无需重新编译整个内核或重启系统。这是最主流、最经典的驱动开发方式。
核心思想:驱动是内核的一部分
首先,必须理解,如果 Linux 驱动运行在内核空间,它与操作系统内核紧密绑定。这意味着:
- 权限极高:驱动故障很可能导致整个系统崩溃(内核恐慌)。
- 编程约束多:不能直接使用标准 C 库(如 `printf`,`malloc`),而必须使用内核提供的对应函数(如 `printk`, `kmalloc`)。
- 并发考虑:内核是多任务环境,驱动必须设计为可重入的,并能正确处理多线程并发访问。
性质:
- 优点:灵活、高效、功能完整,是生产环境的标准做法。
- 缺点:开发调试复杂,错误可能导致内核崩溃。
开发流程:
- 编写模块代码:实现 `module_init` 和 `module_exit` 入口 / 出口函数。
- 编写 Makefile:使用内核的 Kbuild 系统来编译模块。
- 编译:`make` 命令生成 `.ko` 文件。
- 加载:`insmod module.ko` 或 `modprobe module`。
- 卸载:`rmmod module`。
- 查看:`lsmod` 查看已加载模块。
基于标准框架的开发
Linux 内核为各类设备提供了成熟的驱动框架和子系统。开发者不应直接“裸写”驱动,而应集成到这些框架中。这是现代 Linux 驱动开发最推荐和最主要的方法。
核心思想:“实现框架定义的接口,并向框架注册你的驱动”。
常见框架举例:
- 字符设备框架:最常见的框架,用于不适合归类的设备(如传感器、LED、按键)。通过实现 `file_operations` 结构体,定义 `open`, `read`, `write`, `ioctl` 等操作。
- 平台设备框架:用于片上系统(SoC)中的外设,这些设备通常没有传统意义上的总线(如 CPU 内部的 GPIO, I2C 控制器)。它分离了“驱动代码”和“设备资源(地址、中断号)”,通过设备树(Device Tree)进行描述。
- PCI / USB 设备框架:用于标准的 PCI 和 USB 设备。驱动需要向 PCI/USB 子系统注册,并提供探测(`probe`)和断开(`disconnect`)函数。
- 块设备框架:用于磁盘、SSD 等存储设备。
- 网络设备框架:用于网卡。驱动需要填充 `net_device` 结构体,并处理数据包的发送和接收。
### 驱动开发关键技术要点
无论采用哪种框架,以下技术都是必须掌握的:
- 设备模型与 sysfs:理解 `kobject`, `kset`, `ktype` 如何在内核中组织成层次结构,并在用户空间的 /sys/ 下暴露设备信息,实现设备管理和热插拔。
- 设备树:在 ARM、PowerPC 等嵌入式系统中,用于取代冗长的硬件编码代码,以一种数据结构的形式来描述系统的硬件配置。驱动通过内核 API 从设备树中获取资源(内存地址、中断号)。
- 并发与同步:
- 自旋锁:用于短时间内的锁定,特别在中断上下文中。
- 互斥锁:用于可睡眠的上下文,保护较长时间的临界区。
- 信号量 / 完成量:用于任务间的同步。
- 中断处理:
- 顶半部:在中断上下文中快速执行,通常只做最紧急的工作(如读取状态寄存器),然后调度底半部。
- 底半部:用于处理耗时的任务,通常通过任务队列、工作队列或软中断实现,可以在其中睡眠。
- 内存管理:
- 使用 `kmalloc`, `vmalloc` 等内核函数分配内存。
- 理解 DMA 和一致性内存映射,使用 `dma_alloc_coherent` 等函数。
- 内核调试:
- `printk`:最基础的日志输出。
- `dynamic debug`:动态调试。
- `/proc/kcore` 和 `kgdb`:内核调试器(较复杂)。
- `ftrace`, `perf`:性能分析工具。
应用
找到磁盘与 ATA 的对应关系
#!/bin/sh ll /sys/class/ata_port/ ll /dev/disk/by-path
[WIP] Asking for cache data failed
What is a “Asking for cache data failed” warning?
[913914.442594] sd 7:0:0:0: [sdd] Asking for cache data failed [913914.442597] sd 7:0:0:0: [sdd] Assuming drive cache: write through