Linux platform驱动与设备树匹配开发实战 - 第1节
1.1 内核模块基础
Linux 内核采用模块化设计,允许在运行时动态加载和卸载功能代码,这种可扩展的代码单元称为内核模块。驱动开发者通过编写内核模块,将设备驱动、文件系统、协议栈等组件插入内核,无需重新编译整个内核或重启系统。
1.1.1 模块的入口与出口
每个内核模块必须包含两个宏:
- module_init() — 指定模块加载时调用的初始化函数。
- module_exit() — 指定模块卸载时调用的清理函数。
基本框架如下:
#include <linux/module.h>
#include <linux/kernel.h>
static int __init my_driver_init(void)
{
printk(KERN_INFO "My driver loaded\n");
return 0;
}
static void __exit my_driver_exit(void)
{
printk(KERN_INFO "My driver unloaded\n");
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Engineer");
MODULE_DESCRIPTION("A minimal kernel module example");
1.1.2 模块编译与依赖
内核模块的编译依赖当前运行内核的构建系统,通常使用 Makefile 调用内核的 Kbuild 框架:
obj-m := my_driver.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
insmod,卸载使用 rmmod,查看已加载模块使用 lsmod。模块间若存在符号依赖,需按顺序加载。
1.2 驱动分类:字符、块与网络
Linux 驱动按设备与内核的数据交换方式分为三大类,下表总结了它们的核心差异:
| 驱动类型 | 数据交换方式 | 典型设备 | 设备节点 | 访问接口 |
|---|---|---|---|---|
| 字符设备 | 字节流,顺序访问 | 串口、GPIO、I2C、SPI | /dev/xxx |
open/read/write/ioctl |
| 块设备 | 块(512B/4KB),随机访问 | 硬盘、eMMC、SD卡、NVMe | /dev/xxx |
文件系统 + submit_bio |
| 网络设备 | 数据包(sk_buff),异步收发 | 以太网、WiFi、CAN、USB网卡 | 无设备节点 | net_device_ops + socket |
1.2.1 字符设备驱动
字符设备是嵌入式开发中最常见的驱动类型。它通过 struct file_operations 向用户空间暴露操作接口,内核使用主设备号与次设备号进行标识。
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};
1.2.2 块设备驱动
块设备驱动面向存储介质,以块为单位进行读写,内核通过 I/O 调度器 合并与排序请求。块设备驱动需要实现 struct block_device_operations,并注册 gendisk 结构。
1.2.3 网络设备驱动
网络设备驱动不通过设备节点访问,而是通过内核网络协议栈。驱动需注册 struct net_device,并实现 struct net_device_ops 中的 ndo_start_xmit 等回调函数。
1.3 Platform 驱动在 Linux 驱动模型中的位置
Linux 驱动模型以 设备(device)、驱动(driver)、总线(bus) 三者为核心。设备与驱动通过总线进行匹配,匹配成功后调用驱动的 probe 函数完成初始化。
1.3.1 驱动模型三层结构
- 总线(bus):负责匹配设备与驱动,提供匹配回调函数。常见总线有
platform_bus、i2c_bus、spi_bus、pci_bus等。 - 设备(device):描述硬件资源(地址、中断、时钟、DMA),可静态定义或由设备树生成。
- 驱动(driver):实现硬件操作逻辑,包含
probe、remove、suspend、resume等回调。
1.3.2 Platform 驱动的独特地位
Platform 驱动是 Linux 驱动模型中最灵活、最广泛的驱动形式,尤其适用于嵌入式 SoC 内部外设。其核心特征包括:
- 虚拟总线:Platform 总线是内核软件虚拟的总线,不依赖硬件电气连接。
- 资源传递:通过
struct platform_device传递内存地址、中断号、DMA 通道等资源。 - 设备树匹配:现代内核通过设备树(Device Tree)的
compatible属性与驱动进行匹配,实现硬件描述与驱动代码的解耦。 - 热插拔支持:部分 Platform 设备支持运行时动态添加/移除。
1.3.3 与其他驱动类型的关系
Platform 驱动并非独立于三大驱动类型之外,而是位于字符/块/网络驱动之上的一层封装。一个 Platform 驱动最终仍会注册为字符设备、块设备或网络设备,但它的设备发现和资源获取由 Platform 总线机制完成。
| 驱动类型 | 是否可基于 Platform | 典型场景 |
|---|---|---|
| 字符设备 | 是(最常见) | UART、GPIO、I2C控制器、SPI控制器 |
| 块设备 | 是 | NAND Flash控制器、SDHCI控制器 |
| 网络设备 | 是 | 以太网 MAC(如 dwmac、fec) |
1.4 本章小结
- 内核模块是 Linux 驱动的基本载体,使用
module_init/module_exit管理生命周期。 - 驱动按数据交换方式分为字符、块、网络三类,字符驱动在嵌入式开发中占比最高。
- Linux 驱动模型以总线为核心,实现设备与驱动的解耦匹配。
- Platform 驱动是 SoC 内部外设的标准驱动形式,通过设备树
compatible属性完成匹配,是现代嵌入式 Linux 驱动开发的核心技能。